summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorvenkataram-nv <vedavamadath@nvidia.com>2024-07-09 16:18:36 -0700
committerGitHub <noreply@github.com>2024-07-09 16:18:36 -0700
commit0e6c5c518953141f31c09e5f10d3939054f9b1ee (patch)
tree932bddc80408e211e1128ca5901b301c154e67bd
parent1caef5907d0b0f16f686a8fcca479c6afc09f146 (diff)
Warnings for uninitialized values (#4530)
This extends the code for handling uninitialized output parameters. Still needs to handle generic templates and assignment of uninitialized values more carefully. The file containing the relevant code are now in source/slang/slang-ir-use-uninitialized-values.cpp rather than the previous source/slang/slang-ir-use-uninitialized-out-param.h and the top-level function is now checkForUsingUinitializedValues. Additionally a rudimentary test shader has been added for this case, which replaces the old file for out params only; tests/diagnositcs/uninitialized-out.slang becomes tests/diagnositcs/uninitialized.slang. What this does not implement (could be future PRs): * Checking uninitialized fields within constructors * Partially uninitialized values with respect to data structure (e.g. arrays/structs/vector types) * Partially uninitialized values with respect to control flow (e.g. if/else/loop)
-rw-r--r--build/visual-studio/slang/slang.vcxproj4
-rw-r--r--build/visual-studio/slang/slang.vcxproj.filters4
-rw-r--r--source/slang/slang-diagnostic-defs.h10
-rw-r--r--source/slang/slang-ir-use-uninitialized-out-param.cpp150
-rw-r--r--source/slang/slang-ir-use-uninitialized-values.cpp382
-rw-r--r--source/slang/slang-ir-use-uninitialized-values.h (renamed from source/slang/slang-ir-use-uninitialized-out-param.h)2
-rw-r--r--source/slang/slang-lower-to-ir.cpp6
-rw-r--r--tests/bugs/gh-4434.slang24
-rw-r--r--tests/bugs/gh-4441.slang24
-rw-r--r--tests/diagnostics/ctor-ordinary-retval-legal.slang3
-rw-r--r--tests/diagnostics/uninitialized-out.slang57
-rw-r--r--tests/diagnostics/uninitialized.slang263
12 files changed, 687 insertions, 242 deletions
diff --git a/build/visual-studio/slang/slang.vcxproj b/build/visual-studio/slang/slang.vcxproj
index cbafa9975..13eea9f0a 100644
--- a/build/visual-studio/slang/slang.vcxproj
+++ b/build/visual-studio/slang/slang.vcxproj
@@ -492,7 +492,7 @@ IF EXIST ..\..\..\external\slang-glslang\bin\windows-aarch64\release\slang-glsla
<ClInclude Include="..\..\..\source\slang\slang-ir-synthesize-active-mask.h" />
<ClInclude Include="..\..\..\source\slang\slang-ir-translate-glsl-global-var.h" />
<ClInclude Include="..\..\..\source\slang\slang-ir-uniformity.h" />
- <ClInclude Include="..\..\..\source\slang\slang-ir-use-uninitialized-out-param.h" />
+ <ClInclude Include="..\..\..\source\slang\slang-ir-use-uninitialized-values.h" />
<ClInclude Include="..\..\..\source\slang\slang-ir-user-type-hint.h" />
<ClInclude Include="..\..\..\source\slang\slang-ir-util.h" />
<ClInclude Include="..\..\..\source\slang\slang-ir-validate.h" />
@@ -737,7 +737,7 @@ IF EXIST ..\..\..\external\slang-glslang\bin\windows-aarch64\release\slang-glsla
<ClCompile Include="..\..\..\source\slang\slang-ir-synthesize-active-mask.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-ir-translate-glsl-global-var.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-ir-uniformity.cpp" />
- <ClCompile Include="..\..\..\source\slang\slang-ir-use-uninitialized-out-param.cpp" />
+ <ClCompile Include="..\..\..\source\slang\slang-ir-use-uninitialized-values.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-ir-user-type-hint.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-ir-util.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-ir-validate.cpp" />
diff --git a/build/visual-studio/slang/slang.vcxproj.filters b/build/visual-studio/slang/slang.vcxproj.filters
index ab272f7e1..87a46477d 100644
--- a/build/visual-studio/slang/slang.vcxproj.filters
+++ b/build/visual-studio/slang/slang.vcxproj.filters
@@ -564,7 +564,7 @@
<ClInclude Include="..\..\..\source\slang\slang-ir-uniformity.h">
<Filter>Header Files</Filter>
</ClInclude>
- <ClInclude Include="..\..\..\source\slang\slang-ir-use-uninitialized-out-param.h">
+ <ClInclude Include="..\..\..\source\slang\slang-ir-use-uninitialized-values.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\source\slang\slang-ir-user-type-hint.h">
@@ -1295,7 +1295,7 @@
<ClCompile Include="..\..\..\source\slang\slang-ir-uniformity.cpp">
<Filter>Source Files</Filter>
</ClCompile>
- <ClCompile Include="..\..\..\source\slang\slang-ir-use-uninitialized-out-param.cpp">
+ <ClCompile Include="..\..\..\source\slang\slang-ir-use-uninitialized-values.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\source\slang\slang-ir-user-type-hint.cpp">
diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h
index 496fb7e32..7ebe77a8f 100644
--- a/source/slang/slang-diagnostic-defs.h
+++ b/source/slang/slang-diagnostic-defs.h
@@ -598,7 +598,7 @@ DIAGNOSTIC(39999, Error, unableToFindSymbolInModule, "unable to find the mangled
DIAGNOSTIC(39999, Error, overloadedParameterToHigherOrderFunction, "passing overloaded functions to higher order functions is not supported")
-// 38xxx
+// 38xxx
DIAGNOSTIC(38000, Error, entryPointFunctionNotFound, "no function found matching entry point name '$0'")
DIAGNOSTIC(38001, Error, ambiguousEntryPoint, "more than one function matches entry point name '$0'")
@@ -735,9 +735,11 @@ DIAGNOSTIC(41010, Warning, missingReturn, "control flow may reach end of non-'vo
DIAGNOSTIC(41011, Error, profileIncompatibleWithTargetSwitch, "__target_switch has no compatable target with current profile '$0'")
DIAGNOSTIC(41012, Warning, profileImplicitlyUpgraded, "user set `profile` had an implicit upgrade applied to it, atoms added: '$0'")
DIAGNOSTIC(41012, Error, profileImplicitlyUpgradedRestrictive, "user set `profile` had an implicit upgrade applied to it, atoms added: '$0'")
-DIAGNOSTIC(41015, Error, usingUninitializedValue, "use of uninitialized value '$0'")
-DIAGNOSTIC(41016, Warning, returningWithUninitializedOut, "returning without initializing out parameter '$0'")
-DIAGNOSTIC(41017, Warning, returningWithPartiallyUninitializedOut, "returning without fully initializing out parameter '$0'")
+DIAGNOSTIC(41015, Warning, usingUninitializedOut, "use of uninitialized out parameter '$0'")
+DIAGNOSTIC(41016, Warning, usingUninitializedVariable, "use of uninitialized variable '$0'")
+DIAGNOSTIC(41017, Warning, usingUninitializedGlobalVariable, "use of uninitialized global variable '$0'")
+DIAGNOSTIC(41018, Warning, returningWithUninitializedOut, "returning without initializing out parameter '$0'")
+DIAGNOSTIC(41019, Warning, returningWithPartiallyUninitializedOut, "returning without fully initializing out parameter '$0'")
DIAGNOSTIC(41011, Error, typeDoesNotFitAnyValueSize, "type '$0' does not fit in the size required by its conforming interface.")
DIAGNOSTIC(41012, Note, typeAndLimit, "sizeof($0) is $1, limit is $2")
diff --git a/source/slang/slang-ir-use-uninitialized-out-param.cpp b/source/slang/slang-ir-use-uninitialized-out-param.cpp
deleted file mode 100644
index 7e3ef9ca2..000000000
--- a/source/slang/slang-ir-use-uninitialized-out-param.cpp
+++ /dev/null
@@ -1,150 +0,0 @@
-#include "slang-ir-use-uninitialized-out-param.h"
-#include "slang-ir-util.h"
-#include "slang-ir-reachability.h"
-
-namespace Slang
-{
- class DiagnosticSink;
- struct IRModule;
-
- struct StoreSite
- {
- IRInst* storeInst;
- IRInst* address;
- };
-
- void checkForUsingUninitializedOutParams(IRFunc* func, DiagnosticSink* sink)
- {
- List<IRInst*> outParams;
- auto firstBlock = func->getFirstBlock();
- if (!firstBlock)
- return;
-
- ReachabilityContext reachability(func);
-
- for (auto param : firstBlock->getParams())
- {
- if (auto outType = 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 (outType->getValueType()->getOp())
- {
- case kIROp_VerticesType:
- case kIROp_IndicesType:
- case kIROp_PrimitivesType:
- continue;
- default:
- break;
- }
- }
- else
- {
- continue;
- }
- List<IRInst*> addresses;
- addresses.add(param);
- List<StoreSite> stores;
- // Collect all sub-addresses from the param.
- for (Index i = 0; i < addresses.getCount(); i++)
- {
- auto addr = addresses[i];
- for (auto use = addr->firstUse; use; use = use->nextUse)
- {
- switch (use->getUser()->getOp())
- {
- case kIROp_GetElementPtr:
- case kIROp_FieldAddress:
- addresses.add(use->getUser());
- break;
- case kIROp_Store:
- case kIROp_SwizzledStore:
- // If we see a store of this address, add it to stores set.
- if (use == use->getUser()->getOperands())
- stores.add(StoreSite{ use->getUser(), addr });
- break;
- case kIROp_Call:
- case kIROp_SPIRVAsm:
- // If we see a call using this address, treat it as a store.
- stores.add(StoreSite{ use->getUser(), addr });
- break;
- case kIROp_SPIRVAsmOperandInst:
- stores.add(StoreSite{ use->getUser()->getParent(), addr});
- break;
- }
- }
- }
- // Check all address loads.
- List<IRInst*> loadsAndReturns;
- for (auto addr : addresses)
- {
- for (auto use = addr->firstUse; use; use = use->nextUse)
- {
- if (auto load = as<IRLoad>(use->getUser()))
- loadsAndReturns.add(load);
- }
- }
- for(const auto& b : func->getBlocks())
- {
- auto t = as<IRReturn>(b->getTerminator());
- if (!t) continue;
- loadsAndReturns.add(t);
- }
-
- for (auto store : stores)
- {
- // Remove insts from `loads` that is reachable from the store.
- for (Index i = 0; i < loadsAndReturns.getCount();)
- {
- auto load = as<IRLoad>(loadsAndReturns[i]);
- if (load && !canAddressesPotentiallyAlias(func, store.address, load->getPtr()))
- continue;
- if (reachability.isInstReachable(store.storeInst, loadsAndReturns[i]))
- {
- loadsAndReturns.fastRemoveAt(i);
- }
- else
- {
- i++;
- }
- }
- }
- // If there are any loads left, it means they are using uninitialized out params.
- for (auto load : loadsAndReturns)
- {
- sink->diagnose(
- load,
- load->m_op == kIROp_Return
- ? Diagnostics::returningWithUninitializedOut
- : Diagnostics::usingUninitializedValue,
- param);
- }
- }
- }
-
- void checkForUsingUninitializedOutParams(
- IRModule* module,
- DiagnosticSink* sink)
- {
- for (auto inst : module->getGlobalInsts())
- {
- if (auto func = as<IRFunc>(inst))
- {
- checkForUsingUninitializedOutParams(func, sink);
- }
- else if (auto generic = as<IRGeneric>(inst))
- {
- auto retVal = findGenericReturnVal(generic);
- if (auto funcVal = as<IRFunc>(retVal))
- {
- checkForUsingUninitializedOutParams(funcVal, sink);
- }
- }
- }
- }
-}
diff --git a/source/slang/slang-ir-use-uninitialized-values.cpp b/source/slang/slang-ir-use-uninitialized-values.cpp
new file mode 100644
index 000000000..762773ad4
--- /dev/null
+++ b/source/slang/slang-ir-use-uninitialized-values.cpp
@@ -0,0 +1,382 @@
+#include "slang-ir-use-uninitialized-values.h"
+#include "slang-ir-insts.h"
+#include "slang-ir-reachability.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;
+ }
+
+ // Casting to IRUndefined is currently vacuous
+ // (e.g. any IRInst can be cast to IRUndefined)
+ static bool isUndefinedValue(IRInst* inst)
+ {
+ return (inst->m_op == kIROp_undefined);
+ }
+
+ static bool isUndefinedParam(IRParam* param)
+ {
+ auto outType = as<IROutType>(param->getFullType());
+ if (!outType)
+ return false;
+
+ // 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 (outType->getValueType()->getOp())
+ {
+ case kIROp_VerticesType:
+ case kIROp_IndicesType:
+ case kIROp_PrimitivesType:
+ return false;
+ default:
+ break;
+ }
+
+ return true;
+ }
+
+ static bool isAliasable(IRInst* inst)
+ {
+ switch (inst->getOp())
+ {
+ // These instructions generate (implicit) references to inst
+ case kIROp_FieldExtract:
+ case kIROp_FieldAddress:
+ case kIROp_GetElement:
+ case kIROp_GetElementPtr:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+ }
+
+ static bool isDifferentiableFunc(IRInst* func)
+ {
+ for (auto decor = func->getFirstDecoration(); decor; decor = decor->getNextDecoration())
+ {
+ switch (decor->getOp())
+ {
+ case kIROp_ForwardDerivativeDecoration:
+ case kIROp_ForwardDifferentiableDecoration:
+ case kIROp_BackwardDerivativeDecoration:
+ case kIROp_BackwardDifferentiableDecoration:
+ case kIROp_UserDefinedBackwardDerivativeDecoration:
+ return true;
+ default:
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ static bool canIgnoreType(IRType* type)
+ {
+ if (as<IRVoidType>(type))
+ return true;
+
+ // For structs, ignore if its empty
+ if (as<IRStructType>(type))
+ return (type->getFirstChild() == nullptr);
+
+ // Nothing to initialize for a pure interface
+ if (as<IRInterfaceType>(type))
+ return true;
+
+ // For pointers, check the value type (primarily for globals)
+ if (auto ptr = as<IRPtrType>(type))
+ return canIgnoreType(ptr->getValueType());
+
+ // In the case of specializations, check returned type
+ if (auto spec = as<IRSpecialize>(type))
+ {
+ IRInst* base = spec->getBase();
+ IRGeneric* generic = as<IRGeneric>(base);
+ IRInst* inner = findInnerMostGenericReturnVal(generic);
+ IRType* innerType = as<IRType>(inner);
+ return canIgnoreType(innerType);
+ }
+
+ return false;
+ }
+
+ static List<IRInst*> getAliasableInstructions(IRInst* inst)
+ {
+ List<IRInst*> addresses;
+
+ addresses.add(inst);
+ for (auto use = inst->firstUse; use; use = use->nextUse)
+ {
+ IRInst* user = use->getUser();
+
+ // Meta instructions only use the argument type
+ if (isMetaOp(user) || !isAliasable(user))
+ continue;
+
+ addresses.addRange(getAliasableInstructions(user));
+ }
+
+ return addresses;
+ }
+
+ static void collectLoadStore(List<IRInst*>& stores, List<IRInst*>& loads, IRInst* user)
+ {
+ // Meta intrinsics (which evaluate on type) do nothing
+ if (isMetaOp(user))
+ return;
+
+ // Ignore instructions generating more aliases
+ if (isAliasable(user))
+ return;
+
+ switch (user->getOp())
+ {
+ case kIROp_loop:
+ case kIROp_unconditionalBranch:
+ // TODO: Ignore branches for now
+ return;
+
+ // These instructions will store data...
+ case kIROp_Store:
+ case kIROp_SwizzledStore:
+ // TODO: for calls, should make check that the
+ // function is passing as an out param
+ case kIROp_Call:
+ case kIROp_SPIRVAsm:
+ case kIROp_GenericAsm:
+ // For now assume that __intrinsic_asm blocks will do the right thing...
+ stores.add(user);
+ break;
+
+ case kIROp_SPIRVAsmOperandInst:
+ // For SPIRV asm instructions, need to check out the entire
+ // block when doing reachability checks
+ stores.add(user->getParent());
+ break;
+
+ case kIROp_MakeExistential:
+ case kIROp_MakeExistentialWithRTTI:
+ // For specializing generic structs
+ stores.add(user);
+ break;
+
+ // ... and the rest will load/use them
+ default:
+ loads.add(user);
+ break;
+ }
+ }
+
+ static void cancelLoads(ReachabilityContext &reachability, const List<IRInst*>& stores, List<IRInst*>& loads)
+ {
+ // Remove all loads which are reachable from stores
+ for (auto store : stores)
+ {
+ for (Index i = 0; i < loads.getCount(); )
+ {
+ if (reachability.isInstReachable(store, loads[i]))
+ loads.fastRemoveAt(i);
+ else
+ i++;
+ }
+ }
+ }
+
+ static List<IRInst*> getUnresolvedParamLoads(ReachabilityContext &reachability, IRFunc* func, IRInst* inst)
+ {
+ // Collect all aliasable addresses
+ auto addresses = getAliasableInstructions(inst);
+
+ // Partition instructions
+ List<IRInst*> stores;
+ List<IRInst*> loads;
+
+ for (auto alias : addresses)
+ {
+ // TODO: Mark specific parts assigned to for partial initialization checks
+ for (auto use = alias->firstUse; use; use = use->nextUse)
+ {
+ IRInst* user = use->getUser();
+ collectLoadStore(stores, loads, user);
+ }
+ }
+
+ // Only for out params we shall add all returns
+ for (const auto& b : func->getBlocks())
+ {
+ auto t = as<IRReturn>(b->getTerminator());
+ if (!t)
+ continue;
+
+ loads.add(t);
+ }
+
+ cancelLoads(reachability, stores, loads);
+
+ return loads;
+ }
+
+ static List<IRInst*> getUnresolvedVariableLoads(ReachabilityContext &reachability, IRInst* inst)
+ {
+ auto addresses = getAliasableInstructions(inst);
+
+ // Partition instructions
+ List<IRInst*> stores;
+ List<IRInst*> loads;
+
+ for (auto alias : addresses)
+ {
+ for (auto use = alias->firstUse; use; use = use->nextUse)
+ {
+ IRInst* user = use->getUser();
+ collectLoadStore(stores, loads, user);
+ }
+ }
+
+ cancelLoads(reachability, stores, loads);
+
+ return loads;
+ }
+
+ static void checkUninitializedValues(IRFunc* func, DiagnosticSink* sink)
+ {
+ if (isDifferentiableFunc(func))
+ return;
+
+ auto firstBlock = func->getFirstBlock();
+ if (!firstBlock)
+ return;
+
+ ReachabilityContext reachability(func);
+
+ // Check out parameters
+ for (auto param : firstBlock->getParams())
+ {
+ if (!isUndefinedParam(param))
+ continue;
+
+ auto loads = getUnresolvedParamLoads(reachability, func, param);
+ for (auto load : loads)
+ {
+ sink->diagnose(load,
+ as <IRReturn> (load)
+ ? Diagnostics::returningWithUninitializedOut
+ : Diagnostics::usingUninitializedOut,
+ param);
+ }
+ }
+
+ // Check ordinary instructions
+ for (auto inst = firstBlock->getFirstInst(); inst; inst = inst->getNextInst())
+ {
+ if (!isUndefinedValue(inst))
+ continue;
+
+ IRType* type = inst->getFullType();
+ if (canIgnoreType(type))
+ continue;
+
+ auto loads = getUnresolvedVariableLoads(reachability, inst);
+ for (auto load : loads)
+ {
+ sink->diagnose(load,
+ Diagnostics::usingUninitializedVariable,
+ inst);
+ }
+ }
+ }
+
+ static void checkUninitializedGlobals(IRGlobalVar* variable, DiagnosticSink* sink)
+ {
+ IRType* type = variable->getFullType();
+ if (canIgnoreType(type))
+ return;
+
+ // Check for semantic decorations
+ // (e.g. globals like gl_GlobalInvocationID)
+ if (variable->findDecoration<IRSemanticDecoration>())
+ return;
+
+ // Check for initialization blocks
+ for (auto inst : variable->getChildren())
+ {
+ if (as<IRBlock>(inst))
+ return;
+ }
+
+ auto addresses = getAliasableInstructions(variable);
+
+ List<IRInst*> stores;
+ List<IRInst*> loads;
+
+ for (auto alias : addresses)
+ {
+ for (auto use = alias->firstUse; use; use = use->nextUse)
+ {
+ IRInst* user = use->getUser();
+ collectLoadStore(stores, loads, user);
+
+ // Disregard if there is at least one store,
+ // since we cannot tell what the control flow is
+ if (stores.getCount())
+ return;
+ }
+ }
+
+ for (auto load : loads)
+ {
+ sink->diagnose(load,
+ Diagnostics::usingUninitializedGlobalVariable,
+ variable);
+ }
+ }
+
+ void checkForUsingUninitializedValues(IRModule* module, DiagnosticSink* sink)
+ {
+ for (auto inst : module->getGlobalInsts())
+ {
+ 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);
+ }
+ }
+ }
+}
diff --git a/source/slang/slang-ir-use-uninitialized-out-param.h b/source/slang/slang-ir-use-uninitialized-values.h
index fd090c4f9..9b6867a3b 100644
--- a/source/slang/slang-ir-use-uninitialized-out-param.h
+++ b/source/slang/slang-ir-use-uninitialized-values.h
@@ -6,7 +6,7 @@ namespace Slang
class DiagnosticSink;
struct IRModule;
- void checkForUsingUninitializedOutParams(
+ void checkForUsingUninitializedValues(
IRModule* module,
DiagnosticSink* sink);
}
diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp
index c946072f9..6fa2ce67f 100644
--- a/source/slang/slang-lower-to-ir.cpp
+++ b/source/slang/slang-lower-to-ir.cpp
@@ -33,7 +33,7 @@
#include "slang-ir-clone.h"
#include "slang-ir-lower-error-handling.h"
#include "slang-ir-obfuscate-loc.h"
-#include "slang-ir-use-uninitialized-out-param.h"
+#include "slang-ir-use-uninitialized-values.h"
#include "slang-ir-peephole.h"
#include "slang-mangle.h"
#include "slang-type-layout.h"
@@ -10925,8 +10925,8 @@ RefPtr<IRModule> generateIRForTranslationUnit(
// call graph) based on constraints imposed by different instructions.
propagateConstExpr(module, compileRequest->getSink());
- // Check for using uninitialized out parameters.
- checkForUsingUninitializedOutParams(module, compileRequest->getSink());
+ // Check for using uninitialized values
+ checkForUsingUninitializedValues(module, compileRequest->getSink());
// TODO: give error messages if any `undefined` or
// instructions remain.
diff --git a/tests/bugs/gh-4434.slang b/tests/bugs/gh-4434.slang
index 975226008..c9a48d439 100644
--- a/tests/bugs/gh-4434.slang
+++ b/tests/bugs/gh-4434.slang
@@ -15,17 +15,19 @@ RWStructuredBuffer<uint> outputBuffer;
[numthreads(4, 1, 1)]
void computeMain(uint tid : SV_GroupIndex)
{
- bool a, b, c;
- c = and(a, b);
-
- bool1 i, j, k;
- bool2 l, m, n;
- bool3 o, p, q;
- bool4 r, s, t;
- k = and(i, j);
- n = and(m, l);
- q = and(o, p);
- t = and(r, s);
+ bool K = (bool)outputBuffer[tid];
+
+ bool c = and(K, K);
+
+ bool1 K1 = K;
+ bool2 K2 = K;
+ bool3 K3 = K;
+ bool4 K4 = K;
+
+ bool1 k = and(K, K);
+ bool2 n = and(K, K);
+ bool3 q = and(K, K);
+ bool4 t = and(K, K);
k = !and(k, false);
n = !and(n, false);
diff --git a/tests/bugs/gh-4441.slang b/tests/bugs/gh-4441.slang
index 59d577c8d..a2de7e00d 100644
--- a/tests/bugs/gh-4441.slang
+++ b/tests/bugs/gh-4441.slang
@@ -15,17 +15,19 @@ RWStructuredBuffer<uint> outputBuffer;
[numthreads(4, 1, 1)]
void computeMain(uint tid : SV_GroupIndex)
{
- bool a, b, c;
- c = or(a, b);
-
- bool1 i, j, k;
- bool2 l, m, n;
- bool3 o, p, q;
- bool4 r, s, t;
- k = or(i, j);
- n = or(m, l);
- q = or(o, p);
- t = or(r, s);
+ bool K = (bool)outputBuffer[tid];
+
+ bool c = or(K, K);
+
+ bool1 K1 = K;
+ bool2 K2 = K;
+ bool3 K3 = K;
+ bool4 K4 = K;
+
+ bool1 k = or(K, K);
+ bool2 n = or(K, K);
+ bool3 q = or(K, K);
+ bool4 t = or(K, K);
k = or(k, true);
n = or(n, true);
diff --git a/tests/diagnostics/ctor-ordinary-retval-legal.slang b/tests/diagnostics/ctor-ordinary-retval-legal.slang
index f2d1765ff..8117b4c80 100644
--- a/tests/diagnostics/ctor-ordinary-retval-legal.slang
+++ b/tests/diagnostics/ctor-ordinary-retval-legal.slang
@@ -11,7 +11,8 @@
struct S
{
- float v;
+ float v;
+ __init() { v = 0; }
}
struct S1a : S
diff --git a/tests/diagnostics/uninitialized-out.slang b/tests/diagnostics/uninitialized-out.slang
deleted file mode 100644
index d0d87449f..000000000
--- a/tests/diagnostics/uninitialized-out.slang
+++ /dev/null
@@ -1,57 +0,0 @@
-//DIAGNOSTIC_TEST:SIMPLE:
-
-float foo(out float3 v)
-{
- // This should error as we haven't set v before we read from it
- float r = v.x + 1.0;
- // This should warn as we haven't set v before we return
- return r;
-}
-
-// This should warn as we return without x being initialized
-float bar(out float x)
-{
- return 0;
-}
-
-// This should also warn pointing at the implicit return
-void baz(out float x) {}
-
-void twoReturns(bool b, out float y)
-{
- if(b)
- {
- // Should warn
- return;
- }
- y = 0;
- // Shouldn't warn
- return;
-}
-
-void twoOkReturns(bool b, out float y)
-{
- if(b)
- {
- // Shouldn't warn
- unused(y);
- return;
- }
- y = 0;
- // Shouldn't warn
- return;
-}
-
-// TODO: This should warn that n is potentially uninitialized
-int ok(bool b, out int n)
-{
- if(b)
- n = 0;
- return n;
-}
-
-// TODO: This should warn that arr isn't fully initialized
-void partial(out float arr[2])
-{
- arr[0] = 1;
-}
diff --git a/tests/diagnostics/uninitialized.slang b/tests/diagnostics/uninitialized.slang
new file mode 100644
index 000000000..4779f45c9
--- /dev/null
+++ b/tests/diagnostics/uninitialized.slang
@@ -0,0 +1,263 @@
+//TEST:SIMPLE(filecheck=CHK): -target spirv
+
+// TODO:
+// * warn potentially uninitialized variables (control flow)
+// * warn partially uninitialized variables (structs, arrays, etc.)
+// * warn uninitialized fields in constructors
+
+///////////////////////////////////
+// Uninitialized local variables //
+///////////////////////////////////
+
+// Should not warn here (unconditionalBranch)
+float3 unconditional(int mode)
+{
+ float f(float) { return 1; }
+
+ float k0;
+ float k1;
+
+ if (mode == 1)
+ {
+ k1 = 1;
+ k0 = 1;
+
+ const float w = k1 * f(1);
+ k0 = 4.0f * k0 * w;
+ k1 = 2.0f * k1 * w;
+ }
+
+ return k0 + k1;
+}
+
+// Warn here for branches using the variables
+int conditional()
+{
+ int k;
+ //CHK-DAG: warning 41016: use of uninitialized variable 'k'
+ return (k > 0);
+}
+
+// Using unitialized values
+int use_undefined_value(int k)
+{
+ int x;
+ x += k;
+ //CHK-DAG: warning 41016: use of uninitialized variable 'x'
+ return x;
+}
+
+// Returning uninitialized values
+__generic<T>
+T generic_undefined_return()
+{
+ T x;
+ //CHK-DAG: warning 41016: use of uninitialized variable 'x'
+ return x;
+}
+
+// Array variables
+float undefined_array()
+{
+ float array[2];
+ //CHK-DAG: warning 41016: use of uninitialized variable 'array'
+ return array[0];
+}
+
+float filled_array(int mode)
+{
+ float array[2];
+ array[0] = 1.0f;
+ return array[0];
+}
+
+// Structs and nested structs
+struct Data
+{
+ float value;
+};
+
+struct NestedData
+{
+ Data data;
+};
+
+// No warnings here, even thought autodiff generates
+// IR which frequently returns undefined values
+struct DiffStruct : IDifferentiable
+{
+ Data data;
+ float x;
+}
+
+// Same story here
+[ForwardDifferentiable]
+DiffStruct differentiable(float x)
+{
+ DiffStruct ds;
+ ds.x = x;
+ return ds;
+}
+
+// Empty structures should not generate diagnostics
+// for empty default constructors
+struct EmptyStruct
+{
+ __init() {}
+};
+
+// No warnings for empty structs even without __init()
+struct NonEmptyStruct
+{
+ int field;
+
+ __init()
+ {
+ field = 1;
+ }
+};
+
+// No warnings even when __init() is not specified
+struct NoDefault
+{
+ int f(int i)
+ {
+ return i;
+ }
+};
+
+// Constructing the above structs
+int constructors()
+{
+ EmptyStruct empty;
+ NoDefault no_default;
+ return no_default.f(1);
+}
+
+// Using struct fields and nested structs
+float structs()
+{
+ Data inputData = Data(1.0);
+
+ float undefVar;
+ Data undefData;
+ NestedData nestedData;
+
+ float result = inputData.value;
+
+ //CHK-DAG: warning 41016: use of uninitialized variable 'undefVar'
+ result += undefVar;
+
+ //CHK-DAG: warning 41016: use of uninitialized variable 'undefData'
+ result += undefData.value;
+
+ //CHK-DAG: warning 41016: use of uninitialized variable 'nestedData'
+ result += nestedData.data.value;
+
+ return result;
+}
+
+////////////////////////////////////
+// Uninitialized global variables //
+////////////////////////////////////
+
+// Using groupshared variables
+groupshared float4 gsConstexpr = float4(1.0f);
+groupshared float4 gsUndefined;
+
+// OK
+float use_constexpr_initialized_gs()
+{
+ return gsConstexpr.x;
+}
+
+float use_undefined_gs()
+{
+ //CHK-DAG: warning 41017: use of uninitialized global variable 'gsUndefined'
+ return gsUndefined.x;
+}
+
+// Using static variables
+static const float cexprInitialized = 1.0f;
+static float writtenNever;
+static float writtenLater;
+
+// OK
+float use_initialized_static()
+{
+ return cexprInitialized;
+}
+
+// Should detect this and treat it as a store
+void write_to_later()
+{
+ writtenLater = 1.0f;
+}
+
+float use_never_written()
+{
+ //CHK-DAG: warning 41017: use of uninitialized global variable 'writtenNever'
+ return writtenNever;
+}
+
+// OK because of prior store
+float use_later_writte()
+{
+ return writtenLater;
+}
+
+//////////////////////////////////
+// Uninitialized out parameters //
+//////////////////////////////////
+
+// Using before assigning
+float regular_undefined_use(out float3 v)
+{
+ //CHK-DAG: warning 41015: use of uninitialized out parameter 'v'
+ float r = v.x + 1.0;
+
+ //CHK-DAG: warning 41018: returning without initializing out parameter 'v'
+ return r;
+}
+
+// Returning before assigning
+float returning_undefined_use(out float x)
+{
+ //CHK-DAG: warning 41018: returning without initializing out parameter 'x'
+ return 0;
+}
+
+// Implicit, still returning before assigning
+void implicit_undefined_use(out float x)
+{
+ //CHK-DAG: warning 41018: returning without initializing out parameter 'x'
+}
+
+// Warn on potential return paths
+void control_flow_undefined(bool b, out float y)
+{
+ if(b)
+ {
+ //CHK-DAG: warning 41018: returning without initializing out parameter 'y'
+ return;
+ }
+ y = 0;
+ return;
+}
+
+// No warnings if all paths are fine
+void control_flow_defined(bool b, out float y)
+{
+ if(b)
+ {
+ unused(y);
+ return;
+ }
+ y = 0;
+ return;
+}
+
+//CHK-NOT: warning 41015
+//CHK-NOT: warning 41016
+//CHK-NOT: warning 41017
+//CHK-NOT: warning 41018