Skip to content

Commit 3b0de8b

Browse files
authored
Add diagnostic to prevent defining unsized variables. (shader-slang#4168)
* Add diagnostic to prevent defining unsized static variables. * Fix tests. * Add more tests. * Fix to allow defining variables of link-time size. * update diagnostic message. * Fix tests. * Simplify code.
1 parent cc88530 commit 3b0de8b

9 files changed

+170
-68
lines changed

source/slang/slang-ast-decl.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ enum class TypeTag
137137
{
138138
None = 0,
139139
Unsized = 1,
140-
Incomplete = 2
140+
Incomplete = 2,
141+
LinkTimeSized = 4,
141142
};
142143

143144
// Declaration of a type that represents some sort of aggregate

source/slang/slang-check-conformance.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,8 @@ namespace Slang
274274
}
275275
else if (auto intVal = arrayType->getElementCount())
276276
{
277-
sized = !intVal->isLinkTimeVal();
277+
sized = true;
278+
typeTag = (TypeTag)((int)typeTag | (int)TypeTag::LinkTimeSized);
278279
}
279280
if (!sized)
280281
typeTag = (TypeTag)((int)typeTag | (int)TypeTag::Unsized);

source/slang/slang-check-decl.cpp

+18-5
Original file line numberDiff line numberDiff line change
@@ -2040,11 +2040,14 @@ namespace Slang
20402040
}
20412041
}
20422042

2043-
if (auto parentDecl = as<AggTypeDecl>(getParentDecl(varDecl)))
2043+
TypeTag varTypeTags = getTypeTags(varDecl->getType());
2044+
auto parentDecl = as<AggTypeDecl>(getParentDecl(varDecl));
2045+
if (parentDecl)
20442046
{
2045-
auto typeTags = getTypeTags(varDecl->getType());
2046-
parentDecl->addTag(typeTags);
2047-
if ((int)typeTags & (int)TypeTag::Unsized)
2047+
parentDecl->addTag(varTypeTags);
2048+
auto unsizedMask = (int)TypeTag::Unsized;
2049+
bool isUnknownSize = (((int)varTypeTags & unsizedMask) != 0);
2050+
if (isUnknownSize)
20482051
{
20492052
// Unsized decl must appear as the last member of the struct.
20502053
for (auto memberIdx = parentDecl->members.getCount() - 1; memberIdx >= 0; memberIdx--)
@@ -2064,7 +2067,17 @@ namespace Slang
20642067
}
20652068
}
20662069
}
2067-
2070+
bool isGlobalOrLocalVar = !isGlobalShaderParameter(varDecl) && !as<ParamDecl>(varDecl) &&
2071+
(!parentDecl || isEffectivelyStatic(varDecl));
2072+
if (isGlobalOrLocalVar)
2073+
{
2074+
bool isUnsized = (((int)varTypeTags & (int)TypeTag::Unsized) != 0);
2075+
if (isUnsized)
2076+
{
2077+
getSink()->diagnose(varDecl, Diagnostics::varCannotBeUnsized);
2078+
}
2079+
}
2080+
20682081
if (auto elementType = getConstantBufferElementType(varDecl->getType()))
20692082
{
20702083
if (doesTypeHaveTag(elementType, TypeTag::Incomplete))

source/slang/slang-check-overload.cpp

+17-2
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,14 @@ namespace Slang
263263
//
264264
bool success = true;
265265

266+
auto maybeReportGeneralError = [&]()
267+
{
268+
if (context.mode != OverloadResolveContext::Mode::JustTrying)
269+
{
270+
getSink()->diagnose(context.loc, Diagnostics::cannotSpecializeGeneric, candidate.item.declRef);
271+
}
272+
};
273+
266274
Index aa = 0;
267275
for (auto memberRef : getMembers(m_astBuilder, genericDeclRef))
268276
{
@@ -287,7 +295,10 @@ namespace Slang
287295
// or this reference is ill-formed.
288296
auto substType = typeParamRef.substitute(m_astBuilder, typeParamRef.getDecl()->initType.type);
289297
if (!substType)
298+
{
299+
maybeReportGeneralError();
290300
return false;
301+
}
291302
checkedArgs.add(substType);
292303
continue;
293304
}
@@ -355,7 +366,10 @@ namespace Slang
355366
ConstantFoldingCircularityInfo newCircularityInfo(valParamRef.getDecl(), nullptr);
356367
auto defaultVal = tryConstantFoldExpr(valParamRef.substitute(m_astBuilder, valParamRef.getDecl()->initExpr), ConstantFoldingKind::CompileTime, &newCircularityInfo);
357368
if (!defaultVal)
369+
{
370+
maybeReportGeneralError();
358371
return false;
372+
}
359373
checkedArgs.add(defaultVal);
360374
continue;
361375
}
@@ -475,8 +489,9 @@ namespace Slang
475489
break;
476490

