diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2019-01-16 11:50:14 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-01-16 11:50:14 -0800 |
| commit | 8e47a3802d4d74eb11620f147ef5b29b8e931d35 (patch) | |
| tree | 0028cc2b77fa059e4650b7a227996b2d0e98ac91 /source/slang/check.cpp | |
| parent | 86e11e0e111fab60b9517056ac049bfac6e3bd25 (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.
Diffstat (limited to 'source/slang/check.cpp')
| -rw-r--r-- | source/slang/check.cpp | 629 |
1 files changed, 425 insertions, 204 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) |
