summaryrefslogtreecommitdiffstats
path: root/source/slang/slang-stdlib.cpp
diff options
context:
space:
mode:
authorTim Foley <tfoley@nvidia.com>2017-08-31 14:13:28 -0700
committerTim Foley <tfoley@nvidia.com>2017-09-05 12:15:28 -0700
commitff7c190b838dffc79e5acd555f41506cb52a9f47 (patch)
treea4543443ab301cf946089904e889d7018b08252e /source/slang/slang-stdlib.cpp
parent620734080f825cb205b887fa1d6203e35dd60663 (diff)
Move implicit conversion operations to stdlib
- Previously, there were a variety of rules in `check.cpp` to pick the conversion cost for various cases involving scalar, vector, and matrix types. - The main problem of the previous approach is that any lowering pass would need to convert an arbitrary "type cast" node into the right low-level operation(s). - The new approach is that a type conversion (implicit or explicit) always resolves as a call to a constructor/initializer for the destination type. This means that the existing rules around marking operations as builtins should work for lowering. - The support this, the checking logic needs to perform lookup of intializers/constructors when asked to perform conversion between types. It does this by re-using the existing logic for lookup and overload resolution if/when a type was applied in an ordinary context. - Next, we define a modifier that can be attached to constructors/initializers to mark them as suitable for implicit conversion, and associate them with the correct cost to be used when doing overload comparisons. - We add the modifier to all the scalar-to-scalar cases in the stdlib, using the logic that previously existed in semantic checking. - Next we add cases for general vector-to-scalar conversions that also convert type, using the same cost computation as above. - This probably misses various cases, but at this point they can hopefully be added just in the stdlib. - One gotcha here is that in lowering, we need to make sure to lower any kind of call expression to another call expression of the same AST node class, so that we don't lose information on what casts were implicit/hidden in teh source-to-source case. Two notes for potential longer-term changes: 1. There is still some duplication between the type conversion declarations here and the "join" logic for types used for generic arguments. Ideally we'd eventually clean up the "join" logic to be based on convertability, but that isn't a high priority right now, as long as joins continue to pick the right type. 2. It is a bit gross to have to declare all the N^2 conversions for vector/matrix types to duplicate the cases for scalars. For the simple scalar-to-vector case, we might try to support multiple conversion "steps" where both a scalar-to-scalar and a scalar-to-vector step can be allowed (this could be tagged on the modifiers already introduced). That simple option doesn't scale to vector-to-vector element type conversions, though, where you'd really want to make it a generic with a constraint like: vector<T,N> init<U>(vector<U,N> value) where T : ConvertibleFrom<U>; Here the `ConvertibleFrom<U>` interface expresses the fact that a conforming type has an initializer that takes a `U`. What doesn't appear in this context is any notion of conversion costs. We'd need some kind of system for computing the conversion cost of the vector conversion from the cost of the `T` to `U` converion.
Diffstat (limited to 'source/slang/slang-stdlib.cpp')
-rw-r--r--source/slang/slang-stdlib.cpp209
1 files changed, 197 insertions, 12 deletions
diff --git a/source/slang/slang-stdlib.cpp b/source/slang/slang-stdlib.cpp
index 3ec8b9db4..89fcd0800 100644
--- a/source/slang/slang-stdlib.cpp
+++ b/source/slang/slang-stdlib.cpp
@@ -1066,6 +1066,13 @@ namespace Slang
return stdlibPath;
}
+ // We are going to generate the stdlib source code from a more compact
+ // description. For example, we need to generate all the `operator`
+ // declarations for the basic unary and binary math operations on
+ // builtin types. To do this, we will make a big array of all these
+ // types, and associate them with data on their categories/capabilities
+ // so that we generate only the correct operations.
+ //
enum
{
SINT_MASK = 1 << 0,
@@ -1082,21 +1089,142 @@ namespace Slang
ANY_MASK = INT_MASK | FLOAT_MASK | BOOL_MASK,
};
- static const struct {
+ // We are going to declare initializers that allow for conversion between
+ // all of our base types, and we need a way to priotize those conversion
+ // by giving them different costs. Rather than maintain a hard-coded table
+ // of N^2 costs for N basic types, we are going to try to do things a bit
+ // more systematically.
+ //
+ // Every base type will be given a "kind" and a "rank" for conversion.
+ // The kind will classify it as signed/unsigned/float, and the rank will
+ // classify it by its logical bit size (with a distinct rank for pointer-sized
+ // types that logically sits between 32- and 64-bit types).
+ //
+ enum BaseTypeConversionKind : uint8_t
+ {
+ kBaseTypeConversionKind_Signed,
+ kBaseTypeConversionKind_Unsigned,
+ kBaseTypeConversionKind_Float,
+ kBaseTypeConversionKind_Error,
+ };
+ enum BaseTypeConversionRank : uint8_t
+ {
+ kBaseTypeConversionRank_Bool,
+ kBaseTypeConversionRank_Int8,
+ kBaseTypeConversionRank_Int16,
+ kBaseTypeConversionRank_Int32,
+ kBaseTypeConversionRank_IntPtr,
+ kBaseTypeConversionRank_Int64,
+ kBaseTypeConversionRank_Error,
+ };
+
+ // Here we declare the table of all our builtin types, so that we can generate all the relevant declarations.
+ //
+ struct BaseTypeInfo
+ {
char const* name;
BaseType tag;
unsigned flags;
- } kBaseTypes[] = {
- { "void", BaseType::Void, 0 },
- { "int", BaseType::Int, SINT_MASK },
- { "half", BaseType::Half, FLOAT_MASK },
- { "float", BaseType::Float, FLOAT_MASK },
- { "double", BaseType::Double, FLOAT_MASK },
- { "uint", BaseType::UInt, UINT_MASK },
- { "bool", BaseType::Bool, BOOL_MASK },
- { "uint64_t", BaseType::UInt64, UINT_MASK },
+ BaseTypeConversionKind conversionKind;
+ BaseTypeConversionRank conversionRank;
+ };
+ static const BaseTypeInfo kBaseTypes[] = {
+ // TODO: `void` really shouldn't be in the `BaseType` enumeration, since it behaves so differently across the board
+ { "void", BaseType::Void, 0, kBaseTypeConversionKind_Error, kBaseTypeConversionRank_Error},
+
+ { "bool", BaseType::Bool, BOOL_MASK, kBaseTypeConversionKind_Unsigned, kBaseTypeConversionRank_Bool },
+
+ // TODO: should declare explicit types for 8-, 16-, 32- and 64-bit integers
+ { "int", BaseType::Int, SINT_MASK, kBaseTypeConversionKind_Signed, kBaseTypeConversionRank_Int32},
+
+ { "half", BaseType::Half, FLOAT_MASK, kBaseTypeConversionKind_Float, kBaseTypeConversionRank_Int16},
+ { "float", BaseType::Float, FLOAT_MASK, kBaseTypeConversionKind_Float, kBaseTypeConversionRank_Int32},
+ { "double", BaseType::Double, FLOAT_MASK, kBaseTypeConversionKind_Float, kBaseTypeConversionRank_Int64},
+
+ { "uint", BaseType::UInt, UINT_MASK, kBaseTypeConversionKind_Unsigned, kBaseTypeConversionRank_Int32},
+ { "uint64_t", BaseType::UInt64, UINT_MASK, kBaseTypeConversionKind_Unsigned, kBaseTypeConversionRank_Int64},
};
+ // Given two base types, we need to be able to compute the cost of converting between them.
+ ConversionCost getBaseTypeConversionCost(
+ BaseTypeInfo const& toInfo,
+ BaseTypeInfo const& fromInfo)
+ {
+ if(toInfo.conversionKind == fromInfo.conversionKind
+ && toInfo.conversionRank == fromInfo.conversionRank)
+ {
+ // Thse should represent the exact same type.
+ return kConversionCost_None;
+ }
+
+ // Conversions within the same kind are easist to handle
+ if (toInfo.conversionKind == fromInfo.conversionKind)
+ {
+ // If we are converting to a "larger" type, then
+ // we are doing a lossless promotion, and otherwise
+ // we are doing a demotion.
+ if( toInfo.conversionRank > fromInfo.conversionRank)
+ return kConversionCost_RankPromotion;
+ else
+ return kConversionCost_GeneralConversion;
+ }
+
+ // If we are converting from an unsigned integer type to
+ // a signed integer type that is guaranteed to be larger,
+ // then that is also a lossless promotion.
+ //
+ // There is one additional wrinkle here, which is that
+ // a conversion from a 32-bit unsigned integer to a
+ // "pointer-sized" signed integer should be treated
+ // as unsafe, because the pointer size might also be
+ // 32 bits.
+ //
+ // The same basic exemption applied when converting
+ // *from* a pointer-sized unsigned integer.
+ else if(toInfo.conversionKind == kBaseTypeConversionKind_Signed
+ && fromInfo.conversionKind == kBaseTypeConversionKind_Unsigned
+ && toInfo.conversionRank > fromInfo.conversionRank
+ && toInfo.conversionRank != kBaseTypeConversionRank_IntPtr
+ && fromInfo.conversionRank != kBaseTypeConversionRank_IntPtr)
+ {
+ return kConversionCost_UnsignedToSignedPromotion;
+ }
+
+ // Conversion from signed to unsigned is always lossy,
+ // but it is preferred over conversions from unsigned
+ // to signed, for same-size types.
+ else if(toInfo.conversionKind == kBaseTypeConversionKind_Unsigned
+ && fromInfo.conversionKind == kBaseTypeConversionKind_Signed
+ && toInfo.conversionRank >= fromInfo.conversionRank)
+ {
+ return kConversionCost_SignedToUnsignedConversion;
+ }
+
+ // Conversion from an integer to a floating-point type
+ // is never considered a promotion (even when the value
+ // would fit in the available mantissa bits).
+ // If the destination type is at least 32 bits we consider
+ // this a reasonably good conversion, though.
+ //
+ // Note that this means we do *not* consider implicit
+ // conversion to `half` as a good conversion, even for small
+ // types. This makes sense because we relaly want to prefer
+ // conversion to `float` as the default.
+ else if (toInfo.conversionKind == kBaseTypeConversionKind_Float
+ && toInfo.conversionRank >= kBaseTypeConversionRank_Int32)
+ {
+ return kConversionCost_IntegerToFloatConversion;
+ }
+
+ // All other cases are considered as "general" conversions,
+ // where we don't consider any one conversion better than
+ // any others.
+ else
+ {
+ return kConversionCost_GeneralConversion;
+ }
+ }
+
struct OpInfo { IntrinsicOp opCode; char const* opName; unsigned flags; };
static const OpInfo unaryOps[] = {
@@ -1147,7 +1275,6 @@ namespace Slang
{ IntrinsicOp::RshAssign, ">>=", ASSIGNMENT | INT_MASK },
};
-
String Session::getCoreLibraryCode()
{
if (coreLibraryCode.Length() > 0)
@@ -1201,9 +1328,20 @@ namespace Slang
// Declare initializers to convert from various other types
for (int ss = 0; ss < kBaseTypeCount; ++ss)
{
+ // Don't allow conversion from `void`
if (kBaseTypes[ss].tag == BaseType::Void)
continue;
+ // We need to emit a modifier so that the semantic-checking
+ // layer will know it can use these operations for implicit
+ // conversion.
+ ConversionCost conversionCost = getBaseTypeConversionCost(
+ kBaseTypes[tt],
+ kBaseTypes[ss]);
+
+ EMIT_LINE_DIRECTIVE();
+ sb << "__implicit_conversion(" << conversionCost << ")\n";
+
EMIT_LINE_DIRECTIVE();
sb << "__init(" << kBaseTypes[ss].name << " value);\n";
}
@@ -1215,8 +1353,14 @@ namespace Slang
sb << "__generic<T = float, let N : int = 4> __magic_type(Vector) struct vector\n{\n";
sb << " typedef T Element;\n";
- sb << " __init(T value);\n"; // initialize from single scalar
+
+ // Declare initializer taking a single scalar of the elemnt type
+ sb << " __implicit_conversion(" << kConversionCost_ScalarToVector << ")\n";
+ sb << " __init(T value);\n";
+
sb << "};\n";
+
+ // TODO: Probably need to do similar
sb << "__generic<T = float, let R : int = 4, let C : int = 4> __magic_type(Matrix) struct matrix {};\n";
static const struct {
@@ -1306,6 +1450,39 @@ namespace Slang
sb << "}\n";
}
+ // The above extension was generic in the *type* of the vector,
+ // but explicit in the *size*. We will now declare an extension
+ // for each builtin type that is generic in the size.
+ //
+ for (int tt = 0; tt < kBaseTypeCount; ++tt)
+ {
+ if(kBaseTypes[tt].tag == BaseType::Void) continue;
+
+ sb << "__generic<let N : int> __extension vector<"
+ << kBaseTypes[tt].name << ",N>\n{\n";
+
+ for (int ff = 0; ff < kBaseTypeCount; ++ff)
+ {
+ if(kBaseTypes[ff].tag == BaseType::Void) continue;
+
+ // We need a constructor to make a vector from a scalar
+ // of another type.
+
+ if( tt != ff )
+ {
+ auto cost = getBaseTypeConversionCost(
+ kBaseTypes[tt],
+ kBaseTypes[ff]);
+ cost += kConversionCost_ScalarToVector;
+
+ sb << " __implicit_conversion(" << cost << ")\n";
+ sb << " __init(" << kBaseTypes[ff].name << " value);\n";
+ }
+ }
+
+ sb << "}\n";
+ }
+
for( int R = 2; R <= 4; ++R )
for( int C = 2; C <= 4; ++C )
{
@@ -1337,6 +1514,14 @@ namespace Slang
// with implicit conversion, in the `vector` case above
sb << "__generic<U> __init(matrix<U," << R << ", " << C << ">);\n";
+ // initialize from a matrix of larger size
+ for(int rr = R; rr <= 4; ++rr)
+ for( int cc = C; cc <= 4; ++cc )
+ {
+ if(rr == R && cc == C) continue;
+ sb << "__init(matrix<T," << rr << "," << cc << "> value);\n";
+ }
+
sb << "}\n";
}