477491
case OverloadCandidate::Flavor::Generic:
478-
return TryCheckGenericOverloadCandidateTypes(context, candidate);
479-
492+
{
493+
return TryCheckGenericOverloadCandidateTypes(context, candidate);
494+
}
480495
default:
481496
SLANG_UNEXPECTED("unknown flavor of overload candidate");
482497
break;

source/slang/slang-diagnostic-defs.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,10 @@ DIAGNOSTIC(30066, Error, classCanOnlyBeInitializedWithNew, "a class can only be
325325
DIAGNOSTIC(30067, Error, mutatingMethodOnFunctionInputParameterError, "mutating method '$0' called on `in` parameter '$1'; changes will not be visible to caller. copy the parameter into a local variable if this behavior is intended")
326326
DIAGNOSTIC(30068, Warning, mutatingMethodOnFunctionInputParameterWarning, "mutating method '$0' called on `in` parameter '$1'; changes will not be visible to caller. copy the parameter into a local variable if this behavior is intended")
327327

328-
DIAGNOSTIC(30070, Error, unsizedMemberMustAppearLast, "unsized member can only appear as the last member in a composite type.")
328+
DIAGNOSTIC(30070, Error, unsizedMemberMustAppearLast, "member with unknown size at compile time can only appear as the last member in a composite type.")
329+
DIAGNOSTIC(30071, Error, varCannotBeUnsized, "cannot instantiate a variable of unsized type.")
330+
331+
DIAGNOSTIC(30075, Error, cannotSpecializeGeneric, "cannot specialize generic '$0' with the provided arguments.")
329332

330333
DIAGNOSTIC(30100, Error, staticRefToNonStaticMember, "type '$0' cannot be used to refer to non-static member '$1'")
331334
DIAGNOSTIC(30101, Error, cannotDereferenceType, "cannot dereference type '$0', do you mean to use '.'?")

source/slang/slang-lower-to-ir.cpp

+75-58
Original file line numberDiff line numberDiff line change
@@ -7782,6 +7782,33 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
77827782
return false;
77837783
}
77847784

7785+
struct NestedContext
7786+
{
7787+
IRGenEnv subEnvStorage;
7788+
IRBuilder subBuilderStorage;
7789+
IRGenContext subContextStorage;
7790+
7791+
NestedContext(DeclLoweringVisitor* outer)
7792+
: subBuilderStorage(*outer->getBuilder())
7793+
, subContextStorage(*outer->context)
7794+
{
7795+
auto outerContext = outer->context;
7796+
7797+
subEnvStorage.outer = outerContext->env;
7798+
7799+
subContextStorage.irBuilder = &subBuilderStorage;
7800+
subContextStorage.env = &subEnvStorage;
7801+
7802+
subContextStorage.thisType = outerContext->thisType;
7803+
subContextStorage.thisTypeWitness = outerContext->thisTypeWitness;
7804+
7805+
subContextStorage.returnDestination = LoweredValInfo();
7806+
}
7807+
7808+
IRBuilder* getBuilder() { return &subBuilderStorage; }
7809+
IRGenContext* getContext() { return &subContextStorage; }
7810+
};
7811+
77857812
LoweredValInfo lowerGlobalShaderParam(VarDecl* decl)
77867813
{
77877814
IRType* paramType = lowerType(context, decl->getType());
@@ -7938,86 +7965,66 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
79387965
return lowerGlobalConstantDecl(decl);
79397966
}
79407967

7941-
IRType* varType = lowerType(context, decl->getType());
7968+
NestedContext nested(this);
7969+
auto subBuilder = nested.getBuilder();
7970+
auto subContext = nested.getContext();
79427971

7943-
auto builder = getBuilder();
7972+
IRGeneric* outerGeneric = nullptr;
7973+
7974+
// If we are static, then we need to insert the declaration before the parent.
7975+
// This tries to match the behavior of previous `lowerFunctionStaticConstVarDecl` functionality
7976+
if (isFunctionStaticVarDecl(decl))
7977+
{
7978+
// We need to insert the constant at a level above
7979+
// the function being emitted. This will usually
7980+
// be the global scope, but it might be an outer
7981+
// generic if we are lowering a generic function.
7982+
subBuilder->setInsertBefore(subBuilder->getFunc());
7983+
}
7984+
else if (!isFunctionVarDecl(decl))
7985+
{
7986+
outerGeneric = emitOuterGenerics(subContext, decl, decl);
7987+
}
7988+
7989+
IRType* varType = lowerType(subContext, decl->getType());
79447990

