summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2019-01-16 11:50:14 -0800
committerGitHub <noreply@github.com>2019-01-16 11:50:14 -0800
commit8e47a3802d4d74eb11620f147ef5b29b8e931d35 (patch)
tree0028cc2b77fa059e4650b7a227996b2d0e98ac91
parent86e11e0e111fab60b9517056ac049bfac6e3bd25 (diff)
Improve handling of {} initializer list expressions (#778)
Fixes #775 It was reported (in #775) that Slang doesn't handle initializer-list syntax when initializing matrix variables. When starting on a fix for that it became apparent that the time was right to fix two broad issues in the compiler's current handling of `{}`-enclosed initializer lists. The first issue was that the front-end checking of initializer lists wasn't handling the C-style behavior where an initializer list can either contain nested `{}`-enclosed lists for sub-arrays/-structures, or directly contain "leaf" values for initializing those aggregates. For example, the following two variable declarations ought to be equivalent: ```hlsl int4 a[] = { {1, 2, 3, 4}, {5, 6, 7, 8} }; int4 b[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; ``` Getting this distinction right is important because we want to support initializing a matrix either from a list of vectors for its rows, or a list of scalars for its elements (in row-major order). The front-end semantic checking logic for initializer lists was revamped so that it conceptually tries to "read" an expression of a desired type from the initializer list, and decides at each step whether to consume a single expression by coercing it to the desired type, or to recursively read multiple sub-values to construct the type as an aggregate. The logic for deciding between direct vs aggregate initialization could potentially use some tweaking, but luckily it should always handle the case where users introduce explicit `{}`-enclosed sub-lists to make their intention clear, so that existing Slang code should continue to work as before. The second issue was that initializers without the expected number of elements weren't implemented in code generation, so they would lead to internal compiler errors. This change revamps the codegen logic for initializer lists so that it can synthesize default values for fields/elements that were left out during initialization. This includes an attempt to support default initialization of `struct` fields based on explicitly written initialization expressions.
-rw-r--r--source/slang/check.cpp629
-rw-r--r--source/slang/compiler.h4
-rw-r--r--source/slang/diagnostic-defs.h8
-rw-r--r--source/slang/diagnostics.cpp5
-rw-r--r--source/slang/diagnostics.h4
-rw-r--r--source/slang/emit.cpp2
-rw-r--r--source/slang/ir-constexpr.cpp2
-rw-r--r--source/slang/ir-inst-defs.h2
-rw-r--r--source/slang/ir-insts.h5
-rw-r--r--source/slang/ir.cpp10
-rw-r--r--source/slang/lower-to-ir.cpp208
-rw-r--r--source/slang/syntax.cpp30
-rw-r--r--source/slang/type-defs.h4
-rw-r--r--tests/bugs/gh-775.slang32
-rw-r--r--tests/bugs/gh-775.slang.expected.txt4
-rw-r--r--tests/compute/init-list-defaults.slang37
-rw-r--r--tests/compute/init-list-defaults.slang.expected.txt4
-rw-r--r--tests/compute/struct-default-init.slang40
-rw-r--r--tests/compute/struct-default-init.slang.expected.txt4
19 files changed, 797 insertions, 237 deletions
diff --git a/source/slang/check.cpp b/source/slang/check.cpp
index 04c41c4b0..2fa4e9bc7 100644
--- a/source/slang/check.cpp
+++ b/source/slang/check.cpp
@@ -1319,48 +1319,228 @@ namespace Slang
return kConversionCost_Explicit;
}
- // Central engine for implementing implicit coercion logic
- bool TryCoerceImpl(
- RefPtr<Type> toType, // the target type for conversion
- RefPtr<Expr>* outToExpr, // (optional) a place to stuff the target expression
- RefPtr<Type> fromType, // the source type for the conversion
- RefPtr<Expr> fromExpr, // the source expression
- ConversionCost* outCost) // (optional) a place to stuff the conversion cost
+ bool isEffectivelyScalarForInitializerLists(
+ RefPtr<Type> type)
{
- // Easy case: the types are equal
- if (toType->Equals(fromType))
+ if(type->As<ArrayExpressionType>()) return false;
+ if(type->As<VectorExpressionType>()) return false;
+ if(type->As<MatrixExpressionType>()) return false;
+
+ if(type->As<BasicExpressionType>())
{
- if (outToExpr)
- *outToExpr = fromExpr;
- if (outCost)
- *outCost = kConversionCost_None;
return true;
}
- // If either type is an error, then let things pass.
- if (toType->As<ErrorType>() || fromType->As<ErrorType>())
+ if(type->As<ResourceType>())
+ {
+ return true;
+ }
+ if(type->As<UntypedBufferResourceType>())
+ {
+ return true;
+ }
+ if(type->As<SamplerStateType>())
{
- if (outToExpr)
- *outToExpr = CreateImplicitCastExpr(toType, fromExpr);
- if (outCost)
- *outCost = kConversionCost_None;
return true;
}
- // Coercion from an initializer list is allowed for many types
- if( auto fromInitializerListExpr = fromExpr.As<InitializerListExpr>())
+ if(auto declRefType = type->As<DeclRefType>())
{
- auto argCount = fromInitializerListExpr->args.Count();
+ if(declRefType->declRef.As<StructDecl>())
+ return false;
+ }
- // In the case where we need to build a reuslt expression,
- // we will collect the new arguments here
- List<RefPtr<Expr>> coercedArgs;
+ return true;
+ }
+
+ /// Should the provided expression (from an initializer list) be used directly to initialize `toType`?
+ bool shouldUseInitializerDirectly(
+ RefPtr<Type> toType,
+ RefPtr<Expr> fromExpr)
+ {
+ // A nested initializer list should always be used directly.
+ //
+ if(fromExpr.As<InitializerListExpr>())
+ {
+ return true;
+ }
+
+ // If the desired type is a scalar, then we should always initialize
+ // directly, since it isn't an aggregate.
+ //
+ if(isEffectivelyScalarForInitializerLists(toType))
+ return true;
+
+ // If the type we are initializing isn't effectively scalar,
+ // but the initialization expression *is*, then it doesn't
+ // seem like direct initialization is intended.
+ //
+ if(isEffectivelyScalarForInitializerLists(fromExpr->type))
+ return false;
+
+ // Once the above cases are handled, the main thing
+ // we want to check for is whether a direct initialization
+ // is possible (a type conversion exists).
+ //
+ return CanCoerce(toType, fromExpr->type);
+ }
+
+ bool tryReadArgFromInitializerList(
+ RefPtr<Type> toType,
+ RefPtr<Expr>* outToExpr,
+ RefPtr<InitializerListExpr> fromInitializerListExpr,
+ UInt &ioInitArgIndex)
+ {
+ // First, we will check if we have run out of arguments
+ // on the initializer list.
+ //
+ UInt initArgCount = fromInitializerListExpr->args.Count();
+ if(ioInitArgIndex >= initArgCount)
+ {
+ // If we are at the end of the initializer list,
+ // then our ability to read an argument depends
+ // on whether the type we are trying to read
+ // is default-initializable.
+ //
+ // For now, we will just pretend like everything
+ // is default-initializable and move along.
+ return true;
+ }
+
+ // Okay, we have at least one initializer list expression,
+ // so we will look at the next expression and decide
+ // whether to use it to initialize the desired type
+ // directly (possibly via casts), or as the first sub-expression
+ // for aggregate initialization.
+ //
+ auto firstInitExpr = fromInitializerListExpr->args[ioInitArgIndex];
+ if(shouldUseInitializerDirectly(toType, firstInitExpr))
+ {
+ ioInitArgIndex++;
+ return TryCoerceImpl(
+ toType,
+ outToExpr,
+ firstInitExpr->type,
+ firstInitExpr,
+ nullptr);
+ }
+
+ // If there is somehow an error in one of the initialization
+ // expressions, then everything could be thrown off and we
+ // shouldn't keep trying to read arguments.
+ //
+ if( IsErrorExpr(firstInitExpr) )
+ {
+ // Stop reading arguments, as if we'd reached
+ // the end of the list.
+ //
+ ioInitArgIndex = initArgCount;
+ return true;
+ }
+
+ // The fallback case is to recursively read the
+ // type from the same list as an aggregate.
+ //
+ return tryReadAggregateFromInitializerList(
+ toType,
+ outToExpr,
+ fromInitializerListExpr,
+ ioInitArgIndex);
+ }
+
+ bool tryReadAggregateFromInitializerList(
+ RefPtr<Type> inToType,
+ RefPtr<Expr>* outToExpr,
+ RefPtr<InitializerListExpr> fromInitializerListExpr,
+ UInt &ioArgIndex)
+ {
+ auto toType = inToType;
+ UInt argCount = fromInitializerListExpr->args.Count();
- if (auto toVecType = toType->As<VectorExpressionType>())
+ // In the case where we need to build a reuslt expression,
+ // we will collect the new arguments here
+ List<RefPtr<Expr>> coercedArgs;
+
+ if(isEffectivelyScalarForInitializerLists(toType))
+ {
+ // For any type that is effectively a non-aggregate,
+ // we expect to read a single value from the initializer list
+ //
+ if(ioArgIndex < argCount)
+ {
+ auto arg = fromInitializerListExpr->args[ioArgIndex++];
+ return TryCoerceImpl(
+ toType,
+ outToExpr,
+ arg->type,
+ arg,
+ nullptr);
+ }
+ else
+ {
+ // If there wasn't an initialization
+ // expression to be found, then we need
+ // to perform default initialization here.
+ //
+ // We will let this case come through the front-end
+ // as an `InitializerListExpr` with zero arguments,
+ // and then have the IR generation logic deal with
+ // synthesizing default values.
+ }
+ }
+ else if (auto toVecType = toType->As<VectorExpressionType>())
+ {
+ auto toElementCount = toVecType->elementCount;
+ auto toElementType = toVecType->elementType;
+
+ UInt elementCount = 0;
+ if (auto constElementCount = toElementCount.As<ConstantIntVal>())
+ {
+ elementCount = (UInt) constElementCount->value;
+ }
+ else
+ {
+ // We don't know the element count statically,
+ // so what are we supposed to be doing?
+ //
+ if(outToExpr)
+ {
+ getSink()->diagnose(fromInitializerListExpr, Diagnostics::cannotUseInitializerListForVectorOfUnknownSize, toElementCount);
+ }
+ return false;
+ }
+
+ for(UInt ee = 0; ee < elementCount; ++ee)
{
- auto toElementCount = toVecType->elementCount;
- auto toElementType = toVecType->elementType;
+ RefPtr<Expr> coercedArg;
+ bool argResult = tryReadArgFromInitializerList(
+ toElementType,
+ outToExpr ? &coercedArg : nullptr,
+ fromInitializerListExpr,
+ ioArgIndex);
+
+ // No point in trying further if any argument fails
+ if(!argResult)
+ return false;
+
+ if( coercedArg )
+ {
+ coercedArgs.Add(coercedArg);
+ }
+ }
+ }
+ else if(auto toArrayType = toType->As<ArrayExpressionType>())
+ {
+ // TODO(tfoley): If we can compute the size of the array statically,
+ // then we want to check that there aren't too many initializers present
+
+ auto toElementType = toArrayType->baseType;
+ if(auto toElementCount = toArrayType->ArrayLength)
+ {
+ // In the case of a sized array, we need to check that the number
+ // of elements being initialized matches what was declared.
+ //
UInt elementCount = 0;
if (auto constElementCount = toElementCount.As<ConstantIntVal>())
{
@@ -1370,127 +1550,234 @@ namespace Slang
{
// We don't know the element count statically,
// so what are we supposed to be doing?
- elementCount = fromInitializerListExpr->args.Count();
+ //
+ if(outToExpr)
+ {
+ getSink()->diagnose(fromInitializerListExpr, Diagnostics::cannotUseInitializerListForArrayOfUnknownSize, toElementCount);
+ }
+ return false;
}
- // TODO: need to check that the element count
- // for the vector type matches the argument
- // count for the initializer list, or else
- // fix them up to match.
-
- for(auto arg : fromInitializerListExpr->args)
+ for(UInt ee = 0; ee < elementCount; ++ee)
{
RefPtr<Expr> coercedArg;
- ConversionCost argCost;
-
- bool argResult = TryCoerceImpl(
+ bool argResult = tryReadArgFromInitializerList(
toElementType,
outToExpr ? &coercedArg : nullptr,
- arg->type,
- arg,
- outCost ? &argCost : nullptr);
+ fromInitializerListExpr,
+ ioArgIndex);
// No point in trying further if any argument fails
if(!argResult)
return false;
- // TODO(tfoley): what to do with cost?
- // This only matters if/when we allow an initializer list as an argument to
- // an overloaded call.
-
- if( outToExpr )
+ if( coercedArg )
{
coercedArgs.Add(coercedArg);
}
}
}
- //
- // TODO(tfoley): How to handle matrices here?
- // Should they expect individual scalars, or support
- // vectors for the rows?
- //
- if(auto toDeclRefType = toType->As<DeclRefType>())
+ else
{
- auto toTypeDeclRef = toDeclRefType->declRef;
- if(auto toStructDeclRef = toTypeDeclRef.As<StructDecl>())
+ // In the case of an unsized array type, we will use the
+ // number of arguments to the initializer to determine
+ // the element count.
+ //
+ UInt elementCount = 0;
+ while(ioArgIndex < argCount)
{
- // Trying to initialize a `struct` type given an initializer list.
- // We will go through the fields in order and try to match them
- // up with initializer arguments.
-
+ RefPtr<Expr> coercedArg;
+ bool argResult = tryReadArgFromInitializerList(
+ toElementType,
+ outToExpr ? &coercedArg : nullptr,
+ fromInitializerListExpr,
+ ioArgIndex);
- UInt argIndex = 0;
- for(auto fieldDeclRef : getMembersOfType<StructField>(toStructDeclRef))
- {
- if(argIndex >= argCount)
- {
- // We've consumed all the arguments, so we should stop
- break;
- }
+ // No point in trying further if any argument fails
+ if(!argResult)
+ return false;
- auto arg = fromInitializerListExpr->args[argIndex++];
+ elementCount++;
- //
- RefPtr<Expr> coercedArg;
- ConversionCost argCost;
+ if( coercedArg )
+ {
+ coercedArgs.Add(coercedArg);
+ }
+ }
- bool argResult = TryCoerceImpl(
- GetType(fieldDeclRef),
- outToExpr ? &coercedArg : nullptr,
- arg->type,
- arg,
- outCost ? &argCost : nullptr);
+ // We have a new type for the conversion, based on what
+ // we learned.
+ toType = getSession()->getArrayType(
+ toElementType,
+ new ConstantIntVal(elementCount));
+ }
+ }
+ else if(auto toMatrixType = toType->As<MatrixExpressionType>())
+ {
+ // In the general case, the initializer list might comprise
+ // both vectors and scalars.
+ //
+ // The traditional HLSL compilers treat any vectors in
+ // the initializer list exactly equivalent to their sequence
+ // of scalar elements, and don't care how this might, or
+ // might not, align with the rows of the matrix.
+ //
+ // We will draw a line in the sand and say that an initializer
+ // list for a matrix will act as if the matrix type were an
+ // array of vectors for the rows.
- // No point in trying further if any argument fails
- if(!argResult)
- return false;
- // TODO(tfoley): what to do with cost?
- // This only matters if/when we allow an initializer list as an argument to
- // an overloaded call.
+ UInt rowCount = 0;
+ auto toRowType = createVectorType(
+ toMatrixType->getElementType(),
+ toMatrixType->getColumnCount());
- if( outToExpr )
- {
- coercedArgs.Add(coercedArg);
- }
- }
+ if (auto constRowCount = dynamic_cast<ConstantIntVal*>(toMatrixType->getRowCount()))
+ {
+ rowCount = (UInt) constRowCount->value;
+ }
+ else
+ {
+ // We don't know the element count statically,
+ // so what are we supposed to be doing?
+ //
+ if(outToExpr)
+ {
+ getSink()->diagnose(fromInitializerListExpr, Diagnostics::cannotUseInitializerListForMatrixOfUnknownSize, toMatrixType->getRowCount());
}
+ return false;
}
- else if(auto toArrayType = toType->As<ArrayExpressionType>())
+
+ for(UInt rr = 0; rr < rowCount; ++rr)
{
- // TODO(tfoley): If we can compute the size of the array statically,
- // then we want to check that there aren't too many initializers present
+ RefPtr<Expr> coercedArg;
+ bool argResult = tryReadArgFromInitializerList(
+ toRowType,
+ outToExpr ? &coercedArg : nullptr,
+ fromInitializerListExpr,
+ ioArgIndex);
- auto toElementType = toArrayType->baseType;
+ // No point in trying further if any argument fails
+ if(!argResult)
+ return false;
- for(auto& arg : fromInitializerListExpr->args)
+ if( coercedArg )
+ {
+ coercedArgs.Add(coercedArg);
+ }
+ }
+ }
+ else if(auto toDeclRefType = toType->As<DeclRefType>())
+ {
+ auto toTypeDeclRef = toDeclRefType->declRef;
+ if(auto toStructDeclRef = toTypeDeclRef.As<StructDecl>())
+ {
+ // Trying to initialize a `struct` type given an initializer list.
+ // We will go through the fields in order and try to match them
+ // up with initializer arguments.
+ //
+ for(auto fieldDeclRef : getMembersOfType<StructField>(toStructDeclRef))
{
RefPtr<Expr> coercedArg;
- ConversionCost argCost;
-
- bool argResult = TryCoerceImpl(
- toElementType,
- outToExpr ? &coercedArg : nullptr,
- arg->type,
- arg,
- outCost ? &argCost : nullptr);
+ bool argResult = tryReadArgFromInitializerList(
+ GetType(fieldDeclRef),
+ outToExpr ? &coercedArg : nullptr,
+ fromInitializerListExpr,
+ ioArgIndex);
// No point in trying further if any argument fails
if(!argResult)
return false;
- if( outToExpr )
+ if( coercedArg )
{
coercedArgs.Add(coercedArg);
}
}
}
- else
- {
- // By default, we don't allow a type to be initialized using
- // an initializer list.
+ }
+ else
+ {
+ // We shouldn't get to this case in practice,
+ // but just in case we'll consider an initializer
+ // list invalid if we are trying to read something
+ // off of it that wasn't handled by the cases above.
+ //
+ return false;
+ }
+
+ // We were able to coerce all the arguments given, and so
+ // we need to construct a suitable expression to remember the result
+ //
+ if(outToExpr)
+ {
+ auto toInitializerListExpr = new InitializerListExpr();
+ toInitializerListExpr->loc = fromInitializerListExpr->loc;
+ toInitializerListExpr->type = QualType(toType);
+ toInitializerListExpr->args = coercedArgs;
+
+ *outToExpr = toInitializerListExpr;
+ }
+
+ return true;
+ }
+
+ bool tryCoerceInitializerList(
+ RefPtr<Type> toType,
+ RefPtr<Expr>* outToExpr,
+ RefPtr<InitializerListExpr> fromInitializerListExpr)
+ {
+ UInt argCount = fromInitializerListExpr->args.Count();
+ UInt argIndex = 0;
+
+ // TODO: we should handle the special case of `{0}` as an initializer
+ // for arbitrary `struct` types here.
+
+ if(!tryReadAggregateFromInitializerList(toType, outToExpr, fromInitializerListExpr, argIndex))
+ return false;
+
+ if(argIndex != argCount)
+ {
+ getSink()->diagnose(fromInitializerListExpr, Diagnostics::tooManyInitializers, argIndex, argCount);
+ }
+
+ return true;
+ }
+
+
+ // Central engine for implementing implicit coercion logic
+ bool TryCoerceImpl(
+ RefPtr<Type> toType, // the target type for conversion
+ RefPtr<Expr>* outToExpr, // (optional) a place to stuff the target expression
+ RefPtr<Type> fromType, // the source type for the conversion
+ RefPtr<Expr> fromExpr, // the source expression
+ ConversionCost* outCost) // (optional) a place to stuff the conversion cost
+ {
+ // Easy case: the types are equal
+ if (toType->Equals(fromType))
+ {
+ if (outToExpr)
+ *outToExpr = fromExpr;
+ if (outCost)
+ *outCost = kConversionCost_None;
+ return true;
+ }
+
+ // If either type is an error, then let things pass.
+ if (toType->As<ErrorType>() || fromType->As<ErrorType>())
+ {
+ if (outToExpr)
+ *outToExpr = CreateImplicitCastExpr(toType, fromExpr);
+ if (outCost)
+ *outCost = kConversionCost_None;
+ return true;
+ }
+
+ // Coercion from an initializer list is allowed for many types
+ if( auto fromInitializerListExpr = fromExpr.As<InitializerListExpr>())
+ {
+ if(!tryCoerceInitializerList(toType, outToExpr, fromInitializerListExpr))
return false;
- }
// For now, coercion from an initializer list has no cost
if(outCost)
@@ -1498,19 +1785,6 @@ namespace Slang
*outCost = kConversionCost_None;
}
- // We were able to coerce all the arguments given, and so
- // we need to construct a suitable expression to remember the result
- if(outToExpr)
- {
- auto toInitializerListExpr = new InitializerListExpr();
- toInitializerListExpr->loc = fromInitializerListExpr->loc;
- toInitializerListExpr->type = QualType(toType);
- toInitializerListExpr->args = coercedArgs;
-
-
- *outToExpr = toInitializerListExpr;
- }
-
return true;
}
@@ -1859,42 +2133,41 @@ namespace Slang
void CheckVarDeclCommon(RefPtr<VarDeclBase> varDecl)
{
- // Check the type, if one was given
- TypeExp type = CheckUsableType(varDecl->type);
-
- // TODO: Additional validation rules on types should go here,
- // but we need to deal with the fact that some cases might be
- // allowed in one context (e.g., an unsized array parameter)
- // but not in othters (e.g., an unsized array field in a struct).
-
- // Check the initializers, if one was given
- RefPtr<Expr> initExpr = CheckTerm(varDecl->initExpr);
-
- // If a type was given, ...
- if (type.Ptr())
+ if (function || checkingPhase == CheckingPhase::Header)
{
- // then coerce any initializer to the type
- if (initExpr)
+ TypeExp typeExp = CheckUsableType(varDecl->type);
+ varDecl->type = typeExp;
+ if (varDecl->type.Equals(getSession()->getVoidType()))
{
- initExpr = Coerce(type.Ptr(), initExpr);
+ getSink()->diagnose(varDecl, Diagnostics::invalidTypeVoid);
}
}
- else
- {
- // TODO: infer a type from the initializers
- if (!initExpr)
- {
- getSink()->diagnose(varDecl, Diagnostics::unimplemented, "variable declaration with no type must have initializer");
- }
- else
+ if (checkingPhase == CheckingPhase::Body)
+ {
+ if (auto initExpr = varDecl->initExpr)
{
- getSink()->diagnose(varDecl, Diagnostics::unimplemented, "type inference for variable declaration");
+ initExpr = CheckTerm(initExpr);
+ initExpr = Coerce(varDecl->type.Ptr(), initExpr);
+ varDecl->initExpr = initExpr;
+
+ // If this is an array variable, then we first want to give
+ // it a chance to infer an array size from its initializer
+ //
+ // TODO(tfoley): May need to extend this to handle the
+ // multi-dimensional case...
+ //
+ maybeInferArraySizeForVariable(varDecl);
+ //
+ // Next we want to make sure that the declared (or inferred)
+ // size for the array meets whatever language-specific
+ // constraints we want to enforce (e.g., disallow empty
+ // arrays in specific cases)
+ //
+ validateArraySizeForVariable(varDecl);
}
}
-
- varDecl->type = type;
- varDecl->initExpr = initExpr;
+ varDecl->SetCheckState(getCheckedState());
}
// Fill in default substitutions for the 'subtype' part of a type constraint decl
@@ -2535,12 +2808,7 @@ namespace Slang
void visitStructField(StructField* field)
{
- if (field->IsChecked(DeclCheckState::CheckedHeader))
- return;
- // TODO: bottleneck through general-case variable checking
-
- field->type = CheckUsableType(field->type);
- field->SetCheckState(getCheckedState());
+ CheckVarDeclCommon(field);
}
bool doesSignatureMatchRequirement(
@@ -4295,7 +4563,7 @@ namespace Slang
return 1;
}
- void maybeInferArraySizeForVariable(Variable* varDecl)
+ void maybeInferArraySizeForVariable(VarDeclBase* varDecl)
{
// Not an array?
auto arrayType = varDecl->type->AsArrayType();
@@ -4309,14 +4577,8 @@ namespace Slang
auto initExpr = varDecl->initExpr;
if(!initExpr) return;
- // Is the initializer an initializer list?
- if(auto initializerListExpr = initExpr.As<InitializerListExpr>())
- {
- auto argCount = initializerListExpr->args.Count();
- elementCount = new ConstantIntVal(argCount);
- }
// Is the type of the initializer an array type?
- else if(auto arrayInitType = initExpr->type->As<ArrayExpressionType>())
+ if(auto arrayInitType = initExpr->type->As<ArrayExpressionType>())
{
elementCount = arrayInitType->ArrayLength;
}
@@ -4333,7 +4595,7 @@ namespace Slang
elementCount);
}
- void ValidateArraySizeForVariable(Variable* varDecl)
+ void validateArraySizeForVariable(VarDeclBase* varDecl)
{
auto arrayType = varDecl->type->AsArrayType();
if (!arrayType) return;
@@ -4360,48 +4622,7 @@ namespace Slang
void visitVariable(Variable* varDecl)
{
- if (function || checkingPhase == CheckingPhase::Header)
- {
- TypeExp typeExp = CheckUsableType(varDecl->type);
- varDecl->type = typeExp;
- if (varDecl->type.Equals(getSession()->getVoidType()))
- {
- getSink()->diagnose(varDecl, Diagnostics::invalidTypeVoid);
- }
- }
-
- if (checkingPhase == CheckingPhase::Body)
- {
- if (auto initExpr = varDecl->initExpr)
- {
- initExpr = CheckTerm(initExpr);
- varDecl->initExpr = initExpr;
- }
-
- // If this is an array variable, then we first want to give
- // it a chance to infer an array size from its initializer
- //
- // TODO(tfoley): May need to extend this to handle the
- // multi-dimensional case...
- maybeInferArraySizeForVariable(varDecl);
- //
- // Next we want to make sure that the declared (or inferred)
- // size for the array meets whatever language-specific
- // constraints we want to enforce (e.g., disallow empty
- // arrays in specific cases)
- ValidateArraySizeForVariable(varDecl);
-
-
- if (auto initExpr = varDecl->initExpr)
- {
- // TODO(tfoley): should coercion of initializer lists be special-cased
- // here, or handled as a general case for coercion?
-
- initExpr = Coerce(varDecl->type.Ptr(), initExpr);
- varDecl->initExpr = initExpr;
- }
- }
- varDecl->SetCheckState(getCheckedState());
+ CheckVarDeclCommon(varDecl);
}
void visitWhileStmt(WhileStmt *stmt)
diff --git a/source/slang/compiler.h b/source/slang/compiler.h
index 1cfb9201d..d2072387e 100644
--- a/source/slang/compiler.h
+++ b/source/slang/compiler.h
@@ -655,6 +655,10 @@ namespace Slang
Type* elementType,
IntVal* elementCount);
+ RefPtr<VectorExpressionType> getVectorType(
+ RefPtr<Type> elementType,
+ RefPtr<IntVal> elementCount);
+
SyntaxClass<RefObject> findSyntaxClass(Name* name);
Dictionary<Name*, SyntaxClass<RefObject> > mapNameToSyntaxClass;
diff --git a/source/slang/diagnostic-defs.h b/source/slang/diagnostic-defs.h
index 96c1366cd..76e59efa3 100644
--- a/source/slang/diagnostic-defs.h
+++ b/source/slang/diagnostic-defs.h
@@ -283,6 +283,14 @@ DIAGNOSTIC(39999, Fatal, localVariableUsedBeforeDeclared, "local variable '$0' i
// 304xx: generics
DIAGNOSTIC(30400, Error, genericTypeNeedsArgs, "generic type '$0' used without argument")
+// 305xx: initializer lists
+DIAGNOSTIC(30500, Error, tooManyInitializers, "too many initializers (expected $0, got $1)");
+DIAGNOSTIC(30501, Error, cannotUseInitializerListForArrayOfUnknownSize, "cannot use initializer list for array of statically unknown size '$0'");
+DIAGNOSTIC(30502, Error, cannotUseInitializerListForVectorOfUnknownSize, "cannot use initializer list for vector of statically unknown size '$0'");
+DIAGNOSTIC(30503, Error, cannotUseInitializerListForMatrixOfUnknownSize, "cannot use initializer list for matrix of statically unknown size '$0' rows");
+
+
+
DIAGNOSTIC(39999, Error, expectedIntegerConstantWrongType, "expected integer constant (found: '$0')")
DIAGNOSTIC(39999, Error, expectedIntegerConstantNotConstant, "expression does not evaluate to a compile-time constant")
DIAGNOSTIC(39999, Error, expectedIntegerConstantNotLiteral, "could not extract value from integer constant")
diff --git a/source/slang/diagnostics.cpp b/source/slang/diagnostics.cpp
index 4cad7ce94..9a88c041d 100644
--- a/source/slang/diagnostics.cpp
+++ b/source/slang/diagnostics.cpp
@@ -61,6 +61,11 @@ void printDiagnosticArg(StringBuilder& sb, Type* type)
sb << type->ToString();
}
+void printDiagnosticArg(StringBuilder& sb, Val* val)
+{
+ sb << val->ToString();
+}
+
void printDiagnosticArg(StringBuilder& sb, TypeExp const& type)
{
sb << type.type->ToString();
diff --git a/source/slang/diagnostics.h b/source/slang/diagnostics.h
index 4c66ba51e..8e5bbcb64 100644
--- a/source/slang/diagnostics.h
+++ b/source/slang/diagnostics.h
@@ -69,9 +69,10 @@ namespace Slang
class Name;
class Decl;
+ struct QualType;
class Type;
struct TypeExp;
- struct QualType;
+ class Val;
enum class CodeGenTarget;
enum class Stage : SlangStage;
@@ -92,6 +93,7 @@ namespace Slang
void printDiagnosticArg(StringBuilder& sb, CodeGenTarget val);
void printDiagnosticArg(StringBuilder& sb, Stage val);
void printDiagnosticArg(StringBuilder& sb, ProfileVersion val);
+ void printDiagnosticArg(StringBuilder& sb, Val* val);
template<typename T>
void printDiagnosticArg(StringBuilder& sb, RefPtr<T> ptr)
diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp
index 49eaf917b..2e801f0e3 100644
--- a/source/slang/emit.cpp
+++ b/source/slang/emit.cpp
@@ -3581,7 +3581,7 @@ struct EmitVisitor
case kIROp_Construct:
case kIROp_makeVector:
- case kIROp_makeMatrix:
+ case kIROp_MakeMatrix:
// Simple constructor call
if( inst->getOperandCount() == 1 && getTarget(ctx) == CodeGenTarget::HLSL)
{
diff --git a/source/slang/ir-constexpr.cpp b/source/slang/ir-constexpr.cpp
index 684958ffe..163f3d98a 100644
--- a/source/slang/ir-constexpr.cpp
+++ b/source/slang/ir-constexpr.cpp
@@ -78,7 +78,7 @@ bool opCanBeConstExpr(IROp op)
case kIROp_Construct:
case kIROp_makeVector:
case kIROp_makeArray:
- case kIROp_makeMatrix:
+ case kIROp_MakeMatrix:
// TODO: more cases
return true;
diff --git a/source/slang/ir-inst-defs.h b/source/slang/ir-inst-defs.h
index ba1e79b7e..8d7a647f4 100644
--- a/source/slang/ir-inst-defs.h
+++ b/source/slang/ir-inst-defs.h
@@ -192,7 +192,7 @@ INST(BindGlobalGenericParam, bind_global_generic_param, 2, 0)
INST(Construct, construct, 0, 0)
INST(makeVector, makeVector, 0, 0)
-INST(makeMatrix, makeMatrix, 0, 0)
+INST(MakeMatrix, makeMatrix, 0, 0)
INST(makeArray, makeArray, 0, 0)
INST(makeStruct, makeStruct, 0, 0)
diff --git a/source/slang/ir-insts.h b/source/slang/ir-insts.h
index cd5407acd..0c56e8244 100644
--- a/source/slang/ir-insts.h
+++ b/source/slang/ir-insts.h
@@ -816,6 +816,11 @@ struct IRBuilder
UInt argCount,
IRInst* const* args);
+ IRInst* emitMakeMatrix(
+ IRType* type,
+ UInt argCount,
+ IRInst* const* args);
+
IRInst* emitMakeArray(
IRType* type,
UInt argCount,
diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp
index 02a6e9f6a..48716bd87 100644
--- a/source/slang/ir.cpp
+++ b/source/slang/ir.cpp
@@ -1911,6 +1911,14 @@ namespace Slang
return emitIntrinsicInst(type, kIROp_makeVector, argCount, args);
}
+ IRInst* IRBuilder::emitMakeMatrix(
+ IRType* type,
+ UInt argCount,
+ IRInst* const* args)
+ {
+ return emitIntrinsicInst(type, kIROp_MakeMatrix, argCount, args);
+ }
+
IRInst* IRBuilder::emitMakeArray(
IRType* type,
UInt argCount,
@@ -3855,7 +3863,7 @@ namespace Slang
case kIROp_lookup_interface_method:
case kIROp_Construct:
case kIROp_makeVector:
- case kIROp_makeMatrix:
+ case kIROp_MakeMatrix:
case kIROp_makeArray:
case kIROp_makeStruct:
case kIROp_Load: // We are ignoring the possibility of loads from bad addresses, or `volatile` loads
diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp
index 388ca884e..b1bc63fa1 100644
--- a/source/slang/lower-to-ir.cpp
+++ b/source/slang/lower-to-ir.cpp
@@ -1657,6 +1657,127 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
return lowerSubExpr(expr->base);
}
+ LoweredValInfo getSimpleDefaultVal(IRType* type)
+ {
+ if(auto basicType = as<IRBasicType>(type))
+ {
+ switch( basicType->getBaseType() )
+ {
+ default:
+ SLANG_UNEXPECTED("missing case for getting IR default value");
+ UNREACHABLE_RETURN(LoweredValInfo());
+ break;
+
+ case BaseType::Bool:
+ case BaseType::Int8:
+ case BaseType::Int16:
+ case BaseType::Int:
+ case BaseType::Int64:
+ case BaseType::UInt8:
+ case BaseType::UInt16:
+ case BaseType::UInt:
+ case BaseType::UInt64:
+ return LoweredValInfo::simple(getBuilder()->getIntValue(type, 0));
+
+ case BaseType::Half:
+ case BaseType::Float:
+ case BaseType::Double:
+ return LoweredValInfo::simple(getBuilder()->getFloatValue(type, 0.0));
+ }
+ }
+
+ SLANG_UNEXPECTED("missing case for getting IR default value");
+ UNREACHABLE_RETURN(LoweredValInfo());
+ }
+
+ LoweredValInfo getDefaultVal(Type* type)
+ {
+ auto irType = lowerType(context, type);
+ if (auto basicType = type->As<BasicExpressionType>())
+ {
+ return getSimpleDefaultVal(irType);
+ }
+ else if (auto vectorType = type->As<VectorExpressionType>())
+ {
+ UInt elementCount = (UInt) GetIntVal(vectorType->elementCount);
+
+ auto irDefaultValue = getSimpleVal(context, getDefaultVal(vectorType->elementType));
+
+ List<IRInst*> args;
+ for(UInt ee = 0; ee < elementCount; ++ee)
+ {
+ args.Add(irDefaultValue);
+ }
+ return LoweredValInfo::simple(
+ getBuilder()->emitMakeVector(irType, args.Count(), args.Buffer()));
+ }
+ else if (auto matrixType = type->As<MatrixExpressionType>())
+ {
+ UInt rowCount = (UInt) GetIntVal(matrixType->getRowCount());
+
+ auto rowType = matrixType->getRowType();
+
+ auto irDefaultValue = getSimpleVal(context, getDefaultVal(rowType));
+
+ List<IRInst*> args;
+ for(UInt rr = 0; rr < rowCount; ++rr)
+ {
+ args.Add(irDefaultValue);
+ }
+ return LoweredValInfo::simple(
+ getBuilder()->emitMakeMatrix(irType, args.Count(), args.Buffer()));
+ }
+ else if (auto arrayType = type->As<ArrayExpressionType>())
+ {
+ UInt elementCount = (UInt) GetIntVal(arrayType->ArrayLength);
+
+ auto irDefaultElement = getSimpleVal(context, getDefaultVal(arrayType->baseType));
+
+ List<IRInst*> args;
+ for(UInt ee = 0; ee < elementCount; ++ee)
+ {
+ args.Add(irDefaultElement);
+ }
+
+ return LoweredValInfo::simple(
+ getBuilder()->emitMakeArray(irType, args.Count(), args.Buffer()));
+ }
+ else if (auto declRefType = type->As<DeclRefType>())
+ {
+ DeclRef<Decl> declRef = declRefType->declRef;
+ if (auto aggTypeDeclRef = declRef.As<AggTypeDecl>())
+ {
+ List<IRInst*> args;
+ for (auto ff : getMembersOfType<StructField>(aggTypeDeclRef))
+ {
+ if (ff.getDecl()->HasModifier<HLSLStaticModifier>())
+ continue;
+
+ auto irFieldVal = getSimpleVal(context, getDefaultVal(ff));
+ args.Add(irFieldVal);
+ }
+
+ return LoweredValInfo::simple(
+ getBuilder()->emitMakeStruct(irType, args.Count(), args.Buffer()));
+ }
+ }
+
+ SLANG_UNEXPECTED("unexpected type when creating default value");
+ UNREACHABLE_RETURN(LoweredValInfo());
+ }
+
+ LoweredValInfo getDefaultVal(StructField* decl)
+ {
+ if(auto initExpr = decl->initExpr)
+ {
+ return lowerRValueExpr(context, initExpr);
+ }
+ else
+ {
+ return getDefaultVal(decl->type);
+ }
+ }
+
LoweredValInfo visitInitializerListExpr(InitializerListExpr* expr)
{
// Allocate a temporary of the given type
@@ -1666,23 +1787,33 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
UInt argCount = expr->args.Count();
+ // If the initializer list was empty, then the user was
+ // asking for default initialization, which should apply
+ // to (almost) any type.
+ //
+ if(argCount == 0)
+ {
+ return getDefaultVal(type.type);
+ }
+
// Now for each argument in the initializer list,
// fill in the appropriate field of the result
if (auto arrayType = type->As<ArrayExpressionType>())
{
UInt elementCount = (UInt) GetIntVal(arrayType->ArrayLength);
- for (UInt ee = 0; ee < elementCount; ++ee)
+ for (UInt ee = 0; ee < argCount; ++ee)
{
- if (ee < argCount)
- {
- auto argExpr = expr->args[ee];
- LoweredValInfo argVal = lowerRValueExpr(context, argExpr);
- args.Add(getSimpleVal(context, argVal));
- }
- else
+ auto argExpr = expr->args[ee];
+ LoweredValInfo argVal = lowerRValueExpr(context, argExpr);
+ args.Add(getSimpleVal(context, argVal));
+ }
+ if(elementCount > argCount)
+ {
+ auto irDefaultValue = getSimpleVal(context, getDefaultVal(arrayType->baseType));
+ for(UInt ee = argCount; ee < elementCount; ++ee)
{
- SLANG_UNEXPECTED("need to default-initialize array elements");
+ args.Add(irDefaultValue);
}
}
@@ -1692,25 +1823,48 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
else if (auto vectorType = type->As<VectorExpressionType>())
{
UInt elementCount = (UInt) GetIntVal(vectorType->elementCount);
- UInt argCounter = 0;
- for (UInt ee = 0; ee < elementCount; ++ee)
+ for (UInt ee = 0; ee < argCount; ++ee)
{
- UInt argIndex = argCounter++;
- if (argIndex < argCount)
+ auto argExpr = expr->args[ee];
+ LoweredValInfo argVal = lowerRValueExpr(context, argExpr);
+ args.Add(getSimpleVal(context, argVal));
+ }
+ if(elementCount > argCount)
+ {
+ auto irDefaultValue = getSimpleVal(context, getDefaultVal(vectorType->elementType));
+ for(UInt ee = argCount; ee < elementCount; ++ee)
{
- auto argExpr = expr->args[argIndex];
- LoweredValInfo argVal = lowerRValueExpr(context, argExpr);
- args.Add(getSimpleVal(context, argVal));
+ args.Add(irDefaultValue);
}
- else
+ }
+
+ return LoweredValInfo::simple(
+ getBuilder()->emitMakeVector(irType, args.Count(), args.Buffer()));
+ }
+ else if (auto matrixType = type->As<MatrixExpressionType>())
+ {
+ UInt rowCount = (UInt) GetIntVal(matrixType->getRowCount());
+
+ for (UInt rr = 0; rr < argCount; ++rr)
+ {
+ auto argExpr = expr->args[rr];
+ LoweredValInfo argVal = lowerRValueExpr(context, argExpr);
+ args.Add(getSimpleVal(context, argVal));
+ }
+ if(rowCount > argCount)
+ {
+ auto rowType = matrixType->getRowType();
+ auto irDefaultValue = getSimpleVal(context, getDefaultVal(rowType));
+
+ for(UInt rr = argCount; rr < rowCount; ++rr)
{
- SLANG_UNEXPECTED("need to default-initialize vector elements");
+ args.Add(irDefaultValue);
}
}
return LoweredValInfo::simple(
- getBuilder()->emitMakeVector(irType, args.Count(), args.Buffer()));
+ getBuilder()->emitMakeMatrix(irType, args.Count(), args.Buffer()));
}
else if (auto declRefType = type->As<DeclRefType>())
{
@@ -1732,23 +1886,21 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
}
else
{
- SLANG_UNEXPECTED("need to default-initialize struct fields");
+ auto irDefaultValue = getSimpleVal(context, getDefaultVal(ff));
+ args.Add(irDefaultValue);
}
}
return LoweredValInfo::simple(
getBuilder()->emitMakeStruct(irType, args.Count(), args.Buffer()));
}
- else
- {
- SLANG_UNEXPECTED("not sure how to initialize this type");
- }
- }
- else
- {
- SLANG_UNEXPECTED("not sure how to initialize this type");
}
+ // If none of the above cases matched, then we had better
+ // have zero arguments in the initailizer list, in which
+ // case we are just looking for default initialization.
+ //
+ SLANG_UNEXPECTED("unhandled case for initializer list codegen");
UNREACHABLE_RETURN(LoweredValInfo());
}
diff --git a/source/slang/syntax.cpp b/source/slang/syntax.cpp
index 21b3f92c2..320fc576b 100644
--- a/source/slang/syntax.cpp
+++ b/source/slang/syntax.cpp
@@ -1241,6 +1241,36 @@ void Type::accept(IValVisitor* visitor, void* extra)
return findInnerMostGenericSubstitution(declRef.substitutions)->args[2].As<IntVal>().Ptr();
}
+ RefPtr<Type> MatrixExpressionType::getRowType()
+ {
+ if( !mRowType )
+ {
+ mRowType = getSession()->getVectorType(getElementType(), getColumnCount());
+ }
+ return mRowType;
+ }
+
+ RefPtr<VectorExpressionType> Session::getVectorType(
+ RefPtr<Type> elementType,
+ RefPtr<IntVal> elementCount)
+ {
+ auto vectorGenericDecl = findMagicDecl(
+ this, "Vector").As<GenericDecl>();
+ auto vectorTypeDecl = vectorGenericDecl->inner;
+
+ auto substitutions = new GenericSubstitution();
+ substitutions->genericDecl = vectorGenericDecl.Ptr();
+ substitutions->args.Add(elementType);
+ substitutions->args.Add(elementCount);
+
+ auto declRef = DeclRef<Decl>(vectorTypeDecl.Ptr(), substitutions);
+
+ return DeclRefType::Create(
+ this,
+ declRef)->As<VectorExpressionType>();
+ }
+
+
// PtrTypeBase
Type* PtrTypeBase::getValueType()
diff --git a/source/slang/type-defs.h b/source/slang/type-defs.h
index 6e067c990..d9bfecc03 100644
--- a/source/slang/type-defs.h
+++ b/source/slang/type-defs.h
@@ -314,11 +314,15 @@ RAW(
IntVal* getRowCount();
IntVal* getColumnCount();
+ RefPtr<Type> getRowType();
virtual String ToString() override;
protected:
virtual BasicExpressionType* GetScalarType() override;
+
+private:
+ RefPtr<Type> mRowType;
)
END_SYNTAX_CLASS()
diff --git a/tests/bugs/gh-775.slang b/tests/bugs/gh-775.slang
new file mode 100644
index 000000000..f8125d7d4
--- /dev/null
+++ b/tests/bugs/gh-775.slang
@@ -0,0 +1,32 @@
+// gh-775.slang
+//TEST(compute):COMPARE_COMPUTE:
+
+int test(int inVal)
+{
+ float4x4 identity = {
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+ };
+
+ float4 v = float4(inVal, inVal+1, inVal+2, inVal+3);
+
+ v = mul(identity, v);
+
+ return int(dot(v, float4(1, 16, 256, 4096)));
+}
+
+//TEST_INPUT:ubuffer(data=[9 9 9 9], stride=4):dxbinding(0),glbinding(0),out
+RWStructuredBuffer<int> outputBuffer : register(u0);
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ uint tid = dispatchThreadID.x;
+
+ int inVal = int(tid);
+ int outVal = test(inVal);
+
+ outputBuffer[tid] = outVal;
+} \ No newline at end of file
diff --git a/tests/bugs/gh-775.slang.expected.txt b/tests/bugs/gh-775.slang.expected.txt
new file mode 100644
index 000000000..611a87ad6
--- /dev/null
+++ b/tests/bugs/gh-775.slang.expected.txt
@@ -0,0 +1,4 @@
+3210
+4321
+5432
+6543
diff --git a/tests/compute/init-list-defaults.slang b/tests/compute/init-list-defaults.slang
new file mode 100644
index 000000000..d8eb72b5b
--- /dev/null
+++ b/tests/compute/init-list-defaults.slang
@@ -0,0 +1,37 @@
+// init-list-defaults.slang
+//TEST(compute):COMPARE_COMPUTE:
+
+// Confirm that initializer lists correctly default-initialize elements past those specified.
+
+struct Test
+{
+ int4 a;
+ int b[4];
+}
+
+int test(int inVal)
+{
+ Test myArray[4] = {
+ { int4(1), { 2, 3} },
+ { {4, 5, 6, }, { 7, } },
+ };
+
+ return myArray[0].b[inVal]
+ + myArray[1].a[inVal]*16
+ + myArray[inVal].a.x*256
+ + (inVal+1)*4096;
+}
+
+//TEST_INPUT:ubuffer(data=[9 9 9 9], stride=4):dxbinding(0),glbinding(0),out
+RWStructuredBuffer<int> outputBuffer : register(u0);
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ uint tid = dispatchThreadID.x;
+
+ int inVal = int(tid);
+ int outVal = test(inVal);
+
+ outputBuffer[tid] = outVal;
+} \ No newline at end of file
diff --git a/tests/compute/init-list-defaults.slang.expected.txt b/tests/compute/init-list-defaults.slang.expected.txt
new file mode 100644
index 000000000..fa9fe4c8a
--- /dev/null
+++ b/tests/compute/init-list-defaults.slang.expected.txt
@@ -0,0 +1,4 @@
+1142
+2453
+3060
+4000
diff --git a/tests/compute/struct-default-init.slang b/tests/compute/struct-default-init.slang
new file mode 100644
index 000000000..9638465b0
--- /dev/null
+++ b/tests/compute/struct-default-init.slang
@@ -0,0 +1,40 @@
+// struct-default-init.slang
+//TEST(compute):COMPARE_COMPUTE:
+
+struct Test
+{
+ int a;
+ int b = 1;
+ int c;
+ int d = 1 + 1;
+}
+
+int test(int inVal)
+{
+ Test myArray[4] = {
+ { 3, 4, 5, 6 },
+ { 7, 8, 9, },
+ { 10, 11 },
+ { 12, }
+ };
+
+ Test t = myArray[inVal];
+ return t.a * 4096
+ + t.b * 256
+ + t.c * 16
+ + t.d;
+}
+
+//TEST_INPUT:ubuffer(data=[9 9 9 9], stride=4):dxbinding(0),glbinding(0),out
+RWStructuredBuffer<int> outputBuffer : register(u0);
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ uint tid = dispatchThreadID.x;
+
+ int inVal = int(tid);
+ int outVal = test(inVal);
+
+ outputBuffer[tid] = outVal;
+} \ No newline at end of file
diff --git a/tests/compute/struct-default-init.slang.expected.txt b/tests/compute/struct-default-init.slang.expected.txt
new file mode 100644
index 000000000..39ce03d0a
--- /dev/null
+++ b/tests/compute/struct-default-init.slang.expected.txt
@@ -0,0 +1,4 @@
+3456
+7892
+AB02
+C102