https://github.com/shader-slang/slang
Raw File
Tip revision: 0586f3298fa7d554fa2682103eefba88740d6758 authored by jsmall-nvidia on 18 January 2023, 19:11:50 UTC
Upgrade slang-llvm-13.x-33 (#2600)
Tip revision: 0586f32
slang-stdlib.cpp
// slang-stdlib.cpp

#include "slang-compiler.h"
#include "slang-ir.h"
#include "slang-syntax.h"
#include "slang-ir-util.h"
#include "../core/slang-string-util.h"

#define STRINGIZE(x) STRINGIZE2(x)
#define STRINGIZE2(x) #x
#define LINE_STRING STRINGIZE(__LINE__)

namespace Slang
{
    String Session::getStdlibPath()
    {
        if(stdlibPath.getLength() != 0)
            return stdlibPath;

        // Make sure we have a line of text from __FILE__, that we'll extract the filename from
        List<UnownedStringSlice> lines;
        StringUtil::calcLines(UnownedStringSlice::fromLiteral(__FILE__), lines);
        SLANG_ASSERT(lines.getCount() > 0 && lines[0].getLength() > 0);

        // Make the path just the filename to remove issues around path being included on different targets
        stdlibPath = Path::getFileName(lines[0]);
        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,
        BOOL_RESULT = 1 << 2,
        BOOL_MASK   = 1 << 3,
        UINT_MASK   = 1 << 4,

        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 BaseTypeConversionInfo
    {
        char const* name;
        BaseType	tag;
        unsigned    flags;
        BaseTypeConversionKind conversionKind;
        BaseTypeConversionRank conversionRank;
    };
    static const BaseTypeConversionInfo 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 },

        { "int8_t",	    BaseType::Int8,     SINT_MASK,  kBaseTypeConversionKind_Signed,     kBaseTypeConversionRank_Int8},
        { "int16_t",	BaseType::Int16,    SINT_MASK,  kBaseTypeConversionKind_Signed,     kBaseTypeConversionRank_Int16},
        { "int",	    BaseType::Int,      SINT_MASK,  kBaseTypeConversionKind_Signed,     kBaseTypeConversionRank_Int32},
        { "int64_t",	BaseType::Int64,    SINT_MASK,  kBaseTypeConversionKind_Signed,     kBaseTypeConversionRank_Int64},
        { "intptr_t",	BaseType::IntPtr,   SINT_MASK,  kBaseTypeConversionKind_Signed,     kBaseTypeConversionRank_IntPtr},


        { "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},

        { "uint8_t",	BaseType::UInt8,    UINT_MASK,  kBaseTypeConversionKind_Unsigned,   kBaseTypeConversionRank_Int8},
        { "uint16_t",	BaseType::UInt16,   UINT_MASK,  kBaseTypeConversionKind_Unsigned,   kBaseTypeConversionRank_Int16},
        { "uint",	    BaseType::UInt,     UINT_MASK,  kBaseTypeConversionKind_Unsigned,   kBaseTypeConversionRank_Int32},
        { "uint64_t",   BaseType::UInt64,   UINT_MASK,  kBaseTypeConversionKind_Unsigned,   kBaseTypeConversionRank_Int64},
        { "uintptr_t",  BaseType::UIntPtr,  UINT_MASK,  kBaseTypeConversionKind_Unsigned,   kBaseTypeConversionRank_IntPtr},

    };

    // Given two base types, we need to be able to compute the cost of converting between them.
    ConversionCost getBaseTypeConversionCost(
        BaseTypeConversionInfo const& toInfo,
        BaseTypeConversionInfo 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
            && fromInfo.conversionRank >= kBaseTypeConversionRank_Int8)
        {
            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;
        }
    }

    IROp getBaseTypeConversionOp(
        BaseTypeConversionInfo const& toInfo,
        BaseTypeConversionInfo const& fromInfo)
    {
        if (toInfo.tag == fromInfo.tag)
            return kIROp_Nop;

        IROp intrinsicOpCode = kIROp_Nop;
        auto toStyle = getTypeStyle(toInfo.tag);
        auto fromStyle = getTypeStyle(fromInfo.tag);
        if (toStyle == kIROp_BoolType) toStyle = kIROp_IntType;
        if (fromStyle == kIROp_BoolType) fromStyle = kIROp_IntType;
        if (toStyle == kIROp_IntType && fromStyle == kIROp_IntType)
            intrinsicOpCode = kIROp_IntCast;
        if (toStyle == kIROp_IntType && fromStyle == kIROp_FloatType)
            intrinsicOpCode = kIROp_CastFloatToInt;
        if (toStyle == kIROp_FloatType && fromStyle == kIROp_IntType)
            intrinsicOpCode = kIROp_CastIntToFloat;
        if (toStyle == kIROp_FloatType && fromStyle == kIROp_FloatType)
            intrinsicOpCode = kIROp_FloatCast;
        return intrinsicOpCode;
    }

    struct IntrinsicOpInfo { IROp opCode; char const* funcName; char const* opName; char const* interface; unsigned flags; };

    static const IntrinsicOpInfo intrinsicUnaryOps[] = {
        { kIROp_Neg,    "neg",              "-",    "__BuiltinArithmeticType",  ARITHMETIC_MASK },
        { kIROp_Not,    "logicalNot",       "!",    nullptr,                    BOOL_MASK | BOOL_RESULT },
        { kIROp_BitNot, "not",              "~",    "__BuiltinIntegerType",     INT_MASK        },
    };

    static const IntrinsicOpInfo intrinsicBinaryOps[] = {
        {kIROp_Add, "add", "+", "__BuiltinArithmeticType", ARITHMETIC_MASK},
        {kIROp_Sub, "sub", "-", "__BuiltinArithmeticType", ARITHMETIC_MASK},
        {kIROp_Mul, "mul", "*", "__BuiltinArithmeticType", ARITHMETIC_MASK},
        {kIROp_Div, "div", "/", "__BuiltinArithmeticType", ARITHMETIC_MASK},
        {kIROp_IRem, "irem", "%", "__BuiltinIntegerType", INT_MASK},
        {kIROp_FRem, "frem", "%", "__BuiltinFloatingPointType", FLOAT_MASK},
        {kIROp_And, "logicalAnd", "&&", nullptr, BOOL_MASK | BOOL_RESULT},
        {kIROp_Or, "logicalOr", "||", nullptr, BOOL_MASK | BOOL_RESULT},
        {kIROp_BitAnd, "and", "&", "__BuiltinLogicalType", LOGICAL_MASK},
        {kIROp_BitOr, "or", "|", "__BuiltinLogicalType", LOGICAL_MASK},
        {kIROp_BitXor, "xor", "^", "__BuiltinLogicalType", LOGICAL_MASK},
        {kIROp_Eql, "eql", "==", "__BuiltinType", ANY_MASK | BOOL_RESULT},
        {kIROp_Neq, "neq", "!=", "__BuiltinType", ANY_MASK | BOOL_RESULT},
        {kIROp_Greater, "greater", ">", "__BuiltinArithmeticType", ARITHMETIC_MASK | BOOL_RESULT},
        {kIROp_Less, "less", "<", "__BuiltinArithmeticType", ARITHMETIC_MASK | BOOL_RESULT},
        {kIROp_Geq, "geq", ">=", "__BuiltinArithmeticType", ARITHMETIC_MASK | BOOL_RESULT},
        {kIROp_Leq, "leq", "<=", "__BuiltinArithmeticType", ARITHMETIC_MASK | BOOL_RESULT},
    };

    // Both the following functions use these macros.
    // NOTE! They require a variable named path to emit the #line correctly if in source file.
#define SLANG_RAW(TEXT) sb << TEXT;
#define SLANG_SPLICE(EXPR) sb << (EXPR);

#define EMIT_LINE_DIRECTIVE() sb << "#line " << (__LINE__+1) << " \"" << path << "\"\n"

    String Session::getCoreLibraryCode()
    {
#if !defined(SLANG_DISABLE_STDLIB_SOURCE)
        if (coreLibraryCode.getLength() > 0)
            return coreLibraryCode;

        StringBuilder sb;

        const String path = getStdlibPath();

        #include "core.meta.slang.h"

        coreLibraryCode = sb.ProduceString();
#endif

        return coreLibraryCode;
    }

    String Session::getHLSLLibraryCode()
    {
#if !defined(SLANG_DISABLE_STDLIB_SOURCE)
        if (hlslLibraryCode.getLength() > 0)
            return hlslLibraryCode;

        const String path = getStdlibPath();

        StringBuilder sb;

        #include "hlsl.meta.slang.h"

        hlslLibraryCode = sb.ProduceString();
#endif
        return hlslLibraryCode;
    }

    String Session::getAutodiffLibraryCode()
    {
#if !defined(SLANG_DISABLE_STDLIB_SOURCE)
        if (autodiffLibraryCode.getLength() > 0)
            return autodiffLibraryCode;

        const String path = getStdlibPath();

        StringBuilder sb;

        #include "diff.meta.slang.h"

        autodiffLibraryCode = sb.ProduceString();
#endif
        return autodiffLibraryCode;
    }
}
back to top