79457991
// TODO(JS): Do we create something derived from IRGlobalVar? Or do we use
79467992
// a decoration to identify an *actual* global?
79477993

7948-
IRGlobalValueWithCode* irGlobal = builder->createGlobalVar(varType);
7949-
LoweredValInfo globalVal = LoweredValInfo::ptr(irGlobal);
7994+
IRGlobalValueWithCode* irGlobal = subBuilder->createGlobalVar(varType);
79507995

7951-
addLinkageDecoration(context, irGlobal, decl);
7952-
addNameHint(context, irGlobal, decl);
7996+
addLinkageDecoration(subContext, irGlobal, decl);
7997+
addNameHint(subContext, irGlobal, decl);
79537998

7954-
maybeSetRate(context, irGlobal, decl);
7999+
maybeSetRate(subContext, irGlobal, decl);
79558000

7956-
addVarDecorations(context, irGlobal, decl);
7957-
maybeAddDebugLocationDecoration(context, irGlobal);
8001+
addVarDecorations(subContext, irGlobal, decl);
8002+
maybeAddDebugLocationDecoration(subContext, irGlobal);
79588003

79598004
if (decl)
79608005
{
7961-
builder->addHighLevelDeclDecoration(irGlobal, decl);
8006+
subBuilder->addHighLevelDeclDecoration(irGlobal, decl);
79628007
}
79638008

7964-
// A global variable's SSA value is a *pointer* to
7965-
// the underlying storage.
7966-
context->setGlobalValue(decl, globalVal);
7967-
79688009
if( auto initExpr = decl->initExpr )
79698010
{
7970-
IRBuilder subBuilderStorage = *getBuilder();
7971-
IRBuilder* subBuilder = &subBuilderStorage;
7972-
79738011
subBuilder->setInsertInto(irGlobal);
79748012

7975-
IRGenContext subContextStorage = *context;
7976-
IRGenContext* subContext = &subContextStorage;
7977-
7978-
subContext->irBuilder = subBuilder;
7979-
7980-
// TODO: set up a parent IR decl to put the instructions into
7981-
79828013
IRBlock* entryBlock = subBuilder->emitBlock();
79838014
subBuilder->setInsertInto(entryBlock);
79848015

79858016
LoweredValInfo initVal = lowerLValueExpr(subContext, initExpr);
79868017
subContext->irBuilder->emitReturn(getSimpleVal(subContext, initVal));
79878018
}
79888019

7989-
irGlobal->moveToEnd();
8020+
// A global variable's SSA value is a *pointer* to
8021+
// the underlying storage.
8022+
auto loweredValue = LoweredValInfo::ptr(finishOuterGenerics(subBuilder, irGlobal, outerGeneric));
8023+
context->setGlobalValue(decl, loweredValue);
79908024

7991-
return globalVal;
8025+
return loweredValue;
79928026
}
79938027

