Skip to content

Commit 433ce86

Browse files
author
Tim Foley
authored
Initial support for explicit default initializers (shader-slang#1235)
This change makes it so that for a suitable type `MyType`, a variable declaration like: MyType v; is treated as if it were written: MyType v = MyType(); The definition of "suitable" here is that `MyType` needs to have an available `__init` declaration that can be invoked with zero arguments. I've added a test to confirm that the new behavior works in this specific case. There are a bunch of caveats to the feature as it stands today: * Just because `MyType` has a zero-parameter `__init`, that doesn't mean an array type like `MyType[10]` does, so arrays currently remain uninitialized by default. Fixing this gap requires careful consideration because some, but not all, array types should be default-initializable. * The change here should mean that a `struct` type with a field like `MyType f;` should count as having a default initial-value expression for that field, but I haven't confirmed that. * Even if a `struct` provides initial values for all its fields (e.g., `struct S { float f = 0; }`), that doesn't mean it has a default `__init` right now, so those `struct` types will still be left uninitialized by default. Converging all this behavior is still TBD. Just to be clear: there is no provision or plan in Slang to support destructors, RAII, copy constructors, move constructors, overloaded assignment operations, or any other features that buy heavily into the C++ model of how construction and destruction of values gets done. In fact, I'm not even 100% sure I like having this change in place at all, and I think we should reserve the right to revert it and say that only specific stdlib types get to opt in to default initialization along these lines.
1 parent 1f401d0 commit 433ce86

4 files changed

+108
-0
lines changed

source/slang/slang-check-decl.cpp

+67
Original file line numberDiff line numberDiff line change
@@ -829,10 +829,77 @@ namespace Slang
829829
{
830830
if (auto initExpr = varDecl->initExpr)
831831
{
832+
// If the variable has an explicit initial-value expression,
833+
// then we simply need to check that expression and coerce
834+
// it to the tyep of the variable.
835+
//
832836
initExpr = CheckTerm(initExpr);
833837
initExpr = coerce(varDecl->type.Ptr(), initExpr);
834838
varDecl->initExpr = initExpr;
835839
}
840+
else
841+
{
842+
// If a variable doesn't have an explicit initial-value
843+
// expression, it is still possible that it should
844+
// be initialized implicitly, because the type of the
845+
// variable has a default (zero parameter) initializer.
846+
// That is, for types where it is possible, we will
847+
// treat a variable declared like this:
848+
//
849+
// MyType myVar;
850+
//
851+
// as if it were declared as:
852+
//
853+
// MyType myVar = MyType();
854+
//
855+
// Rather than try to code up an ad hoc search for an
856+
// appropriate initializer here, we will instead fall
857+
// back on the general-purpose overload-resolution
858+
// machinery, which can handle looking up initializers
859+
// and filtering them to ones that are applicable
860+
// to our "call site" with zero arguments.
861+
//
862+
auto type = varDecl->getType();
863+
864+
OverloadResolveContext overloadContext;
865+
overloadContext.loc = varDecl->nameAndLoc.loc;
866+
overloadContext.mode = OverloadResolveContext::Mode::JustTrying;
867+
AddTypeOverloadCandidates(type, overloadContext);
868+
869+
if(overloadContext.bestCandidates.getCount() != 0)
870+
{
871+
// If there were multiple equally-good candidates to call,
872+
// then might have an ambiguity.
873+
//
874+
// Before issuing any kind of diagnostic we need to check
875+
// if any of those candidates are actually applicable,
876+
// because if they aren't then we actually just have
877+
// an uninitialized varaible.
878+
//
879+
if(overloadContext.bestCandidates[0].status != OverloadCandidate::Status::Applicable)
880+
return;
881+
882+
getSink()->diagnose(varDecl, Diagnostics::ambiguousDefaultInitializerForType, type);
883+
}
884+
else if(overloadContext.bestCandidate)
885+
{
886+
// If we are in the single-candidate case, then we again
887+
// want to ignore the case where that candidate wasn't
888+
// actually applicable, because declaring a variable
889+
// of a type that *doesn't* have a default initializer
890+
// isn't actually an error.
891+
//
892+
if(overloadContext.bestCandidate->status != OverloadCandidate::Status::Applicable)
893+
return;
894+
895+
// If we had a single best candidate *and* it was applicable,
896+
// then we use it to construct a new initial-value expression
897+
// for the variable, that will be used for all downstream
898+
// code generation.
899+
//
900+
varDecl->initExpr = CompleteOverloadCandidate(overloadContext, *overloadContext.bestCandidate);
901+
}
902+
}
836903
}
837904

838905
// Fill in default substitutions for the 'subtype' part of a type constraint decl

source/slang/slang-diagnostic-defs.h

+2
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,8 @@ DIAGNOSTIC(30504, Error, cannotUseInitializerListForType, "cannot use initialize
314314
// 306xx: variables
315315
DIAGNOSTIC(30600, Error, varWithoutTypeMustHaveInitializer, "a variable declaration without an initial-value expression must be given an explicit type");
316316

317+
DIAGNOSTIC(30610, Error, ambiguousDefaultInitializerForType, "more than one default initializer was found for type '$0'")
318+
317319
// 307xx: parameters
318320
DIAGNOSTIC(30700, Error, outputParameterCannotHaveDefaultValue, "an 'out' or 'inout' parameter cannot have a default-value expression");
319321

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// default-initializer.slang
2+
3+
// Confirm that a type with a default initializer gets
4+
// default-initialized where appropriate.
5+
6+
//TEST(compute):COMPARE_COMPUTE:
7+
//TEST(compute):COMPARE_COMPUTE:-cpu
8+
9+
struct Stuff
10+
{
11+
int value;
12+
13+
__init()
14+
{
15+
value = 16;
16+
}
17+
}
18+
19+
int test(int value)
20+
{
21+
Stuff s;
22+
return value * s.value + value;
23+
}
24+
25+
//TEST_INPUT:ubuffer(data=[0 1 2 3], stride=4):out,name=outputBuffer
26+
RWStructuredBuffer<int> outputBuffer : register(u0);
27+
28+
[numthreads(4, 1, 1)]
29+
void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
30+
{
31+
uint tid = dispatchThreadID.x;
32+
int inVal = outputBuffer[tid];
33+
int outVal = test(inVal);
34+
outputBuffer[tid] = outVal;
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
0
2+
11
3+
22
4+
33

0 commit comments

Comments
 (0)