diff options
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/slang-diagnostic-defs.h | 10 | ||||
| -rw-r--r-- | source/slang/slang-ir-use-uninitialized-out-param.cpp | 150 | ||||
| -rw-r--r-- | source/slang/slang-ir-use-uninitialized-values.cpp | 382 | ||||
| -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.cpp | 6 |
5 files changed, 392 insertions, 158 deletions
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. |
