https://github.com/shader-slang/slang
Tip revision: 0586f3298fa7d554fa2682103eefba88740d6758 authored by jsmall-nvidia on 18 January 2023, 19:11:50 UTC
Upgrade slang-llvm-13.x-33 (#2600)
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;
}
}