diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2017-10-11 14:39:10 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-10-11 14:39:10 -0700 |
| commit | 331ddfa7bfe00bce628dd1aa0f721f24554dfedd (patch) | |
| tree | 8d12ea176366ce2ffe478cf9e0c78639e2a9236e /source/slang/check.cpp | |
| parent | 892b33996d527f430510793308cb9f0859d5041c (diff) | |
Bug fixing (#207)
* Bug fix for vector initializer lists
When a vector was initialized with an initializer list:
float4 f = { 0, 1, 2, 3 };
we were following the logic for `struct` types (since `vector<T,N>` is technically a `struct` declaration in our stdlib...), but the type has no field, so we were (silently!) ignoring the actual operands.
I've applied a simple fix where we cast the operands to the element type of the vector, but a more complete fix will be needed sooner or later where we check the operand counts properly, etc.
* Create implicit cast AST nodes when calling initializers
The logic for dealing with implicit conversions was recently beefed up so that it would look at `__init` declarations in the target type, but in those cases the front-end would always create an `InvokeExpr` even when we would rather get an `ImplicitCastExpr` or (in the "rewrite" case) a `HiddenImplicitCastExpr`.
I've fixed this up for now by constructing a dummy expression to stand in for the "original" call expression when creating the final call (luckily our `TypeCastExpr` is already just a specialized `InvokeExpr`).
A better long-term solution might be to have implicit-ness or hidden-ness be modifiers or flags, rather than needing to use specialized forms of call nodes.
* Fix subscript operator for `RWTexture1D`
The index type was being declared as `uint1` instead of `uint`, and that created problems for downstream HLSL compilation when we introduced expressions like `uav[uint1(index)]` - the compiler would complain that a vector is not a valid index type.
* Fix up constant-folding of integer casts.
The old logic was checking for `InvokeExpr` before `TypeCastExpr`, but in the new setup a type cast *is* an `InvokeExpr`, so that case was never triggering.
All of the constant-folding code really needs to be revisited, though, so that it can use a more general-purpose evaluation scheme like the bytecode (so that we can handle a moral equivalent of `constexpr` in the long run).
* Fix implicit conversion costs for vector types
A recent change made it so that the logic for looking up implicit conversions now uses declarations of initializers in the standard library (rather than hand-coding all the cases in `check.cpp`).
One mistake made there was that we dropped the logic for computing implicit conversions between vectors of the same size, but different element types.
These conversions were still allowed by a catch-all (generic) declaration in the standard library, but that declaration didn't include any implicit conversion cost logic (since it was generic, there was no single cost to use).
This change explicitly enumerates the required conversions with their costs.
It is a bit unfortunate that this is an O(N^2) amount of code for N base types, but that seems unavoidable for now.
* Handle "lowering" of overloaded expressions
If we are in the `-no-checking` mode and the user calls an overloaded function from an `__import`ed file in a way such that Slang can't resolve the intended overload, we were failing to emit the definitions of the potential callees.
This change simply adds a case for `OverloadedExpr` in `lower.cpp` that explicitly lowers all the declarations that might have been referenced.
- There is a potentially for breakage here if we are outputting GLSL and one of the overloads is stage-specific.
- A more refined approach might try to recognize which over the overloaded options are even potentially applicable, and then output only those, but doing this would be way more complicated.
I've added a test case for this behavior, but it is a bit brittle because we need to confirm that we still produce the same error message as unmodified HLSL.
Diffstat (limited to 'source/slang/check.cpp')
| -rw-r--r-- | source/slang/check.cpp | 140 |
1 files changed, 121 insertions, 19 deletions
diff --git a/source/slang/check.cpp b/source/slang/check.cpp index 73d464d95..b35dd2662 100644 --- a/source/slang/check.cpp +++ b/source/slang/check.cpp @@ -671,6 +671,59 @@ namespace Slang // we will collect the new arguments here List<RefPtr<Expr>> coercedArgs; + 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? + elementCount = fromInitializerListExpr->args.Count(); + } + + // 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) + { + RefPtr<Expr> coercedArg; + ConversionCost argCost; + + bool argResult = TryCoerceImpl( + toElementType, + outToExpr ? &coercedArg : nullptr, + arg->type, + arg, + outCost ? &argCost : nullptr); + + // 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 ) + { + 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>()) { auto toTypeDeclRef = toDeclRefType->declRef; @@ -804,9 +857,6 @@ namespace Slang OverloadResolveContext overloadContext; - List<RefPtr<Expr>> args; - args.Add(fromExpr); - overloadContext.disallowNestedConversions = true; overloadContext.argCount = 1; overloadContext.argTypes = &fromType; @@ -884,7 +934,45 @@ namespace Slang if(outToExpr) { + // The logic here is a bit ugly, to deal with the fact that + // `CompleteOverloadCandidate` will, left to its own devices, + // construct a vanilla `InvokeExpr` to represent the call + // to the initializer we found, while we *want* it to + // create some variety of `ImplicitCastExpr`. + // + // Now, it just so happens that `CompleteOverloadCandidate` + // will use the "original" expression if one is available, + // so we'll create one and initialize it here. + // We fill in the location and arguments, but not the + // base expression (the callee), since that will come + // from the selected overload candidate. + // + auto castExpr = createImplicitCastExpr(); + castExpr->loc = fromExpr->loc; + castExpr->Arguments.Add(fromExpr); + // + // Next we need to set our cast expression as the "original" + // expression and then complete the overload process. + // + overloadContext.originalExpr = castExpr; *outToExpr = CompleteOverloadCandidate(overloadContext, *overloadContext.bestCandidate); + // + // However, the above isn't *quite* enough, because + // the process of completing the overload candidate + // might overwrite the argument list that was passed + // in to overload resolution, and in this case that + // "argument list" was just a pointer to `fromExpr`. + // + // That means we need to clear the argument list and + // reload it from `fromExpr` to make sure that we + // got the arguments *after* any transformations + // were applied. + // For right now this probably doesn't matter, + // because we don't allow nested implicit conversions, + // but I'd rather play it safe. + // + castExpr->Arguments.Clear(); + castExpr->Arguments.Add(fromExpr); } return true; @@ -907,22 +995,26 @@ namespace Slang outCost); } - RefPtr<Expr> CreateImplicitCastExpr( - RefPtr<Type> toType, - RefPtr<Expr> fromExpr) + RefPtr<TypeCastExpr> createImplicitCastExpr() { - // In "rewrite" mode, we will generate a different syntax node - // to indicate that this type-cast was implicitly generated - // by the compiler, and shouldn't appear in the output code. - RefPtr<TypeCastExpr> castExpr; if (isRewriteMode()) { - castExpr = new HiddenImplicitCastExpr(); + // In "rewrite" mode, we will generate a different syntax node + // to indicate that this type-cast was implicitly generated + // by the compiler, and shouldn't appear in the output code. + return new HiddenImplicitCastExpr(); } else { - castExpr = new ImplicitCastExpr(); + return new ImplicitCastExpr(); } + } + + RefPtr<Expr> CreateImplicitCastExpr( + RefPtr<Type> toType, + RefPtr<Expr> fromExpr) + { + RefPtr<TypeCastExpr> castExpr = createImplicitCastExpr(); auto typeType = new TypeType(); typeType->type = toType; @@ -2005,7 +2097,17 @@ namespace Slang auto funcDeclRef = funcDeclRefExpr->declRef; auto intrinsicMod = funcDeclRef.getDecl()->FindModifier<IntrinsicOpModifier>(); - if (!intrinsicMod) return nullptr; + if (!intrinsicMod) + { + // We can't constant fold anything that doesn't map to a builtin + // operation right now. + // + // TODO: we should really allow constant-folding for anything + // that can be lowerd to our bytecode... + return nullptr; + } + + // Let's not constant-fold operations with more than a certain number of arguments, for simplicity static const int kMaxArgs = 8; @@ -2188,16 +2290,16 @@ namespace Slang } } - if (auto invokeExpr = dynamic_cast<InvokeExpr*>(expr)) + if(auto castExpr = dynamic_cast<TypeCastExpr*>(expr)) { - auto val = TryConstantFoldExpr(invokeExpr); - if (val) + auto val = TryConstantFoldExpr(castExpr->Arguments[0].Ptr()); + if(val) return val; } - else if(auto castExpr = dynamic_cast<TypeCastExpr*>(expr)) + else if (auto invokeExpr = dynamic_cast<InvokeExpr*>(expr)) { - auto val = TryConstantFoldExpr(castExpr->Arguments[0].Ptr()); - if(val) + auto val = TryConstantFoldExpr(invokeExpr); + if (val) return val; } |
