diff --git a/source/slang/slang-check-conversion.cpp b/source/slang/slang-check-conversion.cpp index 317954157e4..d54482b752b 100644 --- a/source/slang/slang-check-conversion.cpp +++ b/source/slang/slang-check-conversion.cpp @@ -929,6 +929,104 @@ bool SemanticsVisitor::_readAggregateValueFromInitializerList( return true; } +// Recursively walk a type to find resource types that would be default-initialized +// (i.e., not explicitly provided in the initializer list) and emit warnings. +void SemanticsVisitor::_warnAboutDefaultInitializedResources( + Type* type, + UInt argCount, + UInt& ioArgIndex, + SourceLoc loc) +{ + // If we still have arguments available, this type will be explicitly initialized + if (ioArgIndex < argCount) + { + // For resource types, one argument is consumed + if (as(type)) + { + ioArgIndex++; + return; + } + + // For arrays, recurse into elements + if (auto arrayType = as(type)) + { + auto elementType = arrayType->getElementType(); + if (!arrayType->isUnsized()) + { + if (auto constCount = as(arrayType->getElementCount())) + { + UInt elementCount = (UInt)constCount->getValue(); + for (UInt i = 0; i < elementCount; i++) + { + _warnAboutDefaultInitializedResources( + elementType, + argCount, + ioArgIndex, + loc); + } + } + } + return; + } + + // For structs, recurse into fields + if (auto structDeclRef = isDeclRefTypeOf(type)) + { + for (auto field : structDeclRef.getDecl()->getMembersOfType()) + { + if (field->hasModifier()) + continue; + _warnAboutDefaultInitializedResources(field->getType(), argCount, ioArgIndex, loc); + } + return; + } + + // For other types (scalars, vectors, matrices), one argument is consumed + ioArgIndex++; + return; + } + + // No more arguments - this type will be default-initialized + // Check if it contains resource types + if (as(type)) + { + getSink()->diagnose(loc, Diagnostics::cannotDefaultInitializeResourceType, type); + return; + } + + // For arrays, recurse to check element type + if (auto arrayType = as(type)) + { + auto elementType = arrayType->getElementType(); + if (!arrayType->isUnsized()) + { + if (auto constCount = as(arrayType->getElementCount())) + { + UInt elementCount = (UInt)constCount->getValue(); + for (UInt i = 0; i < elementCount; i++) + { + _warnAboutDefaultInitializedResources(elementType, argCount, ioArgIndex, loc); + } + } + } + return; + } + + // For structs, recurse into fields + if (auto structDeclRef = isDeclRefTypeOf(type)) + { + for (auto field : structDeclRef.getDecl()->getMembersOfType()) + { + if (field->hasModifier()) + continue; + _warnAboutDefaultInitializedResources(field->getType(), argCount, ioArgIndex, loc); + } + return; + } + + // Other types (scalars, vectors, matrices) are default-initializable - no warning needed +} + bool SemanticsVisitor::_coerceInitializerList( Type* toType, Expr** outToExpr, @@ -959,6 +1057,19 @@ bool SemanticsVisitor::_coerceInitializerList( return true; } + // Before falling back to legacy initializer list logic, check for resource types + // that would be default-initialized and warn about them. + // Skip this check when synthesizing default initializers (e.g., for ParameterBlocks). + if (!isInSynthesizedDefaultInit()) + { + UInt warningArgIndex = 0; + _warnAboutDefaultInitializedResources( + toType, + argCount, + warningArgIndex, + fromInitializerListExpr->loc); + } + // Try to invoke the synthesized constructor if it exists if (createInvokeExprForSynthesizedCtor(toType, fromInitializerListExpr, outToExpr)) { diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index 0690fefd6d0..3c9cebb333e 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -2646,7 +2646,10 @@ bool isDefaultInitializable(VarDeclBase* varDecl) return true; } -static Expr* constructDefaultConstructorForType(SemanticsVisitor* visitor, Type* type) +static Expr* constructDefaultConstructorForType( + SemanticsVisitor* visitor, + Type* type, + SourceLoc loc) { ConstructorDecl* defaultCtor = nullptr; auto declRefType = as(type); @@ -2677,6 +2680,7 @@ static Expr* constructDefaultConstructorForType(SemanticsVisitor* visitor, Type* if (visitor->isCStyleType(type, visitSet)) { auto initListExpr = visitor->getASTBuilder()->create(); + initListExpr->loc = loc; initListExpr->type = visitor->getASTBuilder()->getInitializerListType(); Expr* outExpr = nullptr; auto fromType = type; @@ -2684,7 +2688,10 @@ static Expr* constructDefaultConstructorForType(SemanticsVisitor* visitor, Type* { fromType = atomicType->getElementType(); } - if (visitor->_coerceInitializerList(fromType, &outExpr, initListExpr)) + // Use a sub-visitor with the synthesized default init flag set to suppress + // warnings about default-initializing resource types. + SemanticsVisitor subVisitor(visitor->withInSynthesizedDefaultInit()); + if (subVisitor._coerceInitializerList(fromType, &outExpr, initListExpr)) return outExpr; } @@ -2699,7 +2706,8 @@ static Expr* constructDefaultInitExprForType(SemanticsVisitor* visitor, VarDeclB if (!isDefaultInitializable(varDecl)) return nullptr; - if (auto defaultInitExpr = constructDefaultConstructorForType(visitor, varDecl->type.type)) + if (auto defaultInitExpr = + constructDefaultConstructorForType(visitor, varDecl->type.type, varDecl->loc)) { return defaultInitExpr; } @@ -9681,7 +9689,27 @@ void SemanticsDeclBodyVisitor::visitParamDecl(ParamDecl* paramDecl) // actual type of the parameter. // initExpr = CheckTerm(initExpr); - initExpr = coerce(CoercionSite::Initializer, typeExpr.type, initExpr, getSink()); + + // For synthesized constructor parameters, the default value is derived + // from the field initializer, which has already been checked and warned + // about. Suppress duplicate warnings by using the synthesized default + // init context. + bool isSynthesizedCtorParam = false; + if (auto ctorDecl = as(paramDecl->parentDecl)) + { + isSynthesizedCtorParam = ctorDecl->findModifier() != nullptr; + } + + if (isSynthesizedCtorParam) + { + SemanticsVisitor subVisitor(withInSynthesizedDefaultInit()); + initExpr = + subVisitor.coerce(CoercionSite::Initializer, typeExpr.type, initExpr, getSink()); + } + else + { + initExpr = coerce(CoercionSite::Initializer, typeExpr.type, initExpr, getSink()); + } paramDecl->initExpr = initExpr; // TODO: a default argument expression needs to @@ -13596,9 +13624,8 @@ static Expr* _getParamDefaultValue(SemanticsVisitor* visitor, VarDeclBase* varDe if (!isDefaultInitializable(varDecl)) return nullptr; - if (auto expr = constructDefaultConstructorForType(visitor, varDecl->type.type)) + if (auto expr = constructDefaultConstructorForType(visitor, varDecl->type.type, varDecl->loc)) { - expr->loc = varDecl->loc; return expr; } diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index f7dc4d714fb..a8bd8f0ce59 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -1073,6 +1073,16 @@ struct SemanticsContext bool getInForLoopSideEffect() { return m_inForLoopSideEffect; } + // Setup the flag to indicate we're synthesizing a default initializer, + // where warnings about default-initializing resource types should be suppressed. + SemanticsContext withInSynthesizedDefaultInit() + { + SemanticsContext result(*this); + result.m_inSynthesizedDefaultInit = true; + return result; + } + + bool isInSynthesizedDefaultInit() { return m_inSynthesizedDefaultInit; } TryClauseType getEnclosingTryClauseType() { return m_enclosingTryClauseType; } @@ -1211,6 +1221,9 @@ struct SemanticsContext // allowed bool m_inForLoopSideEffect = false; + // Flag to track when we're synthesizing a default initializer, where warnings about + // default-initializing resource types should be suppressed. + bool m_inSynthesizedDefaultInit = false; ExpandExpr* m_parentExpandExpr = nullptr; @@ -1734,6 +1747,14 @@ struct SemanticsVisitor : public SemanticsContext Expr** outToExpr, InitializerListExpr* fromInitializerListExpr); + /// Recursively walk a type to find resource types that would be default-initialized + /// and emit warnings. + void _warnAboutDefaultInitializedResources( + Type* type, + UInt argCount, + UInt& ioArgIndex, + SourceLoc loc); + /// Report that implicit type coercion is not possible. bool _failedCoercion(Type* toType, Expr** outToExpr, Expr* fromExpr, DiagnosticSink* sink); diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index aad63deb134..a1d5a0781b5 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -1770,6 +1770,11 @@ DIAGNOSTIC( Error, cannotUseInitializerListForCoopVectorOfUnknownSize, "cannot use initializer list for CoopVector of statically unknown size '$0'") +DIAGNOSTIC( + 30506, + Warning, + cannotDefaultInitializeResourceType, + "cannot default-initialize resource type '$0', leaving uninitialized") // 3062x: variables DIAGNOSTIC( @@ -2575,6 +2580,7 @@ DIAGNOSTIC( "capabilities are: '$2'") DIAGNOSTIC(41015, Warning, usingUninitializedOut, "use of uninitialized out parameter '$0'") DIAGNOSTIC(41016, Warning, usingUninitializedVariable, "use of uninitialized variable '$0'") +DIAGNOSTIC(41016, Warning, usingUninitializedValue, "use of uninitialized value of type '$0'") DIAGNOSTIC( 41017, Warning, diff --git a/source/slang/slang-ir-use-uninitialized-values.cpp b/source/slang/slang-ir-use-uninitialized-values.cpp index 368f366c349..59e244432e3 100644 --- a/source/slang/slang-ir-use-uninitialized-values.cpp +++ b/source/slang/slang-ir-use-uninitialized-values.cpp @@ -640,7 +640,19 @@ static void checkUninitializedValues(IRFunc* func, DiagnosticSink* sink) auto loads = getUnresolvedVariableLoads(reachability, inst); for (auto load : loads) { - sink->diagnose(load, Diagnostics::usingUninitializedVariable, inst); + // Check if we have a meaningful name for the variable + bool hasName = inst->findDecoration() != nullptr || + inst->findDecoration() != nullptr; + + if (hasName) + { + sink->diagnose(load, Diagnostics::usingUninitializedVariable, inst); + } + else + { + // For poison ops and other unnamed instructions, show type instead + sink->diagnose(load, Diagnostics::usingUninitializedValue, type); + } } } } diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index 78be183c304..5e4631dcf38 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -5473,6 +5473,13 @@ struct ExprLoweringVisitorBase : public ExprVisitor return LoweredValInfo::simple( getBuilder()->emitMakeTuple(irType, args.getCount(), args.getBuffer())); } + else if (auto resourceType = as(type)) + { + // In practice, a resource type must always be bound to a particular resource, + // so we return an undefined value here. We expect later instructions to assign + // a valid resource to this value before using it. + return LoweredValInfo::simple(getBuilder()->emitPoison(irType)); + } else if (auto declRefType = as(type)) { DeclRef declRef = declRefType->getDeclRef(); diff --git a/tests/diagnostics/uninitialized-resource-type.slang.expected b/tests/diagnostics/uninitialized-resource-type.slang.expected index a74d7f9fffd..59525411f01 100644 --- a/tests/diagnostics/uninitialized-resource-type.slang.expected +++ b/tests/diagnostics/uninitialized-resource-type.slang.expected @@ -1,5 +1,8 @@ result code = -1 standard error = { +tests/diagnostics/uninitialized-resource-type.slang(10): warning 30506: cannot default-initialize resource type 'Texture2D>', leaving uninitialized + Texture2D bar = {}; + ^ tests/diagnostics/uninitialized-resource-type.slang(40): warning 41016: use of uninitialized variable 'foo' const let result = process(foo); ^ diff --git a/tests/spirv/zero-init-resource-type-field.slang b/tests/spirv/zero-init-resource-type-field.slang new file mode 100644 index 00000000000..a0ded9d8b3e --- /dev/null +++ b/tests/spirv/zero-init-resource-type-field.slang @@ -0,0 +1,45 @@ +//TEST(compute, vulkan):COMPARE_COMPUTE_EX(filecheck-buffer=BUF):-vk -compute -output-using-type -Xslang -Wno-30506 -Xslang -Wno-41016 +//TEST:SIMPLE(filecheck=CHECK):-target spirv + +//TEST_INPUT: Texture2D(size=4, content = zero):name tex1 +Texture2D tex1; +SamplerState samp; + +//TEST_INPUT: ubuffer(data=[0 0 0 0], stride=1, count=4):out,name outputBuffer +RWStructuredBuffer outputBuffer; + +struct S { + float2 uv; + Texture2D t; +}; + +[numthreads(2, 2, 1)] +void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) +{ + uint index = dispatchThreadID.x + dispatchThreadID.y * 2; + float param = float(dispatchThreadID.x) / 2.0; + float2 coord = float2(dispatchThreadID.xy) / 2.0; + + bool b = (param >= 0.5); + + //CHECK: ([[# @LINE+1]]): warning 41016{{.*}}Texture2D + S s = (S)0; + s.uv = coord; + + if (b) { + s.t = tex1; + } + + float4 col = float4(1, 1, 1, 1); + + if (b) { + col = s.t.SampleLevel(samp, s.uv, 0); + } + + outputBuffer[index] = col.x; + // BUF: 1.000000 + // BUF: 0.000000 + // BUF: 1.000000 + // BUF: 0.000000 +} +