7994-
struct NestedContext
7995-
{
7996-
IRGenEnv subEnvStorage;
7997-
IRBuilder subBuilderStorage;
7998-
IRGenContext subContextStorage;
7999-
8000-
NestedContext(DeclLoweringVisitor* outer)
8001-
: subBuilderStorage(*outer->getBuilder())
8002-
, subContextStorage(*outer->context)
8003-
{
8004-
auto outerContext = outer->context;
8005-
8006-
subEnvStorage.outer = outerContext->env;
8007-
8008-
subContextStorage.irBuilder = &subBuilderStorage;
8009-
subContextStorage.env = &subEnvStorage;
8010-
8011-
subContextStorage.thisType = outerContext->thisType;
8012-
subContextStorage.thisTypeWitness = outerContext->thisTypeWitness;
8013-
8014-
subContextStorage.returnDestination = LoweredValInfo();
8015-
}
8016-
8017-
IRBuilder* getBuilder() { return &subBuilderStorage; }
8018-
IRGenContext* getContext() { return &subContextStorage; }
8019-
};
8020-
80218028
LoweredValInfo lowerFunctionStaticConstVarDecl(
80228029
VarDeclBase* decl)
80238030
{
@@ -10235,10 +10242,10 @@ bool canDeclLowerToAGeneric(Decl* decl)
1023510242
// a generic that returns a type (a simple type-level function).
1023610243
if(as<TypeDefDecl>(decl)) return true;
1023710244

10238-
// If we have a variable declaration that is *static* and *const* we can lower to a generic
10245+
// A static member variable declaration can be lowered into a generic.
1023910246
if (auto varDecl = as<VarDecl>(decl))
1024010247
{
10241-
if (varDecl->hasModifier<HLSLStaticModifier>() && varDecl->hasModifier<ConstModifier>())
10248+
if (varDecl->hasModifier<HLSLStaticModifier>())
1024210249
{
1024310250
return !isFunctionVarDecl(varDecl);
1024410251
}
@@ -10360,7 +10367,9 @@ LoweredValInfo emitDeclRef(
1036010367
// We can only really specialize things that map to single values.
1036110368
// It would be an error if we got a non-`None` value that
1036210369
// wasn't somehow a single value.
10363-
auto irGenericVal = getSimpleVal(context, genericVal);
10370+
genericVal = materialize(context, genericVal);
10371+
auto irGenericVal = genericVal.val;
10372+
SLANG_ASSERT(irGenericVal);
1036410373

1036510374
// We have the IR value for the generic we'd like to specialize,
1036610375
// and now we need to get the value for the arguments.
@@ -10392,8 +10401,16 @@ LoweredValInfo emitDeclRef(
1039210401
irGenericVal,
1039310402
irArgs.getCount(),
1039410403
irArgs.getBuffer());
10395-
10396-
return LoweredValInfo::simple(irSpecializedVal);
10404+
switch (genericVal.flavor)
10405+
{
10406+
case LoweredValInfo::Flavor::Simple:
10407+
return LoweredValInfo::simple(irSpecializedVal);
10408+
case LoweredValInfo::Flavor::Ptr:
10409+
return LoweredValInfo::ptr(irSpecializedVal);
10410+
default:
10411+
SLANG_UNEXPECTED("unhandled lowered value flavor");
10412+
UNREACHABLE_RETURN(LoweredValInfo());
10413+
}
1039710414
}
1039810415
else if(auto thisTypeSubst = as<LookupDeclRef>(subst))
1039910416
{

source/slang/slang-syntax.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ void printDiagnosticArg(StringBuilder& sb, Decl* decl)
2525

2626
void printDiagnosticArg(StringBuilder& sb, DeclRefBase* declRefBase)
2727
{
28+
if (!declRefBase)
29+
return;
2830
printDiagnosticArg(sb, declRefBase->getDecl());
2931
}
3032

tests/bugs/gh-4150.slang

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//TEST:SIMPLE(filecheck=CHECK1):-target spirv -DERROR1
2+
//TEST:SIMPLE(filecheck=CHECK2):-target spirv -DERROR2
3+
extern static const int constValue = 1;
4+
5+
struct RWTex<T : __BuiltinRealType, let N : uint>
6+
{
7+
#ifdef ERROR2
8+
// expect error: cannot define static member with unsized type.
9+
// CHECK2:([[# @LINE+1]]): error 30071
10+
static [[vk::binding(0, 0)]] vector<T,N> rwtable[];
11+
#else
12+
static [[vk::binding(0, 0)]] vector<T,N> rwtable[4];
13+
#endif
14+
static vector<T, N> get(uint image_index) { return rwtable[image_index]; }
15+
}
16+
17+
struct Push
18+
{
19+
uint image_id;
20+
};
21+
[[vk::push_constant]] Push p;
22+
RWStructuredBuffer<float3> output;
23+
[shader("compute")]
24+
[numthreads(8, 8, 1)]
25+
void main(uint3 pixel_i : SV_DispatchThreadID)
26+
{
27+
output[0] =
28+
#ifdef ERROR1
29+
// expect error: trying to specialize RWTex, which has two arguments, with only one argument.
30+
// CHECK1:([[# @LINE+1]]): error 30075
31+
RWTex<float3>::get(p.image_id);
32+
#else
33+
RWTex<float, 3>::get(p.image_id);
34+
#endif
35+
//CHECK1:([[# @LINE+1]]): error 30071
36+
static float sa1[];
37+
38+
//CHECK1:([[# @LINE+1]]): error 30071
39+
float sa2[];
40+
41+
//CHECK1-NOT:([[# @LINE+1]]): error
42+
static float sa3[] = { 1, 2, 3 }; // should be ok.
43+
44+
//CHECK1-NOT:([[# @LINE+1]]): error
45+
float sa4[] = { 1, 2, 3 }; // should be ok.
46+
47+
//CHECK1-NOT:([[# @LINE+1]]): error
48+
float sa5[constValue]; // should be ok.
49+
}

tests/diagnostics/unsized.slang

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ extern static const int size = 1;
55
struct V
66
{
77
// CHECK-DAG: ([[# @LINE+1]]): error 30070
8+
int c[];
89
int b[size];
910
int a[];
1011
}

0 commit comments

Comments
 (0)