// slang-stdlib.cpp #include "compiler.h" #include "ir.h" #include "syntax.h" #define STRINGIZE(x) STRINGIZE2(x) #define STRINGIZE2(x) #x #define LINE_STRING STRINGIZE(__LINE__) namespace Slang { String Session::getStdlibPath() { if(stdlibPath.Length() != 0) return stdlibPath; StringBuilder pathBuilder; for( auto cc = __FILE__; *cc; ++cc ) { switch( *cc ) { case '\n': case '\t': case '\\': pathBuilder << "\\"; default: pathBuilder << *cc; break; } } stdlibPath = pathBuilder.ProduceString(); 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, FLOAT_MASK = 1 << 1, COMPARISON = 1 << 2, BOOL_MASK = 1 << 3, UINT_MASK = 1 << 4, ASSIGNMENT = 1 << 5, POSTFIX = 1 << 6, INT_MASK = SINT_MASK | UINT_MASK, ARITHMETIC_MASK = INT_MASK | FLOAT_MASK, LOGICAL_MASK = INT_MASK | BOOL_MASK, ANY_MASK = INT_MASK | FLOAT_MASK | BOOL_MASK, }; // 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; 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 { int32_t opCode; char const* opName; unsigned flags; }; static const OpInfo unaryOps[] = { { kIRPseudoOp_Pos, "+", ARITHMETIC_MASK }, { kIROp_Neg, "-", ARITHMETIC_MASK }, { kIROp_Not, "!", ANY_MASK }, { kIRPseudoOp_BitNot, "~", INT_MASK }, { kIRPseudoOp_PreInc, "++", ARITHMETIC_MASK | ASSIGNMENT }, { kIRPseudoOp_PreDec, "--", ARITHMETIC_MASK | ASSIGNMENT }, { kIRPseudoOp_PostInc, "++", ARITHMETIC_MASK | ASSIGNMENT | POSTFIX }, { kIRPseudoOp_PostDec, "--", 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_Mod, "%", INT_MASK }, { kIROp_And, "&&", LOGICAL_MASK }, { kIROp_Or, "||", LOGICAL_MASK }, { kIROp_BitAnd, "&", LOGICAL_MASK }, { kIROp_BitOr, "|", LOGICAL_MASK }, { kIROp_BitXor, "^", LOGICAL_MASK }, { kIROp_Lsh, "<<", INT_MASK }, { kIROp_Rsh, ">>", INT_MASK }, { kIROp_Eql, "==", ANY_MASK | COMPARISON }, { kIROp_Neq, "!=", ANY_MASK | COMPARISON }, { kIROp_Greater, ">", ARITHMETIC_MASK | COMPARISON }, { kIROp_Less, "<", ARITHMETIC_MASK | COMPARISON }, { kIROp_Geq, ">=", ARITHMETIC_MASK | COMPARISON }, { kIROp_Leq, "<=", ARITHMETIC_MASK | COMPARISON }, { kIRPseudoOp_AddAssign, "+=", ASSIGNMENT | ARITHMETIC_MASK }, { kIRPseudoOp_SubAssign, "-=", ASSIGNMENT | ARITHMETIC_MASK }, { kIRPseudoOp_MulAssign, "*=", ASSIGNMENT | ARITHMETIC_MASK }, { kIRPseudoOp_DivAssign, "/=", ASSIGNMENT | ARITHMETIC_MASK }, { kIRPseudoOp_ModAssign, "%=", ASSIGNMENT | ARITHMETIC_MASK }, { kIRPseudoOp_AndAssign, "&=", ASSIGNMENT | LOGICAL_MASK }, { kIRPseudoOp_OrAssign, "|=", ASSIGNMENT | LOGICAL_MASK }, { kIRPseudoOp_XorAssign, "^=", ASSIGNMENT | LOGICAL_MASK }, { kIRPseudoOp_LshAssign, "<<=", ASSIGNMENT | INT_MASK }, { kIRPseudoOp_RshAssign, ">>=", ASSIGNMENT | INT_MASK }, }; String Session::getCoreLibraryCode() { if (coreLibraryCode.Length() > 0) return coreLibraryCode; StringBuilder sb; String path = getStdlibPath(); #define EMIT_LINE_DIRECTIVE() sb << "#line " << (__LINE__+1) << " \"" << path << "\"\n" #include "core.meta.slang.h" coreLibraryCode = sb.ProduceString(); return coreLibraryCode; } String Session::getHLSLLibraryCode() { if (hlslLibraryCode.Length() > 0) return hlslLibraryCode; StringBuilder sb; #include "hlsl.meta.slang.h" hlslLibraryCode = sb.ProduceString(); return hlslLibraryCode; } // GLSL-specific library code String Session::getGLSLLibraryCode() { if(glslLibraryCode.Length() != 0) return glslLibraryCode; String path = getStdlibPath(); StringBuilder sb; #include "glsl.meta.slang.h" glslLibraryCode = sb.ProduceString(); return glslLibraryCode; } }