summaryrefslogtreecommitdiffstats
path: root/source/slang/slang-stdlib.cpp
diff options
context:
space:
mode:
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";
}