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 | |
| 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.
| -rw-r--r-- | source/slang/check.cpp | 140 | ||||
| -rw-r--r-- | source/slang/core.meta.slang | 30 | ||||
| -rw-r--r-- | source/slang/core.meta.slang.h | 30 | ||||
| -rw-r--r-- | source/slang/lookup.cpp | 8 | ||||
| -rw-r--r-- | source/slang/lower-to-ir.cpp | 5 | ||||
| -rw-r--r-- | source/slang/lower.cpp | 17 | ||||
| -rw-r--r-- | tests/bugs/array-size-static-const.hlsl | 14 | ||||
| -rw-r--r-- | tests/bugs/empty.slang | 5 | ||||
| -rw-r--r-- | tests/bugs/implicit-conversion-binary-op.hlsl | 16 | ||||
| -rw-r--r-- | tests/bugs/import-overload-error.hlsl | 19 | ||||
| -rw-r--r-- | tests/bugs/import-overload-error.slang | 4 | ||||
| -rw-r--r-- | tests/bugs/uav-write-index.hlsl | 31 | ||||
| -rw-r--r-- | tests/bugs/vec-init-list.hlsl | 19 |
13 files changed, 292 insertions, 46 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; } diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang index 3755c51c7..3f1a33edc 100644 --- a/source/slang/core.meta.slang +++ b/source/slang/core.meta.slang @@ -108,6 +108,9 @@ sb << " __implicit_conversion(" << kConversionCost_ScalarToVector << ")\n"; sb << " __intrinsic_op(" << kIROp_constructVectorFromScalar << ")\n"; sb << " __init(T value);\n"; +// Allow initialization from same type +sb << " __init(vector<T,N> value);\n"; + sb << "};\n"; // TODO: Probably need to do similar @@ -190,13 +193,6 @@ for (int N = 2; N <= 4; ++N) sb << ");\n"; } - // initialize from another vector of the same size - // - // TODO(tfoley): this overlaps with implicit conversions. - // We should look for a way that we can define implicit - // conversions directly in the stdlib instead... - sb << "__generic<U> __init(vector<U," << N << ">);\n"; - // Initialize from two vectors, of size M and N-M for(int M = 2; M <= (N-2); ++M) { @@ -230,16 +226,20 @@ for (int tt = 0; tt < kBaseTypeCount; ++tt) { if(kBaseTypes[ff].tag == BaseType::Void) continue; - // We need a constructor to make a vector from a scalar - // of another type. if( tt != ff ) { auto cost = getBaseTypeConversionCost( kBaseTypes[tt], kBaseTypes[ff]); - cost += kConversionCost_ScalarToVector; + // Implicit conversion from a vector of the same + // size, but different element type. + sb << " __implicit_conversion(" << cost << ")\n"; + sb << " __init(vector<" << kBaseTypes[ff].name << ",N> value);\n"; + + // Constructor to make a vector from a scalar of another type. + cost += kConversionCost_ScalarToVector; sb << " __implicit_conversion(" << cost << ")\n"; sb << " __init(" << kBaseTypes[ff].name << " value);\n"; } @@ -557,8 +557,16 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) if(baseShape != TextureType::ShapeCube) { + // TODO: In the case where `access` includes writeability, + // this should have both `get` and `set` accessors. + // subscript operator - sb << "__intrinsic_op __subscript(uint" << kBaseTextureTypes[tt].coordCount + isArray << " location) -> T;\n"; + sb << "__intrinsic_op __subscript(uint"; + if(kBaseTextureTypes[tt].coordCount + isArray > 1) + { + sb << kBaseTextureTypes[tt].coordCount + isArray; + } + sb << " location) -> T;\n"; } if( !isMultisample ) diff --git a/source/slang/core.meta.slang.h b/source/slang/core.meta.slang.h index 864196944..900d05b61 100644 --- a/source/slang/core.meta.slang.h +++ b/source/slang/core.meta.slang.h @@ -110,6 +110,9 @@ sb << " __implicit_conversion(" << kConversionCost_ScalarToVector << ")\n"; sb << " __intrinsic_op(" << kIROp_constructVectorFromScalar << ")\n"; sb << " __init(T value);\n"; +// Allow initialization from same type +sb << " __init(vector<T,N> value);\n"; + sb << "};\n"; // TODO: Probably need to do similar @@ -193,13 +196,6 @@ for (int N = 2; N <= 4; ++N) sb << ");\n"; } - // initialize from another vector of the same size - // - // TODO(tfoley): this overlaps with implicit conversions. - // We should look for a way that we can define implicit - // conversions directly in the stdlib instead... - sb << "__generic<U> __init(vector<U," << N << ">);\n"; - // Initialize from two vectors, of size M and N-M for(int M = 2; M <= (N-2); ++M) { @@ -233,16 +229,20 @@ for (int tt = 0; tt < kBaseTypeCount; ++tt) { if(kBaseTypes[ff].tag == BaseType::Void) continue; - // We need a constructor to make a vector from a scalar - // of another type. if( tt != ff ) { auto cost = getBaseTypeConversionCost( kBaseTypes[tt], kBaseTypes[ff]); - cost += kConversionCost_ScalarToVector; + // Implicit conversion from a vector of the same + // size, but different element type. + sb << " __implicit_conversion(" << cost << ")\n"; + sb << " __init(vector<" << kBaseTypes[ff].name << ",N> value);\n"; + + // Constructor to make a vector from a scalar of another type. + cost += kConversionCost_ScalarToVector; sb << " __implicit_conversion(" << cost << ")\n"; sb << " __init(" << kBaseTypes[ff].name << " value);\n"; } @@ -560,8 +560,16 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) if(baseShape != TextureType::ShapeCube) { + // TODO: In the case where `access` includes writeability, + // this should have both `get` and `set` accessors. + // subscript operator - sb << "__intrinsic_op __subscript(uint" << kBaseTextureTypes[tt].coordCount + isArray << " location) -> T;\n"; + sb << "__intrinsic_op __subscript(uint"; + if(kBaseTextureTypes[tt].coordCount + isArray > 1) + { + sb << kBaseTextureTypes[tt].coordCount + isArray; + } + sb << " location) -> T;\n"; } if( !isMultisample ) diff --git a/source/slang/lookup.cpp b/source/slang/lookup.cpp index ccdbe8fbf..95a1b3326 100644 --- a/source/slang/lookup.cpp +++ b/source/slang/lookup.cpp @@ -40,6 +40,9 @@ void buildMemberDictionary(ContainerDecl* decl) decl->memberDictionary.Clear(); decl->transparentMembers.Clear(); + // are we a generic? + GenericDecl* genericDecl = dynamic_cast<GenericDecl*>(decl); + for (auto m : decl->Members) { auto name = m->getName(); @@ -56,6 +59,11 @@ void buildMemberDictionary(ContainerDecl* decl) if (!name) continue; + // Ignore the "inner" member of a generic declaration + if (genericDecl && m == genericDecl->inner) + continue; + + m->nextInContainerWithSameName = nullptr; Decl* next = nullptr; diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp index b9307b007..2f3456d0c 100644 --- a/source/slang/lower-to-ir.cpp +++ b/source/slang/lower-to-ir.cpp @@ -1452,11 +1452,6 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo> return ensureDecl(context, expr->declRef); } - LoweredValInfo visitTypeCastExpr(TypeCastExpr* expr) - { - SLANG_UNIMPLEMENTED_X("codegen for type cast expression"); - } - LoweredValInfo visitSelectExpr(SelectExpr* expr) { SLANG_UNIMPLEMENTED_X("codegen for select expression"); diff --git a/source/slang/lower.cpp b/source/slang/lower.cpp index 6d848062c..b620a7837 100644 --- a/source/slang/lower.cpp +++ b/source/slang/lower.cpp @@ -956,6 +956,23 @@ struct LoweringVisitor return LoweredExpr(loweredExpr); } + LoweredExpr visitOverloadedExpr( + OverloadedExpr* expr) + { + // The presence of an overloaded expression in the output + // means that some amount of semantic checking failed. + // Thus we don't need to worry about semantically transforming + // the expression itself, but we *do* want to ensure that any + // of the declarations that the user might have been referring + // to get lowered so they will appear in the output. + for (auto item : expr->lookupResult2.items) + { + translateDeclRef(item.declRef); + } + + return expr; + } + Name* getName(String const& text) { return shared->compileRequest->getNamePool()->getName(text); diff --git a/tests/bugs/array-size-static-const.hlsl b/tests/bugs/array-size-static-const.hlsl new file mode 100644 index 000000000..fe15d402d --- /dev/null +++ b/tests/bugs/array-size-static-const.hlsl @@ -0,0 +1,14 @@ +// array-size-static-const.hlsl +//TEST:COMPARE_HLSL: -profile cs_5_0 -target dxbc-assembly + +// The bug in this case is that were have a (hidden) +// cast from the `uint` constant to `int` to get +// the size of the array, and this cast was tripping +// up the constant-folding logic. + +static const uint n = 16; +groupshared float b[n]; + +[numthreads(1,1,1)] +void main() +{} diff --git a/tests/bugs/empty.slang b/tests/bugs/empty.slang new file mode 100644 index 000000000..ac4b3b7ca --- /dev/null +++ b/tests/bugs/empty.slang @@ -0,0 +1,5 @@ +//TEST_IGNORE_FILE: +// empty.slang + +// This is just an empty file so that tets +// that need to `__import` something can. diff --git a/tests/bugs/implicit-conversion-binary-op.hlsl b/tests/bugs/implicit-conversion-binary-op.hlsl new file mode 100644 index 000000000..75ff737da --- /dev/null +++ b/tests/bugs/implicit-conversion-binary-op.hlsl @@ -0,0 +1,16 @@ +// implicit-conversion-binary-op.hlsl +//TEST:COMPARE_HLSL: -profile ps_5_0 -target dxbc-assembly + +// Make sure that we can pick resolve the right overload +// to call when applying a binary operator to vectors +// with different element types. We should pick +// the "better" of the two element types, and not +// get an ambiguity error. + +float4 main( + float4 a : A, + uint4 b : B + ) : SV_Target +{ + return a * b; +} diff --git a/tests/bugs/import-overload-error.hlsl b/tests/bugs/import-overload-error.hlsl new file mode 100644 index 000000000..328bb5b26 --- /dev/null +++ b/tests/bugs/import-overload-error.hlsl @@ -0,0 +1,19 @@ +//TEST:COMPARE_HLSL: -profile cs_5_0 -target dxbc-assembly -no-checking + +#ifdef __SLANG__ +__import import_overload_error; +#else + +void foo(int a) {} +void foo(float b) {} + +#endif + +void main() +{ +// Note(tfoley): futzing around with tokens to +// make sure error message gets reported at a +// consistent location between languages. +int a; +foo(); +} diff --git a/tests/bugs/import-overload-error.slang b/tests/bugs/import-overload-error.slang new file mode 100644 index 000000000..e52ce78bb --- /dev/null +++ b/tests/bugs/import-overload-error.slang @@ -0,0 +1,4 @@ +//TEST_IGNORE_FILE: + +void foo(int a) {} +void foo(float b) {} diff --git a/tests/bugs/uav-write-index.hlsl b/tests/bugs/uav-write-index.hlsl new file mode 100644 index 000000000..667c73e89 --- /dev/null +++ b/tests/bugs/uav-write-index.hlsl @@ -0,0 +1,31 @@ +//TEST:COMPARE_HLSL: -profile cs_5_0 -target dxbc-assembly -no-checking + +// Make sure we handle complex UAV write patterns + +// Force import of Slang to ensure that some +// checking takes place: +#ifdef __SLANG__ +__import empty; +#endif + +struct Bar +{ + uint bar; +}; + +RWStructuredBuffer<Bar> gUAV : register(u0); + +void foo(RWTexture1D<float2> uav) +{ + uint index = gUAV.IncrementCounter(); + gUAV[index].bar = 1; + uav[index] = float2(0,0); +} + +RWTexture1D<float2> gUAV2 : register(u1); + +[numthreads(1,1,1)] +void main() +{ + foo(gUAV2); +} diff --git a/tests/bugs/vec-init-list.hlsl b/tests/bugs/vec-init-list.hlsl new file mode 100644 index 000000000..be1bc5c6f --- /dev/null +++ b/tests/bugs/vec-init-list.hlsl @@ -0,0 +1,19 @@ +//TEST:COMPARE_HLSL: -profile vs_5_0 -target dxbc-assembly + +// Check handling of initializer list for vector + +cbuffer C : register(b0) +{ + float4 a; +}; + +float w0(float x) { return x; } +float w1(float x) { return x; } +float w2(float x) { return x; } +float w3(float x) { return x; } + +float4 main() : SV_Position +{ + float4 wx = { w0(a.x), w1(a.x), w2(a.x), w3(a.x), }; + return wx; +} |
