diff options
| -rw-r--r-- | source/slang/core.meta.slang | 292 | ||||
| -rw-r--r-- | source/slang/core.meta.slang.h | 375 | ||||
| -rw-r--r-- | source/slang/hlsl.meta.slang | 193 | ||||
| -rw-r--r-- | source/slang/hlsl.meta.slang.h | 195 | ||||
| -rw-r--r-- | source/slang/slang-check-decl.cpp | 434 | ||||
| -rw-r--r-- | source/slang/slang-check-impl.h | 37 | ||||
| -rw-r--r-- | source/slang/slang-check-overload.cpp | 223 | ||||
| -rw-r--r-- | source/slang/slang-emit-cpp.cpp | 31 | ||||
| -rw-r--r-- | source/slang/slang-emit-cpp.h | 1 | ||||
| -rw-r--r-- | source/slang/slang-hlsl-intrinsic-set.h | 1 | ||||
| -rw-r--r-- | source/slang/slang-lookup.cpp | 787 | ||||
| -rw-r--r-- | source/slang/slang-lookup.h | 9 | ||||
| -rw-r--r-- | source/slang/slang-stdlib.cpp | 78 | ||||
| -rw-r--r-- | source/slang/slang-syntax.cpp | 13 | ||||
| -rw-r--r-- | source/slang/slang-syntax.h | 2 | ||||
| -rw-r--r-- | tests/diagnostics/enum-implicit-conversion.slang.expected | 4 | ||||
| -rw-r--r-- | tests/front-end/diamond.slang | 32 |
17 files changed, 1797 insertions, 910 deletions
diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang index 392922d38..2df6cb36b 100644 --- a/source/slang/core.meta.slang +++ b/source/slang/core.meta.slang @@ -21,7 +21,14 @@ syntax globallycoherent : GloballyCoherentModifier; interface __BuiltinType {} // A type that can be used for arithmetic operations -interface __BuiltinArithmeticType : __BuiltinType {} +interface __BuiltinArithmeticType : __BuiltinType +{ + /// Initialize from a 32-bit signed integer value. + __init(int value); +} + + /// A type that can be used for logical/bitwsie operations +interface __BuiltinLogicalType : __BuiltinType {} // A type that logically has a sign (positive/negative/zero) interface __BuiltinSignedArithmeticType : __BuiltinArithmeticType {} @@ -31,14 +38,16 @@ interface __BuiltinIntegerType : __BuiltinArithmeticType {} // A type that can represent non-integers -interface __BuiltinRealType : __BuiltinArithmeticType {} +interface __BuiltinRealType : __BuiltinSignedArithmeticType {} // A type that uses a floating-point representation -interface __BuiltinFloatingPointType : __BuiltinRealType, __BuiltinSignedArithmeticType +interface __BuiltinFloatingPointType : __BuiltinRealType { - // A builtin floating-point type must have an initializer that takes - // a floating-point value... + /// Initialize from a 32-bit floating-point value. __init(float value); + + /// Get the value of the mathematical constant pi in this type. + static This getPi(); } // A type resulting from an `enum` declaration. @@ -116,52 +125,58 @@ __generic<T, let N : int> __intrinsic_op(select) vector<T,N> operator?:(vector<b ${{{{ // We are going to use code generation to produce the // declarations for all of our base types. - static const int kBaseTypeCount = sizeof(kBaseTypes) / sizeof(kBaseTypes[0]); for (int tt = 0; tt < kBaseTypeCount; ++tt) { - EMIT_LINE_DIRECTIVE(); - sb << "__builtin_type(" << int(kBaseTypes[tt].tag) << ") struct " << kBaseTypes[tt].name; - - // Declare interface conformances for this type +}}}} - sb << "\n : __BuiltinType\n"; +__builtin_type($(int(kBaseTypes[tt].tag))) +struct $(kBaseTypes[tt].name) + : __BuiltinType +${{{{ switch (kBaseTypes[tt].tag) { case BaseType::Half: case BaseType::Float: case BaseType::Double: - sb << "\n , __BuiltinFloatingPointType\n"; - sb << "\n , __BuiltinRealType\n"; - sb << "\n , __BuiltinSignedArithmeticType\n"; - sb << "\n , __BuiltinArithmeticType\n"; - sb << "\n , __BuiltinType\n"; +}}}} + , __BuiltinFloatingPointType + , __BuiltinRealType + , __BuiltinSignedArithmeticType + , __BuiltinArithmeticType +${{{{ break; case BaseType::Int8: case BaseType::Int16: case BaseType::Int: case BaseType::Int64: - sb << "\n , __BuiltinSignedArithmeticType\n"; +}}}} + , __BuiltinSignedArithmeticType +${{{{ ; // fall through to: case BaseType::UInt8: case BaseType::UInt16: case BaseType::UInt: case BaseType::UInt64: - sb << "\n , __BuiltinArithmeticType\n"; - sb << "\n , __BuiltinIntegerType\n"; +}}}} + , __BuiltinArithmeticType + , __BuiltinIntegerType +${{{{ ; // fall through to: case BaseType::Bool: - sb << "\n , __BuiltinType\n"; +}}}} + , __BuiltinLogicalType +${{{{ break; default: break; } +}}}} +{ - sb << "\n{\n"; - - +${{{{ // Declare initializers to convert from various other types for (int ss = 0; ss < kBaseTypeCount; ++ss) { @@ -175,12 +190,12 @@ for (int tt = 0; tt < kBaseTypeCount; ++tt) ConversionCost conversionCost = getBaseTypeConversionCost( kBaseTypes[tt], kBaseTypes[ss]); +}}}} - EMIT_LINE_DIRECTIVE(); - sb << "__implicit_conversion(" << conversionCost << ")\n"; + __implicit_conversion($(conversionCost)) + __init($(kBaseTypes[ss].name) value); - EMIT_LINE_DIRECTIVE(); - sb << "__init(" << kBaseTypes[ss].name << " value);\n"; +${{{{ } // If this is a basic integer type, then define explicit @@ -206,13 +221,33 @@ ${{{{ break; } - sb << "};\n"; + // If this is a floating-point type, then we need to + // define the basic `getPi()` function that is used + // to implement generic versions of `degrees()` and + // `radians()`. + // + switch (kBaseTypes[tt].tag) + { + default: + break; + case BaseType::Half: + case BaseType::Float: + case BaseType::Double: +}}}} + static $(kBaseTypes[tt].name) getPi() { return $(kBaseTypes[tt].name)(3.14159265358979323846264338328); } +${{{{ + break; + } +}}}} + +} + +${{{{ } // Declare built-in pointer type // (eventually we can have the traditional syntax sugar for this) }}}} - __generic<T> __magic_type(PtrType) __intrinsic_type($(kIROp_PtrType)) @@ -242,26 +277,31 @@ __intrinsic_type($(kIROp_StringType)) struct String {}; -${{{{ -// Declare vector and matrix types - -sb << "__generic<T = float, let N : int = 4> __magic_type(Vector) struct vector\n{\n"; -sb << " typedef T Element;\n"; + /// An `N` component vector with elements of type `T`. +__generic<T = float, let N : int = 4> +__magic_type(Vector) +struct vector +{ + /// The element type of the vector + typedef T Element; -// Declare initializer taking a single scalar of the elemnt type -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"; + /// Initialize a vector where all elements have the same scalar `value`. + __implicit_conversion($(kConversionCost_ScalarToVector)) + __intrinsic_op($(kIROp_constructVectorFromScalar)) + __init(T value); -sb << "};\n"; -}}}} + /// Initialize a vector from a value of the same type + // TODO: we should revise semantic checking so this kind of "identity" conversion is not required + __init(vector<T,N> value); +} + /// A matrix with `R` rows and `C` columns, with elements of type `T`. __generic<T = float, let R : int = 4, let C : int = 4> __magic_type(Matrix) -struct matrix {}; +struct matrix +{ +} ${{{{ static const struct { @@ -310,20 +350,24 @@ for (int tt = 0; tt < kTypeCount; ++tt) } // Declare additional built-in generic types -// EMIT_LINE_DIRECTIVE(); +}}}} +__generic<T> +__intrinsic_type($(kIROp_ConstantBufferType)) +__magic_type(ConstantBuffer) +struct ConstantBuffer {} -sb << "__generic<T>\n"; -sb << "__intrinsic_type(" << kIROp_ConstantBufferType << ")\n"; -sb << "__magic_type(ConstantBuffer) struct ConstantBuffer {};\n"; +__generic<T> +__intrinsic_type($(kIROp_TextureBufferType)) +__magic_type(TextureBuffer) +struct TextureBuffer {} -sb << "__generic<T>\n"; -sb << "__intrinsic_type(" << kIROp_TextureBufferType << ")\n"; -sb << "__magic_type(TextureBuffer) struct TextureBuffer {};\n"; +__generic<T> +__intrinsic_type($(kIROp_ParameterBlockType)) +__magic_type(ParameterBlockType) +struct ParameterBlock {} -sb << "__generic<T>\n"; -sb << "__intrinsic_type(" << kIROp_ParameterBlockType << ")\n"; -sb << "__magic_type(ParameterBlockType) struct ParameterBlock {};\n"; +${{{{ static const char* kComponentNames[]{ "x", "y", "z", "w" }; static const char* kVectorNames[]{ "", "x", "xy", "xyz", "xyzw" }; @@ -454,20 +498,25 @@ for( int C = 2; C <= 4; ++C ) sb << "}\n"; } - -// Declare built-in texture and sampler types +}}}} + /// Sampling state for filtered texture fetches. +__magic_type(SamplerState, $(int(SamplerStateFlavor::SamplerState))) +__intrinsic_type($(kIROp_SamplerStateType)) +struct SamplerState +{ +} -sb << "__magic_type(SamplerState," << int(SamplerStateFlavor::SamplerState) << ")\n"; -sb << "__intrinsic_type(" << kIROp_SamplerStateType << ")\n"; -sb << "struct SamplerState {};"; + /// Sampling state for filtered texture fetches that include a comparison operation before filtering. +__magic_type(SamplerState, $(int(SamplerStateFlavor::SamplerComparisonState))) +__intrinsic_type($(kIROp_SamplerComparisonStateType)) +struct SamplerComparisonState +{ +} -sb << "__magic_type(SamplerState," << int(SamplerStateFlavor::SamplerComparisonState) << ")\n"; -sb << "__intrinsic_type(" << kIROp_SamplerComparisonStateType << ")\n"; -sb << "struct SamplerComparisonState {};"; +${{{{ -// TODO(tfoley): Need to handle `RW*` variants of texture types as well... static const struct { char const* name; TextureFlavor::Shape baseShape; @@ -1403,6 +1452,9 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) for (auto op : unaryOps) { + char const* fixity = (op.flags & POSTFIX) != 0 ? "__postfix " : "__prefix "; + char const* qual = (op.flags & ASSIGNMENT) != 0 ? "in out " : ""; + for (auto type : kBaseTypes) { if ((type.flags & op.flags) == 0) @@ -1411,9 +1463,6 @@ for (auto op : unaryOps) char const* resultType = type.name; if (op.flags & BOOL_RESULT) resultType = "bool"; - char const* fixity = (op.flags & POSTFIX) != 0 ? "__postfix " : "__prefix "; - char const* qual = (op.flags & ASSIGNMENT) != 0 ? "in out " : ""; - // scalar version sb << fixity; sb << "__intrinsic_op(" << int(op.opCode) << ") " << resultType << " operator" << op.opName << "(" << qual << type.name << " value);\n"; @@ -1428,10 +1477,35 @@ for (auto op : unaryOps) sb << fixity; sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << qual << "matrix<" << type.name << ",N,M> value);\n"; } + + // Synthesize generic versions + if(op.interface) + { + char const* resultType = "T"; + if (op.flags & BOOL_RESULT) resultType = "bool"; + + // scalar version + sb << fixity; + sb << "__generic<T : " << op.interface << ">\n"; + sb << "__intrinsic_op(" << int(op.opCode) << ") " << resultType << " operator" << op.opName << "(" << qual << "T value);\n"; + + // vector version + sb << "__generic<T : " << op.interface << ", let N : int> "; + sb << fixity; + sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << qual << "vector<T,N> value);\n"; + + // matrix version + sb << "__generic<T : " << op.interface << ", let N : int, let M : int> "; + sb << fixity; + sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << qual << "matrix<T,N,M> value);\n"; + } } for (auto op : binaryOps) { + char const* leftQual = ""; + if(op.flags & ASSIGNMENT) leftQual = "in out "; + for (auto type : kBaseTypes) { if ((type.flags & op.flags) == 0) @@ -1443,10 +1517,15 @@ for (auto op : binaryOps) if (op.flags & BOOL_RESULT) resultType = "bool"; - char const* leftQual = ""; - if(op.flags & ASSIGNMENT) leftQual = "in out "; - - // TODO: handle `SHIFT` + // TODO: We should handle a `SHIFT` flag on the op + // by changing `rightType` to `int` in order to + // account for the fact that the shift amount should + // always have a fixed type independent of the LHS. + // + // (It is unclear why this change hadn't been made + // already, so it is possible that such a change + // breaks overload resolution or other parts of + // the compiler) // scalar version sb << "__intrinsic_op(" << int(op.opCode) << ") " << resultType << " operator" << op.opName << "(" << leftQual << leftType << " left, " << rightType << " right);\n"; @@ -1459,9 +1538,38 @@ for (auto op : binaryOps) sb << "__generic<let N : int, let M : int> "; sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << leftQual << "matrix<" << leftType << ",N,M> left, matrix<" << rightType << ",N,M> right);\n"; - // We are going to go ahead and explicitly define combined - // operations for the scalar-op-vector, etc. cases, rather - // than rely on promotion rules. + // We currently synthesize addiitonal overloads + // for the case where one or the other operand + // is a scalar. This choice serves a few purposes: + // + // 1. It avoids introducing scalar-to-vector or + // scalar-to-matrix promotions before the operator, + // which might allow some back ends to produce + // more optimal code. + // + // 2. It avoids concerns about making overload resolution + // and the inference rules for `N` and `M` able to + // handle the mixed vector/scalar or matrix/scalar case. + // + // 3. Having explicit overloads for the matrix/scalar cases + // here means that we do *not* need to support a general + // implicit conversion from scalars to matrices, unless + // we decide we want to. + // + // Note: Case (2) of the motivation shouldn't really apply + // any more, because we end up having to support similar + // inteference for built-in binary math functions where + // vectors and scalars might be combined (and where defining + // additional overloads to cover all the combinations doesn't + // seem practical or desirable). + // + // TODO: We should consider whether dropping these extra + // overloads is possible and worth it. The optimization + // concern (1) could possibly be addressed in specific + // back-ends. The issue (3) about not wanting to support + // implicit scalar-to-matrix conversion may be moot if + // we end up needing to support mixed scalar/matrix input + // for builtin in non-operator functions anyway. // scalar-vector and scalar-matrix if (!(op.flags & ASSIGNMENT)) @@ -1480,6 +1588,46 @@ for (auto op : binaryOps) sb << "__generic<let N : int, let M : int> "; sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << leftQual << "matrix<" << leftType << ",N,M> left, " << rightType << " right);\n"; } + + // Synthesize generic versions + if(op.interface) + { + char const* leftType = "T"; + char const* rightType = leftType; + char const* resultType = leftType; + + if (op.flags & BOOL_RESULT) resultType = "bool"; + // TODO: handle `SHIFT` + + // scalar version + sb << "__generic<T : " << op.interface << ">\n"; + sb << "__intrinsic_op(" << int(op.opCode) << ") " << resultType << " operator" << op.opName << "(" << leftQual << leftType << " left, " << rightType << " right);\n"; + + // vector version + sb << "__generic<T : " << op.interface << ", let N : int> "; + sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << leftQual << "vector<" << leftType << ",N> left, vector<" << rightType << ",N> right);\n"; + + // matrix version + sb << "__generic<T : " << op.interface << ", let N : int, let M : int> "; + sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << leftQual << "matrix<" << leftType << ",N,M> left, matrix<" << rightType << ",N,M> right);\n"; + + // scalar-vector and scalar-matrix + if (!(op.flags & ASSIGNMENT)) + { + sb << "__generic<T : " << op.interface << ", let N : int> "; + sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << leftQual << leftType << " left, vector<" << rightType << ",N> right);\n"; + + sb << "__generic<T : " << op.interface << ", let N : int, let M : int> "; + sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << leftQual << leftType << " left, matrix<" << rightType << ",N,M> right);\n"; + } + + // vector-scalar and matrix-scalar + sb << "__generic<T : " << op.interface << ", let N : int> "; + sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << leftQual << "vector<" << leftType << ",N> left, " << rightType << " right);\n"; + + sb << "__generic<T : " << op.interface << ", let N : int, let M : int> "; + sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << leftQual << "matrix<" << leftType << ",N,M> left, " << rightType << " right);\n"; + } } }}}} diff --git a/source/slang/core.meta.slang.h b/source/slang/core.meta.slang.h index c2ed8cfc4..f793d0c1b 100644 --- a/source/slang/core.meta.slang.h +++ b/source/slang/core.meta.slang.h @@ -21,7 +21,14 @@ SLANG_RAW("// A type that can be used as an operand for builtins\n") SLANG_RAW("interface __BuiltinType {}\n") SLANG_RAW("\n") SLANG_RAW("// A type that can be used for arithmetic operations\n") -SLANG_RAW("interface __BuiltinArithmeticType : __BuiltinType {}\n") +SLANG_RAW("interface __BuiltinArithmeticType : __BuiltinType\n") +SLANG_RAW("{\n") +SLANG_RAW(" /// Initialize from a 32-bit signed integer value.\n") +SLANG_RAW(" __init(int value);\n") +SLANG_RAW("}\n") +SLANG_RAW("\n") +SLANG_RAW(" /// A type that can be used for logical/bitwsie operations\n") +SLANG_RAW("interface __BuiltinLogicalType : __BuiltinType {}\n") SLANG_RAW("\n") SLANG_RAW("// A type that logically has a sign (positive/negative/zero)\n") SLANG_RAW("interface __BuiltinSignedArithmeticType : __BuiltinArithmeticType {}\n") @@ -31,14 +38,16 @@ SLANG_RAW("interface __BuiltinIntegerType : __BuiltinArithmeticType\n") SLANG_RAW("{}\n") SLANG_RAW("\n") SLANG_RAW("// A type that can represent non-integers\n") -SLANG_RAW("interface __BuiltinRealType : __BuiltinArithmeticType {}\n") +SLANG_RAW("interface __BuiltinRealType : __BuiltinSignedArithmeticType {}\n") SLANG_RAW("\n") SLANG_RAW("// A type that uses a floating-point representation\n") -SLANG_RAW("interface __BuiltinFloatingPointType : __BuiltinRealType, __BuiltinSignedArithmeticType\n") +SLANG_RAW("interface __BuiltinFloatingPointType : __BuiltinRealType\n") SLANG_RAW("{\n") -SLANG_RAW(" // A builtin floating-point type must have an initializer that takes\n") -SLANG_RAW(" // a floating-point value...\n") +SLANG_RAW(" /// Initialize from a 32-bit floating-point value.\n") SLANG_RAW(" __init(float value);\n") +SLANG_RAW("\n") +SLANG_RAW(" /// Get the value of the mathematical constant pi in this type.\n") +SLANG_RAW(" static This getPi();\n") SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("// A type resulting from an `enum` declaration.\n") @@ -119,51 +128,69 @@ SLANG_RAW("\n") // We are going to use code generation to produce the // declarations for all of our base types. - static const int kBaseTypeCount = sizeof(kBaseTypes) / sizeof(kBaseTypes[0]); for (int tt = 0; tt < kBaseTypeCount; ++tt) { - EMIT_LINE_DIRECTIVE(); - sb << "__builtin_type(" << int(kBaseTypes[tt].tag) << ") struct " << kBaseTypes[tt].name; - - // Declare interface conformances for this type - - sb << "\n : __BuiltinType\n"; +SLANG_RAW("#line 131 \"core.meta.slang\"") +SLANG_RAW("\n") +SLANG_RAW("\n") +SLANG_RAW("__builtin_type(") +SLANG_SPLICE(int(kBaseTypes[tt].tag) +) +SLANG_RAW(")\n") +SLANG_RAW("struct ") +SLANG_SPLICE(kBaseTypes[tt].name +) +SLANG_RAW("\n") +SLANG_RAW(" : __BuiltinType\n") +SLANG_RAW("\n") switch (kBaseTypes[tt].tag) { case BaseType::Half: case BaseType::Float: case BaseType::Double: - sb << "\n , __BuiltinFloatingPointType\n"; - sb << "\n , __BuiltinRealType\n"; - sb << "\n , __BuiltinSignedArithmeticType\n"; - sb << "\n , __BuiltinArithmeticType\n"; - sb << "\n , __BuiltinType\n"; +SLANG_RAW("#line 143 \"core.meta.slang\"") +SLANG_RAW("\n") +SLANG_RAW(" , __BuiltinFloatingPointType\n") +SLANG_RAW(" , __BuiltinRealType\n") +SLANG_RAW(" , __BuiltinSignedArithmeticType\n") +SLANG_RAW(" , __BuiltinArithmeticType\n") + break; case BaseType::Int8: case BaseType::Int16: case BaseType::Int: case BaseType::Int64: - sb << "\n , __BuiltinSignedArithmeticType\n"; +SLANG_RAW("#line 154 \"core.meta.slang\"") +SLANG_RAW("\n") +SLANG_RAW(" , __BuiltinSignedArithmeticType\n") + ; // fall through to: case BaseType::UInt8: case BaseType::UInt16: case BaseType::UInt: case BaseType::UInt64: - sb << "\n , __BuiltinArithmeticType\n"; - sb << "\n , __BuiltinIntegerType\n"; +SLANG_RAW("#line 162 \"core.meta.slang\"") +SLANG_RAW("\n") +SLANG_RAW(" , __BuiltinArithmeticType\n") +SLANG_RAW(" , __BuiltinIntegerType\n") + ; // fall through to: case BaseType::Bool: - sb << "\n , __BuiltinType\n"; +SLANG_RAW("#line 168 \"core.meta.slang\"") +SLANG_RAW("\n") +SLANG_RAW(" , __BuiltinLogicalType\n") + break; default: break; } - - sb << "\n{\n"; - +SLANG_RAW("#line 176 \"core.meta.slang\"") +SLANG_RAW("\n") +SLANG_RAW("{\n") +SLANG_RAW("\n") // Declare initializers to convert from various other types for (int ss = 0; ss < kBaseTypeCount; ++ss) @@ -178,12 +205,19 @@ for (int tt = 0; tt < kBaseTypeCount; ++tt) ConversionCost conversionCost = getBaseTypeConversionCost( kBaseTypes[tt], kBaseTypes[ss]); +SLANG_RAW("#line 193 \"core.meta.slang\"") +SLANG_RAW("\n") +SLANG_RAW("\n") +SLANG_RAW(" __implicit_conversion(") +SLANG_SPLICE(conversionCost +) +SLANG_RAW(")\n") +SLANG_RAW(" __init(") +SLANG_SPLICE(kBaseTypes[ss].name +) +SLANG_RAW(" value);\n") +SLANG_RAW("\n") - EMIT_LINE_DIRECTIVE(); - sb << "__implicit_conversion(" << conversionCost << ")\n"; - - EMIT_LINE_DIRECTIVE(); - sb << "__init(" << kBaseTypes[ss].name << " value);\n"; } // If this is a basic integer type, then define explicit @@ -199,7 +233,7 @@ for (int tt = 0; tt < kBaseTypeCount; ++tt) // TODO: should this cover the full gamut of integer types? case BaseType::Int: case BaseType::UInt: -SLANG_RAW("#line 199 \"core.meta.slang\"") +SLANG_RAW("#line 214 \"core.meta.slang\"") SLANG_RAW("\n") SLANG_RAW(" __generic<T:__EnumType>\n") SLANG_RAW(" __init(T value);\n") @@ -210,13 +244,41 @@ SLANG_RAW(" __init(T value);\n") break; } - sb << "};\n"; + // If this is a floating-point type, then we need to + // define the basic `getPi()` function that is used + // to implement generic versions of `degrees()` and + // `radians()`. + // + switch (kBaseTypes[tt].tag) + { + default: + break; + case BaseType::Half: + case BaseType::Float: + case BaseType::Double: +SLANG_RAW("#line 236 \"core.meta.slang\"") +SLANG_RAW("\n") +SLANG_RAW(" static ") +SLANG_SPLICE(kBaseTypes[tt].name +) +SLANG_RAW(" getPi() { return ") +SLANG_SPLICE(kBaseTypes[tt].name +) +SLANG_RAW("(3.14159265358979323846264338328); }\n") + + break; + } +SLANG_RAW("#line 241 \"core.meta.slang\"") +SLANG_RAW("\n") +SLANG_RAW("\n") +SLANG_RAW("}\n") +SLANG_RAW("\n") + } // Declare built-in pointer type // (eventually we can have the traditional syntax sugar for this) -SLANG_RAW("#line 214 \"core.meta.slang\"") -SLANG_RAW("\n") +SLANG_RAW("#line 250 \"core.meta.slang\"") SLANG_RAW("\n") SLANG_RAW("__generic<T>\n") SLANG_RAW("__magic_type(PtrType)\n") @@ -262,27 +324,37 @@ SLANG_RAW(")\n") SLANG_RAW("struct String\n") SLANG_RAW("{};\n") SLANG_RAW("\n") - -// Declare vector and matrix types - -sb << "__generic<T = float, let N : int = 4> __magic_type(Vector) struct vector\n{\n"; -sb << " typedef T Element;\n"; - -// Declare initializer taking a single scalar of the elemnt type -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"; -SLANG_RAW("#line 260 \"core.meta.slang\"") +SLANG_RAW(" /// An `N` component vector with elements of type `T`.\n") +SLANG_RAW("__generic<T = float, let N : int = 4>\n") +SLANG_RAW("__magic_type(Vector)\n") +SLANG_RAW("struct vector\n") +SLANG_RAW("{\n") +SLANG_RAW(" /// The element type of the vector\n") +SLANG_RAW(" typedef T Element;\n") +SLANG_RAW("\n") +SLANG_RAW("\n") +SLANG_RAW(" /// Initialize a vector where all elements have the same scalar `value`.\n") +SLANG_RAW(" __implicit_conversion(") +SLANG_SPLICE(kConversionCost_ScalarToVector +) +SLANG_RAW(")\n") +SLANG_RAW(" __intrinsic_op(") +SLANG_SPLICE(kIROp_constructVectorFromScalar +) +SLANG_RAW(")\n") +SLANG_RAW(" __init(T value);\n") SLANG_RAW("\n") +SLANG_RAW(" /// Initialize a vector from a value of the same type\n") +SLANG_RAW(" // TODO: we should revise semantic checking so this kind of \"identity\" conversion is not required\n") +SLANG_RAW(" __init(vector<T,N> value);\n") +SLANG_RAW("}\n") SLANG_RAW("\n") +SLANG_RAW(" /// A matrix with `R` rows and `C` columns, with elements of type `T`.\n") SLANG_RAW("__generic<T = float, let R : int = 4, let C : int = 4>\n") SLANG_RAW("__magic_type(Matrix)\n") -SLANG_RAW("struct matrix {};\n") +SLANG_RAW("struct matrix\n") +SLANG_RAW("{\n") +SLANG_RAW("}\n") SLANG_RAW("\n") static const struct { @@ -331,20 +403,34 @@ for (int tt = 0; tt < kTypeCount; ++tt) } // Declare additional built-in generic types -// EMIT_LINE_DIRECTIVE(); - - -sb << "__generic<T>\n"; -sb << "__intrinsic_type(" << kIROp_ConstantBufferType << ")\n"; -sb << "__magic_type(ConstantBuffer) struct ConstantBuffer {};\n"; - -sb << "__generic<T>\n"; -sb << "__intrinsic_type(" << kIROp_TextureBufferType << ")\n"; -sb << "__magic_type(TextureBuffer) struct TextureBuffer {};\n"; +SLANG_RAW("#line 353 \"core.meta.slang\"") +SLANG_RAW("\n") +SLANG_RAW("\n") +SLANG_RAW("__generic<T>\n") +SLANG_RAW("__intrinsic_type(") +SLANG_SPLICE(kIROp_ConstantBufferType +) +SLANG_RAW(")\n") +SLANG_RAW("__magic_type(ConstantBuffer)\n") +SLANG_RAW("struct ConstantBuffer {}\n") +SLANG_RAW("\n") +SLANG_RAW("__generic<T>\n") +SLANG_RAW("__intrinsic_type(") +SLANG_SPLICE(kIROp_TextureBufferType +) +SLANG_RAW(")\n") +SLANG_RAW("__magic_type(TextureBuffer)\n") +SLANG_RAW("struct TextureBuffer {}\n") +SLANG_RAW("\n") +SLANG_RAW("__generic<T>\n") +SLANG_RAW("__intrinsic_type(") +SLANG_SPLICE(kIROp_ParameterBlockType +) +SLANG_RAW(")\n") +SLANG_RAW("__magic_type(ParameterBlockType)\n") +SLANG_RAW("struct ParameterBlock {}\n") +SLANG_RAW("\n") -sb << "__generic<T>\n"; -sb << "__intrinsic_type(" << kIROp_ParameterBlockType << ")\n"; -sb << "__magic_type(ParameterBlockType) struct ParameterBlock {};\n"; static const char* kComponentNames[]{ "x", "y", "z", "w" }; static const char* kVectorNames[]{ "", "x", "xy", "xyz", "xyzw" }; @@ -475,20 +561,38 @@ for( int C = 2; C <= 4; ++C ) sb << "}\n"; } +SLANG_RAW("#line 501 \"core.meta.slang\"") +SLANG_RAW("\n") +SLANG_RAW("\n") +SLANG_RAW("\n") +SLANG_RAW(" /// Sampling state for filtered texture fetches.\n") +SLANG_RAW("__magic_type(SamplerState, ") +SLANG_SPLICE(int(SamplerStateFlavor::SamplerState) +) +SLANG_RAW(")\n") +SLANG_RAW("__intrinsic_type(") +SLANG_SPLICE(kIROp_SamplerStateType +) +SLANG_RAW(")\n") +SLANG_RAW("struct SamplerState\n") +SLANG_RAW("{\n") +SLANG_RAW("}\n") +SLANG_RAW("\n") +SLANG_RAW(" /// Sampling state for filtered texture fetches that include a comparison operation before filtering.\n") +SLANG_RAW("__magic_type(SamplerState, ") +SLANG_SPLICE(int(SamplerStateFlavor::SamplerComparisonState) +) +SLANG_RAW(")\n") +SLANG_RAW("__intrinsic_type(") +SLANG_SPLICE(kIROp_SamplerComparisonStateType +) +SLANG_RAW(")\n") +SLANG_RAW("struct SamplerComparisonState\n") +SLANG_RAW("{\n") +SLANG_RAW("}\n") +SLANG_RAW("\n") -// Declare built-in texture and sampler types - - - -sb << "__magic_type(SamplerState," << int(SamplerStateFlavor::SamplerState) << ")\n"; -sb << "__intrinsic_type(" << kIROp_SamplerStateType << ")\n"; -sb << "struct SamplerState {};"; - -sb << "__magic_type(SamplerState," << int(SamplerStateFlavor::SamplerComparisonState) << ")\n"; -sb << "__intrinsic_type(" << kIROp_SamplerComparisonStateType << ")\n"; -sb << "struct SamplerComparisonState {};"; -// TODO(tfoley): Need to handle `RW*` variants of texture types as well... static const struct { char const* name; TextureFlavor::Shape baseShape; @@ -1424,6 +1528,9 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) for (auto op : unaryOps) { + char const* fixity = (op.flags & POSTFIX) != 0 ? "__postfix " : "__prefix "; + char const* qual = (op.flags & ASSIGNMENT) != 0 ? "in out " : ""; + for (auto type : kBaseTypes) { if ((type.flags & op.flags) == 0) @@ -1432,9 +1539,6 @@ for (auto op : unaryOps) char const* resultType = type.name; if (op.flags & BOOL_RESULT) resultType = "bool"; - char const* fixity = (op.flags & POSTFIX) != 0 ? "__postfix " : "__prefix "; - char const* qual = (op.flags & ASSIGNMENT) != 0 ? "in out " : ""; - // scalar version sb << fixity; sb << "__intrinsic_op(" << int(op.opCode) << ") " << resultType << " operator" << op.opName << "(" << qual << type.name << " value);\n"; @@ -1449,10 +1553,35 @@ for (auto op : unaryOps) sb << fixity; sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << qual << "matrix<" << type.name << ",N,M> value);\n"; } + + // Synthesize generic versions + if(op.interface) + { + char const* resultType = "T"; + if (op.flags & BOOL_RESULT) resultType = "bool"; + + // scalar version + sb << fixity; + sb << "__generic<T : " << op.interface << ">\n"; + sb << "__intrinsic_op(" << int(op.opCode) << ") " << resultType << " operator" << op.opName << "(" << qual << "T value);\n"; + + // vector version + sb << "__generic<T : " << op.interface << ", let N : int> "; + sb << fixity; + sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << qual << "vector<T,N> value);\n"; + + // matrix version + sb << "__generic<T : " << op.interface << ", let N : int, let M : int> "; + sb << fixity; + sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << qual << "matrix<T,N,M> value);\n"; + } } for (auto op : binaryOps) { + char const* leftQual = ""; + if(op.flags & ASSIGNMENT) leftQual = "in out "; + for (auto type : kBaseTypes) { if ((type.flags & op.flags) == 0) @@ -1464,10 +1593,15 @@ for (auto op : binaryOps) if (op.flags & BOOL_RESULT) resultType = "bool"; - char const* leftQual = ""; - if(op.flags & ASSIGNMENT) leftQual = "in out "; - - // TODO: handle `SHIFT` + // TODO: We should handle a `SHIFT` flag on the op + // by changing `rightType` to `int` in order to + // account for the fact that the shift amount should + // always have a fixed type independent of the LHS. + // + // (It is unclear why this change hadn't been made + // already, so it is possible that such a change + // breaks overload resolution or other parts of + // the compiler) // scalar version sb << "__intrinsic_op(" << int(op.opCode) << ") " << resultType << " operator" << op.opName << "(" << leftQual << leftType << " left, " << rightType << " right);\n"; @@ -1480,9 +1614,38 @@ for (auto op : binaryOps) sb << "__generic<let N : int, let M : int> "; sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << leftQual << "matrix<" << leftType << ",N,M> left, matrix<" << rightType << ",N,M> right);\n"; - // We are going to go ahead and explicitly define combined - // operations for the scalar-op-vector, etc. cases, rather - // than rely on promotion rules. + // We currently synthesize addiitonal overloads + // for the case where one or the other operand + // is a scalar. This choice serves a few purposes: + // + // 1. It avoids introducing scalar-to-vector or + // scalar-to-matrix promotions before the operator, + // which might allow some back ends to produce + // more optimal code. + // + // 2. It avoids concerns about making overload resolution + // and the inference rules for `N` and `M` able to + // handle the mixed vector/scalar or matrix/scalar case. + // + // 3. Having explicit overloads for the matrix/scalar cases + // here means that we do *not* need to support a general + // implicit conversion from scalars to matrices, unless + // we decide we want to. + // + // Note: Case (2) of the motivation shouldn't really apply + // any more, because we end up having to support similar + // inteference for built-in binary math functions where + // vectors and scalars might be combined (and where defining + // additional overloads to cover all the combinations doesn't + // seem practical or desirable). + // + // TODO: We should consider whether dropping these extra + // overloads is possible and worth it. The optimization + // concern (1) could possibly be addressed in specific + // back-ends. The issue (3) about not wanting to support + // implicit scalar-to-matrix conversion may be moot if + // we end up needing to support mixed scalar/matrix input + // for builtin in non-operator functions anyway. // scalar-vector and scalar-matrix if (!(op.flags & ASSIGNMENT)) @@ -1501,8 +1664,48 @@ for (auto op : binaryOps) sb << "__generic<let N : int, let M : int> "; sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << leftQual << "matrix<" << leftType << ",N,M> left, " << rightType << " right);\n"; } + + // Synthesize generic versions + if(op.interface) + { + char const* leftType = "T"; + char const* rightType = leftType; + char const* resultType = leftType; + + if (op.flags & BOOL_RESULT) resultType = "bool"; + // TODO: handle `SHIFT` + + // scalar version + sb << "__generic<T : " << op.interface << ">\n"; + sb << "__intrinsic_op(" << int(op.opCode) << ") " << resultType << " operator" << op.opName << "(" << leftQual << leftType << " left, " << rightType << " right);\n"; + + // vector version + sb << "__generic<T : " << op.interface << ", let N : int> "; + sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << leftQual << "vector<" << leftType << ",N> left, vector<" << rightType << ",N> right);\n"; + + // matrix version + sb << "__generic<T : " << op.interface << ", let N : int, let M : int> "; + sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << leftQual << "matrix<" << leftType << ",N,M> left, matrix<" << rightType << ",N,M> right);\n"; + + // scalar-vector and scalar-matrix + if (!(op.flags & ASSIGNMENT)) + { + sb << "__generic<T : " << op.interface << ", let N : int> "; + sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << leftQual << leftType << " left, vector<" << rightType << ",N> right);\n"; + + sb << "__generic<T : " << op.interface << ", let N : int, let M : int> "; + sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << leftQual << leftType << " left, matrix<" << rightType << ",N,M> right);\n"; + } + + // vector-scalar and matrix-scalar + sb << "__generic<T : " << op.interface << ", let N : int> "; + sb << "__intrinsic_op(" << int(op.opCode) << ") vector<" << resultType << ",N> operator" << op.opName << "(" << leftQual << "vector<" << leftType << ",N> left, " << rightType << " right);\n"; + + sb << "__generic<T : " << op.interface << ", let N : int, let M : int> "; + sb << "__intrinsic_op(" << int(op.opCode) << ") matrix<" << resultType << ",N,M> operator" << op.opName << "(" << leftQual << "matrix<" << leftType << ",N,M> left, " << rightType << " right);\n"; + } } -SLANG_RAW("#line 1484 \"core.meta.slang\"") +SLANG_RAW("#line 1632 \"core.meta.slang\"") SLANG_RAW("\n") SLANG_RAW("\n") SLANG_RAW("// Specialized function\n") diff --git a/source/slang/hlsl.meta.slang b/source/slang/hlsl.meta.slang index de81cd9f8..b020ef4d4 100644 --- a/source/slang/hlsl.meta.slang +++ b/source/slang/hlsl.meta.slang @@ -350,6 +350,9 @@ void abort(); __generic<T : __BuiltinSignedArithmeticType> T abs(T x); +/*{ + return x < 0 ? -x : x; +}*/ __generic<T : __BuiltinSignedArithmeticType, let N : int> __target_intrinsic(hlsl) @@ -387,9 +390,37 @@ matrix<T, N, M> acos(matrix<T, N, M> x) } // Test if all components are non-zero (HLSL SM 1.0) -__generic<T : __BuiltinType> bool all(T x); -__generic<T : __BuiltinType, let N : int> bool all(vector<T,N> x); -__generic<T : __BuiltinType, let N : int, let M : int> bool all(matrix<T,N,M> x); +__generic<T : __BuiltinType> +__target_intrinsic(glsl, "bool($0)") +bool all(T x); + +__generic<T : __BuiltinType, let N : int> +__target_intrinsic(hlsl) +__target_intrinsic(glsl, "all(bvec$N0($0))") +bool all(vector<T,N> x); +// TODO: implementation of `all()` in the stdlib is +// blocked on fixing implementation of `bool` vector +// `getAt` on the CUDA codegen path. +/* +{ + bool result = true; + for(int i = 0; i < N; ++i) + result = result && all(x[i]); + return result; +} +*/ + +__generic<T : __BuiltinType, let N : int, let M : int> +__target_intrinsic(hlsl) +bool all(matrix<T,N,M> x); +/* +{ + bool result = true; + for(int i = 0; i < N; ++i) + result = result && all(x[i]); + return result; +} +*/ // Barrier for writes to all memory spaces (HLSL SM 5.0) __target_intrinsic(glsl, "memoryBarrier(), groupMemoryBarrier(), memoryBarrierImage(), memoryBarrierBuffer()") @@ -715,27 +746,24 @@ matrix<T,N,M> clamp(matrix<T,N,M> x, matrix<T,N,M> minBound, matrix<T,N,M> maxBo // Clip (discard) fragment conditionally __generic<T : __BuiltinFloatingPointType> __target_intrinsic(hlsl) -void clip(T x); -// TODO: filling this in here requires ability to invoke `operator<(T,T)` -/*{ +void clip(T x) +{ if(x < T(0)) discard; -}*/ +} __generic<T : __BuiltinFloatingPointType, let N : int> __target_intrinsic(hlsl) -void clip(vector<T,N> x); -// TODO: filling this in here requires ability to invoke `operator<(T,T)` -/*{ +void clip(vector<T,N> x) +{ if(any(x < T(0))) discard; -}*/ +} __generic<T : __BuiltinFloatingPointType, let N : int, let M : int> __target_intrinsic(hlsl) -void clip(matrix<T,N,M> x); -// TODO: filling this in here requires ability to invoke `operator<(T,T)` -/*{ +void clip(matrix<T,N,M> x) +{ if(any(x < T(0))) discard; -}*/ +} // Cosine __generic<T : __BuiltinFloatingPointType> @@ -783,15 +811,13 @@ uint countbits(uint value); __generic<T : __BuiltinArithmeticType> __target_intrinsic(hlsl) __target_intrinsic(glsl) -vector<T,3> cross(vector<T,3> left, vector<T,3> right); -// TODO: filling this in here requires ability to invoke `operator*(T,T)`, etc. -/*{ +vector<T,3> cross(vector<T,3> left, vector<T,3> right) +{ return vector<T,3>( left.y * right.z - left.z * right.y, left.z * right.x - left.x * right.z, left.x * right.y - left.y * right.x); -}*/ - +} // Convert encoded color __target_intrinsic(hlsl) @@ -807,54 +833,83 @@ __target_intrinsic(glsl, dFdx) T ddx(T x); __generic<T : __BuiltinFloatingPointType, let N : int> +__target_intrinsic(hlsl) __target_intrinsic(glsl, dFdx) -vector<T,N> ddx(vector<T,N> x); +vector<T, N> ddx(vector<T, N> x) +{ + VECTOR_MAP_UNARY(T, N, ddx, x); +} __generic<T : __BuiltinFloatingPointType, let N : int, let M : int> -__target_intrinsic(glsl, dFdx) -matrix<T,N,M> ddx(matrix<T,N,M> x); +__target_intrinsic(hlsl) +matrix<T, N, M> ddx(matrix<T, N, M> x) +{ + MATRIX_MAP_UNARY(T, N, M, ddx, x); +} __generic<T : __BuiltinFloatingPointType> +__target_intrinsic(hlsl) __glsl_extension(GL_ARB_derivative_control) __target_intrinsic(glsl, dFdxCoarse) T ddx_coarse(T x); __generic<T : __BuiltinFloatingPointType, let N : int> +__target_intrinsic(hlsl) __glsl_extension(GL_ARB_derivative_control) __target_intrinsic(glsl, dFdxCoarse) -vector<T,N> ddx_coarse(vector<T,N> x); +vector<T, N> ddx_coarse(vector<T, N> x) +{ + VECTOR_MAP_UNARY(T, N, ddx_coarse, x); +} __generic<T : __BuiltinFloatingPointType, let N : int, let M : int> -__glsl_extension(GL_ARB_derivative_control) -__target_intrinsic(glsl, dFdxCoarse) -matrix<T,N,M> ddx_coarse(matrix<T,N,M> x); +__target_intrinsic(hlsl) +matrix<T, N, M> ddx_coarse(matrix<T, N, M> x) +{ + MATRIX_MAP_UNARY(T, N, M, ddx_coarse, x); +} __generic<T : __BuiltinFloatingPointType> +__target_intrinsic(hlsl) __glsl_extension(GL_ARB_derivative_control) __target_intrinsic(glsl, dFdxFine) T ddx_fine(T x); __generic<T : __BuiltinFloatingPointType, let N : int> +__target_intrinsic(hlsl) __glsl_extension(GL_ARB_derivative_control) __target_intrinsic(glsl, dFdxFine) -vector<T,N> ddx_fine(vector<T,N> x); +vector<T, N> ddx_fine(vector<T, N> x) +{ + VECTOR_MAP_UNARY(T, N, ddx_fine, x); +} __generic<T : __BuiltinFloatingPointType, let N : int, let M : int> -__glsl_extension(GL_ARB_derivative_control) -__target_intrinsic(glsl, dFdxFine) -matrix<T,N,M> ddx_fine(matrix<T,N,M> x); +__target_intrinsic(hlsl) +matrix<T, N, M> ddx_fine(matrix<T, N, M> x) +{ + MATRIX_MAP_UNARY(T, N, M, ddx_fine, x); +} __generic<T : __BuiltinFloatingPointType> +__target_intrinsic(hlsl) __target_intrinsic(glsl, dFdy) T ddy(T x); __generic<T : __BuiltinFloatingPointType, let N : int> +__target_intrinsic(hlsl) __target_intrinsic(glsl, dFdy) -vector<T,N> ddy(vector<T,N> x); +vector<T, N> ddy(vector<T, N> x) +{ + VECTOR_MAP_UNARY(T, N, ddy, x); +} __generic<T : __BuiltinFloatingPointType, let N : int, let M : int> -__target_intrinsic(glsl, dFdy) - matrix<T,N,M> ddy(matrix<T,N,M> x); +__target_intrinsic(hlsl) +matrix<T, N, M> ddy(matrix<T, N, M> x) +{ + MATRIX_MAP_UNARY(T, N, M, ddy, x); +} __generic<T : __BuiltinFloatingPointType> __glsl_extension(GL_ARB_derivative_control) @@ -862,44 +917,61 @@ __target_intrinsic(glsl, dFdyCoarse) T ddy_coarse(T x); __generic<T : __BuiltinFloatingPointType, let N : int> +__target_intrinsic(hlsl) __glsl_extension(GL_ARB_derivative_control) __target_intrinsic(glsl, dFdyCoarse) -vector<T,N> ddy_coarse(vector<T,N> x); +vector<T, N> ddy_coarse(vector<T, N> x) +{ + VECTOR_MAP_UNARY(T, N, ddy_coarse, x); +} __generic<T : __BuiltinFloatingPointType, let N : int, let M : int> -__glsl_extension(GL_ARB_derivative_control) -__target_intrinsic(glsl, dFdyCoarse) -matrix<T,N,M> ddy_coarse(matrix<T,N,M> x); +__target_intrinsic(hlsl) +matrix<T, N, M> ddy_coarse(matrix<T, N, M> x) +{ + MATRIX_MAP_UNARY(T, N, M, ddy_coarse, x); +} __generic<T : __BuiltinFloatingPointType> +__target_intrinsic(hlsl) __glsl_extension(GL_ARB_derivative_control) __target_intrinsic(glsl, dFdyFine) T ddy_fine(T x); __generic<T : __BuiltinFloatingPointType, let N : int> +__target_intrinsic(hlsl) __glsl_extension(GL_ARB_derivative_control) __target_intrinsic(glsl, dFdyFine) -vector<T,N> ddy_fine(vector<T,N> x); +vector<T, N> ddy_fine(vector<T, N> x) +{ + VECTOR_MAP_UNARY(T, N, ddy_fine, x); +} __generic<T : __BuiltinFloatingPointType, let N : int, let M : int> -__glsl_extension(GL_ARB_derivative_control) -__target_intrinsic(glsl, dFdyFine) -matrix<T,N,M> ddy_fine(matrix<T,N,M> x); +__target_intrinsic(hlsl) +matrix<T, N, M> ddy_fine(matrix<T, N, M> x) +{ + MATRIX_MAP_UNARY(T, N, M, ddy_fine, x); +} // Radians to degrees __generic<T : __BuiltinFloatingPointType> __target_intrinsic(hlsl) -__target_intrinsic(hlsl) -T degrees(T x); -// TODO: filling this in here requires ability to invoke `operator*` on T, -// and convert a constant to `T` for the conversion factor +__target_intrinsic(glsl) +T degrees(T x) +{ + return x * (T(180) / T.getPi()); +} __generic<T : __BuiltinFloatingPointType, let N : int> __target_intrinsic(hlsl) -__target_intrinsic(hlsl) -vector<T,N> degrees(vector<T,N> x); +__target_intrinsic(glsl) +vector<T, N> degrees(vector<T, N> x) +{ + VECTOR_MAP_UNARY(T, N, degrees, x); +} __generic<T : __BuiltinFloatingPointType, let N : int, let M : int> __target_intrinsic(hlsl) @@ -923,11 +995,26 @@ void DeviceMemoryBarrierWithGroupSync(); // Vector distance -__generic<T : __BuiltinFloatingPointType, let N : int> T distance(vector<T,N> x, vector<T,N> y); +__generic<T : __BuiltinFloatingPointType, let N : int> +__target_intrinsic(hlsl) +__target_intrinsic(glsl) +T distance(vector<T, N> x, vector<T, N> y) +{ + return length(x - y); +} // Vector dot product -__generic<T : __BuiltinArithmeticType, let N : int> T dot(vector<T,N> x, vector<T,N> y); +__generic<T : __BuiltinArithmeticType, let N : int> +__target_intrinsic(hlsl) +__target_intrinsic(glsl) +T dot(vector<T, N> x, vector<T, N> y) +{ + T result = T(0); + for(int i = 0; i < N; ++i) + result += x[i] * y[i]; + return result; +} // Helper for computing distance terms for lighting (obsolete) @@ -1040,10 +1127,10 @@ vector<uint,N> f32tof16(vector<float,N> value); __generic<T : __BuiltinFloatingPointType, let N : int> __target_intrinsic(hlsl) __target_intrinsic(glsl) -vector<T,N> faceforward(vector<T,N> n, vector<T,N> i, vector<T,N> ng); -/*{ +vector<T,N> faceforward(vector<T,N> n, vector<T,N> i, vector<T,N> ng) +{ return dot(ng, i) < T(0.0f) ? n : -n; -}*/ +} // Find first set bit starting at high bit and working down __target_intrinsic(glsl,"findMSB") diff --git a/source/slang/hlsl.meta.slang.h b/source/slang/hlsl.meta.slang.h index 17d7aa0bc..677fa48f1 100644 --- a/source/slang/hlsl.meta.slang.h +++ b/source/slang/hlsl.meta.slang.h @@ -399,6 +399,9 @@ SLANG_RAW("// Absolute value (HLSL SM 1.0)\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinSignedArithmeticType>\n") SLANG_RAW("T abs(T x);\n") +SLANG_RAW("/*{\n") +SLANG_RAW(" return x < 0 ? -x : x;\n") +SLANG_RAW("}*/\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinSignedArithmeticType, let N : int>\n") SLANG_RAW("__target_intrinsic(hlsl)\n") @@ -436,9 +439,37 @@ SLANG_RAW(" MATRIX_MAP_UNARY(T, N, M, acos, x);\n") SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("// Test if all components are non-zero (HLSL SM 1.0)\n") -SLANG_RAW("__generic<T : __BuiltinType> bool all(T x);\n") -SLANG_RAW("__generic<T : __BuiltinType, let N : int> bool all(vector<T,N> x);\n") -SLANG_RAW("__generic<T : __BuiltinType, let N : int, let M : int> bool all(matrix<T,N,M> x);\n") +SLANG_RAW("__generic<T : __BuiltinType>\n") +SLANG_RAW("__target_intrinsic(glsl, \"bool($0)\")\n") +SLANG_RAW("bool all(T x);\n") +SLANG_RAW("\n") +SLANG_RAW("__generic<T : __BuiltinType, let N : int>\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") +SLANG_RAW("__target_intrinsic(glsl, \"all(bvec$N0($0))\")\n") +SLANG_RAW("bool all(vector<T,N> x);\n") +SLANG_RAW("// TODO: implementation of `all()` in the stdlib is\n") +SLANG_RAW("// blocked on fixing implementation of `bool` vector\n") +SLANG_RAW("// `getAt` on the CUDA codegen path.\n") +SLANG_RAW("/*\n") +SLANG_RAW("{\n") +SLANG_RAW(" bool result = true;\n") +SLANG_RAW(" for(int i = 0; i < N; ++i)\n") +SLANG_RAW(" result = result && all(x[i]);\n") +SLANG_RAW(" return result;\n") +SLANG_RAW("}\n") +SLANG_RAW("*/\n") +SLANG_RAW("\n") +SLANG_RAW("__generic<T : __BuiltinType, let N : int, let M : int>\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") +SLANG_RAW("bool all(matrix<T,N,M> x);\n") +SLANG_RAW("/*\n") +SLANG_RAW("{\n") +SLANG_RAW(" bool result = true;\n") +SLANG_RAW(" for(int i = 0; i < N; ++i)\n") +SLANG_RAW(" result = result && all(x[i]);\n") +SLANG_RAW(" return result;\n") +SLANG_RAW("}\n") +SLANG_RAW("*/\n") SLANG_RAW("\n") SLANG_RAW("// Barrier for writes to all memory spaces (HLSL SM 5.0)\n") SLANG_RAW("__target_intrinsic(glsl, \"memoryBarrier(), groupMemoryBarrier(), memoryBarrierImage(), memoryBarrierBuffer()\")\n") @@ -791,27 +822,24 @@ SLANG_RAW("\n") SLANG_RAW("// Clip (discard) fragment conditionally\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType>\n") SLANG_RAW("__target_intrinsic(hlsl)\n") -SLANG_RAW("void clip(T x);\n") -SLANG_RAW("// TODO: filling this in here requires ability to invoke `operator<(T,T)`\n") -SLANG_RAW("/*{\n") +SLANG_RAW("void clip(T x)\n") +SLANG_RAW("{\n") SLANG_RAW(" if(x < T(0)) discard;\n") -SLANG_RAW("}*/\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int>\n") SLANG_RAW("__target_intrinsic(hlsl)\n") -SLANG_RAW("void clip(vector<T,N> x);\n") -SLANG_RAW("// TODO: filling this in here requires ability to invoke `operator<(T,T)`\n") -SLANG_RAW("/*{\n") +SLANG_RAW("void clip(vector<T,N> x)\n") +SLANG_RAW("{\n") SLANG_RAW(" if(any(x < T(0))) discard;\n") -SLANG_RAW("}*/\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int, let M : int>\n") SLANG_RAW("__target_intrinsic(hlsl)\n") -SLANG_RAW("void clip(matrix<T,N,M> x);\n") -SLANG_RAW("// TODO: filling this in here requires ability to invoke `operator<(T,T)`\n") -SLANG_RAW("/*{\n") +SLANG_RAW("void clip(matrix<T,N,M> x)\n") +SLANG_RAW("{\n") SLANG_RAW(" if(any(x < T(0))) discard;\n") -SLANG_RAW("}*/\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("// Cosine\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType>\n") @@ -859,15 +887,13 @@ SLANG_RAW("// Cross product\n") SLANG_RAW("__generic<T : __BuiltinArithmeticType>\n") SLANG_RAW("__target_intrinsic(hlsl)\n") SLANG_RAW("__target_intrinsic(glsl)\n") -SLANG_RAW("vector<T,3> cross(vector<T,3> left, vector<T,3> right);\n") -SLANG_RAW("// TODO: filling this in here requires ability to invoke `operator*(T,T)`, etc.\n") -SLANG_RAW("/*{\n") +SLANG_RAW("vector<T,3> cross(vector<T,3> left, vector<T,3> right)\n") +SLANG_RAW("{\n") SLANG_RAW(" return vector<T,3>(\n") SLANG_RAW(" left.y * right.z - left.z * right.y,\n") SLANG_RAW(" left.z * right.x - left.x * right.z,\n") SLANG_RAW(" left.x * right.y - left.y * right.x);\n") -SLANG_RAW("}*/\n") -SLANG_RAW("\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("// Convert encoded color\n") SLANG_RAW("__target_intrinsic(hlsl)\n") @@ -883,54 +909,83 @@ SLANG_RAW("__target_intrinsic(glsl, dFdx)\n") SLANG_RAW("T ddx(T x);\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int>\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") SLANG_RAW("__target_intrinsic(glsl, dFdx)\n") -SLANG_RAW("vector<T,N> ddx(vector<T,N> x);\n") +SLANG_RAW("vector<T, N> ddx(vector<T, N> x)\n") +SLANG_RAW("{\n") +SLANG_RAW(" VECTOR_MAP_UNARY(T, N, ddx, x);\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int, let M : int>\n") -SLANG_RAW("__target_intrinsic(glsl, dFdx)\n") -SLANG_RAW("matrix<T,N,M> ddx(matrix<T,N,M> x);\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") +SLANG_RAW("matrix<T, N, M> ddx(matrix<T, N, M> x)\n") +SLANG_RAW("{\n") +SLANG_RAW(" MATRIX_MAP_UNARY(T, N, M, ddx, x);\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType>\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") SLANG_RAW("__glsl_extension(GL_ARB_derivative_control)\n") SLANG_RAW("__target_intrinsic(glsl, dFdxCoarse)\n") SLANG_RAW("T ddx_coarse(T x);\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int>\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") SLANG_RAW("__glsl_extension(GL_ARB_derivative_control)\n") SLANG_RAW("__target_intrinsic(glsl, dFdxCoarse)\n") -SLANG_RAW("vector<T,N> ddx_coarse(vector<T,N> x);\n") +SLANG_RAW("vector<T, N> ddx_coarse(vector<T, N> x)\n") +SLANG_RAW("{\n") +SLANG_RAW(" VECTOR_MAP_UNARY(T, N, ddx_coarse, x);\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int, let M : int>\n") -SLANG_RAW("__glsl_extension(GL_ARB_derivative_control)\n") -SLANG_RAW("__target_intrinsic(glsl, dFdxCoarse)\n") -SLANG_RAW("matrix<T,N,M> ddx_coarse(matrix<T,N,M> x);\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") +SLANG_RAW("matrix<T, N, M> ddx_coarse(matrix<T, N, M> x)\n") +SLANG_RAW("{\n") +SLANG_RAW(" MATRIX_MAP_UNARY(T, N, M, ddx_coarse, x);\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType>\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") SLANG_RAW("__glsl_extension(GL_ARB_derivative_control)\n") SLANG_RAW("__target_intrinsic(glsl, dFdxFine)\n") SLANG_RAW("T ddx_fine(T x);\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int>\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") SLANG_RAW("__glsl_extension(GL_ARB_derivative_control)\n") SLANG_RAW("__target_intrinsic(glsl, dFdxFine)\n") -SLANG_RAW("vector<T,N> ddx_fine(vector<T,N> x);\n") +SLANG_RAW("vector<T, N> ddx_fine(vector<T, N> x)\n") +SLANG_RAW("{\n") +SLANG_RAW(" VECTOR_MAP_UNARY(T, N, ddx_fine, x);\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int, let M : int>\n") -SLANG_RAW("__glsl_extension(GL_ARB_derivative_control)\n") -SLANG_RAW("__target_intrinsic(glsl, dFdxFine)\n") -SLANG_RAW("matrix<T,N,M> ddx_fine(matrix<T,N,M> x);\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") +SLANG_RAW("matrix<T, N, M> ddx_fine(matrix<T, N, M> x)\n") +SLANG_RAW("{\n") +SLANG_RAW(" MATRIX_MAP_UNARY(T, N, M, ddx_fine, x);\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType>\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") SLANG_RAW("__target_intrinsic(glsl, dFdy)\n") SLANG_RAW("T ddy(T x);\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int>\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") SLANG_RAW("__target_intrinsic(glsl, dFdy)\n") -SLANG_RAW("vector<T,N> ddy(vector<T,N> x);\n") +SLANG_RAW("vector<T, N> ddy(vector<T, N> x)\n") +SLANG_RAW("{\n") +SLANG_RAW(" VECTOR_MAP_UNARY(T, N, ddy, x);\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int, let M : int>\n") -SLANG_RAW("__target_intrinsic(glsl, dFdy)\n") -SLANG_RAW(" matrix<T,N,M> ddy(matrix<T,N,M> x);\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") +SLANG_RAW("matrix<T, N, M> ddy(matrix<T, N, M> x)\n") +SLANG_RAW("{\n") +SLANG_RAW(" MATRIX_MAP_UNARY(T, N, M, ddy, x);\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType>\n") SLANG_RAW("__glsl_extension(GL_ARB_derivative_control)\n") @@ -938,44 +993,61 @@ SLANG_RAW("__target_intrinsic(glsl, dFdyCoarse)\n") SLANG_RAW("T ddy_coarse(T x);\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int>\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") SLANG_RAW("__glsl_extension(GL_ARB_derivative_control)\n") SLANG_RAW("__target_intrinsic(glsl, dFdyCoarse)\n") -SLANG_RAW("vector<T,N> ddy_coarse(vector<T,N> x);\n") +SLANG_RAW("vector<T, N> ddy_coarse(vector<T, N> x)\n") +SLANG_RAW("{\n") +SLANG_RAW(" VECTOR_MAP_UNARY(T, N, ddy_coarse, x);\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int, let M : int>\n") -SLANG_RAW("__glsl_extension(GL_ARB_derivative_control)\n") -SLANG_RAW("__target_intrinsic(glsl, dFdyCoarse)\n") -SLANG_RAW("matrix<T,N,M> ddy_coarse(matrix<T,N,M> x);\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") +SLANG_RAW("matrix<T, N, M> ddy_coarse(matrix<T, N, M> x)\n") +SLANG_RAW("{\n") +SLANG_RAW(" MATRIX_MAP_UNARY(T, N, M, ddy_coarse, x);\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType>\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") SLANG_RAW("__glsl_extension(GL_ARB_derivative_control)\n") SLANG_RAW("__target_intrinsic(glsl, dFdyFine)\n") SLANG_RAW("T ddy_fine(T x);\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int>\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") SLANG_RAW("__glsl_extension(GL_ARB_derivative_control)\n") SLANG_RAW("__target_intrinsic(glsl, dFdyFine)\n") -SLANG_RAW("vector<T,N> ddy_fine(vector<T,N> x);\n") +SLANG_RAW("vector<T, N> ddy_fine(vector<T, N> x)\n") +SLANG_RAW("{\n") +SLANG_RAW(" VECTOR_MAP_UNARY(T, N, ddy_fine, x);\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int, let M : int>\n") -SLANG_RAW("__glsl_extension(GL_ARB_derivative_control)\n") -SLANG_RAW("__target_intrinsic(glsl, dFdyFine)\n") -SLANG_RAW("matrix<T,N,M> ddy_fine(matrix<T,N,M> x);\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") +SLANG_RAW("matrix<T, N, M> ddy_fine(matrix<T, N, M> x)\n") +SLANG_RAW("{\n") +SLANG_RAW(" MATRIX_MAP_UNARY(T, N, M, ddy_fine, x);\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("\n") SLANG_RAW("// Radians to degrees\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType>\n") SLANG_RAW("__target_intrinsic(hlsl)\n") -SLANG_RAW("__target_intrinsic(hlsl)\n") -SLANG_RAW("T degrees(T x);\n") -SLANG_RAW("// TODO: filling this in here requires ability to invoke `operator*` on T,\n") -SLANG_RAW("// and convert a constant to `T` for the conversion factor\n") +SLANG_RAW("__target_intrinsic(glsl)\n") +SLANG_RAW("T degrees(T x)\n") +SLANG_RAW("{\n") +SLANG_RAW(" return x * (T(180) / T.getPi());\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int>\n") SLANG_RAW("__target_intrinsic(hlsl)\n") -SLANG_RAW("__target_intrinsic(hlsl)\n") -SLANG_RAW("vector<T,N> degrees(vector<T,N> x);\n") +SLANG_RAW("__target_intrinsic(glsl)\n") +SLANG_RAW("vector<T, N> degrees(vector<T, N> x)\n") +SLANG_RAW("{\n") +SLANG_RAW(" VECTOR_MAP_UNARY(T, N, degrees, x);\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int, let M : int>\n") SLANG_RAW("__target_intrinsic(hlsl)\n") @@ -999,11 +1071,26 @@ SLANG_RAW("void DeviceMemoryBarrierWithGroupSync();\n") SLANG_RAW("\n") SLANG_RAW("// Vector distance\n") SLANG_RAW("\n") -SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int> T distance(vector<T,N> x, vector<T,N> y);\n") +SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int>\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") +SLANG_RAW("__target_intrinsic(glsl)\n") +SLANG_RAW("T distance(vector<T, N> x, vector<T, N> y)\n") +SLANG_RAW("{\n") +SLANG_RAW(" return length(x - y);\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("// Vector dot product\n") SLANG_RAW("\n") -SLANG_RAW("__generic<T : __BuiltinArithmeticType, let N : int> T dot(vector<T,N> x, vector<T,N> y);\n") +SLANG_RAW("__generic<T : __BuiltinArithmeticType, let N : int>\n") +SLANG_RAW("__target_intrinsic(hlsl)\n") +SLANG_RAW("__target_intrinsic(glsl)\n") +SLANG_RAW("T dot(vector<T, N> x, vector<T, N> y)\n") +SLANG_RAW("{\n") +SLANG_RAW(" T result = T(0);\n") +SLANG_RAW(" for(int i = 0; i < N; ++i)\n") +SLANG_RAW(" result += x[i] * y[i];\n") +SLANG_RAW(" return result;\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("// Helper for computing distance terms for lighting (obsolete)\n") SLANG_RAW("\n") @@ -1116,10 +1203,10 @@ SLANG_RAW("// Flip surface normal to face forward, if needed\n") SLANG_RAW("__generic<T : __BuiltinFloatingPointType, let N : int>\n") SLANG_RAW("__target_intrinsic(hlsl)\n") SLANG_RAW("__target_intrinsic(glsl)\n") -SLANG_RAW("vector<T,N> faceforward(vector<T,N> n, vector<T,N> i, vector<T,N> ng);\n") -SLANG_RAW("/*{\n") +SLANG_RAW("vector<T,N> faceforward(vector<T,N> n, vector<T,N> i, vector<T,N> ng)\n") +SLANG_RAW("{\n") SLANG_RAW(" return dot(ng, i) < T(0.0f) ? n : -n;\n") -SLANG_RAW("}*/\n") +SLANG_RAW("}\n") SLANG_RAW("\n") SLANG_RAW("// Find first set bit starting at high bit and working down\n") SLANG_RAW("__target_intrinsic(glsl,\"findMSB\")\n") @@ -2460,7 +2547,7 @@ for (int aa = 0; aa < kBaseBufferAccessLevelCount; ++aa) sb << "};\n"; } -SLANG_RAW("#line 2387 \"hlsl.meta.slang\"") +SLANG_RAW("#line 2474 \"hlsl.meta.slang\"") SLANG_RAW("\n") SLANG_RAW("\n") SLANG_RAW("\n") diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index c2b356eff..ad3f506a6 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -1407,7 +1407,7 @@ namespace Slang bool SemanticsVisitor::findWitnessForInterfaceRequirement( ConformanceCheckingContext* context, - DeclRef<AggTypeDeclBase> typeDeclRef, + Type* type, InheritanceDecl* inheritanceDecl, DeclRef<InterfaceDecl> interfaceDeclRef, DeclRef<Decl> requiredMemberDeclRef, @@ -1447,7 +1447,7 @@ namespace Slang RefPtr<WitnessTable> satisfyingWitnessTable = checkConformanceToType( context, - typeDeclRef, + type, requiredInheritanceDeclRef.getDecl(), getBaseType(requiredInheritanceDeclRef)); @@ -1464,42 +1464,34 @@ namespace Slang // since only same-name members will be able to // satisfy the requirement. // - // TODO: this won't work right now for members that - // don't have names, which right now includes - // initializers/constructors. Name* name = requiredMemberDeclRef.GetName(); - // We are basically looking up members of the - // given type, but we need to be a bit careful. - // We *cannot* perfom lookup "through" inheritance - // declarations for this or other interfaces, - // since that would let us satisfy a requirement - // with itself. - // - // There's also an interesting question of whether - // we can/should support innterface requirements - // being satisfied via `__transparent` members. - // This seems like a "clever" idea rather than - // a useful one, and IR generation would - // need to construct real IR to trampoline over - // to the implementation. - // - // The final case that can't be reduced to just - // "a directly declared member with the same name" - // is the case where the type inherits a member - // that can satisfy the requirement from a base type. - // We are ignoring implementation inheritance for - // now, so we won't worry about this. - - // Make sure that by-name lookup is possible. - buildMemberDictionary(typeDeclRef.getDecl()); - auto lookupResult = lookUpLocal(getSession(), this, name, typeDeclRef); - - if (!lookupResult.isValid()) - { - getSink()->diagnose(inheritanceDecl, Diagnostics::typeDoesntImplementInterfaceRequirement, typeDeclRef, requiredMemberDeclRef); - return false; - } + // We start by looking up members of the same + // name, on the type that is claiming to conform. + // + // This lookup step could include members that + // we might not actually want to consider: + // + // * Lookup through a type `Foo` where `Foo : IBar` + // will be able to find members of `IBar`, which + // somewhat obviously shouldn't apply when + // determining if `Foo` satisfies the requirements + // of `IBar`. + // + // * Lookup in the presence of `__transparent` members + // may produce references to declarations on a *field* + // of the type rather than the type. Conformance through + // transparent members could be supported in theory, + // but would require synthesizing proxy/forwarding + // implementations in the type itself. + // + // We will punt on the second issue for now (since + // transparent members aren't currently exposed as + // a general-purpose feature for users), and rely + // on subsequent checking in this function to + // rule out inherited abstract members. + // + auto lookupResult = lookUpMember(getSession(), this, name, type); // Iterate over the members and look for one that matches // the expected signature for the requirement. @@ -1516,13 +1508,13 @@ namespace Slang // of "candidates" for satisfaction of the requirement, // and if nothing is found we print the candidates - getSink()->diagnose(inheritanceDecl, Diagnostics::typeDoesntImplementInterfaceRequirement, typeDeclRef, requiredMemberDeclRef); + getSink()->diagnose(inheritanceDecl, Diagnostics::typeDoesntImplementInterfaceRequirement, type, requiredMemberDeclRef); return false; } RefPtr<WitnessTable> SemanticsVisitor::checkInterfaceConformance( ConformanceCheckingContext* context, - DeclRef<AggTypeDeclBase> typeDeclRef, + Type* type, InheritanceDecl* inheritanceDecl, DeclRef<InterfaceDecl> interfaceDeclRef) { @@ -1564,7 +1556,7 @@ namespace Slang { auto requirementSatisfied = findWitnessForInterfaceRequirement( context, - typeDeclRef, + type, inheritanceDecl, interfaceDeclRef, requiredMemberDeclRef, @@ -1615,7 +1607,7 @@ namespace Slang { auto requirementSatisfied = findWitnessForInterfaceRequirement( context, - typeDeclRef, + type, inheritanceDecl, interfaceDeclRef, requiredInheritanceDeclRef, @@ -1638,7 +1630,7 @@ namespace Slang RefPtr<WitnessTable> SemanticsVisitor::checkConformanceToType( ConformanceCheckingContext* context, - DeclRef<AggTypeDeclBase> typeDeclRef, + Type* type, InheritanceDecl* inheritanceDecl, Type* baseType) { @@ -1652,7 +1644,7 @@ namespace Slang // required by that interface. return checkInterfaceConformance( context, - typeDeclRef, + type, inheritanceDecl, baseInterfaceDeclRef); } @@ -1663,41 +1655,43 @@ namespace Slang } bool SemanticsVisitor::checkConformance( - DeclRef<AggTypeDeclBase> declRef, + Type* type, InheritanceDecl* inheritanceDecl) { - declRef = createDefaultSubstitutionsIfNeeded(getSession(), declRef).as<AggTypeDeclBase>(); - - // Don't check conformances for abstract types that - // are being used to express *required* conformances. - if (auto assocTypeDeclRef = declRef.as<AssocTypeDecl>()) - { - // An associated type declaration represents a requirement - // in an outer interface declaration, and its members - // (type constraints) represent additional requirements. - return true; - } - else if (auto interfaceDeclRef = declRef.as<InterfaceDecl>()) + if( auto declRefType = as<DeclRefType>(type) ) { - // HACK: Our semantics as they stand today are that an - // `extension` of an interface that adds a new inheritance - // clause acts *as if* that inheritnace clause had been - // attached to the original `interface` decl: that is, - // it adds additional requirements. - // - // This is *not* a reasonable semantic to keep long-term, - // but it is required for some of our current example - // code to work. - return true; - } + auto declRef = declRefType->declRef; + // Don't check conformances for abstract types that + // are being used to express *required* conformances. + if (auto assocTypeDeclRef = declRef.as<AssocTypeDecl>()) + { + // An associated type declaration represents a requirement + // in an outer interface declaration, and its members + // (type constraints) represent additional requirements. + return true; + } + else if (auto interfaceDeclRef = declRef.as<InterfaceDecl>()) + { + // HACK: Our semantics as they stand today are that an + // `extension` of an interface that adds a new inheritance + // clause acts *as if* that inheritnace clause had been + // attached to the original `interface` decl: that is, + // it adds additional requirements. + // + // This is *not* a reasonable semantic to keep long-term, + // but it is required for some of our current example + // code to work. + return true; + } + } // Look at the type being inherited from, and validate // appropriately. auto baseType = inheritanceDecl->base.type; ConformanceCheckingContext context; - RefPtr<WitnessTable> witnessTable = checkConformanceToType(&context, declRef, inheritanceDecl, baseType); + RefPtr<WitnessTable> witnessTable = checkConformanceToType(&context, type, inheritanceDecl, baseType); if(!witnessTable) return false; @@ -1707,15 +1701,12 @@ namespace Slang void SemanticsVisitor::checkExtensionConformance(ExtensionDecl* decl) { - if (auto targetDeclRefType = as<DeclRefType>(decl->targetType)) + auto declRef = createDefaultSubstitutionsIfNeeded(getSession(), makeDeclRef(decl)).as<ExtensionDecl>(); + auto targetType = GetTargetType(declRef); + + for (auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>()) { - if (auto aggTypeDeclRef = targetDeclRefType->declRef.as<AggTypeDecl>()) - { - for (auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>()) - { - checkConformance(aggTypeDeclRef, inheritanceDecl); - } - } + checkConformance(targetType, inheritanceDecl); } } @@ -1740,6 +1731,10 @@ namespace Slang { // For non-interface types we need to check conformance. // + + auto declRef = createDefaultSubstitutionsIfNeeded(getSession(), makeDeclRef(decl)).as<AggTypeDeclBase>(); + auto type = DeclRefType::Create(getSession(), declRef); + // TODO: Need to figure out what this should do for // `abstract` types if we ever add them. Should they // be required to implement all interface requirements, @@ -1747,7 +1742,7 @@ namespace Slang // (That's what C# does). for (auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>()) { - checkConformance(makeDeclRef(decl), inheritanceDecl); + checkConformance(type, inheritanceDecl); } } } @@ -2070,7 +2065,7 @@ namespace Slang void SemanticsVisitor::getGenericParams( GenericDecl* decl, List<Decl*>& outParams, - List<GenericTypeConstraintDecl*> outConstraints) + List<GenericTypeConstraintDecl*>& outConstraints) { for (auto dd : decl->Members) { @@ -2087,100 +2082,223 @@ namespace Slang } bool SemanticsVisitor::doGenericSignaturesMatch( - GenericDecl* fst, - GenericDecl* snd) - { - // First we'll extract the parameters and constraints - // in each generic signature. We will consider parameters - // and constraints separately so that we are independent - // of the order in which constraints are given (that is, - // a constraint like `<T : IFoo>` would be considered - // the same as `<T>` with a later `where T : IFoo`. - - List<Decl*> fstParams; - List<GenericTypeConstraintDecl*> fstConstraints; - getGenericParams(fst, fstParams, fstConstraints); - - List<Decl*> sndParams; - List<GenericTypeConstraintDecl*> sndConstraints; - getGenericParams(snd, sndParams, sndConstraints); - - // For there to be any hope of a match, the - // two need to have the same number of parameters. - Index paramCount = fstParams.getCount(); - if (paramCount != sndParams.getCount()) + GenericDecl* left, + GenericDecl* right, + RefPtr<GenericSubstitution>* outSubstRightToLeft) + { + // Our first goal here is to determine if `left` and + // `right` have equivalent lists of explicit + // generic parameters. + // + // Once we have determined that the explicit generic + // parameters match, we will look at the constraints + // placed on those parameters to see if they are + // equivalent. + // + // We thus start by extracting the explicit parameters + // and the constraints from each declaration. + // + List<Decl*> leftParams; + List<GenericTypeConstraintDecl*> leftConstraints; + getGenericParams(left, leftParams, leftConstraints); + + List<Decl*> rightParams; + List<GenericTypeConstraintDecl*> rightConstraints; + getGenericParams(right, rightParams, rightConstraints); + + // For there to be any hope of a match, the two decls + // need to have the same number of explicit parameters. + // + Index paramCount = leftParams.getCount(); + if(paramCount != rightParams.getCount()) return false; - // Now we'll walk through the parameters. - for (Index pp = 0; pp < paramCount; ++pp) + // Next we will walk through the parameters and look + // for a pair-wise match. + // + for(Index pp = 0; pp < paramCount; ++pp) { - Decl* fstParam = fstParams[pp]; - Decl* sndParam = sndParams[pp]; + Decl* leftParam = leftParams[pp]; + Decl* rightParam = rightParams[pp]; - if (auto fstTypeParam = as<GenericTypeParamDecl>(fstParam)) + if (auto leftTypeParam = as<GenericTypeParamDecl>(leftParam)) { - if (auto sndTypeParam = as<GenericTypeParamDecl>(sndParam)) + if (auto rightTypeParam = as<GenericTypeParamDecl>(rightParam)) { - // TODO: is there any validation that needs to be performed here? - } - else - { - // Type and non-type parameters can't match. - return false; + // Right now any two type parameters are a match. + // Names are irrelevant to matching, and any constraints + // on the type parameters are represented as implicit + // extra parameters of the generic. + // + // TODO: If we ever supported type parameters with + // higher kinds we might need to make a check here + // that the kind of each parameter matches (which + // would in a sense be a kind of recursive check + // of the generic signature of the parameter). + // + continue; } } - else if (auto fstValueParam = as<GenericValueParamDecl>(fstParam)) + else if (auto leftValueParam = as<GenericValueParamDecl>(leftParam)) { - if (auto sndValueParam = as<GenericValueParamDecl>(sndParam)) + if (auto rightValueParam = as<GenericValueParamDecl>(rightParam)) { - // Need to check that the parameters have the same type. + // In this case we have two generic value parameters, + // and they should only be considered to match if + // they have the same type. // // Note: We are assuming here that the type of a value // parameter cannot be dependent on any of the type // parameters in the same signature. This is a reasonable // assumption for now, but could get thorny down the road. - if (!fstValueParam->getType()->Equals(sndValueParam->getType())) + // + if (!leftValueParam->getType()->Equals(rightValueParam->getType())) { - // Type mismatch. + // If the value parameters have non-matching types, + // then the full generic signatures do not match. + // return false; } - // TODO: This is not the right place to check on default - // values for the parameter, because they won't affect - // the signature, but we should make sure to do validation - // later on (e.g., that only one declaration can/should - // be allowed to provide a default). - } - else - { - // Value and non-value parameters can't match. - return false; + // Generic value parameters with the same type are + // always considered to match. + // + continue; } } + + // If we get to this point, then we have two parameters that + // were of different syntatic categories (e.g., one type parameter + // and one value parameter), so the signatures clearly don't match. + // + return false; } - // If we got this far, then it means the parameter signatures *seem* - // to match up all right, but now we need to check that the constraints - // placed on those parameters are also consistent. - // - // For now I'm going to assume/require that all declarations must - // declare the signature in a way that matches exactly. - Index constraintCount = fstConstraints.getCount(); - if(constraintCount != sndConstraints.getCount()) + // At this point we know that the explicit generic parameters + // of `left` and `right` are aligned, but we need to check + // that the constraints that each declaration places on + // its parameters match. + // + // A first challenge that arises is that `left` and `right` + // will each express the constraints in terms of their + // own parameters. For example, consider the following + // declarations: + // + // void foo1<T : IFoo>(T value); + // void foo2<U : IFoo>(U value); + // + // It is "obvious" to a human that the signatures here + // match, but `foo1` has a constraint `T : IFoo` while + // `foo2` has a constraint `U : IFoo`, and since `T` + // and `U` are distinct `Decl`s, those constraints + // are not obviously equivalent. + // + // We will work around this first issue by creating + // a substitution taht lists all the parameters of + // `left`, which we can use to specialize `right` + // so that it aligns. + // + // In terms of the example above, this is like constructing + // `foo2<T>` so that its constraint, after specialization, + // looks like `T : IFoo`. + // + auto& substRightToLeft = *outSubstRightToLeft; + substRightToLeft = createDummySubstitutions(left); + substRightToLeft->genericDecl = right; + + // We should now be able to enumerate the constraints + // on `right` in a way that uses the same type parameters + // as `left`, using `rightDeclRef`. + // + // At this point a second problem arises: if/when we support + // more flexibility in how generic parameter constraints are + // specified, it will be possible for two declarations to + // list the "same" constraints in very different ways. + // + // For example, if we support a `where` clause for separating + // the constraints from the parameters, then the following + // two declarations should have equivalent signatures: + // + // void foo1<T>(T value) + // where T : IFoo + // { ... } + // + // void foo2<T : IFoo>(T value) + // { ... } + // + // Similarly, if we allow for general compositions of interfaces + // to be used as constraints, then there can be more than one + // way to specify the same constraints: + // + // void foo1<T : IFoo&IBar>(T value); + // void foo2<T : IBar&IFoo>(T value); + // + // Adding support for equality constraints in `where` clauses + // also creates opportunities for multiple equivalent expressions: + // + // void foo1<T,U>(...) where T.A == U.A; + // void foo2<T,U>(...) where U.A == T.A; + // + // A robsut version of the checking logic here should attempt + // to *canonicalize* all of the constraints. Canonicalization + // should involve putting constraints into a deterministic + // order (e.g., for a generic with `<T,U>` all the constraints + // on `T` should come before those on `U`), rewriting individual + // constraints into a canonical form (e.g., `T : IFoo & IBar` + // should turn into two constraints: `T : IFoo` and `T : IBar`), + // etc. + // + // Once the constraints are in a canonical form we should be able + // to test them for pairwise equivalent. As a safety measure we + // could also try to test whether one set of constraints implies + // the other (since implication in both directions should imply + // equivalence, in which case our canonicalization had better + // have produced the same result). + // + // For now we are taking a simpler short-cut by assuming + // that constraints are already in a canonical form, which + // is reasonable for now as the syntax only allows a single + // constraint per parameter, specified on the parameter itself. + // + // Under the assumption of canonical constraints, we can + // assume that different numbers of constraints must indicate + // a signature mismatch. + // + Index constraintCount = leftConstraints.getCount(); + if(constraintCount != rightConstraints.getCount()) return false; for (Index cc = 0; cc < constraintCount; ++cc) { - //auto fstConstraint = fstConstraints[cc]; - //auto sndConstraint = sndConstraints[cc]; + // Note that we use a plain `Decl` pointer for the left + // constraint, but need to use a `DeclRef` for the right + // constraint so that we can take the substitution + // arguments into account. + // + GenericTypeConstraintDecl* leftConstraint = leftConstraints[cc]; + DeclRef<GenericTypeConstraintDecl> rightConstraint(rightConstraints[cc], substRightToLeft); + + // For now, every constraint has the form `sub : sup` + // to indicate that `sub` must be a subtype of `sup`. + // + // Two such constraints are equivalent if their `sub` + // and `sup` types are pairwise equivalent. + // + auto leftSub = leftConstraint->sub; + auto rightSub = GetSub(rightConstraint); + if(!leftSub->Equals(rightSub)) + return false; - // TODO: the challenge here is that the - // constraints are going to be expressed - // in terms of the parameters, which means - // we need to be doing substitution here. + auto leftSup = leftConstraint->sup; + auto rightSup = GetSup(rightConstraint); + if(!leftSup->Equals(rightSup)) + return false; } - // HACK: okay, we'll just assume things match for now. + // If we have checked all of the (canonicalized) constraints + // and found them to be pairwise equivalent then the two + // generic signatures seem to match. + // return true; } @@ -2313,25 +2431,18 @@ namespace Slang // If we are working with generic functions, then we need to // consider if their generic signatures match. + // if(newGenericDecl) { - SLANG_ASSERT(oldGenericDecl); // already checked above - if(!doGenericSignaturesMatch(newGenericDecl, oldGenericDecl)) - return SLANG_OK; - - // Now we need specialize the declaration references - // consistently, so that we can compare. + // If one declaration is generic, the other must be. + // (This condition was already checked above) // - // First we create a "dummy" set of substitutions that - // just reference the parameters of the first generic. - // - auto subst = createDummySubstitutions(newGenericDecl); - // - // Then we use those parameters to specialize the *other* - // generic. - // - subst->genericDecl = oldGenericDecl; - oldDeclRef.substitutions.substitutions = subst; + SLANG_ASSERT(oldGenericDecl); + + // As part of checking if the generic signatures match, + // we will produce a substitution that can be used to + // reference `oldGenericDecl` with the generic parameters + // substituted for those of `newDecl`. // // One way to think about it is that if we have these // declarations (ignore the name differences...): @@ -2342,7 +2453,14 @@ namespace Slang // // newDecl: // void foo2<U>(U x); // - // Then we will compare `foo2` against `foo1<U>`. + // Then we will compare the parameter types of `foo2` + // against the specialization `foo1<U>`. + // + RefPtr<GenericSubstitution> subst; + if(!doGenericSignaturesMatch(newGenericDecl, oldGenericDecl, &subst)) + return SLANG_OK; + + oldDeclRef.substitutions.substitutions = subst; } // If the parameter signatures don't match, then don't worry diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index f51e62699..8b32f8612 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -742,7 +742,7 @@ namespace Slang // bool findWitnessForInterfaceRequirement( ConformanceCheckingContext* context, - DeclRef<AggTypeDeclBase> typeDeclRef, + Type* type, InheritanceDecl* inheritanceDecl, DeclRef<InterfaceDecl> interfaceDeclRef, DeclRef<Decl> requiredMemberDeclRef, @@ -754,22 +754,21 @@ namespace Slang // members to satisfy all the requirements in the interface. RefPtr<WitnessTable> checkInterfaceConformance( ConformanceCheckingContext* context, - DeclRef<AggTypeDeclBase> typeDeclRef, + Type* type, InheritanceDecl* inheritanceDecl, DeclRef<InterfaceDecl> interfaceDeclRef); RefPtr<WitnessTable> checkConformanceToType( ConformanceCheckingContext* context, - DeclRef<AggTypeDeclBase> typeDeclRef, + Type* type, InheritanceDecl* inheritanceDecl, Type* baseType); - // Check that the type (or extension) declaration `declRef`, - // which declares that it inherits from another type via - // `inheritanceDecl` actually does what it needs to - // for that inheritance to be valid. + /// Check that `type` which has declared that it inherits from (and/or implements) + /// another type via `inheritanceDecl` actually does what it needs to for that + /// inheritance to be valid. bool checkConformance( - DeclRef<AggTypeDeclBase> declRef, + Type* type, InheritanceDecl* inheritanceDecl); void checkExtensionConformance(ExtensionDecl* decl); @@ -787,11 +786,15 @@ namespace Slang void getGenericParams( GenericDecl* decl, List<Decl*>& outParams, - List<GenericTypeConstraintDecl*> outConstraints); + List<GenericTypeConstraintDecl*>& outConstraints); + /// Determine if `left` and `right` have matching generic signatures. + /// If they do, then outputs a substitution to `ioSubstRightToLeft` that + /// can be used to specialize `right` to the parameters of `left`. bool doGenericSignaturesMatch( - GenericDecl* fst, - GenericDecl* snd); + GenericDecl* left, + GenericDecl* right, + RefPtr<GenericSubstitution>* outSubstRightToLeft); // Check if two functions have the same signature for the purposes // of overload resolution. @@ -1124,11 +1127,21 @@ namespace Slang OverloadCandidate* left, OverloadCandidate* right); + /// If `declRef` representations a specialization of a generic, returns the number of specialized generic arguments. + /// Otherwise, returns zero. + /// + Int getSpecializedParamCount(DeclRef<Decl> const& declRef); + /// Compare items `left` and `right` produced by lookup, to see if one should be favored for overloading. int CompareLookupResultItems( LookupResultItem const& left, LookupResultItem const& right); + /// Compare items `left` and `right` being considered as overload candidates, and determine if one should be favored for structural reasons. + int compareOverloadCandidateSpecificity( + LookupResultItem const& left, + LookupResultItem const& right); + void AddOverloadCandidateInner( OverloadResolveContext& context, OverloadCandidate& candidate); @@ -1237,6 +1250,8 @@ namespace Slang void formatDeclParams(StringBuilder& sb, DeclRef<Decl> declRef); + void formatDeclResultType(StringBuilder& sb, DeclRef<Decl> const& declRef); + void formatDeclSignature(StringBuilder& sb, DeclRef<Decl> declRef); String getDeclSignatureString(DeclRef<Decl> declRef); diff --git a/source/slang/slang-check-overload.cpp b/source/slang/slang-check-overload.cpp index ace495512..41aa94a35 100644 --- a/source/slang/slang-check-overload.cpp +++ b/source/slang/slang-check-overload.cpp @@ -497,6 +497,33 @@ namespace Slang return false; } + /// If `declRef` representations a specialization of a generic, returns the number of specialized generic arguments. + /// Otherwise, returns zero. + /// + Int SemanticsVisitor::getSpecializedParamCount(DeclRef<Decl> const& declRef) + { + if(!declRef) + return 0; + + // A specialization of a generic must point at the + // "inner" declaration of a generic. That means that + // the parent of the decl ref must be a generic. + // + auto parentGeneric = declRef.GetParent().as<GenericDecl>(); + if(!parentGeneric) + return 0; + // + // Furthermore, the declaration we are considering + // must be the single "inner" declaration of the + // parent generic (and not somthing like a generic + // parameter). + // + if( parentGeneric.getDecl()->inner.Ptr() != declRef.getDecl()) + return 0; + + return CountParameters(parentGeneric).required; + } + int SemanticsVisitor::CompareLookupResultItems( LookupResultItem const& left, LookupResultItem const& right) @@ -534,6 +561,87 @@ namespace Slang return 0; } + int SemanticsVisitor::compareOverloadCandidateSpecificity( + LookupResultItem const& left, + LookupResultItem const& right) + { + // HACK: if both items refer to the same declaration, + // then arbitrarily pick one. + if(left.declRef.Equals(right.declRef)) + return -1; + + // There is a very general rule that we would like to enforce + // in principle: + // + // Given candidates A and B, if A being applicable to some + // arguments implies that B is also applicable, but not vice versa, + // then A is a more specific/specialized candidate than B. + // + // A number of conclusions follow from this general rule. + // For example, a non-generic declaration will always be + // more specific than a generic declaration that was specialized + // to matching types: + // + // int doThing(int a); + // T doThing<T>(T a); + // + // It is clear that if the non-generic `doThing` is applicable + // to an argument `x`, then `doThing<int>` is also applicable to + // `x`. However, knowing that the generic `doThing` was applicable + // to some `y` doesn't tell us that the non-generic `doThing` can + // be called on `y`, because `y` could have some type that can't + // convert to `int`. + // + // Similarly, a generic declaration with a subset of the parameters + // of another generic is always more specialized: + // + // int doThing<T>(vector<T,3> value); + // int doThing<T, let N : int>(vector<T,N> value); + // + // Here we know that both overloads can apply to `float3`, but only + // one can apply to `float4`, so the first overload is more + // specialized/specific. + // + // As a final example, a generic which places more constraints + // on its generic parameters is more specific, all other things + // being equal: + // + // int doThing<T : IFoo>( T value ); + // int doThing<T>(T value); + // + // In this case we know that the first overload is applicable + // to a strict subset of the types that the second overload can + // apply to. + // + // The above rules represent the idealized principles we want + // to implement, but actually implementing that full check here + // could make overload resolution far more expensive. + // + // For now we are going to do something far simpler and hackier, + // which is to say that a candidate with more generic parameters + // is always preferred over one with fewer. + // + // TODO: We could extend this definition to account for constraints + // on generic parameters in the count, which would handle the + // need to prefer a more-constrained generic when possible. + // + // TODO: In the long run we should clearly replace this with + // the more general "does A being applicable imply B being applicable" + // test. + // + // TODO: The principle stated here doesn't take the actual + // arguments or their types into account, and it might be that + // in some cases disambiguation of which declaration should be + // preferred will depend on knowing the actual arguments. + // + auto leftSpecCount = getSpecializedParamCount(left.declRef); + auto rightSpecCount = getSpecializedParamCount(right.declRef); + if(leftSpecCount != rightSpecCount) + return int(leftSpecCount - rightSpecCount); + + return 0; + } + int SemanticsVisitor::CompareOverloadCandidates( OverloadCandidate* left, OverloadCandidate* right) @@ -546,17 +654,66 @@ namespace Slang // the costs of their type conversion sequences if(left->status == OverloadCandidate::Status::Applicable) { + // If one candidate incurred less cost related to + // implicit conversion of arguments to matching + // parameter types, then we should prefer that + // candidate. + // + // TODO: This eventually should be refined into + // a test that checks conversion cost per-argument, + // and only considers a candidate "better" if it + // has lower cost for at least one argument, and + // does not have higher cost for any. + // if (left->conversionCostSum != right->conversionCostSum) return left->conversionCostSum - right->conversionCostSum; - // If all conversion costs match, then we should consider - // whether one of the two items/declarations should be - // preferred based on grounds that have nothing to do - // with applicability or conversion costs. + // If both candidates appear to be equally good when it + // comes to the per-argument conversions required, + // then we have two other categories of criteria we + // can look at to disambiguate things: + // + // 1. We can look at how the lookup process found `left` and `right` + // do decide which is a better match based purely on how "far away" + // they are for lookup purposes. A canonincal example here would + // be if one declaration shadows or overrides the other. + // + // 2. We can look at parameter lists of `left` and `right`, their types, etc. + // do decide which is a better match based purely on structure. + // Canonical examples in this case would be preferring a non-generic + // candidate over a generic one, preferring a non-variadic candidate + // over a variadic one, and preferring a candidate with fewer + // default parameters over one with more. + // + // Deciding how to order/interleave these two categories of criteria + // is an important design decision. + // + // For example, consider: + // + // float f(float x); + // + // struct S + // { + // int f<T>(T x); + // + // float g(float y) { return f(y); } + // } + // + // In terms of structural/type matching, the global `f` is a more specialized + // candidate at the call site, while in terms of lookup/lexical crieteria + // the `S.f` declaration is better. + // + // For now we are considering lookup/overriding concerns first (so + // we would bias in favor of selecting `S.f` in the above example), and then + // structural/type concerns, but a more nuanced approach may be + // required in the future to better match programmer intuition. // auto itemDiff = CompareLookupResultItems(left->item, right->item); if(itemDiff) return itemDiff; + auto specificityDiff = compareOverloadCandidateSpecificity(left->item, right->item); + if(specificityDiff) + return specificityDiff; } return 0; @@ -988,6 +1145,22 @@ namespace Slang sb << val->ToString(); } + static void formatDeclName(StringBuilder& sb, Decl* decl) + { + if(as<ConstructorDecl>(decl)) + { + sb << "init"; + } + else if(as<SubscriptDecl>(decl)) + { + sb << "subscript"; + } + else + { + sb << getText(decl->getName()); + } + } + void SemanticsVisitor::formatDeclPath(StringBuilder& sb, DeclRef<Decl> declRef) { // Find the parent declaration @@ -1008,7 +1181,7 @@ namespace Slang sb << "."; } - sb << getText(declRef.GetName()); + formatDeclName(sb, declRef.getDecl()); // If the parent declaration is a generic, then we need to print out its // signature @@ -1018,10 +1191,29 @@ namespace Slang SLANG_RELEASE_ASSERT(genSubst); SLANG_RELEASE_ASSERT(genSubst->genericDecl == parentGenericDeclRef.getDecl()); + // If the name we printed previously was an operator + // that ends with `<`, then immediately printing the + // generic arguments inside `<...>` may cause it to + // be hard to parse the operator name visually. + // + // We thus include a space between the declaration name + // and its generic arguments in this case. + // + if(sb.endsWith("<")) sb << " "; + sb << "<"; bool first = true; for(auto arg : genSubst->args) { + // When printing the representation of a sepcialized + // generic declaration we don't want to include the + // argument values for subtype witnesses since these + // do not correspond to parameters of the generic + // as the user sees it. + // + if(as<Witness>(arg)) + continue; + if(!first) sb << ", "; formatVal(sb, arg); first = false; @@ -1085,10 +1277,31 @@ namespace Slang } } + static void formatDeclKindPrefix(StringBuilder& sb, Decl* decl) + { + if(as<FuncDecl>(decl)) + { + sb << "func "; + } + } + + void SemanticsVisitor::formatDeclResultType(StringBuilder& sb, DeclRef<Decl> const& declRef) + { + if(as<ConstructorDecl>(declRef)) + {} + else if(auto callableDeclRef = declRef.as<CallableDecl>()) + { + sb << " -> "; + formatType(sb, GetResultType(callableDeclRef)); + } + } + void SemanticsVisitor::formatDeclSignature(StringBuilder& sb, DeclRef<Decl> declRef) { + formatDeclKindPrefix(sb, declRef.getDecl()); formatDeclPath(sb, declRef); formatDeclParams(sb, declRef); + formatDeclResultType(sb, declRef); } String SemanticsVisitor::getDeclSignatureString(DeclRef<Decl> declRef) diff --git a/source/slang/slang-emit-cpp.cpp b/source/slang/slang-emit-cpp.cpp index 4c2e6032d..7fb04c33b 100644 --- a/source/slang/slang-emit-cpp.cpp +++ b/source/slang/slang-emit-cpp.cpp @@ -951,33 +951,6 @@ void CPPSourceEmitter::_emitVecMatMulDefinition(const UnownedStringSlice& funcNa writer->emit("}\n\n"); } -void CPPSourceEmitter::_emitCrossDefinition(const UnownedStringSlice& funcName, const HLSLIntrinsic* specOp) -{ - _emitSignature(funcName, specOp); - - SourceWriter* writer = getSourceWriter(); - - writer->emit("\n{\n"); - writer->indent(); - - writer->emit("return "); - if (m_target == CodeGenTarget::CUDASource) - { - m_writer->emit("make_"); - emitType(specOp->returnType); - writer->emit("( a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x ); \n"); - } - else - { - emitType(specOp->returnType); - writer->emit("{ a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x }; \n"); - } - - - writer->dedent(); - writer->emit("}\n\n"); -} - UnownedStringSlice CPPSourceEmitter::_getAndEmitSpecializedOperationDefinition(HLSLIntrinsic::Op op, IRType*const* argTypes, Int argCount, IRType* retType) { HLSLIntrinsic intrinsic; @@ -1422,10 +1395,6 @@ void CPPSourceEmitter::emitSpecializedOperationDefinition(const HLSLIntrinsic* s { return _emitAnyAllDefinition(_getFuncName(specOp), specOp); } - case Op::Cross: - { - return _emitCrossDefinition(_getFuncName(specOp), specOp); - } case Op::Normalize: { return _emitNormalizeDefinition(_getFuncName(specOp), specOp); diff --git a/source/slang/slang-emit-cpp.h b/source/slang/slang-emit-cpp.h index da8d026ee..7f9046643 100644 --- a/source/slang/slang-emit-cpp.h +++ b/source/slang/slang-emit-cpp.h @@ -97,7 +97,6 @@ protected: // Really we don't want any of these defined like they are here, they should be defined in slang stdlib void _emitAnyAllDefinition(const UnownedStringSlice& funcName, const HLSLIntrinsic* specOp); - void _emitCrossDefinition(const UnownedStringSlice& funcName, const HLSLIntrinsic* specOp); void _emitLengthDefinition(const UnownedStringSlice& funcName, const HLSLIntrinsic* specOp); void _emitNormalizeDefinition(const UnownedStringSlice& funcName, const HLSLIntrinsic* specOp); void _emitReflectDefinition(const UnownedStringSlice& funcName, const HLSLIntrinsic* specOp); diff --git a/source/slang/slang-hlsl-intrinsic-set.h b/source/slang/slang-hlsl-intrinsic-set.h index 22e5b29e5..6ab5480b3 100644 --- a/source/slang/slang-hlsl-intrinsic-set.h +++ b/source/slang/slang-hlsl-intrinsic-set.h @@ -104,7 +104,6 @@ just constructXXXFromScalar. Would be good if there was a suitable name to encom x(Max, "max", 2) \ x(Pow, "pow", 2) \ x(FMod, "fmod", 2) \ - x(Cross, "cross", 2) \ x(Reflect, "reflect", 2) \ \ x(SmoothStep, "smoothstep", 3) \ diff --git a/source/slang/slang-lookup.cpp b/source/slang/slang-lookup.cpp index 49f0bbf0c..02f74579c 100644 --- a/source/slang/slang-lookup.cpp +++ b/source/slang/slang-lookup.cpp @@ -25,14 +25,6 @@ struct BreadcrumbInfo BreadcrumbInfo* prev = nullptr; }; -void DoLocalLookupImpl( - Session* session, - Name* name, - DeclRef<ContainerDecl> containerDeclRef, - LookupRequest const& request, - LookupResult& result, - BreadcrumbInfo* inBreadcrumbs); - // void buildMemberDictionary(ContainerDecl* decl) @@ -169,146 +161,28 @@ LookupResultItem CreateLookupResultItem( return item; } -void DoMemberLookupImpl( - Session* session, - Name* name, - RefPtr<Type> baseType, - LookupRequest const& request, - LookupResult& ioResult, - BreadcrumbInfo* breadcrumbs) -{ - if (!baseType) - { - return; - } - - // If the type was pointer-like, then dereference it - // automatically here. - if (auto pointerLikeType = as<PointerLikeType>(baseType)) - { - // Need to leave a breadcrumb to indicate that we - // did an implicit dereference here - BreadcrumbInfo derefBreacrumb; - derefBreacrumb.kind = LookupResultItem::Breadcrumb::Kind::Deref; - derefBreacrumb.prev = breadcrumbs; - - // Recursively perform lookup on the result of deref - return DoMemberLookupImpl( - session, - name, pointerLikeType->elementType, request, ioResult, &derefBreacrumb); - } - - // Default case: no dereference needed - - if (auto baseDeclRefType = as<DeclRefType>(baseType)) - { - if (auto baseAggTypeDeclRef = baseDeclRefType->declRef.as<AggTypeDecl>()) - { - DoLocalLookupImpl( - session, - name, baseAggTypeDeclRef, request, ioResult, breadcrumbs); - } - } - - // TODO(tfoley): any other cases to handle here? -} - -void DoMemberLookupImpl( +static void _lookUpMembersInValue( Session* session, Name* name, - DeclRef<Decl> baseDeclRef, + DeclRef<Decl> valueDeclRef, LookupRequest const& request, LookupResult& ioResult, - BreadcrumbInfo* breadcrumbs) -{ - auto baseType = getTypeForDeclRef( - session, - baseDeclRef, - SourceLoc()); - return DoMemberLookupImpl( - session, - name, baseType, request, ioResult, breadcrumbs); -} - -// If we are about to perform lookup through an interface, then -// we need to specialize the decl-ref to that interface to include -// a "this type" subtitution. This function applies that substition -// when it is required, and returns the existing `declRef` otherwise. -DeclRef<Decl> maybeSpecializeInterfaceDeclRef( - RefPtr<Type> subType, - RefPtr<Type> superType, - DeclRef<Decl> superTypeDeclRef, // The decl-ref we are going to perform lookup in - DeclRef<TypeConstraintDecl> constraintDeclRef) // The type constraint that told us our type is a subtype -{ - if (auto superInterfaceDeclRef = superTypeDeclRef.as<InterfaceDecl>()) - { - // TODO: This case should probably loop back into the semantic - // checking logic (when available) in order to ensure that - // appropriate witness values have been registered for (at least) - // the associated type requirements of the super-type. - - // Create a subtype witness value to note the subtype relationship - // that makes this specialization valid. - // - // Note: this is to ensure that we can specialize the subtype witness - // later (e.g., by replacing a subtype witness that represents a generic - // constraint parameter with the concrete generic arguments that - // are used at a particular call site to the generic). - RefPtr<DeclaredSubtypeWitness> subtypeWitness = new DeclaredSubtypeWitness(); - subtypeWitness->declRef = constraintDeclRef; - subtypeWitness->sub = subType; - subtypeWitness->sup = superType; - - RefPtr<ThisTypeSubstitution> thisTypeSubst = new ThisTypeSubstitution(); - thisTypeSubst->interfaceDecl = superInterfaceDeclRef.getDecl(); - thisTypeSubst->witness = subtypeWitness; - thisTypeSubst->outer = superInterfaceDeclRef.substitutions.substitutions; - - auto specializedInterfaceDeclRef = DeclRef<Decl>(superInterfaceDeclRef.getDecl(), thisTypeSubst); - return specializedInterfaceDeclRef; - } - - return superTypeDeclRef; -} - -// Same as the above, but we are specializing a type instead of a decl-ref -RefPtr<Type> maybeSpecializeInterfaceDeclRef( - Session* session, - RefPtr<Type> subType, - RefPtr<Type> superType, // The type we are going to perform lookup in - DeclRef<TypeConstraintDecl> constraintDeclRef) // The type constraint that told us our type is a subtype -{ - if (auto superDeclRefType = as<DeclRefType>(superType)) - { - if (auto superInterfaceDeclRef = superDeclRefType->declRef.as<InterfaceDecl>()) - { - auto specializedInterfaceDeclRef = maybeSpecializeInterfaceDeclRef( - subType, - superType, - superInterfaceDeclRef, - constraintDeclRef); - auto specializedInterfaceType = DeclRefType::Create(session, specializedInterfaceDeclRef); - return specializedInterfaceType; - } - } - - return superType; -} - - -// Look for members of the given name in the given container for declarations -void DoLocalLookupImpl( + BreadcrumbInfo* breadcrumbs); + + /// Look up direct members (those declared in `containerDeclRef` itself, as well + /// as transitively through any direct members that are marked "transparent." + /// + /// This function does *not* deal with looking up through `extension`s, + /// inheritance clauses, etc. + /// +static void _lookUpDirectAndTransparentMembers( Session* session, Name* name, - DeclRef<ContainerDecl> containerDeclRef, + DeclRef<ContainerDecl> containerDeclRef, LookupRequest const& request, - LookupResult& result, - BreadcrumbInfo* inBreadcrumbs) + LookupResult& result, + BreadcrumbInfo* inBreadcrumbs) { - if (result.lookedupDecls.Contains(containerDeclRef)) - return; - result.lookedupDecls.Add(containerDeclRef); - ContainerDecl* containerDecl = containerDeclRef.getDecl(); // Ensure that the lookup dictionary in the container is up to date @@ -334,10 +208,8 @@ void DoLocalLookupImpl( AddToLookupResult(result, CreateLookupResultItem(DeclRef<Decl>(m, containerDeclRef.substitutions), inBreadcrumbs)); } - // TODO(tfoley): should we look up in the transparent decls // if we already has a hit in the current container? - for(auto transparentInfo : containerDecl->transparentMembers) { // The reference to the transparent member should use whatever @@ -352,7 +224,7 @@ void DoLocalLookupImpl( memberRefBreadcrumb.declRef = transparentMemberDeclRef; memberRefBreadcrumb.prev = inBreadcrumbs; - DoMemberLookupImpl( + _lookUpMembersInValue( session, name, transparentMemberDeclRef, @@ -360,97 +232,363 @@ void DoLocalLookupImpl( result, &memberRefBreadcrumb); } +} - // Consider lookup via extension - if( auto aggTypeDeclRef = containerDeclRef.as<AggTypeDecl>() ) +static RefPtr<SubtypeWitness> _makeSubtypeWitness( + Type* subType, + SubtypeWitness* subToMidWitness, + Type* superType, + DeclRef<TypeConstraintDecl> midToSuperConstraint) +{ + if(subToMidWitness) + { + RefPtr<TransitiveSubtypeWitness> transitiveWitness = new TransitiveSubtypeWitness(); + transitiveWitness->subToMid = subToMidWitness; + transitiveWitness->midToSup = midToSuperConstraint; + transitiveWitness->sub = subType; + transitiveWitness->sup = superType; + return transitiveWitness; + } + else { - if (request.semantics) + RefPtr<DeclaredSubtypeWitness> declaredWitness = new DeclaredSubtypeWitness(); + declaredWitness->declRef = midToSuperConstraint; + declaredWitness->sub = subType; + declaredWitness->sup = superType; + return declaredWitness; + } +} + +// Same as the above, but we are specializing a type instead of a decl-ref +static RefPtr<Type> _maybeSpecializeSuperType( + Session* session, + Type* superType, + SubtypeWitness* subIsSuperWitness) +{ + if (auto superDeclRefType = as<DeclRefType>(superType)) + { + if (auto superInterfaceDeclRef = superDeclRefType->declRef.as<InterfaceDecl>()) { - ensureDecl(request.semantics, containerDeclRef.getDecl(), DeclCheckState::ReadyForLookup); + RefPtr<ThisTypeSubstitution> thisTypeSubst = new ThisTypeSubstitution(); + thisTypeSubst->interfaceDecl = superInterfaceDeclRef.getDecl(); + thisTypeSubst->witness = subIsSuperWitness; + thisTypeSubst->outer = superInterfaceDeclRef.substitutions.substitutions; + + auto specializedInterfaceDeclRef = DeclRef<Decl>(superInterfaceDeclRef.getDecl(), thisTypeSubst); + + auto specializedInterfaceType = DeclRefType::Create(session, specializedInterfaceDeclRef); + return specializedInterfaceType; } + } + + return superType; +} + +static void _lookUpMembersInType( + Session* session, + Name* name, + RefPtr<Type> type, + LookupRequest const& request, + LookupResult& ioResult, + BreadcrumbInfo* breadcrumbs); + +static void _lookUpMembersInSuperTypeImpl( + Session* session, + Name* name, + Type* leafType, + Type* superType, + SubtypeWitness* leafIsSuperWitness, + LookupRequest const& request, + LookupResult& ioResult, + BreadcrumbInfo* inBreadcrumbs); - RefPtr<Type> type = DeclRefType::Create( - session, - aggTypeDeclRef); - for (auto ext = GetCandidateExtensions(aggTypeDeclRef); ext; ext = ext->nextCandidateExtension) +static void _lookUpMembersInSuperType( + Session* session, + Name* name, + Type* leafType, + SubtypeWitness* leafIsIntermediateWitness, + DeclRef<TypeConstraintDecl> intermediateIsSuperConstraint, + LookupRequest const& request, + LookupResult& ioResult, + BreadcrumbInfo* inBreadcrumbs) +{ + if( request.semantics ) + { + ensureDecl(request.semantics, intermediateIsSuperConstraint, DeclCheckState::CanUseBaseOfInheritanceDecl); + } + + // The super-type in the constraint (e.g., `Foo` in `T : Foo`) + // will tell us a type we should use for lookup. + // + auto superType = GetSup(intermediateIsSuperConstraint); + // + // We will go ahead and perform lookup using `superType`, + // after dealing with some details. + + auto leafIsSuperWitness = _makeSubtypeWitness( + leafType, + leafIsIntermediateWitness, + superType, + intermediateIsSuperConstraint); + + // If we are looking up through an interface type, then + // we need to be sure that we add an appropriate + // "this type" substitution here, since that needs to + // be applied to any members we look up. + // + superType = _maybeSpecializeSuperType( + session, + superType, + leafIsSuperWitness); + + // We need to track the indirection we took in lookup, + // so that we can construct an appropriate AST on the other + // side that includes the "upcast" from sub-type to super-type. + // + BreadcrumbInfo breadcrumb; + breadcrumb.prev = inBreadcrumbs; + breadcrumb.kind = LookupResultItem::Breadcrumb::Kind::Constraint; + breadcrumb.declRef = intermediateIsSuperConstraint; + breadcrumb.prev = inBreadcrumbs; + + // TODO: Need to consider case where this might recurse infinitely (e.g., + // if an inheritance clause does something like `Bad<T> : Bad<Bad<T>>`. + // + // TODO: The even simpler thing we need to worry about here is that if + // there is ever a "diamond" relationship in the inheritance hierarchy, + // we might end up seeing the same interface via different "paths" and + // we wouldn't want that to lead to overload-resolution failure. + // + _lookUpMembersInSuperTypeImpl(session, name, leafType, superType, leafIsSuperWitness, request, ioResult, &breadcrumb); +} + +static void _lookUpMembersInSuperTypeDeclImpl( + Session* session, + Name* name, + Type* leafType, + Type* superType, + SubtypeWitness* leafIsSuperWitness, + DeclRef<Decl> declRef, + LookupRequest const& request, + LookupResult& ioResult, + BreadcrumbInfo* inBreadcrumbs) +{ + auto semantics = request.semantics; + if( semantics ) + { + ensureDecl(semantics, declRef.getDecl(), DeclCheckState::ReadyForLookup); + } + + if (auto genericTypeParamDeclRef = declRef.as<GenericTypeParamDecl>()) + { + // If the type we are doing lookup in is a generic type parameter, + // then the members it provides can only be discovered by looking + // at the constraints that are placed on that type. + + auto genericDeclRef = genericTypeParamDeclRef.GetParent().as<GenericDecl>(); + assert(genericDeclRef); + + for(auto constraintDeclRef : getMembersOfType<GenericTypeConstraintDecl>(genericDeclRef)) { - auto extDeclRef = ApplyExtensionToType(request.semantics, ext, type); - if (!extDeclRef) - continue; + if( semantics ) + { + ensureDecl(semantics, constraintDeclRef, DeclCheckState::CanUseBaseOfInheritanceDecl); + } - // TODO: eventually we need to insert a breadcrumb here so that - // the constructed result can somehow indicate that a member - // was found through an extension. + // Does this constraint pertain to the type we are working on? + // + // We want constraints of the form `T : Foo` where `T` is the + // generic parameter in question, and `Foo` is whatever we are + // constraining it to. + auto subType = GetSub(constraintDeclRef); + auto subDeclRefType = as<DeclRefType>(subType); + if(!subDeclRefType) + continue; + if(!subDeclRefType->declRef.Equals(genericTypeParamDeclRef)) + continue; - DoLocalLookupImpl( + _lookUpMembersInSuperType( session, - name, extDeclRef, request, result, inBreadcrumbs); + name, + leafType, + leafIsSuperWitness, + constraintDeclRef, + request, + ioResult, + inBreadcrumbs); } - } - // for interface decls, also lookup in the base interfaces - if (request.semantics) + else if (declRef.as<AssocTypeDecl>() || declRef.as<GlobalGenericParamDecl>()) { - // TODO: - // The logic here is a bit gross, because it tries to work in terms of - // decl-refs instead of types (e.g., it asserts that the target type - // for an `extension` declaration must be a decl-ref type). - // - // This code should be converted to do a type-based lookup - // through declared bases for *any* aggregate type declaration. - // I think that logic is present in the type-based lookup path, but - // it would be needed here for when doing lookup from inside an - // aggregate declaration. - - // if we are looking at an extension, find the target decl that we are extending - DeclRef<Decl> targetDeclRef = containerDeclRef; - RefPtr<DeclRefType> targetDeclRefType; - if (auto extDeclRef = containerDeclRef.as<ExtensionDecl>()) + for (auto constraintDeclRef : getMembersOfType<TypeConstraintDecl>(declRef.as<ContainerDecl>())) { - ensureDecl(request.semantics, extDeclRef.getDecl(), DeclCheckState::CanUseExtensionTargetType); - - targetDeclRefType = as<DeclRefType>(extDeclRef.getDecl()->targetType); - SLANG_ASSERT(targetDeclRefType); - int diff = 0; - targetDeclRef = targetDeclRefType->declRef.as<ContainerDecl>().SubstituteImpl(containerDeclRef.substitutions, &diff); + _lookUpMembersInSuperType( + session, + name, + leafType, + leafIsSuperWitness, + constraintDeclRef, + request, + ioResult, + inBreadcrumbs); } - - // When looking up inside a type, we want to also perform lookup via - // the types it inherits from, in case of them defines the member we are looking for. + } + else if(auto aggTypeDeclBaseRef = declRef.as<AggTypeDeclBase>()) + { + // In this case we are peforming lookup in the context of an aggregate + // type or an `extension`, so the first thing to do is to look for + // matching members declared directly in the body of the type/`extension`. // - // TODO: Need to be careful that this doesn't allow a type to satisfy an interface - // requirement using the original declaration of that requirement... + _lookUpDirectAndTransparentMembers(session, name, aggTypeDeclBaseRef, request, ioResult, inBreadcrumbs); + // There are further lookup steps that we can only perform when a + // semantic checking context is available to us. That means that + // during parsing, lookup will fail to find members under `name` + // if they required following these paths. + // + if(semantics) { - if(!targetDeclRefType) + if(auto aggTypeDeclRef = aggTypeDeclBaseRef.as<AggTypeDecl>()) { - targetDeclRefType = DeclRefType::Create(session, targetDeclRef); + // If the declaration we are looking at is a nominal type declaration, + // then we want to consider any `extension`s that have been associated + // directly with that type. + // + ensureDecl(request.semantics, aggTypeDeclRef.getDecl(), DeclCheckState::ReadyForLookup); + for(auto extDecl = GetCandidateExtensions(aggTypeDeclRef); extDecl; extDecl = extDecl->nextCandidateExtension) + { + // Note: In this case `extDecl` is an extension that was declared to apply + // (conditionally) to `aggTypeDeclRef`, which is the decl-ref part of + // `superType`. Thus when looking for a substitution to apply to the + // extension, we need to apply it to `superType` and not to `leafType`. + // + auto extDeclRef = ApplyExtensionToType(request.semantics, extDecl, superType); + if (!extDeclRef) + continue; + + // TODO: eventually we need to insert a breadcrumb here so that + // the constructed result can somehow indicate that a member + // was found through an extension. + // + _lookUpMembersInSuperTypeDeclImpl( + session, + name, + leafType, + superType, + leafIsSuperWitness, + extDeclRef, + request, + ioResult, + inBreadcrumbs); + } } - auto baseInterfaces = getMembersOfType<InheritanceDecl>(containerDeclRef); - for (auto inheritanceDeclRef : baseInterfaces) + // For both aggregate types and their `extension`s, we want lookup to follow + // through the declared inheritance relationships on each declaration. + // + ensureDecl(semantics, aggTypeDeclBaseRef.getDecl(), DeclCheckState::CanEnumerateBases); + for (auto inheritanceDeclRef : getMembersOfType<InheritanceDecl>(aggTypeDeclBaseRef)) { - ensureDecl(request.semantics, inheritanceDeclRef.getDecl(), DeclCheckState::CanUseBaseOfInheritanceDecl); + ensureDecl(semantics, inheritanceDeclRef.getDecl(), DeclCheckState::CanUseBaseOfInheritanceDecl); + _lookUpMembersInSuperType(session, name, leafType, leafIsSuperWitness, inheritanceDeclRef, request, ioResult, inBreadcrumbs); + } + } + } +} + +static void _lookUpMembersInSuperTypeImpl( + Session* session, + Name* name, + Type* leafType, + Type* superType, + SubtypeWitness* leafIsSuperWitness, + LookupRequest const& request, + LookupResult& ioResult, + BreadcrumbInfo* inBreadcrumbs) +{ + // If the type was pointer-like, then dereference it + // automatically here. + if (auto pointerLikeType = as<PointerLikeType>(superType)) + { + // Need to leave a breadcrumb to indicate that we + // did an implicit dereference here + BreadcrumbInfo derefBreacrumb; + derefBreacrumb.kind = LookupResultItem::Breadcrumb::Kind::Deref; + derefBreacrumb.prev = inBreadcrumbs; - auto baseType = inheritanceDeclRef.getDecl()->base.type.dynamicCast<DeclRefType>(); - SLANG_ASSERT(baseType); - int diff = 0; - auto baseInterfaceDeclRef = baseType->declRef.SubstituteImpl(containerDeclRef.substitutions, &diff); + // Recursively perform lookup on the result of deref + _lookUpMembersInType( + session, + name, pointerLikeType->elementType, request, ioResult, &derefBreacrumb); + return; + } - baseInterfaceDeclRef = maybeSpecializeInterfaceDeclRef( - targetDeclRefType, - baseType, - baseInterfaceDeclRef, - inheritanceDeclRef); + // Default case: no dereference needed - DoLocalLookupImpl(session, name, baseInterfaceDeclRef.as<ContainerDecl>(), request, result, inBreadcrumbs); - } - } + if(auto declRefType = as<DeclRefType>(superType)) + { + auto declRef = declRefType->declRef; + + _lookUpMembersInSuperTypeDeclImpl(session, name, leafType, superType, leafIsSuperWitness, declRef, request, ioResult, inBreadcrumbs); + } +} + + /// Perform lookup for `name` in the context of `type`. + /// + /// This operation does the kind of lookup we'd expect if `name` + /// was used inside of a member function on `type`, or if the + /// user wrote `obj.<name>` for a variable `obj` of the given + /// `type`. + /// + /// Looking up members in `type` includes lookup through any + /// constraints or inheritance relationships that expand the + /// set of members visible on `type`. + /// +static void _lookUpMembersInType( + Session* session, + Name* name, + RefPtr<Type> type, + LookupRequest const& request, + LookupResult& ioResult, + BreadcrumbInfo* breadcrumbs) +{ + if (!type) + { + return; } + + _lookUpMembersInSuperTypeImpl(session, name, type, type, nullptr, request, ioResult, breadcrumbs); +} + + /// Look up members by `name` in the given `valueDeclRef`. + /// + /// If `valueDeclRef` represents a reference to a variable + /// or other named and typed value, then this performs the + /// kind of lookup we'd expect for `valueDeclRef.<name>`. + /// +static void _lookUpMembersInValue( + Session* session, + Name* name, + DeclRef<Decl> valueDeclRef, + LookupRequest const& request, + LookupResult& ioResult, + BreadcrumbInfo* breadcrumbs) +{ + // Looking up `name` in the context of a value can + // be reduced to the problem of looking up `name` + // in the *type* of that value. + // + auto valueType = getTypeForDeclRef( + session, + valueDeclRef, + SourceLoc()); + return _lookUpMembersInType( + session, + name, valueType, request, ioResult, breadcrumbs); } -void DoLookupImpl( +static void _lookUpInScopes( Session* session, Name* name, LookupRequest const& request, @@ -472,52 +610,74 @@ void DoLookupImpl( if(!containerDecl) continue; + // TODO: If we need default substitutions to be applied to + // the `containerDecl`, then it might make sense to have + // each `link` in the scope store a decl-ref instead of + // just a decl. + // DeclRef<ContainerDecl> containerDeclRef = DeclRef<Decl>(containerDecl, createDefaultSubstitutions(session, containerDecl)).as<ContainerDecl>(); - BreadcrumbInfo breadcrumb; - BreadcrumbInfo* breadcrumbs = nullptr; - - // Depending on the kind of container we are looking into, - // we may need to insert something like a `this` expression - // to resolve the lookup result. + // If the container we are looking into represents a type + // or an `extension` of a type, then we need to treat + // this step as lookup into the `this` variable (or the + // `This` type), which means including any `extension`s + // or inheritance clauses in the lookup process. // - // Note: We are checking for `AggTypeDeclBase` here, and not - // just `AggTypeDecl`, because we want to catch `extension` - // declarations as well. + // Note: The `AggTypeDeclBase` class is the common superclass + // between `AggTypeDecl` and `ExtensionDecl`. // - if (auto aggTypeDeclRef = containerDeclRef.as<AggTypeDeclBase>()) + if (auto aggTypeDeclBaseRef = containerDeclRef.as<AggTypeDeclBase>()) { + // When reconstructing the final expression for a result + // looked up through the tyep or extension, we will need + // a `this` expression (or a `This` type expression) to + // mark the base of the member reference, so we create + // a "breadcrumb" here to trakc that fact. + // + BreadcrumbInfo breadcrumb; breadcrumb.kind = LookupResultItem::Breadcrumb::Kind::This; breadcrumb.thisParameterMode = thisParameterMode; - breadcrumb.declRef = aggTypeDeclRef; + breadcrumb.declRef = aggTypeDeclBaseRef; breadcrumb.prev = nullptr; - breadcrumbs = &breadcrumb; - } - - // Now perform "local" lookup in the context of the container, - // as if we were looking up a member directly. - - // if we are currently in an extension decl, perform local lookup - // in the target decl we are extending - if (auto extDeclRef = containerDeclRef.as<ExtensionDecl>()) - { - if (extDeclRef.getDecl()->targetType) + RefPtr<Type> type; + if(auto extDeclRef = aggTypeDeclBaseRef.as<ExtensionDecl>()) { - if (auto targetDeclRef = as<DeclRefType>(extDeclRef.getDecl()->targetType)) + if( request.semantics ) { - if (auto aggDeclRef = targetDeclRef->declRef.as<AggTypeDecl>()) - { - containerDeclRef = extDeclRef.Substitute(aggDeclRef); - } + ensureDecl(request.semantics, extDeclRef.getDecl(), DeclCheckState::CanUseExtensionTargetType); } + + // If we are doing lookup from inside an `extension` + // declaration, then the `this` expression will have + // a type that uses the "target type" of the `extension`. + // + type = GetTargetType(extDeclRef); } + else + { + assert(aggTypeDeclBaseRef.as<AggTypeDecl>()); + type = DeclRefType::Create(session, aggTypeDeclBaseRef); + } + + _lookUpMembersInType(session, name, type, request, result, &breadcrumb); + } + else + { + // The default case is when the scope doesn't represent a + // type or `extension` declaration, so we can look up members + // in that scope much more simply. + // + _lookUpDirectAndTransparentMembers(session, name, containerDeclRef, request, result, nullptr); } - DoLocalLookupImpl( - session, - name, containerDeclRef, request, result, breadcrumbs); + // Before we proceed up to the next outer scope to perform lookup + // again, we need to consider what the current scope tells us + // about how to interpret uses of `this`. For example, if + // we are inside a `[mutating]` method, then the implicit `this` + // that we use for lookup should be an l-value. + // if( containerDeclRef.is<ConstructorDecl>() ) { thisParameterMode = LookupResultItem::Breadcrumb::ThisParameterMode::Mutating; @@ -546,16 +706,6 @@ void DoLookupImpl( // If we run out of scopes, then we are done. } -LookupResult DoLookup( - Session* session, - Name* name, - LookupRequest const& request) -{ - LookupResult result; - DoLookupImpl(session, name, request, result); - return result; -} - LookupResult lookUp( Session* session, SemanticsVisitor* semantics, @@ -567,24 +717,9 @@ LookupResult lookUp( request.semantics = semantics; request.scope = scope; request.mask = mask; - return DoLookup(session, name, request); -} - -// perform lookup within the context of a particular container declaration, -// and do *not* look further up the chain -LookupResult lookUpLocal( - Session* session, - SemanticsVisitor* semantics, - Name* name, - DeclRef<ContainerDecl> containerDeclRef, - LookupMask mask) -{ - LookupRequest request; - request.semantics = semantics; - request.mask = mask; LookupResult result; - DoLocalLookupImpl(session, name, containerDeclRef, request, result, nullptr); + _lookUpInScopes(session, name, request, result); return result; } @@ -597,128 +732,6 @@ void lookUpMemberImpl( BreadcrumbInfo* inBreadcrumbs, LookupMask mask); -// Perform lookup "through" the given constraint decl-ref, -// which should show that `subType` is a sub-type of some -// super-type (e.g., an interface). -// -void lookUpThroughConstraint( - Session* session, - SemanticsVisitor* semantics, - Name* name, - Type* subType, - DeclRef<TypeConstraintDecl> constraintDeclRef, - LookupResult& ioResult, - BreadcrumbInfo* inBreadcrumbs, - LookupMask mask) -{ - // The super-type in the constraint (e.g., `Foo` in `T : Foo`) - // will tell us a type we should use for lookup. - // - auto superType = GetSup(constraintDeclRef); - // - // We will go ahead and perform lookup using `superType`, - // after dealing with some details. - - // If we are looking up through an interface type, then - // we need to be sure that we add an appropriate - // "this type" substitution here, since that needs to - // be applied to any members we look up. - // - superType = maybeSpecializeInterfaceDeclRef( - session, - subType, - superType, - constraintDeclRef); - - // We need to track the indirection we took in lookup, - // so that we can construct an appropriate AST on the other - // side that includes the "upcase" from sub-type to super-type. - // - BreadcrumbInfo breadcrumb; - breadcrumb.prev = inBreadcrumbs; - breadcrumb.kind = LookupResultItem::Breadcrumb::Kind::Constraint; - breadcrumb.declRef = constraintDeclRef; - - // TODO: Need to consider case where this might recurse infinitely (e.g., - // if an inheritance clause does something like `Bad<T> : Bad<Bad<T>>`. - // - // TODO: The even simpler thing we need to worry about here is that if - // there is ever a "diamond" relationship in the inheritance hierarchy, - // we might end up seeing the same interface via different "paths" and - // we wouldn't want that to lead to overload-resolution failure. - // - lookUpMemberImpl(session, semantics, name, superType, ioResult, &breadcrumb, mask); -} - -void lookUpMemberImpl( - Session* session, - SemanticsVisitor* semantics, - Name* name, - Type* type, - LookupResult& ioResult, - BreadcrumbInfo* inBreadcrumbs, - LookupMask mask) -{ - if (auto declRefType = as<DeclRefType>(type)) - { - auto declRef = declRefType->declRef; - if (declRef.as<AssocTypeDecl>() || declRef.as<GlobalGenericParamDecl>()) - { - for (auto constraintDeclRef : getMembersOfType<TypeConstraintDecl>(declRef.as<ContainerDecl>())) - { - lookUpThroughConstraint( - session, - semantics, - name, - type, - constraintDeclRef, - ioResult, - inBreadcrumbs, - mask); - } - } - else if (auto aggTypeDeclRef = declRef.as<AggTypeDecl>()) - { - LookupRequest request; - request.semantics = semantics; - - DoLocalLookupImpl(session, name, aggTypeDeclRef, request, ioResult, inBreadcrumbs); - } - else if (auto genericTypeParamDeclRef = declRef.as<GenericTypeParamDecl>()) - { - auto genericDeclRef = genericTypeParamDeclRef.GetParent().as<GenericDecl>(); - assert(genericDeclRef); - - for(auto constraintDeclRef : getMembersOfType<GenericTypeConstraintDecl>(genericDeclRef)) - { - // Does this constraint pertain to the type we are working on? - // - // We want constraints of the form `T : Foo` where `T` is the - // generic parameter in question, and `Foo` is whatever we are - // constraining it to. - auto subType = GetSub(constraintDeclRef); - auto subDeclRefType = as<DeclRefType>(subType); - if(!subDeclRefType) - continue; - if(!subDeclRefType->declRef.Equals(genericTypeParamDeclRef)) - continue; - - lookUpThroughConstraint( - session, - semantics, - name, - type, - constraintDeclRef, - ioResult, - inBreadcrumbs, - mask); - } - } - - } - -} - LookupResult lookUpMember( Session* session, SemanticsVisitor* semantics, @@ -726,8 +739,12 @@ LookupResult lookUpMember( Type* type, LookupMask mask) { + LookupRequest request; + request.semantics = semantics; + request.mask = mask; + LookupResult result; - lookUpMemberImpl(session, semantics, name, type, result, nullptr, mask); + _lookUpMembersInType(session, name, type, request, result, nullptr); return result; } diff --git a/source/slang/slang-lookup.h b/source/slang/slang-lookup.h index eaf51f1e5..f9e3839d9 100644 --- a/source/slang/slang-lookup.h +++ b/source/slang/slang-lookup.h @@ -24,15 +24,6 @@ LookupResult lookUp( RefPtr<Scope> scope, LookupMask mask = LookupMask::Default); -// perform lookup within the context of a particular container declaration, -// and do *not* look further up the chain -LookupResult lookUpLocal( - Session* session, - SemanticsVisitor* semantics, - Name* name, - DeclRef<ContainerDecl> containerDeclRef, - LookupMask mask = LookupMask::Default); - // Perform member lookup in the context of a type LookupResult lookUpMember( Session* session, diff --git a/source/slang/slang-stdlib.cpp b/source/slang/slang-stdlib.cpp index 551a911c8..b23d1970b 100644 --- a/source/slang/slang-stdlib.cpp +++ b/source/slang/slang-stdlib.cpp @@ -199,50 +199,50 @@ namespace Slang } } - struct OpInfo { IntrinsicOp opCode; char const* opName; unsigned flags; }; + struct OpInfo { IntrinsicOp opCode; char const* opName; char const* interface; unsigned flags; }; static const OpInfo unaryOps[] = { - { kCompoundIntrinsicOp_Pos, "+", ARITHMETIC_MASK }, - { kIROp_Neg, "-", ARITHMETIC_MASK }, - { kIROp_Not, "!", BOOL_MASK | BOOL_RESULT }, - { kIROp_BitNot, "~", INT_MASK }, - { kCompoundIntrinsicOp_PreInc, "++", ARITHMETIC_MASK | ASSIGNMENT }, - { kCompoundIntrinsicOp_PreDec, "--", ARITHMETIC_MASK | ASSIGNMENT }, - { kCompoundIntrinsicOp_PostInc, "++", ARITHMETIC_MASK | ASSIGNMENT | POSTFIX }, - { kCompoundIntrinsicOp_PostDec, "--", ARITHMETIC_MASK | ASSIGNMENT | POSTFIX }, + { kCompoundIntrinsicOp_Pos, "+", "__BuiltinArithmeticType", ARITHMETIC_MASK }, + { kIROp_Neg, "-", "__BuiltinArithmeticType", ARITHMETIC_MASK }, + { kIROp_Not, "!", nullptr, BOOL_MASK | BOOL_RESULT }, + { kIROp_BitNot, "~", "__BuiltinIntegerType", INT_MASK }, + { kCompoundIntrinsicOp_PreInc, "++", "__BuiltinArithmeticType", ARITHMETIC_MASK | ASSIGNMENT }, + { kCompoundIntrinsicOp_PreDec, "--", "__BuiltinArithmeticType", ARITHMETIC_MASK | ASSIGNMENT }, + { kCompoundIntrinsicOp_PostInc, "++", "__BuiltinArithmeticType", ARITHMETIC_MASK | ASSIGNMENT | POSTFIX }, + { kCompoundIntrinsicOp_PostDec, "--", "__BuiltinArithmeticType", ARITHMETIC_MASK | ASSIGNMENT | POSTFIX }, }; static const OpInfo binaryOps[] = { - { kIROp_Add, "+", ARITHMETIC_MASK }, - { kIROp_Sub, "-", ARITHMETIC_MASK }, - { kIROp_Mul, "*", ARITHMETIC_MASK }, - { kIROp_Div, "/", ARITHMETIC_MASK }, - { kIROp_IRem, "%", INT_MASK }, - { kIROp_FRem, "%", FLOAT_MASK }, - { kIROp_And, "&&", BOOL_MASK | BOOL_RESULT}, - { kIROp_Or, "||", BOOL_MASK | BOOL_RESULT }, - { kIROp_BitAnd, "&", LOGICAL_MASK }, - { kIROp_BitOr, "|", LOGICAL_MASK }, - { kIROp_BitXor, "^", LOGICAL_MASK }, - { kIROp_Lsh, "<<", INT_MASK }, - { kIROp_Rsh, ">>", INT_MASK }, - { kIROp_Eql, "==", ANY_MASK | BOOL_RESULT }, - { kIROp_Neq, "!=", ANY_MASK | BOOL_RESULT }, - { kIROp_Greater, ">", ARITHMETIC_MASK | BOOL_RESULT }, - { kIROp_Less, "<", ARITHMETIC_MASK | BOOL_RESULT }, - { kIROp_Geq, ">=", ARITHMETIC_MASK | BOOL_RESULT }, - { kIROp_Leq, "<=", ARITHMETIC_MASK | BOOL_RESULT }, - { kCompoundIntrinsicOp_AddAssign, "+=", ASSIGNMENT | ARITHMETIC_MASK }, - { kCompoundIntrinsicOp_SubAssign, "-=", ASSIGNMENT | ARITHMETIC_MASK }, - { kCompoundIntrinsicOp_MulAssign, "*=", ASSIGNMENT | ARITHMETIC_MASK }, - { kCompoundIntrinsicOp_DivAssign, "/=", ASSIGNMENT | ARITHMETIC_MASK }, - { kCompoundIntrinsicOp_IRemAssign, "%=", ASSIGNMENT | INT_MASK }, - { kCompoundIntrinsicOp_FRemAssign, "%=", ASSIGNMENT | FLOAT_MASK }, - { kCompoundIntrinsicOp_AndAssign, "&=", ASSIGNMENT | LOGICAL_MASK }, - { kCompoundIntrinsicOp_OrAssign, "|=", ASSIGNMENT | LOGICAL_MASK }, - { kCompoundIntrinsicOp_XorAssign, "^=", ASSIGNMENT | LOGICAL_MASK }, - { kCompoundIntrinsicOp_LshAssign, "<<=", ASSIGNMENT | INT_MASK }, - { kCompoundIntrinsicOp_RshAssign, ">>=", ASSIGNMENT | INT_MASK }, + { kIROp_Add, "+", "__BuiltinArithmeticType", ARITHMETIC_MASK }, + { kIROp_Sub, "-", "__BuiltinArithmeticType", ARITHMETIC_MASK }, + { kIROp_Mul, "*", "__BuiltinArithmeticType", ARITHMETIC_MASK }, + { kIROp_Div, "/", "__BuiltinArithmeticType", ARITHMETIC_MASK }, + { kIROp_IRem, "%", "__BuiltinIntegerType", INT_MASK }, + { kIROp_FRem, "%", "__BuiltinFloatingPointType", FLOAT_MASK }, + { kIROp_And, "&&", nullptr, BOOL_MASK | BOOL_RESULT}, + { kIROp_Or, "||", nullptr, BOOL_MASK | BOOL_RESULT }, + { kIROp_BitAnd, "&", "__BuiltinLogicalType", LOGICAL_MASK }, + { kIROp_BitOr, "|", "__BuiltinLogicalType", LOGICAL_MASK }, + { kIROp_BitXor, "^", "__BuiltinLogicalType", LOGICAL_MASK }, + { kIROp_Lsh, "<<", "__BuiltinIntegerType", INT_MASK }, + { kIROp_Rsh, ">>", "__BuiltinIntegerType", INT_MASK }, + { kIROp_Eql, "==", "__BuiltinType", ANY_MASK | BOOL_RESULT }, + { kIROp_Neq, "!=", "__BuiltinType", ANY_MASK | BOOL_RESULT }, + { kIROp_Greater, ">", "__BuiltinArithmeticType", ARITHMETIC_MASK | BOOL_RESULT }, + { kIROp_Less, "<", "__BuiltinArithmeticType", ARITHMETIC_MASK | BOOL_RESULT }, + { kIROp_Geq, ">=", "__BuiltinArithmeticType", ARITHMETIC_MASK | BOOL_RESULT }, + { kIROp_Leq, "<=", "__BuiltinArithmeticType", ARITHMETIC_MASK | BOOL_RESULT }, + { kCompoundIntrinsicOp_AddAssign, "+=", "__BuiltinArithmeticType", ASSIGNMENT | ARITHMETIC_MASK }, + { kCompoundIntrinsicOp_SubAssign, "-=", "__BuiltinArithmeticType", ASSIGNMENT | ARITHMETIC_MASK }, + { kCompoundIntrinsicOp_MulAssign, "*=", "__BuiltinArithmeticType", ASSIGNMENT | ARITHMETIC_MASK }, + { kCompoundIntrinsicOp_DivAssign, "/=", "__BuiltinArithmeticType", ASSIGNMENT | ARITHMETIC_MASK }, + { kCompoundIntrinsicOp_IRemAssign, "%=", "__BuiltinIntegerType", ASSIGNMENT | INT_MASK }, + { kCompoundIntrinsicOp_FRemAssign, "%=", "__BuiltinFloatingPointType", ASSIGNMENT | FLOAT_MASK }, + { kCompoundIntrinsicOp_AndAssign, "&=", "__BuiltinLogicalType", ASSIGNMENT | LOGICAL_MASK }, + { kCompoundIntrinsicOp_OrAssign, "|=", "__BuiltinLogicalType", ASSIGNMENT | LOGICAL_MASK }, + { kCompoundIntrinsicOp_XorAssign, "^=", "__BuiltinLogicalType", ASSIGNMENT | LOGICAL_MASK }, + { kCompoundIntrinsicOp_LshAssign, "<<=", "__BuiltinIntegerType", ASSIGNMENT | INT_MASK }, + { kCompoundIntrinsicOp_RshAssign, ">>=", "__BuiltinIntegerType", ASSIGNMENT | INT_MASK }, }; String Session::getCoreLibraryCode() diff --git a/source/slang/slang-syntax.cpp b/source/slang/slang-syntax.cpp index cc2cf46a1..d479ba32a 100644 --- a/source/slang/slang-syntax.cpp +++ b/source/slang/slang-syntax.cpp @@ -1548,7 +1548,18 @@ void Type::accept(IValVisitor* visitor, void* extra) if (auto thisTypeSubst = as<ThisTypeSubstitution>(subst)) { - return witness->EqualsVal(thisTypeSubst->witness); + // For our purposes, two this-type substitutions are + // equivalent if they have the same type as `This`, + // even if the specific witness values they use + // might differ. + // + if(this->interfaceDecl != thisTypeSubst->interfaceDecl) + return false; + + if(!this->witness->sub->Equals(thisTypeSubst->witness->sub)) + return false; + + return true; } return false; } diff --git a/source/slang/slang-syntax.h b/source/slang/slang-syntax.h index f8f506236..9c3945b4d 100644 --- a/source/slang/slang-syntax.h +++ b/source/slang/slang-syntax.h @@ -1168,8 +1168,6 @@ namespace Slang // used at all, to avoid allocation. List<LookupResultItem> items; - HashSet<DeclRef<ContainerDecl>> lookedupDecls; - // Was at least one result found? bool isValid() const { return item.declRef.getDecl() != nullptr; } diff --git a/tests/diagnostics/enum-implicit-conversion.slang.expected b/tests/diagnostics/enum-implicit-conversion.slang.expected index 1fc0c3dc6..c102c6012 100644 --- a/tests/diagnostics/enum-implicit-conversion.slang.expected +++ b/tests/diagnostics/enum-implicit-conversion.slang.expected @@ -7,8 +7,8 @@ tests/diagnostics/enum-implicit-conversion.slang(34): note: explicit conversion tests/diagnostics/enum-implicit-conversion.slang(35): error 30019: expected an expression of type 'uint', got 'Color' tests/diagnostics/enum-implicit-conversion.slang(35): note: explicit conversion from 'Color' to 'uint' is possible tests/diagnostics/enum-implicit-conversion.slang(42): error 39999: ambiguous call to overloaded operation with arguments of type (Color) -tests/diagnostics/enum-implicit-conversion.slang(18): note 39999: candidate: foo(uint) -tests/diagnostics/enum-implicit-conversion.slang(17): note 39999: candidate: foo(int) +tests/diagnostics/enum-implicit-conversion.slang(18): note 39999: candidate: func foo(uint) -> int +tests/diagnostics/enum-implicit-conversion.slang(17): note 39999: candidate: func foo(int) -> int } standard output = { } diff --git a/tests/front-end/diamond.slang b/tests/front-end/diamond.slang new file mode 100644 index 000000000..bcb1bbd43 --- /dev/null +++ b/tests/front-end/diamond.slang @@ -0,0 +1,32 @@ +// diamond.slang + +//TEST:SIMPLE: + +// Test to confirm that Slang can handle a "diamond" +// multiple inheritance pattern between interfaces, +// without having lookup ambiguity issues at use +// sites. + +interface A { float getA(); } + +interface B : A {} + +interface C : A {} + +interface D : B, C {} + +float doIt<T : D>(T value) +{ + return value.getA(); +} + +struct Thing : D +{ + float getA() { return 0.0f; } +} + +float test() +{ + Thing thing; + return doIt(thing); +}
\ No newline at end of file |
