From 0d9071bd1511ee2cb7d6ba6ce9e250d25613ddca Mon Sep 17 00:00:00 2001 From: jsmall-nvidia Date: Tue, 4 Jun 2019 15:10:26 -0400 Subject: Review improvements on #971: WIP: Support for other source target languages (#974) * * Added SourceStyle to CLikeSourceEmitter, to limit cases to actual target types. * Made Impl methods _ prefixed * Small tidyup * * SourceStream -> SourceWriter * use slang-emit- prefix on SourceWriter file * * Remove EmitContext -> merge into CLikeSourceEmitter * slang-c-like-source-emitter -> slang-emit-source.cpp * ExtensionUsageTracker -> GLSLExtensionTracker slang-extension-usage-tracker.cpp/.h -> slang-emit-glsl-extension-tracker.cpp/.h * emit-source.cpp.h -> emit-c-like.cpp/.h * Small fix to move where some _ prefixed functions are declared in CLikeSourceEmitter. --- source/slang/slang-c-like-source-emitter.cpp | 5811 ------------------- source/slang/slang-c-like-source-emitter.h | 379 -- source/slang/slang-emit-c-like.cpp | 5848 ++++++++++++++++++++ source/slang/slang-emit-c-like.h | 473 ++ source/slang/slang-emit-context.cpp | 7 - source/slang/slang-emit-context.h | 79 - source/slang/slang-emit-glsl-extension-tracker.cpp | 44 + source/slang/slang-emit-glsl-extension-tracker.h | 34 + source/slang/slang-emit-source-writer.cpp | 396 ++ source/slang/slang-emit-source-writer.h | 113 + source/slang/slang-emit.cpp | 44 +- source/slang/slang-extension-usage-tracker.cpp | 44 - source/slang/slang-extension-usage-tracker.h | 34 - source/slang/slang-ir-glsl-legalize.cpp | 12 +- source/slang/slang-ir-glsl-legalize.h | 4 +- source/slang/slang-source-stream.cpp | 396 -- source/slang/slang-source-stream.h | 113 - source/slang/slang.vcxproj | 14 +- source/slang/slang.vcxproj.filters | 32 +- 19 files changed, 6956 insertions(+), 6921 deletions(-) delete mode 100644 source/slang/slang-c-like-source-emitter.cpp delete mode 100644 source/slang/slang-c-like-source-emitter.h create mode 100644 source/slang/slang-emit-c-like.cpp create mode 100644 source/slang/slang-emit-c-like.h delete mode 100644 source/slang/slang-emit-context.cpp delete mode 100644 source/slang/slang-emit-context.h create mode 100644 source/slang/slang-emit-glsl-extension-tracker.cpp create mode 100644 source/slang/slang-emit-glsl-extension-tracker.h create mode 100644 source/slang/slang-emit-source-writer.cpp create mode 100644 source/slang/slang-emit-source-writer.h delete mode 100644 source/slang/slang-extension-usage-tracker.cpp delete mode 100644 source/slang/slang-extension-usage-tracker.h delete mode 100644 source/slang/slang-source-stream.cpp delete mode 100644 source/slang/slang-source-stream.h (limited to 'source') diff --git a/source/slang/slang-c-like-source-emitter.cpp b/source/slang/slang-c-like-source-emitter.cpp deleted file mode 100644 index dacb5e9b5..000000000 --- a/source/slang/slang-c-like-source-emitter.cpp +++ /dev/null @@ -1,5811 +0,0 @@ -// slang-c-like-source-emitter.cpp -#include "slang-c-like-source-emitter.h" - -#include "../core/slang-writer.h" -#include "slang-ir-bind-existentials.h" -#include "slang-ir-dce.h" -#include "slang-ir-entry-point-uniforms.h" -#include "slang-ir-glsl-legalize.h" - -#include "slang-ir-link.h" -#include "slang-ir-restructure-scoping.h" -#include "slang-ir-specialize.h" -#include "slang-ir-specialize-resources.h" -#include "slang-ir-ssa.h" -#include "slang-ir-union.h" -#include "slang-ir-validate.h" -#include "slang-legalize-types.h" -#include "slang-lower-to-ir.h" -#include "slang-mangle.h" -#include "slang-name.h" -#include "slang-syntax.h" -#include "slang-type-layout.h" -#include "slang-visitor.h" - -#include "slang-source-stream.h" -#include "slang-emit-context.h" -#include "slang-mangled-lexer.h" - -#include - -namespace Slang { - -// represents a declarator for use in emitting types -struct CLikeSourceEmitter::EDeclarator -{ - enum class Flavor - { - name, - Array, - UnsizedArray, - }; - Flavor flavor; - EDeclarator* next = nullptr; - - // Used for `Flavor::name` - Name* name; - SourceLoc loc; - - // Used for `Flavor::Array` - IRInst* elementCount; -}; - -struct CLikeSourceEmitter::IRDeclaratorInfo -{ - enum class Flavor - { - Simple, - Ptr, - Array, - }; - - Flavor flavor; - IRDeclaratorInfo* next; - union - { - String const* name; - IRInst* elementCount; - }; -}; - -// A chain of variables to use for emitting semantic/layout info -struct CLikeSourceEmitter::EmitVarChain -{ - VarLayout* varLayout; - EmitVarChain* next; - - EmitVarChain() - : varLayout(0) - , next(0) - {} - - EmitVarChain(VarLayout* varLayout) - : varLayout(varLayout) - , next(0) - {} - - EmitVarChain(VarLayout* varLayout, EmitVarChain* next) - : varLayout(varLayout) - , next(next) - {} -}; - -struct CLikeSourceEmitter::ComputeEmitActionsContext -{ - IRInst* moduleInst; - HashSet openInsts; - Dictionary mapInstToLevel; - List* actions; -}; - -/* !!!!!!!!!!!!!!!!!!!!!!!!!!!! CLikeSourceEmitter !!!!!!!!!!!!!!!!!!!!!!!!!! */ - -CLikeSourceEmitter::CLikeSourceEmitter(EmitContext* context) - : m_context(context), - m_stream(context->stream) -{} - -// -// Types -// - -void CLikeSourceEmitter::emitDeclarator(EDeclarator* declarator) -{ - if (!declarator) return; - - m_stream->emit(" "); - - switch (declarator->flavor) - { - case EDeclarator::Flavor::name: - m_stream->emitName(declarator->name, declarator->loc); - break; - - case EDeclarator::Flavor::Array: - emitDeclarator(declarator->next); - m_stream->emit("["); - if(auto elementCount = declarator->elementCount) - { - emitVal(elementCount, getInfo(EmitOp::General)); - } - m_stream->emit("]"); - break; - - case EDeclarator::Flavor::UnsizedArray: - emitDeclarator(declarator->next); - m_stream->emit("[]"); - break; - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unknown declarator flavor"); - break; - } -} - -void CLikeSourceEmitter::emitGLSLTypePrefix(IRType* type, bool promoteHalfToFloat) -{ - switch (type->op) - { - case kIROp_FloatType: - // no prefix - break; - - case kIROp_Int8Type: m_stream->emit("i8"); break; - case kIROp_Int16Type: m_stream->emit("i16"); break; - case kIROp_IntType: m_stream->emit("i"); break; - case kIROp_Int64Type: m_stream->emit("i64"); break; - - case kIROp_UInt8Type: m_stream->emit("u8"); break; - case kIROp_UInt16Type: m_stream->emit("u16"); break; - case kIROp_UIntType: m_stream->emit("u"); break; - case kIROp_UInt64Type: m_stream->emit("u64"); break; - - case kIROp_BoolType: m_stream->emit("b"); break; - - case kIROp_HalfType: - { - _requireHalf(); - if (promoteHalfToFloat) - { - // no prefix - } - else - { - m_stream->emit("f16"); - } - break; - } - case kIROp_DoubleType: m_stream->emit("d"); break; - - case kIROp_VectorType: - emitGLSLTypePrefix(cast(type)->getElementType(), promoteHalfToFloat); - break; - - case kIROp_MatrixType: - emitGLSLTypePrefix(cast(type)->getElementType(), promoteHalfToFloat); - break; - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled GLSL type prefix"); - break; - } -} - -void CLikeSourceEmitter::emitHLSLTextureType(IRTextureTypeBase* texType) -{ - switch(texType->getAccess()) - { - case SLANG_RESOURCE_ACCESS_READ: - break; - - case SLANG_RESOURCE_ACCESS_READ_WRITE: - m_stream->emit("RW"); - break; - - case SLANG_RESOURCE_ACCESS_RASTER_ORDERED: - m_stream->emit("RasterizerOrdered"); - break; - - case SLANG_RESOURCE_ACCESS_APPEND: - m_stream->emit("Append"); - break; - - case SLANG_RESOURCE_ACCESS_CONSUME: - m_stream->emit("Consume"); - break; - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled resource access mode"); - break; - } - - switch (texType->GetBaseShape()) - { - case TextureFlavor::Shape::Shape1D: m_stream->emit("Texture1D"); break; - case TextureFlavor::Shape::Shape2D: m_stream->emit("Texture2D"); break; - case TextureFlavor::Shape::Shape3D: m_stream->emit("Texture3D"); break; - case TextureFlavor::Shape::ShapeCube: m_stream->emit("TextureCube"); break; - case TextureFlavor::Shape::ShapeBuffer: m_stream->emit("Buffer"); break; - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled resource shape"); - break; - } - - if (texType->isMultisample()) - { - m_stream->emit("MS"); - } - if (texType->isArray()) - { - m_stream->emit("Array"); - } - m_stream->emit("<"); - emitType(texType->getElementType()); - m_stream->emit(" >"); -} - -void CLikeSourceEmitter::emitGLSLTextureOrTextureSamplerType(IRTextureTypeBase* type, char const* baseName) -{ - if (type->getElementType()->op == kIROp_HalfType) - { - // Texture access is always as float types if half is specified - - } - else - { - emitGLSLTypePrefix(type->getElementType(), true); - } - - m_stream->emit(baseName); - switch (type->GetBaseShape()) - { - case TextureFlavor::Shape::Shape1D: m_stream->emit("1D"); break; - case TextureFlavor::Shape::Shape2D: m_stream->emit("2D"); break; - case TextureFlavor::Shape::Shape3D: m_stream->emit("3D"); break; - case TextureFlavor::Shape::ShapeCube: m_stream->emit("Cube"); break; - case TextureFlavor::Shape::ShapeBuffer: m_stream->emit("Buffer"); break; - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled resource shape"); - break; - } - - if (type->isMultisample()) - { - m_stream->emit("MS"); - } - if (type->isArray()) - { - m_stream->emit("Array"); - } -} - -void CLikeSourceEmitter::emitGLSLTextureType( - IRTextureType* texType) -{ - switch(texType->getAccess()) - { - case SLANG_RESOURCE_ACCESS_READ_WRITE: - case SLANG_RESOURCE_ACCESS_RASTER_ORDERED: - emitGLSLTextureOrTextureSamplerType(texType, "image"); - break; - - default: - emitGLSLTextureOrTextureSamplerType(texType, "texture"); - break; - } -} - -void CLikeSourceEmitter::emitGLSLTextureSamplerType(IRTextureSamplerType* type) -{ - emitGLSLTextureOrTextureSamplerType(type, "sampler"); -} - -void CLikeSourceEmitter::emitGLSLImageType(IRGLSLImageType* type) -{ - emitGLSLTextureOrTextureSamplerType(type, "image"); -} - -void CLikeSourceEmitter::emitTextureType(IRTextureType* texType) -{ - switch(m_context->target) - { - case CodeGenTarget::HLSL: - emitHLSLTextureType(texType); - break; - - case CodeGenTarget::GLSL: - emitGLSLTextureType(texType); - break; - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target"); - break; - } -} - -void CLikeSourceEmitter::emitTextureSamplerType(IRTextureSamplerType* type) -{ - switch(m_context->target) - { - case CodeGenTarget::GLSL: - emitGLSLTextureSamplerType(type); - break; - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "this target should see combined texture-sampler types"); - break; - } -} - -void CLikeSourceEmitter::emitImageType(IRGLSLImageType* type) -{ - switch(m_context->target) - { - case CodeGenTarget::HLSL: - emitHLSLTextureType(type); - break; - - case CodeGenTarget::GLSL: - emitGLSLImageType(type); - break; - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "this target should see GLSL image types"); - break; - } -} - -static IROp _getCType(IROp op) -{ - switch (op) - { - case kIROp_VoidType: - case kIROp_BoolType: - { - return op; - } - case kIROp_Int8Type: - case kIROp_Int16Type: - case kIROp_IntType: - case kIROp_UInt8Type: - case kIROp_UInt16Type: - case kIROp_UIntType: - { - // Promote all these to Int - return kIROp_IntType; - } - case kIROp_Int64Type: - case kIROp_UInt64Type: - { - // Promote all these to Int16, we can just vary the call to make these work - return kIROp_Int64Type; - } - case kIROp_DoubleType: - { - return kIROp_DoubleType; - } - case kIROp_HalfType: - case kIROp_FloatType: - { - // Promote both to float - return kIROp_FloatType; - } - default: - { - SLANG_ASSERT(!"Unhandled type"); - return kIROp_undefined; - } - } -} - -static UnownedStringSlice _getCTypeVecPostFix(IROp op) -{ - switch (op) - { - case kIROp_BoolType: return UnownedStringSlice::fromLiteral("B"); - case kIROp_IntType: return UnownedStringSlice::fromLiteral("I"); - case kIROp_FloatType: return UnownedStringSlice::fromLiteral("F"); - case kIROp_Int64Type: return UnownedStringSlice::fromLiteral("I64"); - case kIROp_DoubleType: return UnownedStringSlice::fromLiteral("F64"); - default: return UnownedStringSlice::fromLiteral("?"); - } -} - -#if 0 -static UnownedStringSlice _getCTypeName(IROp op) -{ - switch (op) - { - case kIROp_BoolType: return UnownedStringSlice::fromLiteral("Bool"); - case kIROp_IntType: return UnownedStringSlice::fromLiteral("I32"); - case kIROp_FloatType: return UnownedStringSlice::fromLiteral("F32"); - case kIROp_Int64Type: return UnownedStringSlice::fromLiteral("I64"); - case kIROp_DoubleType: return UnownedStringSlice::fromLiteral("F64"); - default: return UnownedStringSlice::fromLiteral("?"); - } -} -#endif - -void CLikeSourceEmitter::_emitCVecType(IROp op, Int size) -{ - m_stream->emit("Vec"); - const UnownedStringSlice postFix = _getCTypeVecPostFix(_getCType(op)); - m_stream->emit(postFix); - if (postFix.size() > 1) - { - m_stream->emit("_"); - } - m_stream->emit(size); -} - -void CLikeSourceEmitter::_emitCMatType(IROp op, IRIntegerValue rowCount, IRIntegerValue colCount) -{ - m_stream->emit("Mat"); - const UnownedStringSlice postFix = _getCTypeVecPostFix(_getCType(op)); - m_stream->emit(postFix); - if (postFix.size() > 1) - { - m_stream->emit("_"); - } - m_stream->emit(rowCount); - m_stream->emit(colCount); -} - -void CLikeSourceEmitter::_emitCFunc(BuiltInCOp cop, IRType* type) -{ - emitSimpleTypeImpl(type); - m_stream->emit("_"); - - switch (cop) - { - case BuiltInCOp::Init: m_stream->emit("init"); - case BuiltInCOp::Splat: m_stream->emit("splat"); break; - } -} - -void CLikeSourceEmitter::emitVectorTypeName(IRType* elementType, IRIntegerValue elementCount) -{ - switch(m_context->target) - { - case CodeGenTarget::GLSL: - case CodeGenTarget::GLSL_Vulkan: - case CodeGenTarget::GLSL_Vulkan_OneDesc: - { - if (elementCount > 1) - { - emitGLSLTypePrefix(elementType); - m_stream->emit("vec"); - m_stream->emit(elementCount); - } - else - { - emitSimpleTypeImpl(elementType); - } - } - break; - - case CodeGenTarget::HLSL: - // TODO(tfoley): should really emit these with sugar - m_stream->emit("vector<"); - emitType(elementType); - m_stream->emit(","); - m_stream->emit(elementCount); - m_stream->emit(">"); - break; - - case CodeGenTarget::CSource: - case CodeGenTarget::CPPSource: - _emitCVecType(elementType->op, Int(elementCount)); - break; - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target"); - break; - } -} - -void CLikeSourceEmitter::emitVectorTypeImpl(IRVectorType* vecType) -{ - IRInst* elementCountInst = vecType->getElementCount(); - if (elementCountInst->op != kIROp_IntLit) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "Expecting an integral size for vector size"); - return; - } - - const IRConstant* irConst = (const IRConstant*)elementCountInst; - const IRIntegerValue elementCount = irConst->value.intVal; - if (elementCount <= 0) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "Vector size must be greater than 0"); - return; - } - - auto* elementType = vecType->getElementType(); - - emitVectorTypeName(elementType, elementCount); -} - -void CLikeSourceEmitter::emitMatrixTypeImpl(IRMatrixType* matType) -{ - switch(m_context->target) - { - case CodeGenTarget::GLSL: - case CodeGenTarget::GLSL_Vulkan: - case CodeGenTarget::GLSL_Vulkan_OneDesc: - { - emitGLSLTypePrefix(matType->getElementType()); - m_stream->emit("mat"); - emitVal(matType->getRowCount(), getInfo(EmitOp::General)); - // TODO(tfoley): only emit the next bit - // for non-square matrix - m_stream->emit("x"); - emitVal(matType->getColumnCount(), getInfo(EmitOp::General)); - } - break; - - case CodeGenTarget::HLSL: - // TODO(tfoley): should really emit these with sugar - m_stream->emit("matrix<"); - emitType(matType->getElementType()); - m_stream->emit(","); - emitVal(matType->getRowCount(), getInfo(EmitOp::General)); - m_stream->emit(","); - emitVal(matType->getColumnCount(), getInfo(EmitOp::General)); - m_stream->emit("> "); - break; - - case CodeGenTarget::CPPSource: - case CodeGenTarget::CSource: - { - const auto rowCount = static_cast(matType->getRowCount())->value.intVal; - const auto colCount = static_cast(matType->getColumnCount())->value.intVal; - - _emitCMatType(matType->getElementType()->op, rowCount, colCount); - break; - } - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target"); - break; - } -} - -void CLikeSourceEmitter::emitSamplerStateType(IRSamplerStateTypeBase* samplerStateType) -{ - switch(m_context->target) - { - case CodeGenTarget::HLSL: - default: - switch (samplerStateType->op) - { - case kIROp_SamplerStateType: m_stream->emit("SamplerState"); break; - case kIROp_SamplerComparisonStateType: m_stream->emit("SamplerComparisonState"); break; - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled sampler state flavor"); - break; - } - break; - - case CodeGenTarget::GLSL: - switch (samplerStateType->op) - { - case kIROp_SamplerStateType: m_stream->emit("sampler"); break; - case kIROp_SamplerComparisonStateType: m_stream->emit("samplerShadow"); break; - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled sampler state flavor"); - break; - } - break; - break; - } -} - -void CLikeSourceEmitter::emitStructuredBufferType(IRHLSLStructuredBufferTypeBase* type) -{ - switch(m_context->target) - { - case CodeGenTarget::HLSL: - default: - { - switch (type->op) - { - case kIROp_HLSLStructuredBufferType: m_stream->emit("StructuredBuffer"); break; - case kIROp_HLSLRWStructuredBufferType: m_stream->emit("RWStructuredBuffer"); break; - case kIROp_HLSLRasterizerOrderedStructuredBufferType: m_stream->emit("RasterizerOrderedStructuredBuffer"); break; - case kIROp_HLSLAppendStructuredBufferType: m_stream->emit("AppendStructuredBuffer"); break; - case kIROp_HLSLConsumeStructuredBufferType: m_stream->emit("ConsumeStructuredBuffer"); break; - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled structured buffer type"); - break; - } - - m_stream->emit("<"); - emitType(type->getElementType()); - m_stream->emit(" >"); - } - break; - - case CodeGenTarget::GLSL: - // TODO: We desugar global variables with structured-buffer type into GLSL - // `buffer` declarations, but we don't currently handle structured-buffer types - // in other contexts (e.g., as function parameters). The simplest thing to do - // would be to emit a `StructuredBuffer` as `Foo[]` and `RWStructuredBuffer` - // as `in out Foo[]`, but that is starting to get into the realm of transformations - // that should really be handled during legalization, rather than during emission. - // - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "structured buffer type used unexpectedly"); - break; - } -} - -void CLikeSourceEmitter::emitUntypedBufferType(IRUntypedBufferResourceType* type) -{ - switch(m_context->target) - { - case CodeGenTarget::HLSL: - default: - { - switch (type->op) - { - case kIROp_HLSLByteAddressBufferType: m_stream->emit("ByteAddressBuffer"); break; - case kIROp_HLSLRWByteAddressBufferType: m_stream->emit("RWByteAddressBuffer"); break; - case kIROp_HLSLRasterizerOrderedByteAddressBufferType: m_stream->emit("RasterizerOrderedByteAddressBuffer"); break; - case kIROp_RaytracingAccelerationStructureType: m_stream->emit("RaytracingAccelerationStructure"); break; - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled buffer type"); - break; - } - } - break; - - case CodeGenTarget::GLSL: - { - switch (type->op) - { - case kIROp_RaytracingAccelerationStructureType: - requireGLSLExtension("GL_NV_ray_tracing"); - m_stream->emit("accelerationStructureNV"); - break; - - // TODO: These "translations" are obviously wrong for GLSL. - case kIROp_HLSLByteAddressBufferType: m_stream->emit("ByteAddressBuffer"); break; - case kIROp_HLSLRWByteAddressBufferType: m_stream->emit("RWByteAddressBuffer"); break; - case kIROp_HLSLRasterizerOrderedByteAddressBufferType: m_stream->emit("RasterizerOrderedByteAddressBuffer"); break; - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled buffer type"); - break; - } - } - break; - } -} - -void CLikeSourceEmitter::_requireHalf() -{ - if (getTarget() == CodeGenTarget::GLSL) - { - m_context->extensionUsageTracker.requireGLSLHalfExtension(); - } -} - -void CLikeSourceEmitter::emitSimpleTypeImpl(IRType* type) -{ - switch (type->op) - { - default: - break; - - case kIROp_VoidType: m_stream->emit("void"); return; - case kIROp_BoolType: m_stream->emit("bool"); return; - - case kIROp_Int8Type: m_stream->emit("int8_t"); return; - case kIROp_Int16Type: m_stream->emit("int16_t"); return; - case kIROp_IntType: m_stream->emit("int"); return; - case kIROp_Int64Type: m_stream->emit("int64_t"); return; - - case kIROp_UInt8Type: m_stream->emit("uint8_t"); return; - case kIROp_UInt16Type: m_stream->emit("uint16_t"); return; - case kIROp_UIntType: m_stream->emit("uint"); return; - case kIROp_UInt64Type: m_stream->emit("uint64_t"); return; - - case kIROp_HalfType: - { - _requireHalf(); - if (getTarget() == CodeGenTarget::GLSL) - { - m_stream->emit("float16_t"); - } - else - { - m_stream->emit("half"); - } - return; - } - case kIROp_FloatType: m_stream->emit("float"); return; - case kIROp_DoubleType: m_stream->emit("double"); return; - - case kIROp_VectorType: - emitVectorTypeImpl((IRVectorType*)type); - return; - - case kIROp_MatrixType: - emitMatrixTypeImpl((IRMatrixType*)type); - return; - - case kIROp_SamplerStateType: - case kIROp_SamplerComparisonStateType: - emitSamplerStateType(cast(type)); - return; - - case kIROp_StructType: - m_stream->emit(getIRName(type)); - return; - } - - // TODO: Ideally the following should be data-driven, - // based on meta-data attached to the definitions of - // each of these IR opcodes. - - if (auto texType = as(type)) - { - emitTextureType(texType); - return; - } - else if (auto textureSamplerType = as(type)) - { - emitTextureSamplerType(textureSamplerType); - return; - } - else if (auto imageType = as(type)) - { - emitImageType(imageType); - return; - } - else if (auto structuredBufferType = as(type)) - { - emitStructuredBufferType(structuredBufferType); - return; - } - else if(auto untypedBufferType = as(type)) - { - emitUntypedBufferType(untypedBufferType); - return; - } - - // HACK: As a fallback for HLSL targets, assume that the name of the - // instruction being used is the same as the name of the HLSL type. - if(m_context->target == CodeGenTarget::HLSL) - { - auto opInfo = getIROpInfo(type->op); - m_stream->emit(opInfo.name); - UInt operandCount = type->getOperandCount(); - if(operandCount) - { - m_stream->emit("<"); - for(UInt ii = 0; ii < operandCount; ++ii) - { - if(ii != 0) m_stream->emit(", "); - emitVal(type->getOperand(ii), getInfo(EmitOp::General)); - } - m_stream->emit(" >"); - } - - return; - } - - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled type"); -} - -void CLikeSourceEmitter::emitArrayTypeImpl(IRArrayType* arrayType, EDeclarator* declarator) -{ - EDeclarator arrayDeclarator; - arrayDeclarator.flavor = EDeclarator::Flavor::Array; - arrayDeclarator.next = declarator; - arrayDeclarator.elementCount = arrayType->getElementCount(); - - emitTypeImpl(arrayType->getElementType(), &arrayDeclarator); -} - -void CLikeSourceEmitter::emitUnsizedArrayTypeImpl(IRUnsizedArrayType* arrayType, EDeclarator* declarator) -{ - EDeclarator arrayDeclarator; - arrayDeclarator.flavor = EDeclarator::Flavor::UnsizedArray; - arrayDeclarator.next = declarator; - - emitTypeImpl(arrayType->getElementType(), &arrayDeclarator); -} - -void CLikeSourceEmitter::emitTypeImpl(IRType* type, EDeclarator* declarator) -{ - switch (type->op) - { - default: - emitSimpleTypeImpl(type); - emitDeclarator(declarator); - break; - - case kIROp_RateQualifiedType: - { - auto rateQualifiedType = cast(type); - emitTypeImpl(rateQualifiedType->getValueType(), declarator); - } - break; - - case kIROp_ArrayType: - emitArrayTypeImpl(cast(type), declarator); - break; - - case kIROp_UnsizedArrayType: - emitUnsizedArrayTypeImpl(cast(type), declarator); - break; - } - -} - -void CLikeSourceEmitter::emitType( - IRType* type, - SourceLoc const& typeLoc, - Name* name, - SourceLoc const& nameLoc) -{ - m_stream->advanceToSourceLocation(typeLoc); - - EDeclarator nameDeclarator; - nameDeclarator.flavor = EDeclarator::Flavor::name; - nameDeclarator.name = name; - nameDeclarator.loc = nameLoc; - emitTypeImpl(type, &nameDeclarator); -} - -void CLikeSourceEmitter::emitType(IRType* type, Name* name) -{ - emitType(type, SourceLoc(), name, SourceLoc()); -} - -void CLikeSourceEmitter::emitType(IRType* type, const String& name) -{ - // HACK: the rest of the code wants a `Name`, - // so we'll create one for a bit... - Name tempName; - tempName.text = name; - - emitType(type, SourceLoc(), &tempName, SourceLoc()); -} - - -void CLikeSourceEmitter::emitType(IRType* type) -{ - emitTypeImpl(type, nullptr); -} - -// -// Expressions -// - -bool CLikeSourceEmitter::maybeEmitParens(EmitOpInfo& outerPrec, EmitOpInfo prec) -{ - bool needParens = (prec.leftPrecedence <= outerPrec.leftPrecedence) - || (prec.rightPrecedence <= outerPrec.rightPrecedence); - - if (needParens) - { - m_stream->emit("("); - - outerPrec = getInfo(EmitOp::None); - } - return needParens; -} - -void CLikeSourceEmitter::maybeCloseParens(bool needClose) -{ - if(needClose) m_stream->emit(")"); -} - -bool CLikeSourceEmitter::isTargetIntrinsicModifierApplicable(const String& targetName) -{ - switch(m_context->target) - { - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target"); - return false; - - case CodeGenTarget::CSource: return targetName == "c"; - case CodeGenTarget::CPPSource: return targetName == "cpp"; - case CodeGenTarget::GLSL: return targetName == "glsl"; - case CodeGenTarget::HLSL: return targetName == "hlsl"; - } -} - -void CLikeSourceEmitter::emitType(IRType* type, Name* name, SourceLoc const& nameLoc) -{ - emitType( - type, - SourceLoc(), - name, - nameLoc); -} - -void CLikeSourceEmitter::emitType(IRType* type, NameLoc const& nameAndLoc) -{ - emitType(type, nameAndLoc.name, nameAndLoc.loc); -} - -bool CLikeSourceEmitter::isTargetIntrinsicModifierApplicable( - IRTargetIntrinsicDecoration* decoration) -{ - auto targetName = String(decoration->getTargetName()); - - // If no target name was specified, then the modifier implicitly - // applies to all targets. - if(targetName.getLength() == 0) - return true; - - return isTargetIntrinsicModifierApplicable(targetName); -} - -void CLikeSourceEmitter::emitStringLiteral( - String const& value) -{ - m_stream->emit("\""); - for (auto c : value) - { - // TODO: This needs a more complete implementation, - // especially if we want to support Unicode. - - char buffer[] = { c, 0 }; - switch (c) - { - default: - m_stream->emit(buffer); - break; - - case '\"': m_stream->emit("\\\""); - case '\'': m_stream->emit("\\\'"); - case '\\': m_stream->emit("\\\\"); - case '\n': m_stream->emit("\\n"); - case '\r': m_stream->emit("\\r"); - case '\t': m_stream->emit("\\t"); - } - } - m_stream->emit("\""); -} - -void CLikeSourceEmitter::requireGLSLExtension(String const& name) -{ - m_context->extensionUsageTracker.requireGLSLExtension(name); -} - -void CLikeSourceEmitter::requireGLSLVersion(ProfileVersion version) -{ - if (m_context->target != CodeGenTarget::GLSL) - return; - - m_context->extensionUsageTracker.requireGLSLVersion(version); -} - -void CLikeSourceEmitter::requireGLSLVersion(int version) -{ - switch (version) - { -#define CASE(NUMBER) \ - case NUMBER: requireGLSLVersion(ProfileVersion::GLSL_##NUMBER); break - - CASE(110); - CASE(120); - CASE(130); - CASE(140); - CASE(150); - CASE(330); - CASE(400); - CASE(410); - CASE(420); - CASE(430); - CASE(440); - CASE(450); - -#undef CASE - } -} - -void CLikeSourceEmitter::setSampleRateFlag() -{ - m_context->entryPointLayout->flags |= EntryPointLayout::Flag::usesAnySampleRateInput; -} - -void CLikeSourceEmitter::doSampleRateInputCheck(Name* name) -{ - auto text = getText(name); - if (text == "gl_SampleID") - { - setSampleRateFlag(); - } -} - -void CLikeSourceEmitter::emitVal(IRInst* val, EmitOpInfo const& outerPrec) -{ - if(auto type = as(val)) - { - emitType(type); - } - else - { - emitIRInstExpr(val, IREmitMode::Default, outerPrec); - } -} - -UInt CLikeSourceEmitter::getBindingOffset(EmitVarChain* chain, LayoutResourceKind kind) -{ - UInt offset = 0; - for(auto cc = chain; cc; cc = cc->next) - { - if(auto resInfo = cc->varLayout->FindResourceInfo(kind)) - { - offset += resInfo->index; - } - } - return offset; -} - -UInt CLikeSourceEmitter::getBindingSpace(EmitVarChain* chain, LayoutResourceKind kind) -{ - UInt space = 0; - for(auto cc = chain; cc; cc = cc->next) - { - auto varLayout = cc->varLayout; - if(auto resInfo = varLayout->FindResourceInfo(kind)) - { - space += resInfo->space; - } - if(auto resInfo = varLayout->FindResourceInfo(LayoutResourceKind::RegisterSpace)) - { - space += resInfo->index; - } - } - return space; -} - -void CLikeSourceEmitter::emitHLSLRegisterSemantic(LayoutResourceKind kind, EmitVarChain* chain, char const* uniformSemanticSpelling) -{ - if(!chain) - return; - if(!chain->varLayout->FindResourceInfo(kind)) - return; - - UInt index = getBindingOffset(chain, kind); - UInt space = getBindingSpace(chain, kind); - - switch(kind) - { - case LayoutResourceKind::Uniform: - { - UInt offset = index; - - // The HLSL `c` register space is logically grouped in 16-byte registers, - // while we try to traffic in byte offsets. That means we need to pick - // a register number, based on the starting offset in 16-byte register - // units, and then a "component" within that register, based on 4-byte - // offsets from there. We cannot support more fine-grained offsets than that. - - m_stream->emit(" : "); - m_stream->emit(uniformSemanticSpelling); - m_stream->emit("(c"); - - // Size of a logical `c` register in bytes - auto registerSize = 16; - - // Size of each component of a logical `c` register, in bytes - auto componentSize = 4; - - size_t startRegister = offset / registerSize; - m_stream->emit(int(startRegister)); - - size_t byteOffsetInRegister = offset % registerSize; - - // If this field doesn't start on an even register boundary, - // then we need to emit additional information to pick the - // right component to start from - if (byteOffsetInRegister != 0) - { - // The value had better occupy a whole number of components. - SLANG_RELEASE_ASSERT(byteOffsetInRegister % componentSize == 0); - - size_t startComponent = byteOffsetInRegister / componentSize; - - static const char* kComponentNames[] = {"x", "y", "z", "w"}; - m_stream->emit("."); - m_stream->emit(kComponentNames[startComponent]); - } - m_stream->emit(")"); - } - break; - - case LayoutResourceKind::RegisterSpace: - case LayoutResourceKind::GenericResource: - case LayoutResourceKind::ExistentialTypeParam: - case LayoutResourceKind::ExistentialObjectParam: - // ignore - break; - default: - { - m_stream->emit(" : register("); - switch( kind ) - { - case LayoutResourceKind::ConstantBuffer: - m_stream->emit("b"); - break; - case LayoutResourceKind::ShaderResource: - m_stream->emit("t"); - break; - case LayoutResourceKind::UnorderedAccess: - m_stream->emit("u"); - break; - case LayoutResourceKind::SamplerState: - m_stream->emit("s"); - break; - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled HLSL register type"); - break; - } - m_stream->emit(index); - if(space) - { - m_stream->emit(", space"); - m_stream->emit(space); - } - m_stream->emit(")"); - } - } -} - -void CLikeSourceEmitter::emitHLSLRegisterSemantics(EmitVarChain* chain, char const* uniformSemanticSpelling) -{ - if (!chain) return; - - auto layout = chain->varLayout; - - switch( m_context->target ) - { - default: - return; - - case CodeGenTarget::HLSL: - break; - } - - for( auto rr : layout->resourceInfos ) - { - emitHLSLRegisterSemantic(rr.kind, chain, uniformSemanticSpelling); - } -} - -void CLikeSourceEmitter::emitHLSLRegisterSemantics(VarLayout* varLayout, char const* uniformSemanticSpelling) -{ - if(!varLayout) - return; - - EmitVarChain chain(varLayout); - emitHLSLRegisterSemantics(&chain, uniformSemanticSpelling); -} - -void CLikeSourceEmitter::emitHLSLParameterGroupFieldLayoutSemantics(EmitVarChain* chain) -{ - if(!chain) - return; - - auto layout = chain->varLayout; - for( auto rr : layout->resourceInfos ) - { - emitHLSLRegisterSemantic(rr.kind, chain, "packoffset"); - } -} - - -void CLikeSourceEmitter::emitHLSLParameterGroupFieldLayoutSemantics(RefPtr fieldLayout, EmitVarChain* inChain) -{ - EmitVarChain chain(fieldLayout, inChain); - emitHLSLParameterGroupFieldLayoutSemantics(&chain); -} - -bool CLikeSourceEmitter::emitGLSLLayoutQualifier(LayoutResourceKind kind, EmitVarChain* chain) -{ - if(!chain) - return false; - if(!chain->varLayout->FindResourceInfo(kind)) - return false; - - UInt index = getBindingOffset(chain, kind); - UInt space = getBindingSpace(chain, kind); - switch(kind) - { - case LayoutResourceKind::Uniform: - { - // Explicit offsets require a GLSL extension (which - // is not universally supported, it seems) or a new - // enough GLSL version (which we don't want to - // universally require), so for right now we - // won't actually output explicit offsets for uniform - // shader parameters. - // - // TODO: We should fix this so that we skip any - // extra work for parameters that are laid out as - // expected by the default rules, but do *something* - // for parameters that need non-default layout. - // - // Using the `GL_ARB_enhanced_layouts` feature is one - // option, but we should also be able to do some - // things by introducing padding into the declaration - // (padding insertion would probably be best done at - // the IR level). - bool useExplicitOffsets = false; - if (useExplicitOffsets) - { - requireGLSLExtension("GL_ARB_enhanced_layouts"); - - m_stream->emit("layout(offset = "); - m_stream->emit(index); - m_stream->emit(")\n"); - } - } - break; - - case LayoutResourceKind::VertexInput: - case LayoutResourceKind::FragmentOutput: - m_stream->emit("layout(location = "); - m_stream->emit(index); - m_stream->emit(")\n"); - break; - - case LayoutResourceKind::SpecializationConstant: - m_stream->emit("layout(constant_id = "); - m_stream->emit(index); - m_stream->emit(")\n"); - break; - - case LayoutResourceKind::ConstantBuffer: - case LayoutResourceKind::ShaderResource: - case LayoutResourceKind::UnorderedAccess: - case LayoutResourceKind::SamplerState: - case LayoutResourceKind::DescriptorTableSlot: - m_stream->emit("layout(binding = "); - m_stream->emit(index); - if(space) - { - m_stream->emit(", set = "); - m_stream->emit(space); - } - m_stream->emit(")\n"); - break; - - case LayoutResourceKind::PushConstantBuffer: - m_stream->emit("layout(push_constant)\n"); - break; - case LayoutResourceKind::ShaderRecord: - m_stream->emit("layout(shaderRecordNV)\n"); - break; - - } - return true; -} - -void CLikeSourceEmitter::emitGLSLLayoutQualifiers(RefPtr layout, EmitVarChain* inChain, LayoutResourceKind filter) -{ - if(!layout) return; - - switch( getTarget()) - { - default: - return; - - case CodeGenTarget::GLSL: - break; - } - - EmitVarChain chain(layout, inChain); - - for( auto info : layout->resourceInfos ) - { - // Skip info that doesn't match our filter - if (filter != LayoutResourceKind::None - && filter != info.kind) - { - continue; - } - - emitGLSLLayoutQualifier(info.kind, &chain); - } -} - -void CLikeSourceEmitter::emitGLSLVersionDirective() -{ - auto effectiveProfile = m_context->effectiveProfile; - if(effectiveProfile.getFamily() == ProfileFamily::GLSL) - { - requireGLSLVersion(effectiveProfile.GetVersion()); - } - - // HACK: We aren't picking GLSL versions carefully right now, - // and so we might end up only requiring the initial 1.10 version, - // even though even basic functionality needs a higher version. - // - // For now, we'll work around this by just setting the minimum required - // version to a high one: - // - // TODO: Either correctly compute a minimum required version, or require - // the user to specify a version as part of the target. - m_context->extensionUsageTracker.requireGLSLVersion(ProfileVersion::GLSL_450); - - auto requiredProfileVersion = m_context->extensionUsageTracker.getRequiredGLSLProfileVersion(); - switch (requiredProfileVersion) - { -#define CASE(TAG, VALUE) \ - case ProfileVersion::TAG: m_stream->emit("#version " #VALUE "\n"); return - - CASE(GLSL_110, 110); - CASE(GLSL_120, 120); - CASE(GLSL_130, 130); - CASE(GLSL_140, 140); - CASE(GLSL_150, 150); - CASE(GLSL_330, 330); - CASE(GLSL_400, 400); - CASE(GLSL_410, 410); - CASE(GLSL_420, 420); - CASE(GLSL_430, 430); - CASE(GLSL_440, 440); - CASE(GLSL_450, 450); - CASE(GLSL_460, 460); -#undef CASE - - default: - break; - } - - // No information is available for us to guess a profile, - // so it seems like we need to pick one out of thin air. - // - // Ideally we should infer a minimum required version based - // on the constructs we have seen used in the user's code - // - // For now we just fall back to a reasonably recent version. - - m_stream->emit("#version 420\n"); -} - -void CLikeSourceEmitter::emitGLSLPreprocessorDirectives() -{ - switch(getTarget()) - { - // Don't emit this stuff unless we are targetting GLSL - default: - return; - - case CodeGenTarget::GLSL: - break; - } - - emitGLSLVersionDirective(); -} - -void CLikeSourceEmitter::emitLayoutDirectives(TargetRequest* targetReq) -{ - // We are going to emit the target-language-specific directives - // needed to get the default matrix layout to match what was requested - // for the given target. - // - // Note: we do not rely on the defaults for the target language, - // because a user could take the HLSL/GLSL generated by Slang and pass - // it to another compiler with non-default options specified on - // the command line, leading to all kinds of trouble. - // - // TODO: We need an approach to "global" layout directives that will work - // in the presence of multiple modules. If modules A and B were each - // compiled with different assumptions about how layout is performed, - // then types/variables defined in those modules should be emitted in - // a way that is consistent with that layout... - - auto matrixLayoutMode = targetReq->getDefaultMatrixLayoutMode(); - - switch(m_context->target) - { - default: - return; - - case CodeGenTarget::GLSL: - // Reminder: the meaning of row/column major layout - // in our semantics is the *opposite* of what GLSL - // calls them, because what they call "columns" - // are what we call "rows." - // - switch(matrixLayoutMode) - { - case kMatrixLayoutMode_RowMajor: - default: - m_stream->emit("layout(column_major) uniform;\n"); - m_stream->emit("layout(column_major) buffer;\n"); - break; - - case kMatrixLayoutMode_ColumnMajor: - m_stream->emit("layout(row_major) uniform;\n"); - m_stream->emit("layout(row_major) buffer;\n"); - break; - } - break; - - case CodeGenTarget::HLSL: - switch(matrixLayoutMode) - { - case kMatrixLayoutMode_RowMajor: - default: - m_stream->emit("#pragma pack_matrix(row_major)\n"); - break; - - case kMatrixLayoutMode_ColumnMajor: - m_stream->emit("#pragma pack_matrix(column_major)\n"); - break; - } - break; - } -} - -UInt CLikeSourceEmitter::allocateUniqueID() -{ - return m_context->uniqueIDCounter++; -} - -// IR-level emit logic - -UInt CLikeSourceEmitter::getID(IRInst* value) -{ - auto& mapIRValueToID = m_context->mapIRValueToID; - - UInt id = 0; - if (mapIRValueToID.TryGetValue(value, id)) - return id; - - id = allocateUniqueID(); - mapIRValueToID.Add(value, id); - return id; -} - -/// "Scrub" a name so that it complies with restrictions of the target language. -String CLikeSourceEmitter::scrubName(const String& name) -{ - // We will use a plain `U` as a dummy character to insert - // whenever we need to insert things to make a string into - // valid name. - // - char const* dummyChar = "U"; - - // Special case a name that is the empty string, just in case. - if(name.getLength() == 0) - return dummyChar; - - // Otherwise, we are going to walk over the name byte by byte - // and write some legal characters to the output as we go. - StringBuilder sb; - - if(getTarget() == CodeGenTarget::GLSL) - { - // GLSL reserverse all names that start with `gl_`, - // so if we are in danger of collision, then make - // our name start with a dummy character instead. - if(name.startsWith("gl_")) - { - sb.append(dummyChar); - } - } - - // We will also detect user-defined names that - // might overlap with our convention for mangled names, - // to avoid an possible collision. - if(name.startsWith("_S")) - { - sb.Append(dummyChar); - } - - // TODO: This is where we might want to consult - // a dictionary of reserved words for the chosen target - // - // if(isReservedWord(name)) { sb.Append(dummyChar); } - // - - // We need to track the previous byte in - // order to detect consecutive underscores for GLSL. - int prevChar = -1; - - for(auto c : name) - { - // We will treat a dot character just like an underscore - // for the purposes of producing a scrubbed name, so - // that we translate `SomeType.someMethod` into - // `SomeType_someMethod`. - // - // By handling this case at the top of this loop, we - // ensure that a `.`-turned-`_` is handled just like - // a `_` in the original name, and will be properly - // scrubbed for GLSL output. - // - if(c == '.') - { - c = '_'; - } - - if(((c >= 'a') && (c <= 'z')) - || ((c >= 'A') && (c <= 'Z'))) - { - // Ordinary ASCII alphabetic characters are assumed - // to always be okay. - } - else if((c >= '0') && (c <= '9')) - { - // We don't want to allow a digit as the first - // byte in a name, since the result wouldn't - // be a valid identifier in many target languages. - if(prevChar == -1) - { - sb.append(dummyChar); - } - } - else if(c == '_') - { - // We will collapse any consecutive sequence of `_` - // characters into a single one (this means that - // some names that were unique in the original - // code might not resolve to unique names after - // scrubbing, but that was true in general). - - if(prevChar == '_') - { - // Skip this underscore, so we don't output - // more than one in a row. - continue; - } - } - else - { - // If we run into a character that wouldn't normally - // be allowed in an identifier, we need to translate - // it into something that *is* valid. - // - // Our solution for now will be very clumsy: we will - // emit `x` and then the hexadecimal version of - // the byte we were given. - sb.append("x"); - sb.append(uint32_t((unsigned char) c), 16); - - // We don't want to apply the default handling below, - // so skip to the top of the loop now. - prevChar = c; - continue; - } - - sb.append(c); - prevChar = c; - } - - return sb.ProduceString(); -} - -String CLikeSourceEmitter::generateIRName(IRInst* inst) -{ - // If the instruction names something - // that should be emitted as a target intrinsic, - // then use that name instead. - if(auto intrinsicDecoration = findTargetIntrinsicDecoration(inst)) - { - return String(intrinsicDecoration->getDefinition()); - } - - // If we have a name hint on the instruction, then we will try to use that - // to provide the actual name in the output code. - // - // We need to be careful that the name follows the rules of the target language, - // so there is a "scrubbing" step that needs to be applied here. - // - // We also need to make sure that the name won't collide with other declarations - // that might have the same name hint applied, so we will still unique - // them by appending the numeric ID of the instruction. - // - // TODO: Find cases where we can drop the suffix safely. - // - // TODO: When we start having to handle symbols with external linkage for - // things like DXIL libraries, we will need to *not* use the friendly - // names for stuff that should be link-able. - // - if(auto nameHintDecoration = inst->findDecoration()) - { - // The name we output will basically be: - // - // _ - // - // Except that we will "scrub" the name hint first, - // and we will omit the underscore if the (scrubbed) - // name hint already ends with one. - // - - String nameHint = nameHintDecoration->getName(); - nameHint = scrubName(nameHint); - - StringBuilder sb; - sb.append(nameHint); - - // Avoid introducing a double underscore - if(!nameHint.endsWith("_")) - { - sb.append("_"); - } - - String key = sb.ProduceString(); - UInt count = 0; - m_context->uniqueNameCounters.TryGetValue(key, count); - - m_context->uniqueNameCounters[key] = count+1; - - sb.append(Int32(count)); - return sb.ProduceString(); - } - - // If the instruction has a mangled name, then emit using that. - if(auto linkageDecoration = inst->findDecoration()) - { - return linkageDecoration->getMangledName(); - } - - // Otherwise fall back to a construct temporary name - // for the instruction. - StringBuilder sb; - sb << "_S"; - sb << Int32(getID(inst)); - - return sb.ProduceString(); -} - -String CLikeSourceEmitter::getIRName(IRInst* inst) -{ - String name; - if(!m_context->mapInstToName.TryGetValue(inst, name)) - { - name = generateIRName(inst); - m_context->mapInstToName.Add(inst, name); - } - return name; -} -void CLikeSourceEmitter::emitDeclarator(IRDeclaratorInfo* declarator) -{ - if(!declarator) - return; - - switch( declarator->flavor ) - { - case IRDeclaratorInfo::Flavor::Simple: - m_stream->emit(" "); - m_stream->emit(*declarator->name); - break; - - case IRDeclaratorInfo::Flavor::Ptr: - m_stream->emit("*"); - emitDeclarator(declarator->next); - break; - - case IRDeclaratorInfo::Flavor::Array: - emitDeclarator(declarator->next); - m_stream->emit("["); - emitIROperand(declarator->elementCount, IREmitMode::Default, getInfo(EmitOp::General)); - m_stream->emit("]"); - break; - } -} - -void CLikeSourceEmitter::emitIRSimpleValue(IRInst* inst) -{ - switch(inst->op) - { - case kIROp_IntLit: - m_stream->emit(((IRConstant*) inst)->value.intVal); - break; - - case kIROp_FloatLit: - m_stream->emit(((IRConstant*) inst)->value.floatVal); - break; - - case kIROp_BoolLit: - { - bool val = ((IRConstant*)inst)->value.intVal != 0; - m_stream->emit(val ? "true" : "false"); - } - break; - - default: - SLANG_UNIMPLEMENTED_X("val case for emit"); - break; - } - -} - -CodeGenTarget CLikeSourceEmitter::getTarget() -{ - return m_context->target; -} - -bool CLikeSourceEmitter::shouldFoldIRInstIntoUseSites(IRInst* inst, IREmitMode mode) -{ - // Certain opcodes should never/always be folded in - switch( inst->op ) - { - default: - break; - - // Never fold these in, because they represent declarations - // - case kIROp_Var: - case kIROp_GlobalVar: - case kIROp_GlobalConstant: - case kIROp_GlobalParam: - case kIROp_Param: - case kIROp_Func: - return false; - - // Always fold these in, because they are trivial - // - case kIROp_IntLit: - case kIROp_FloatLit: - case kIROp_BoolLit: - return true; - - // Always fold these in, because their results - // cannot be represented in the type system of - // our current targets. - // - // TODO: when we add C/C++ as an optional target, - // we could consider lowering insts that result - // in pointers directly. - // - case kIROp_FieldAddress: - case kIROp_getElementPtr: - case kIROp_Specialize: - return true; - } - - // Always fold when we are inside a global constant initializer - if (mode == IREmitMode::GlobalConstant) - return true; - - switch( inst->op ) - { - default: - break; - - // HACK: don't fold these in because we currently lower - // them to initializer lists, which aren't allowed in - // general expression contexts. - // - // Note: we are doing this check *after* the check for `GlobalConstant` - // mode, because otherwise we'd fail to emit initializer lists in - // the main place where we want/need them. - // - case kIROp_makeStruct: - case kIROp_makeArray: - return false; - - } - - // Instructions with specific result *types* will usually - // want to be folded in, because they aren't allowed as types - // for temporary variables. - auto type = inst->getDataType(); - - // Unwrap any layers of array-ness from the type, so that - // we can look at the underlying data type, in case we - // should *never* expose a value of that type - while (auto arrayType = as(type)) - { - type = arrayType->getElementType(); - } - - // Don't allow temporaries of pointer types to be created. - if(as(type)) - { - return true; - } - - // First we check for uniform parameter groups, - // because a `cbuffer` or GLSL `uniform` block - // does not have a first-class type that we can - // pass around. - // - // TODO: We need to ensure that type legalization - // cleans up cases where we use a parameter group - // or parameter block type as a function parameter... - // - if(as(type)) - { - // TODO: we need to be careful here, because - // HLSL shader model 6 allows these as explicit - // types. - return true; - } - // - // The stream-output and patch types need to be handled - // too, because they are not really first class (especially - // not in GLSL, but they also seem to confuse the HLSL - // compiler when they get used as temporaries). - // - else if (as(type)) - { - return true; - } - else if (as(type)) - { - return true; - } - - - // GLSL doesn't allow texture/resource types to - // be used as first-class values, so we need - // to fold them into their use sites in all cases - if (getTarget() == CodeGenTarget::GLSL) - { - if(as(type)) - { - return true; - } - else if(as(type)) - { - return true; - } - else if(as(type)) - { - return true; - } - else if(as(type)) - { - return true; - } - } - - // If the instruction is at global scope, then it might represent - // a constant (e.g., the value of an enum case). - // - if(as(inst->getParent())) - { - if(!inst->mightHaveSideEffects()) - return true; - } - - // Having dealt with all of the cases where we *must* fold things - // above, we can now deal with the more general cases where we - // *should not* fold things. - - // Don't fold something with no users: - if(!inst->hasUses()) - return false; - - // Don't fold something that has multiple users: - if(inst->hasMoreThanOneUse()) - return false; - - // Don't fold something that might have side effects: - if(inst->mightHaveSideEffects()) - return false; - - // Don't fold instructions that are marked `[precise]`. - // This could in principle be extended to any other - // decorations that affect the semantics of an instruction - // in ways that require a temporary to be introduced. - // - if(inst->findDecoration()) - return false; - - // Okay, at this point we know our instruction must have a single use. - auto use = inst->firstUse; - SLANG_ASSERT(use); - SLANG_ASSERT(!use->nextUse); - - auto user = use->getUser(); - - // We'd like to figure out if it is safe to fold our instruction into `user` - - // First, let's make sure they are in the same block/parent: - if(inst->getParent() != user->getParent()) - return false; - - // Now let's look at all the instructions between this instruction - // and the user. If any of them might have side effects, then lets - // bail out now. - for(auto ii = inst->getNextInst(); ii != user; ii = ii->getNextInst()) - { - if(!ii) - { - // We somehow reached the end of the block without finding - // the user, which doesn't make sense if uses dominate - // defs. Let's just play it safe and bail out. - return false; - } - - if(ii->mightHaveSideEffects()) - return false; - } - - // Okay, if we reach this point then the user comes later in - // the same block, and there are no instructions with side - // effects in between, so it seems safe to fold things in. - return true; -} - -void CLikeSourceEmitter::emitIROperand(IRInst* inst, IREmitMode mode, EmitOpInfo const& outerPrec) -{ - if( shouldFoldIRInstIntoUseSites(inst, mode) ) - { - emitIRInstExpr(inst, mode, outerPrec); - return; - } - - switch(inst->op) - { - case 0: // nothing yet - default: - m_stream->emit(getIRName(inst)); - break; - } -} - -void CLikeSourceEmitter::emitIRArgs(IRInst* inst, IREmitMode mode) -{ - UInt argCount = inst->getOperandCount(); - IRUse* args = inst->getOperands(); - - m_stream->emit("("); - for(UInt aa = 0; aa < argCount; ++aa) - { - if(aa != 0) m_stream->emit(", "); - emitIROperand(args[aa].get(), mode, getInfo(EmitOp::General)); - } - m_stream->emit(")"); -} - -void CLikeSourceEmitter::emitIRType(IRType* type, String const& name) -{ - emitType(type, name); -} - -void CLikeSourceEmitter::emitIRType(IRType* type, Name* name) -{ - emitType(type, name); -} - -void CLikeSourceEmitter::emitIRType(IRType* type) -{ - emitType(type); -} - -void CLikeSourceEmitter::emitIRRateQualifiers(IRRate* rate) -{ - if(!rate) return; - - if(as(rate)) - { - switch( getTarget() ) - { - case CodeGenTarget::GLSL: - m_stream->emit("const "); - break; - - default: - break; - } - } - - if (as(rate)) - { - switch( getTarget() ) - { - case CodeGenTarget::HLSL: - m_stream->emit("groupshared "); - break; - - case CodeGenTarget::GLSL: - m_stream->emit("shared "); - break; - - default: - break; - } - } -} - -void CLikeSourceEmitter::emitIRRateQualifiers(IRInst* value) -{ - emitIRRateQualifiers(value->getRate()); -} - -void CLikeSourceEmitter::emitIRInstResultDecl(IRInst* inst) -{ - auto type = inst->getDataType(); - if(!type) - return; - - if (as(type)) - return; - - emitIRTempModifiers(inst); - - emitIRRateQualifiers(inst); - - emitIRType(type, getIRName(inst)); - m_stream->emit(" = "); -} - -IRTargetIntrinsicDecoration* CLikeSourceEmitter::findTargetIntrinsicDecoration(IRInst* inst) -{ - for(auto dd : inst->getDecorations()) - { - if (dd->op != kIROp_TargetIntrinsicDecoration) - continue; - - auto targetIntrinsic = (IRTargetIntrinsicDecoration*)dd; - if (isTargetIntrinsicModifierApplicable(targetIntrinsic)) - return targetIntrinsic; - } - - return nullptr; -} - -/* static */bool CLikeSourceEmitter::isOrdinaryName(String const& name) -{ - char const* cursor = name.begin(); - char const* end = name.end(); - - while(cursor != end) - { - int c = *cursor++; - if( (c >= 'a') && (c <= 'z') ) continue; - if( (c >= 'A') && (c <= 'Z') ) continue; - if( c == '_' ) continue; - - return false; - } - return true; -} - -void CLikeSourceEmitter::emitTargetIntrinsicCallExpr( - IRCall* inst, - IRFunc* /* func */, - IRTargetIntrinsicDecoration* targetIntrinsic, - IREmitMode mode, - EmitOpInfo const& inOuterPrec) -{ - auto outerPrec = inOuterPrec; - - IRUse* args = inst->getOperands(); - Index argCount = inst->getOperandCount(); - - // First operand was the function to be called - args++; - argCount--; - - auto name = String(targetIntrinsic->getDefinition()); - - if(isOrdinaryName(name)) - { - // Simple case: it is just an ordinary name, so we call it like a builtin. - auto prec = getInfo(EmitOp::Postfix); - bool needClose = maybeEmitParens(outerPrec, prec); - - m_stream->emit(name); - m_stream->emit("("); - for (Index aa = 0; aa < argCount; ++aa) - { - if (aa != 0) m_stream->emit(", "); - emitIROperand(args[aa].get(), mode, getInfo(EmitOp::General)); - } - m_stream->emit(")"); - - maybeCloseParens(needClose); - return; - } - else - { - int openParenCount = 0; - - const auto returnType = inst->getDataType(); - - // If it returns void -> then we don't need parenthesis - if (as(returnType) == nullptr) - { - m_stream->emit("("); - openParenCount++; - } - - // General case: we are going to emit some more complex text. - - char const* cursor = name.begin(); - char const* end = name.end(); - while(cursor != end) - { - char c = *cursor++; - if( c != '$' ) - { - // Not an escape sequence - m_stream->emitRawTextSpan(&c, &c+1); - continue; - } - - SLANG_RELEASE_ASSERT(cursor != end); - - char d = *cursor++; - - switch (d) - { - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - { - // Simple case: emit one of the direct arguments to the call - Index argIndex = d - '0'; - SLANG_RELEASE_ASSERT((0 <= argIndex) && (argIndex < argCount)); - m_stream->emit("("); - emitIROperand(args[argIndex].get(), mode, getInfo(EmitOp::General)); - m_stream->emit(")"); - } - break; - - case 'p': - { - // If we are calling a D3D texturing operation in the form t.Foo(s, ...), - // then this form will pair up the t and s arguments as needed for a GLSL - // texturing operation. - SLANG_RELEASE_ASSERT(argCount >= 2); - - auto textureArg = args[0].get(); - auto samplerArg = args[1].get(); - - if (auto baseTextureType = as(textureArg->getDataType())) - { - emitGLSLTextureOrTextureSamplerType(baseTextureType, "sampler"); - - if (auto samplerType = as(samplerArg->getDataType())) - { - if (as(samplerType)) - { - m_stream->emit("Shadow"); - } - } - - m_stream->emit("("); - emitIROperand(textureArg, mode, getInfo(EmitOp::General)); - m_stream->emit(","); - emitIROperand(samplerArg, mode, getInfo(EmitOp::General)); - m_stream->emit(")"); - } - else - { - SLANG_UNEXPECTED("bad format in intrinsic definition"); - } - } - break; - - case 'c': - { - // When doing texture access in glsl the result may need to be cast. - // In particular if the underlying texture is 'half' based, glsl only accesses (read/write) - // as float. So we need to cast to a half type on output. - // When storing into a texture it is still the case the value written must be half - but - // we don't need to do any casting there as half is coerced to float without a problem. - SLANG_RELEASE_ASSERT(argCount >= 1); - - auto textureArg = args[0].get(); - if (auto baseTextureType = as(textureArg->getDataType())) - { - auto elementType = baseTextureType->getElementType(); - IRBasicType* underlyingType = nullptr; - if (auto basicType = as(elementType)) - { - underlyingType = basicType; - } - else if (auto vectorType = as(elementType)) - { - underlyingType = as(vectorType->getElementType()); - } - - // We only need to output a cast if the underlying type is half. - if (underlyingType && underlyingType->op == kIROp_HalfType) - { - emitSimpleTypeImpl(elementType); - m_stream->emit("("); - openParenCount++; - } - } - } - break; - - case 'z': - { - // If we are calling a D3D texturing operation in the form t.Foo(s, ...), - // where `t` is a `Texture*`, then this is the step where we try to - // properly swizzle the output of the equivalent GLSL call into the right - // shape. - SLANG_RELEASE_ASSERT(argCount >= 1); - - auto textureArg = args[0].get(); - if (auto baseTextureType = as(textureArg->getDataType())) - { - auto elementType = baseTextureType->getElementType(); - if (auto basicType = as(elementType)) - { - // A scalar result is expected - m_stream->emit(".x"); - } - else if (auto vectorType = as(elementType)) - { - // A vector result is expected - auto elementCount = GetIntVal(vectorType->getElementCount()); - - if (elementCount < 4) - { - char const* swiz[] = { "", ".x", ".xy", ".xyz", "" }; - m_stream->emit(swiz[elementCount]); - } - } - else - { - // What other cases are possible? - } - } - else - { - SLANG_UNEXPECTED("bad format in intrinsic definition"); - } - } - break; - - case 'N': - { - // Extract the element count from a vector argument so that - // we can use it in the constructed expression. - - SLANG_RELEASE_ASSERT(*cursor >= '0' && *cursor <= '9'); - Index argIndex = (*cursor++) - '0'; - SLANG_RELEASE_ASSERT(argCount > argIndex); - - auto vectorArg = args[argIndex].get(); - if (auto vectorType = as(vectorArg->getDataType())) - { - auto elementCount = GetIntVal(vectorType->getElementCount()); - m_stream->emit(elementCount); - } - else - { - SLANG_UNEXPECTED("bad format in intrinsic definition"); - } - } - break; - - case 'V': - { - // Take an argument of some scalar/vector type and pad - // it out to a 4-vector with the same element type - // (this is the inverse of `$z`). - // - SLANG_RELEASE_ASSERT(*cursor >= '0' && *cursor <= '9'); - Index argIndex = (*cursor++) - '0'; - SLANG_RELEASE_ASSERT(argCount > argIndex); - - auto arg = args[argIndex].get(); - IRIntegerValue elementCount = 1; - IRType* elementType = arg->getDataType(); - if (auto vectorType = as(elementType)) - { - elementCount = GetIntVal(vectorType->getElementCount()); - elementType = vectorType->getElementType(); - } - - if(elementCount == 4) - { - // In the simple case, the operand is already a 4-vector, - // so we can just emit it as-is. - emitIROperand(arg, mode, getInfo(EmitOp::General)); - } - else - { - // Otherwise, we need to construct a 4-vector from the - // value we have, padding it out with zero elements as - // needed. - // - emitVectorTypeName(elementType, 4); - m_stream->emit("("); - emitIROperand(arg, mode, getInfo(EmitOp::General)); - for(IRIntegerValue ii = elementCount; ii < 4; ++ii) - { - m_stream->emit(", "); - if(getTarget() == CodeGenTarget::GLSL) - { - emitSimpleTypeImpl(elementType); - m_stream->emit("(0)"); - } - else - { - m_stream->emit("0"); - } - } - m_stream->emit(")"); - } - } - break; - - case 'a': - { - // We have an operation that needs to lower to either - // `atomic*` or `imageAtomic*` for GLSL, depending on - // whether its first operand is a subscript into an - // array. This `$a` is the first `a` in `atomic`, - // so we will replace it accordingly. - // - // TODO: This distinction should be made earlier, - // with the front-end picking the right overload - // based on the "address space" of the argument. - - Index argIndex = 0; - SLANG_RELEASE_ASSERT(argCount > argIndex); - - auto arg = args[argIndex].get(); - if(arg->op == kIROp_ImageSubscript) - { - m_stream->emit("imageA"); - } - else - { - m_stream->emit("a"); - } - } - break; - - case 'A': - { - // We have an operand that represents the destination - // of an atomic operation in GLSL, and it should - // be lowered based on whether it is an ordinary l-value, - // or an image subscript. In the image subscript case - // this operand will turn into multiple arguments - // to the `imageAtomic*` function. - // - - Index argIndex = 0; - SLANG_RELEASE_ASSERT(argCount > argIndex); - - auto arg = args[argIndex].get(); - if(arg->op == kIROp_ImageSubscript) - { - if(getTarget() == CodeGenTarget::GLSL) - { - // TODO: we don't handle the multisample - // case correctly here, where the last - // component of the image coordinate needs - // to be broken out into its own argument. - // - m_stream->emit("("); - emitIROperand(arg->getOperand(0), mode, getInfo(EmitOp::General)); - m_stream->emit("), "); - - // The coordinate argument will have been computed - // as a `vector` because that is how the - // HLSL image subscript operations are defined. - // In contrast, the GLSL `imageAtomic*` operations - // expect `vector` coordinates, so we - // hill hackily insert the conversion here as - // part of the intrinsic op. - // - auto coords = arg->getOperand(1); - auto coordsType = coords->getDataType(); - - auto coordsVecType = as(coordsType); - IRIntegerValue elementCount = 1; - if(coordsVecType) - { - coordsType = coordsVecType->getElementType(); - elementCount = GetIntVal(coordsVecType->getElementCount()); - } - - SLANG_ASSERT(coordsType->op == kIROp_UIntType); - - if (elementCount > 1) - { - m_stream->emit("ivec"); - m_stream->emit(elementCount); - } - else - { - m_stream->emit("int"); - } - - m_stream->emit("("); - emitIROperand(arg->getOperand(1), mode, getInfo(EmitOp::General)); - m_stream->emit(")"); - } - else - { - m_stream->emit("("); - emitIROperand(arg, mode, getInfo(EmitOp::General)); - m_stream->emit(")"); - } - } - else - { - m_stream->emit("("); - emitIROperand(arg, mode, getInfo(EmitOp::General)); - m_stream->emit(")"); - } - } - break; - - // We will use the `$X` case as a prefix for - // special logic needed when cross-compiling ray-tracing - // shaders. - case 'X': - { - SLANG_RELEASE_ASSERT(*cursor); - switch(*cursor++) - { - case 'P': - { - // The `$XP` case handles looking up - // the associated `location` for a variable - // used as the argument ray payload at a - // trace call site. - - Index argIndex = 0; - SLANG_RELEASE_ASSERT(argCount > argIndex); - auto arg = args[argIndex].get(); - auto argLoad = as(arg); - SLANG_RELEASE_ASSERT(argLoad); - auto argVar = argLoad->getOperand(0); - m_stream->emit(getRayPayloadLocation(argVar)); - } - break; - - case 'C': - { - // The `$XC` case handles looking up - // the associated `location` for a variable - // used as the argument callable payload at a - // call site. - - Index argIndex = 0; - SLANG_RELEASE_ASSERT(argCount > argIndex); - auto arg = args[argIndex].get(); - auto argLoad = as(arg); - SLANG_RELEASE_ASSERT(argLoad); - auto argVar = argLoad->getOperand(0); - m_stream->emit(getCallablePayloadLocation(argVar)); - } - break; - - case 'T': - { - // The `$XT` case handles selecting between - // the `gl_HitTNV` and `gl_RayTmaxNV` builtins, - // based on what stage we are using: - switch( m_context->entryPoint->getStage() ) - { - default: - m_stream->emit("gl_RayTmaxNV"); - break; - - case Stage::AnyHit: - case Stage::ClosestHit: - m_stream->emit("gl_HitTNV"); - break; - } - } - break; - - default: - SLANG_RELEASE_ASSERT(false); - break; - } - } - break; - - default: - SLANG_UNEXPECTED("bad format in intrinsic definition"); - break; - } - } - - // Close any remaining open parens - for (; openParenCount > 0; --openParenCount) - { - m_stream->emit(")"); - } - } -} - -void CLikeSourceEmitter::emitIntrinsicCallExpr( - IRCall* inst, - IRFunc* func, - IREmitMode mode, - EmitOpInfo const& inOuterPrec) -{ - auto outerPrec = inOuterPrec; - bool needClose = false; - - // For a call with N arguments, the instruction will - // have N+1 operands. We will start consuming operands - // starting at the index 1. - UInt operandCount = inst->getOperandCount(); - UInt argCount = operandCount - 1; - UInt operandIndex = 1; - - - // - if (auto targetIntrinsicDecoration = findTargetIntrinsicDecoration(func)) - { - emitTargetIntrinsicCallExpr( - inst, - func, - targetIntrinsicDecoration, - mode, - outerPrec); - return; - } - - // Our current strategy for dealing with intrinsic - // calls is to "un-mangle" the mangled name, in - // order to figure out what the user was originally - // calling. This is a bit messy, and there might - // be better strategies (including just stuffing - // a pointer to the original decl onto the callee). - - // If the intrinsic the user is calling is a generic, - // then the mangled name will have been set on the - // outer-most generic, and not on the leaf value - // (which is `func` above), so we need to walk - // upwards to find it. - // - IRInst* valueForName = func; - for(;;) - { - auto parentBlock = as(valueForName->parent); - if(!parentBlock) - break; - - auto parentGeneric = as(parentBlock->parent); - if(!parentGeneric) - break; - - valueForName = parentGeneric; - } - - // If we reach this point, we are assuming that the value - // has some kind of linkage, and thus a mangled name. - // - auto linkageDecoration = valueForName->findDecoration(); - SLANG_ASSERT(linkageDecoration); - auto mangledName = String(linkageDecoration->getMangledName()); - - - // We will use the `MangledLexer` to - // help us split the original name into its pieces. - MangledLexer lexer(mangledName); - - // We'll read through the qualified name of the - // symbol (e.g., `Texture2D.Sample`) and then - // only keep the last segment of the name (e.g., - // the `Sample` part). - auto name = lexer.readSimpleName(); - - // We will special-case some names here, that - // represent callable declarations that aren't - // ordinary functions, and thus may use different - // syntax. - if(name == "operator[]") - { - // The user is invoking a built-in subscript operator - - auto prec = getInfo(EmitOp::Postfix); - needClose = maybeEmitParens(outerPrec, prec); - - emitIROperand(inst->getOperand(operandIndex++), mode, leftSide(outerPrec, prec)); - m_stream->emit("["); - emitIROperand(inst->getOperand(operandIndex++), mode, getInfo(EmitOp::General)); - m_stream->emit("]"); - - if(operandIndex < operandCount) - { - m_stream->emit(" = "); - emitIROperand(inst->getOperand(operandIndex++), mode, getInfo(EmitOp::General)); - } - - maybeCloseParens(needClose); - return; - } - - auto prec = getInfo(EmitOp::Postfix); - needClose = maybeEmitParens(outerPrec, prec); - - // The mangled function name currently records - // the number of explicit parameters, and thus - // doesn't include the implicit `this` parameter. - // We can compare the argument and parameter counts - // to figure out whether we have a member function call. - UInt paramCount = lexer.readParamCount(); - - if(argCount != paramCount) - { - // Looks like a member function call - emitIROperand(inst->getOperand(operandIndex), mode, leftSide(outerPrec, prec)); - m_stream->emit("."); - operandIndex++; - } - // fixing issue #602 for GLSL sign function: https://github.com/shader-slang/slang/issues/602 - bool glslSignFix = getTarget() == CodeGenTarget::GLSL && name == "sign"; - if (glslSignFix) - { - if (auto vectorType = as(inst->getDataType())) - { - m_stream->emit("ivec"); - m_stream->emit(as(vectorType->getElementCount())->value.intVal); - m_stream->emit("("); - } - else if (auto scalarType = as(inst->getDataType())) - { - m_stream->emit("int("); - } - else - glslSignFix = false; - } - m_stream->emit(name); - m_stream->emit("("); - bool first = true; - for(; operandIndex < operandCount; ++operandIndex ) - { - if(!first) m_stream->emit(", "); - emitIROperand(inst->getOperand(operandIndex), mode, getInfo(EmitOp::General)); - first = false; - } - m_stream->emit(")"); - if (glslSignFix) - m_stream->emit(")"); - maybeCloseParens(needClose); -} - -void CLikeSourceEmitter::emitIRCallExpr(IRCall* inst, IREmitMode mode, EmitOpInfo outerPrec) -{ - auto funcValue = inst->getOperand(0); - - // Does this function declare any requirements on GLSL version or - // extensions, which should affect our output? - if(getTarget() == CodeGenTarget::GLSL) - { - auto decoratedValue = funcValue; - while (auto specInst = as(decoratedValue)) - { - decoratedValue = getSpecializedValue(specInst); - } - - for( auto decoration : decoratedValue->getDecorations() ) - { - switch(decoration->op) - { - default: - break; - - case kIROp_RequireGLSLExtensionDecoration: - requireGLSLExtension(String(((IRRequireGLSLExtensionDecoration*)decoration)->getExtensionName())); - break; - - case kIROp_RequireGLSLVersionDecoration: - requireGLSLVersion(int(((IRRequireGLSLVersionDecoration*)decoration)->getLanguageVersion())); - break; - } - } - } - - // We want to detect any call to an intrinsic operation, - // that we can emit it directly without mangling, etc. - if(auto irFunc = asTargetIntrinsic(funcValue)) - { - emitIntrinsicCallExpr(inst, irFunc, mode, outerPrec); - } - else - { - auto prec = getInfo(EmitOp::Postfix); - bool needClose = maybeEmitParens(outerPrec, prec); - - emitIROperand(funcValue, mode, leftSide(outerPrec, prec)); - m_stream->emit("("); - UInt argCount = inst->getOperandCount(); - for( UInt aa = 1; aa < argCount; ++aa ) - { - auto operand = inst->getOperand(aa); - if (as(operand->getDataType())) - continue; - if(aa != 1) m_stream->emit(", "); - emitIROperand(inst->getOperand(aa), mode, getInfo(EmitOp::General)); - } - m_stream->emit(")"); - - maybeCloseParens(needClose); - } -} - -static const char* _getGLSLVectorCompareFunctionName(IROp op) -{ - // Glsl vector comparisons use functions... - // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/equal.xhtml - - switch (op) - { - case kIROp_Eql: return "equal"; - case kIROp_Neq: return "notEqual"; - case kIROp_Greater: return "greaterThan"; - case kIROp_Less: return "lessThan"; - case kIROp_Geq: return "greaterThanEqual"; - case kIROp_Leq: return "lessThanEqual"; - default: return nullptr; - } -} - -void CLikeSourceEmitter::_maybeEmitGLSLCast(IRType* castType, IRInst* inst, IREmitMode mode) -{ - // Wrap in cast if a cast type is specified - if (castType) - { - emitIRType(castType); - m_stream->emit("("); - - // Emit the operand - emitIROperand(inst, mode, getInfo(EmitOp::General)); - - m_stream->emit(")"); - } - else - { - // Emit the operand - emitIROperand(inst, mode, getInfo(EmitOp::General)); - } -} - -void CLikeSourceEmitter::emitNot(IRInst* inst, IREmitMode mode, EmitOpInfo& ioOuterPrec, bool* outNeedClose) -{ - IRInst* operand = inst->getOperand(0); - - if (getTarget() == CodeGenTarget::GLSL) - { - if (auto vectorType = as(operand->getDataType())) - { - // Handle as a function call - auto prec = getInfo(EmitOp::Postfix); - *outNeedClose = maybeEmitParens(ioOuterPrec, prec); - - m_stream->emit("not("); - emitIROperand(operand, mode, getInfo(EmitOp::General)); - m_stream->emit(")"); - return; - } - } - - auto prec = getInfo(EmitOp::Prefix); - *outNeedClose = maybeEmitParens(ioOuterPrec, prec); - - m_stream->emit("!"); - emitIROperand(operand, mode, rightSide(prec, ioOuterPrec)); -} - - -void CLikeSourceEmitter::emitComparison(IRInst* inst, IREmitMode mode, EmitOpInfo& ioOuterPrec, const EmitOpInfo& opPrec, bool* needCloseOut) -{ - if (getTarget() == CodeGenTarget::GLSL) - { - IRInst* left = inst->getOperand(0); - IRInst* right = inst->getOperand(1); - - auto leftVectorType = as(left->getDataType()); - auto rightVectorType = as(right->getDataType()); - - // If either side is a vector handle as a vector - if (leftVectorType || rightVectorType) - { - const char* funcName = _getGLSLVectorCompareFunctionName(inst->op); - SLANG_ASSERT(funcName); - - // Determine the vector type - const auto vecType = leftVectorType ? leftVectorType : rightVectorType; - - // Handle as a function call - auto prec = getInfo(EmitOp::Postfix); - *needCloseOut = maybeEmitParens(ioOuterPrec, prec); - - m_stream->emit(funcName); - m_stream->emit("("); - _maybeEmitGLSLCast((leftVectorType ? nullptr : vecType), left, mode); - m_stream->emit(","); - _maybeEmitGLSLCast((rightVectorType ? nullptr : vecType), right, mode); - m_stream->emit(")"); - - return; - } - } - - *needCloseOut = maybeEmitParens(ioOuterPrec, opPrec); - - emitIROperand(inst->getOperand(0), mode, leftSide(ioOuterPrec, opPrec)); - m_stream->emit(" "); - m_stream->emit(opPrec.op); - m_stream->emit(" "); - emitIROperand(inst->getOperand(1), mode, rightSide(ioOuterPrec, opPrec)); -} - - -void CLikeSourceEmitter::emitIRInstExpr(IRInst* inst, IREmitMode mode, const EmitOpInfo& inOuterPrec) -{ - EmitOpInfo outerPrec = inOuterPrec; - bool needClose = false; - switch(inst->op) - { - case kIROp_IntLit: - case kIROp_FloatLit: - case kIROp_BoolLit: - emitIRSimpleValue(inst); - break; - - case kIROp_Construct: - case kIROp_makeVector: - case kIROp_MakeMatrix: - // Simple constructor call - - switch (getTarget()) - { - case CodeGenTarget::HLSL: - { - if (inst->getOperandCount() == 1) - { - auto prec = getInfo(EmitOp::Prefix); - needClose = maybeEmitParens(outerPrec, prec); - - // Need to emit as cast for HLSL - m_stream->emit("("); - emitIRType(inst->getDataType()); - m_stream->emit(") "); - emitIROperand(inst->getOperand(0), mode, rightSide(outerPrec, prec)); - break; - } - /* fallthru*/ - } - case CodeGenTarget::GLSL: - { - emitIRType(inst->getDataType()); - emitIRArgs(inst, mode); - break; - } - case CodeGenTarget::CPPSource: - case CodeGenTarget::CSource: - { - if (inst->getOperandCount() == 1) - { - _emitCFunc(BuiltInCOp::Splat, inst->getDataType()); - emitIRArgs(inst, mode); - } - else - { - _emitCFunc(BuiltInCOp::Init, inst->getDataType()); - emitIRArgs(inst, mode); - } - break; - } - } - break; - case kIROp_constructVectorFromScalar: - - // Simple constructor call - if( getTarget() == CodeGenTarget::HLSL ) - { - auto prec = getInfo(EmitOp::Prefix); - needClose = maybeEmitParens(outerPrec, prec); - - m_stream->emit("("); - emitIRType(inst->getDataType()); - m_stream->emit(")"); - - emitIROperand(inst->getOperand(0), mode, rightSide(outerPrec,prec)); - } - else - { - auto prec = getInfo(EmitOp::Postfix); - needClose = maybeEmitParens(outerPrec, prec); - - emitIRType(inst->getDataType()); - m_stream->emit("("); - emitIROperand(inst->getOperand(0), mode, getInfo(EmitOp::General)); - m_stream->emit(")"); - } - break; - - case kIROp_FieldExtract: - { - // Extract field from aggregate - - IRFieldExtract* fieldExtract = (IRFieldExtract*) inst; - - auto prec = getInfo(EmitOp::Postfix); - needClose = maybeEmitParens(outerPrec, prec); - - auto base = fieldExtract->getBase(); - emitIROperand(base, mode, leftSide(outerPrec, prec)); - m_stream->emit("."); - if(getTarget() == CodeGenTarget::GLSL - && as(base->getDataType())) - { - m_stream->emit("_data."); - } - m_stream->emit(getIRName(fieldExtract->getField())); - } - break; - - case kIROp_FieldAddress: - { - // Extract field "address" from aggregate - - IRFieldAddress* ii = (IRFieldAddress*) inst; - - auto prec = getInfo(EmitOp::Postfix); - needClose = maybeEmitParens(outerPrec, prec); - - auto base = ii->getBase(); - emitIROperand(base, mode, leftSide(outerPrec, prec)); - m_stream->emit("."); - if(getTarget() == CodeGenTarget::GLSL - && as(base->getDataType())) - { - m_stream->emit("_data."); - } - m_stream->emit(getIRName(ii->getField())); - } - break; - - -#define CASE_COMPARE(OPCODE, PREC, OP) \ - case OPCODE: \ - emitComparison(inst, mode, outerPrec, getInfo(EmitOp::PREC), &needClose); \ - break - -#define CASE(OPCODE, PREC, OP) \ - case OPCODE: \ - needClose = maybeEmitParens(outerPrec, getInfo(EmitOp::PREC)); \ - emitIROperand(inst->getOperand(0), mode, leftSide(outerPrec, getInfo(EmitOp::PREC))); \ - m_stream->emit(" " #OP " "); \ - emitIROperand(inst->getOperand(1), mode, rightSide(outerPrec, getInfo(EmitOp::PREC))); \ - break - - CASE(kIROp_Add, Add, +); - CASE(kIROp_Sub, Sub, -); - CASE(kIROp_Div, Div, /); - CASE(kIROp_Mod, Mod, %); - - CASE(kIROp_Lsh, Lsh, <<); - CASE(kIROp_Rsh, Rsh, >>); - - // TODO: Need to pull out component-wise - // comparison cases for matrices/vectors - CASE_COMPARE(kIROp_Eql, Eql, ==); - CASE_COMPARE(kIROp_Neq, Neq, !=); - CASE_COMPARE(kIROp_Greater, Greater, >); - CASE_COMPARE(kIROp_Less, Less, <); - CASE_COMPARE(kIROp_Geq, Geq, >=); - CASE_COMPARE(kIROp_Leq, Leq, <=); - - CASE(kIROp_BitXor, BitXor, ^); - - CASE(kIROp_And, And, &&); - CASE(kIROp_Or, Or, ||); - -#undef CASE - - // Component-wise multiplication needs to be special cased, - // because GLSL uses infix `*` to express inner product - // when working with matrices. - case kIROp_Mul: - // Are we targetting GLSL, and are both operands matrices? - if(getTarget() == CodeGenTarget::GLSL - && as(inst->getOperand(0)->getDataType()) - && as(inst->getOperand(1)->getDataType())) - { - m_stream->emit("matrixCompMult("); - emitIROperand(inst->getOperand(0), mode, getInfo(EmitOp::General)); - m_stream->emit(", "); - emitIROperand(inst->getOperand(1), mode, getInfo(EmitOp::General)); - m_stream->emit(")"); - } - else - { - // Default handling is to just rely on infix - // `operator*`. - auto prec = getInfo(EmitOp::Mul); - needClose = maybeEmitParens(outerPrec, prec); - emitIROperand(inst->getOperand(0), mode, leftSide(outerPrec, prec)); - m_stream->emit(" * "); - emitIROperand(inst->getOperand(1), mode, rightSide(prec, outerPrec)); - } - break; - - case kIROp_Not: - { - emitNot(inst, mode, outerPrec, &needClose); - } - break; - - case kIROp_Neg: - { - auto prec = getInfo(EmitOp::Prefix); - needClose = maybeEmitParens(outerPrec, prec); - - m_stream->emit("-"); - emitIROperand(inst->getOperand(0), mode, rightSide(prec, outerPrec)); - } - break; - - case kIROp_BitNot: - { - auto prec = getInfo(EmitOp::Prefix); - needClose = maybeEmitParens(outerPrec, prec); - - if (as(inst->getDataType())) - { - m_stream->emit("!"); - } - else - { - m_stream->emit("~"); - } - emitIROperand(inst->getOperand(0), mode, rightSide(prec, outerPrec)); - } - break; - - case kIROp_BitAnd: - { - auto prec = getInfo(EmitOp::BitAnd); - needClose = maybeEmitParens(outerPrec, prec); - - // TODO: handle a bitwise And of a vector of bools by casting to - // a uvec and performing the bitwise operation - - emitIROperand(inst->getOperand(0), mode, leftSide(outerPrec, prec)); - - // Are we targetting GLSL, and are both operands scalar bools? - // In that case convert the operation to a logical And - if (getTarget() == CodeGenTarget::GLSL - && as(inst->getOperand(0)->getDataType()) - && as(inst->getOperand(1)->getDataType())) - { - m_stream->emit("&&"); - } - else - { - m_stream->emit("&"); - } - - emitIROperand(inst->getOperand(1), mode, rightSide(outerPrec, prec)); - } - break; - - case kIROp_BitOr: - { - auto prec = getInfo(EmitOp::BitOr); - needClose = maybeEmitParens(outerPrec, prec); - - // TODO: handle a bitwise Or of a vector of bools by casting to - // a uvec and performing the bitwise operation - - emitIROperand(inst->getOperand(0), mode, leftSide(outerPrec, prec)); - - // Are we targetting GLSL, and are both operands scalar bools? - // In that case convert the operation to a logical Or - if (getTarget() == CodeGenTarget::GLSL - && as(inst->getOperand(0)->getDataType()) - && as(inst->getOperand(1)->getDataType())) - { - m_stream->emit("||"); - } - else - { - m_stream->emit("|"); - } - - emitIROperand(inst->getOperand(1), mode, rightSide(outerPrec, prec)); - } - break; - - case kIROp_Load: - { - auto base = inst->getOperand(0); - emitIROperand(base, mode, outerPrec); - if(getTarget() == CodeGenTarget::GLSL - && as(base->getDataType())) - { - m_stream->emit("._data"); - } - } - break; - - case kIROp_Store: - { - auto prec = getInfo(EmitOp::Assign); - needClose = maybeEmitParens(outerPrec, prec); - - emitIROperand(inst->getOperand(0), mode, leftSide(outerPrec, prec)); - m_stream->emit(" = "); - emitIROperand(inst->getOperand(1), mode, rightSide(prec, outerPrec)); - } - break; - - case kIROp_Call: - { - emitIRCallExpr((IRCall*)inst, mode, outerPrec); - } - break; - - case kIROp_GroupMemoryBarrierWithGroupSync: - m_stream->emit("GroupMemoryBarrierWithGroupSync()"); - break; - - case kIROp_getElement: - case kIROp_getElementPtr: - case kIROp_ImageSubscript: - // HACK: deal with translation of GLSL geometry shader input arrays. - if(auto decoration = inst->getOperand(0)->findDecoration()) - { - auto prec = getInfo(EmitOp::Postfix); - needClose = maybeEmitParens(outerPrec, prec); - - m_stream->emit(decoration->getOuterArrayName()); - m_stream->emit("["); - emitIROperand(inst->getOperand(1), mode, getInfo(EmitOp::General)); - m_stream->emit("]."); - emitIROperand(inst->getOperand(0), mode, rightSide(prec, outerPrec)); - break; - } - else - { - auto prec = getInfo(EmitOp::Postfix); - needClose = maybeEmitParens(outerPrec, prec); - - emitIROperand( inst->getOperand(0), mode, leftSide(outerPrec, prec)); - m_stream->emit("["); - emitIROperand(inst->getOperand(1), mode, getInfo(EmitOp::General)); - m_stream->emit("]"); - } - break; - - case kIROp_Mul_Vector_Matrix: - case kIROp_Mul_Matrix_Vector: - case kIROp_Mul_Matrix_Matrix: - if(getTarget() == CodeGenTarget::GLSL) - { - // GLSL expresses inner-product multiplications - // with the ordinary infix `*` operator. - // - // Note that the order of the operands is reversed - // compared to HLSL (and Slang's internal representation) - // because the notion of what is a "row" vs. a "column" - // is reversed between HLSL/Slang and GLSL. - // - auto prec = getInfo(EmitOp::Mul); - needClose = maybeEmitParens(outerPrec, prec); - - emitIROperand(inst->getOperand(1), mode, leftSide(outerPrec, prec)); - m_stream->emit(" * "); - emitIROperand(inst->getOperand(0), mode, rightSide(prec, outerPrec)); - } - else - { - m_stream->emit("mul("); - emitIROperand(inst->getOperand(0), mode, getInfo(EmitOp::General)); - m_stream->emit(", "); - emitIROperand(inst->getOperand(1), mode, getInfo(EmitOp::General)); - m_stream->emit(")"); - } - break; - - case kIROp_swizzle: - { - auto prec = getInfo(EmitOp::Postfix); - needClose = maybeEmitParens(outerPrec, prec); - - auto ii = (IRSwizzle*)inst; - emitIROperand(ii->getBase(), mode, leftSide(outerPrec, prec)); - m_stream->emit("."); - const Index elementCount = Index(ii->getElementCount()); - for (Index ee = 0; ee < elementCount; ++ee) - { - IRInst* irElementIndex = ii->getElementIndex(ee); - SLANG_RELEASE_ASSERT(irElementIndex->op == kIROp_IntLit); - IRConstant* irConst = (IRConstant*)irElementIndex; - - UInt elementIndex = (UInt)irConst->value.intVal; - SLANG_RELEASE_ASSERT(elementIndex < 4); - - char const* kComponents[] = { "x", "y", "z", "w" }; - m_stream->emit(kComponents[elementIndex]); - } - } - break; - - case kIROp_Specialize: - { - emitIROperand(inst->getOperand(0), mode, outerPrec); - } - break; - - case kIROp_Select: - { - if (getTarget() == CodeGenTarget::GLSL && - inst->getOperand(0)->getDataType()->op != kIROp_BoolType) - { - // For GLSL, emit a call to `mix` if condition is a vector - m_stream->emit("mix("); - emitIROperand(inst->getOperand(2), mode, leftSide(getInfo(EmitOp::General), getInfo(EmitOp::General))); - m_stream->emit(", "); - emitIROperand(inst->getOperand(1), mode, leftSide(getInfo(EmitOp::General), getInfo(EmitOp::General))); - m_stream->emit(", "); - emitIROperand(inst->getOperand(0), mode, leftSide(getInfo(EmitOp::General), getInfo(EmitOp::General))); - m_stream->emit(")"); - } - else - { - auto prec = getInfo(EmitOp::Conditional); - needClose = maybeEmitParens(outerPrec, prec); - - emitIROperand(inst->getOperand(0), mode, leftSide(outerPrec, prec)); - m_stream->emit(" ? "); - emitIROperand(inst->getOperand(1), mode, prec); - m_stream->emit(" : "); - emitIROperand(inst->getOperand(2), mode, rightSide(prec, outerPrec)); - } - } - break; - - case kIROp_Param: - m_stream->emit(getIRName(inst)); - break; - - case kIROp_makeArray: - case kIROp_makeStruct: - { - // TODO: initializer-list syntax may not always - // be appropriate, depending on the context - // of the expression. - - m_stream->emit("{ "); - UInt argCount = inst->getOperandCount(); - for (UInt aa = 0; aa < argCount; ++aa) - { - if (aa != 0) m_stream->emit(", "); - emitIROperand(inst->getOperand(aa), mode, getInfo(EmitOp::General)); - } - m_stream->emit(" }"); - } - break; - - case kIROp_BitCast: - { - // TODO: we can simplify the logic for arbitrary bitcasts - // by always bitcasting the source to a `uint*` type (if it - // isn't already) and then bitcasting that to the destination - // type (if it isn't already `uint*`. - // - // For now we are assuming the source type is *already* - // a `uint*` type of the appropriate size. - // -// auto fromType = extractBaseType(inst->getOperand(0)->getDataType()); - auto toType = extractBaseType(inst->getDataType()); - switch(getTarget()) - { - case CodeGenTarget::GLSL: - switch(toType) - { - default: - m_stream->emit("/* unhandled */"); - break; - - case BaseType::UInt: - break; - - case BaseType::Int: - emitIRType(inst->getDataType()); - break; - - case BaseType::Float: - m_stream->emit("uintBitsToFloat("); - break; - } - break; - - case CodeGenTarget::HLSL: - switch(toType) - { - default: - m_stream->emit("/* unhandled */"); - break; - - case BaseType::UInt: - break; - case BaseType::Int: - m_stream->emit("("); - emitIRType(inst->getDataType()); - m_stream->emit(")"); - break; - case BaseType::Float: - m_stream->emit("asfloat"); - break; - } - break; - - - default: - SLANG_UNEXPECTED("unhandled codegen target"); - break; - } - - m_stream->emit("("); - emitIROperand(inst->getOperand(0), mode, getInfo(EmitOp::General)); - m_stream->emit(")"); - } - break; - - default: - m_stream->emit("/* unhandled */"); - break; - } - maybeCloseParens(needClose); -} - -BaseType CLikeSourceEmitter::extractBaseType(IRType* inType) -{ - auto type = inType; - for(;;) - { - if(auto irBaseType = as(type)) - { - return irBaseType->getBaseType(); - } - else if(auto vecType = as(type)) - { - type = vecType->getElementType(); - continue; - } - else - { - return BaseType::Void; - } - } -} - -void CLikeSourceEmitter::emitIRInst(IRInst* inst, IREmitMode mode) -{ - try - { - emitIRInstImpl(inst, mode); - } - // Don't emit any context message for an explicit `AbortCompilationException` - // because it should only happen when an error is already emitted. - catch(AbortCompilationException&) { throw; } - catch(...) - { - m_context->noteInternalErrorLoc(inst->sourceLoc); - throw; - } -} - -void CLikeSourceEmitter::emitIRInstImpl(IRInst* inst, IREmitMode mode) -{ - if (shouldFoldIRInstIntoUseSites(inst, mode)) - { - return; - } - - m_stream->advanceToSourceLocation(inst->sourceLoc); - - switch(inst->op) - { - default: - emitIRInstResultDecl(inst); - emitIRInstExpr(inst, mode, getInfo(EmitOp::General)); - m_stream->emit(";\n"); - break; - - case kIROp_undefined: - { - auto type = inst->getDataType(); - emitIRType(type, getIRName(inst)); - m_stream->emit(";\n"); - } - break; - - case kIROp_Var: - { - auto ptrType = cast(inst->getDataType()); - auto valType = ptrType->getValueType(); - - auto name = getIRName(inst); - emitIRRateQualifiers(inst); - emitIRType(valType, name); - m_stream->emit(";\n"); - } - break; - - case kIROp_Param: - // Don't emit parameters, since they are declared as part of the function. - break; - - case kIROp_FieldAddress: - // skip during code emit, since it should be - // folded into use site(s) - break; - - case kIROp_ReturnVoid: - m_stream->emit("return;\n"); - break; - - case kIROp_ReturnVal: - m_stream->emit("return "); - emitIROperand(((IRReturnVal*) inst)->getVal(), mode, getInfo(EmitOp::General)); - m_stream->emit(";\n"); - break; - - case kIROp_discard: - m_stream->emit("discard;\n"); - break; - - case kIROp_swizzleSet: - { - auto ii = (IRSwizzleSet*)inst; - emitIRInstResultDecl(inst); - emitIROperand(inst->getOperand(0), mode, getInfo(EmitOp::General)); - m_stream->emit(";\n"); - - auto subscriptOuter = getInfo(EmitOp::General); - auto subscriptPrec = getInfo(EmitOp::Postfix); - bool needCloseSubscript = maybeEmitParens(subscriptOuter, subscriptPrec); - - emitIROperand(inst, mode, leftSide(subscriptOuter, subscriptPrec)); - m_stream->emit("."); - UInt elementCount = ii->getElementCount(); - for (UInt ee = 0; ee < elementCount; ++ee) - { - IRInst* irElementIndex = ii->getElementIndex(ee); - SLANG_RELEASE_ASSERT(irElementIndex->op == kIROp_IntLit); - IRConstant* irConst = (IRConstant*)irElementIndex; - - UInt elementIndex = (UInt)irConst->value.intVal; - SLANG_RELEASE_ASSERT(elementIndex < 4); - - char const* kComponents[] = { "x", "y", "z", "w" }; - m_stream->emit(kComponents[elementIndex]); - } - maybeCloseParens(needCloseSubscript); - - m_stream->emit(" = "); - emitIROperand(inst->getOperand(1), mode, getInfo(EmitOp::General)); - m_stream->emit(";\n"); - } - break; - - case kIROp_SwizzledStore: - { - auto subscriptOuter = getInfo(EmitOp::General); - auto subscriptPrec = getInfo(EmitOp::Postfix); - bool needCloseSubscript = maybeEmitParens(subscriptOuter, subscriptPrec); - - - auto ii = cast(inst); - emitIROperand(ii->getDest(), mode, leftSide(subscriptOuter, subscriptPrec)); - m_stream->emit("."); - UInt elementCount = ii->getElementCount(); - for (UInt ee = 0; ee < elementCount; ++ee) - { - IRInst* irElementIndex = ii->getElementIndex(ee); - SLANG_RELEASE_ASSERT(irElementIndex->op == kIROp_IntLit); - IRConstant* irConst = (IRConstant*)irElementIndex; - - UInt elementIndex = (UInt)irConst->value.intVal; - SLANG_RELEASE_ASSERT(elementIndex < 4); - - char const* kComponents[] = { "x", "y", "z", "w" }; - m_stream->emit(kComponents[elementIndex]); - } - maybeCloseParens(needCloseSubscript); - - m_stream->emit(" = "); - emitIROperand(ii->getSource(), mode, getInfo(EmitOp::General)); - m_stream->emit(";\n"); - } - break; - } -} - -void CLikeSourceEmitter::emitIRSemantics(VarLayout* varLayout) -{ - if(varLayout->flags & VarLayoutFlag::HasSemantic) - { - m_stream->emit(" : "); - m_stream->emit(varLayout->semanticName); - if(varLayout->semanticIndex) - { - m_stream->emit(varLayout->semanticIndex); - } - } -} - -void CLikeSourceEmitter::emitIRSemantics(IRInst* inst) -{ - // Don't emit semantics if we aren't translating down to HLSL - switch (getTarget()) - { - case CodeGenTarget::HLSL: - break; - - default: - return; - } - - if (auto semanticDecoration = inst->findDecoration()) - { - m_stream->emit(" : "); - m_stream->emit(semanticDecoration->getSemanticName()); - return; - } - - if(auto layoutDecoration = inst->findDecoration()) - { - auto layout = layoutDecoration->getLayout(); - if(auto varLayout = as(layout)) - { - emitIRSemantics(varLayout); - } - else if (auto entryPointLayout = as(layout)) - { - if(auto resultLayout = entryPointLayout->resultLayout) - { - emitIRSemantics(resultLayout); - } - } - } -} - -VarLayout* CLikeSourceEmitter::getVarLayout(IRInst* var) -{ - auto decoration = var->findDecoration(); - if (!decoration) - return nullptr; - - return (VarLayout*) decoration->getLayout(); -} - -void CLikeSourceEmitter::emitIRLayoutSemantics(IRInst* inst, char const* uniformSemanticSpelling) -{ - auto layout = getVarLayout(inst); - if (layout) - { - emitHLSLRegisterSemantics(layout, uniformSemanticSpelling); - } -} - -void CLikeSourceEmitter::emitPhiVarAssignments(UInt argCount, IRUse* args, IRBlock* targetBlock) -{ - UInt argCounter = 0; - for (auto pp = targetBlock->getFirstParam(); pp; pp = pp->getNextParam()) - { - UInt argIndex = argCounter++; - - if (argIndex >= argCount) - { - SLANG_UNEXPECTED("not enough arguments for branch"); - break; - } - - IRInst* arg = args[argIndex].get(); - - auto outerPrec = getInfo(EmitOp::General); - auto prec = getInfo(EmitOp::Assign); - - emitIROperand(pp, IREmitMode::Default, leftSide(outerPrec, prec)); - m_stream->emit(" = "); - emitIROperand(arg, IREmitMode::Default, rightSide(prec, outerPrec)); - m_stream->emit(";\n"); - } -} - -void CLikeSourceEmitter::emitRegion(Region* inRegion) -{ - // We will use a loop so that we can process sequential (simple) - // regions iteratively rather than recursively. - // This is effectively an emulation of tail recursion. - Region* region = inRegion; - while(region) - { - // What flavor of region are we trying to emit? - switch(region->getFlavor()) - { - case Region::Flavor::Simple: - { - // A simple region consists of a basic block followed - // by another region. - // - auto simpleRegion = (SimpleRegion*) region; - - // We start by outputting all of the non-terminator - // instructions in the block. - // - auto block = simpleRegion->block; - auto terminator = block->getTerminator(); - for (auto inst = block->getFirstInst(); inst != terminator; inst = inst->getNextInst()) - { - emitIRInst(inst, IREmitMode::Default); - } - - // Next we have to deal with the terminator instruction - // itself. In many cases, the terminator will have been - // turned into a block of its own, but certain cases - // of terminators are simple enough that we just fold - // them into the current block. - // - m_stream->advanceToSourceLocation(terminator->sourceLoc); - switch(terminator->op) - { - default: - // Don't do anything with the terminator, and assume - // its behavior has been folded into the next region. - break; - - case kIROp_ReturnVal: - case kIROp_ReturnVoid: - case kIROp_discard: - // For extremely simple terminators, we just handle - // them here, so that we don't have to allocate - // separate `Region`s for them. - emitIRInst(terminator, IREmitMode::Default); - break; - - // We will also handle any unconditional branches - // here, since they may have arguments to pass - // to the target block (our encoding of SSA - // "phi" operations). - // - // TODO: A better approach would be to move out of SSA - // as an IR pass, and introduce explicit variables to - // replace any "phi nodes." This would avoid possible - // complications if we ever end up in the bad case where - // one of the block arguments on a branch is also - // a parameter of the target block, so that the order - // of operations is important. - // - case kIROp_unconditionalBranch: - { - auto t = (IRUnconditionalBranch*)terminator; - UInt argCount = t->getOperandCount(); - static const UInt kFixedArgCount = 1; - emitPhiVarAssignments( - argCount - kFixedArgCount, - t->getOperands() + kFixedArgCount, - t->getTargetBlock()); - } - break; - case kIROp_loop: - { - auto t = (IRLoop*) terminator; - UInt argCount = t->getOperandCount(); - static const UInt kFixedArgCount = 3; - emitPhiVarAssignments( - argCount - kFixedArgCount, - t->getOperands() + kFixedArgCount, - t->getTargetBlock()); - - } - break; - } - - // If the terminator required a full region to represent - // its behavior in a structured form, then we will move - // along to that region now. - // - // We do this iteratively rather than recursively, by - // jumping back to the top of our loop with a new - // value for `region`. - // - region = simpleRegion->nextRegion; - continue; - } - - // Break and continue regions are trivial to handle, as long as we - // don't need to consider multi-level break/continue (which we - // don't for now). - case Region::Flavor::Break: - m_stream->emit("break;\n"); - break; - case Region::Flavor::Continue: - m_stream->emit("continue;\n"); - break; - - case Region::Flavor::If: - { - auto ifRegion = (IfRegion*) region; - - // TODO: consider simplifying the code in - // the case where `ifRegion == null` - // so that we output `if(!condition) { elseRegion }` - // instead of the current `if(condition) {} else { elseRegion }` - - m_stream->emit("if("); - emitIROperand(ifRegion->condition, IREmitMode::Default, getInfo(EmitOp::General)); - m_stream->emit(")\n{\n"); - m_stream->indent(); - emitRegion(ifRegion->thenRegion); - m_stream->dedent(); - m_stream->emit("}\n"); - - // Don't emit the `else` region if it would be empty - // - if(auto elseRegion = ifRegion->elseRegion) - { - m_stream->emit("else\n{\n"); - m_stream->indent(); - emitRegion(elseRegion); - m_stream->dedent(); - m_stream->emit("}\n"); - } - - // Continue with the region after the `if`. - // - // TODO: consider just constructing a `SimpleRegion` - // around an `IfRegion` to handle this sequencing, - // rather than making `IfRegion` serve as both a - // conditional and a sequence. - // - region = ifRegion->nextRegion; - continue; - } - break; - - case Region::Flavor::Loop: - { - auto loopRegion = (LoopRegion*) region; - auto loopInst = loopRegion->loopInst; - - // If the user applied an explicit decoration to the loop, - // to control its unrolling behavior, then pass that - // along in the output code (if the target language - // supports the semantics of the decoration). - // - if (auto loopControlDecoration = loopInst->findDecoration()) - { - switch (loopControlDecoration->getMode()) - { - case kIRLoopControl_Unroll: - // Note: loop unrolling control is only available in HLSL, not GLSL - if(getTarget() == CodeGenTarget::HLSL) - { - m_stream->emit("[unroll]\n"); - } - break; - - default: - break; - } - } - - m_stream->emit("for(;;)\n{\n"); - m_stream->indent(); - emitRegion(loopRegion->body); - m_stream->dedent(); - m_stream->emit("}\n"); - - // Continue with the region after the loop - region = loopRegion->nextRegion; - continue; - } - - case Region::Flavor::Switch: - { - auto switchRegion = (SwitchRegion*) region; - - // Emit the start of our statement. - m_stream->emit("switch("); - emitIROperand(switchRegion->condition, IREmitMode::Default, getInfo(EmitOp::General)); - m_stream->emit(")\n{\n"); - - auto defaultCase = switchRegion->defaultCase; - for(auto currentCase : switchRegion->cases) - { - for(auto caseVal : currentCase->values) - { - m_stream->emit("case "); - emitIROperand(caseVal, IREmitMode::Default, getInfo(EmitOp::General)); - m_stream->emit(":\n"); - } - if(currentCase.Ptr() == defaultCase) - { - m_stream->emit("default:\n"); - } - - m_stream->indent(); - m_stream->emit("{\n"); - m_stream->indent(); - emitRegion(currentCase->body); - m_stream->dedent(); - m_stream->emit("}\n"); - m_stream->dedent(); - } - - m_stream->emit("}\n"); - - // Continue with the region after the `switch` - region = switchRegion->nextRegion; - continue; - } - break; - } - break; - } -} - -/// Emit high-level language statements from a structured region tree. -void CLikeSourceEmitter::emitRegionTree(RegionTree* regionTree) -{ - emitRegion(regionTree->rootRegion); -} - -bool CLikeSourceEmitter::isDefinition(IRFunc* func) -{ - // For now, we use a simple approach: a function is - // a definition if it has any blocks, and a declaration otherwise. - return func->getFirstBlock() != nullptr; -} - -String CLikeSourceEmitter::getIRFuncName(IRFunc* func) -{ - if (auto entryPointLayout = asEntryPoint(func)) - { - // GLSL will always need to use `main` as the - // name for an entry-point function, but other - // targets should try to use the original name. - // - // TODO: always use `main`, and have any code - // that wraps this know to use `main` instead - // of the original entry-point name... - // - if (getTarget() != CodeGenTarget::GLSL) - { - return getText(entryPointLayout->entryPoint->getName()); - } - - // - - return "main"; - } - else - { - return getIRName(func); - } -} - -void CLikeSourceEmitter::emitAttributeSingleString(const char* name, FuncDecl* entryPoint, Attribute* attrib) -{ - assert(attrib); - - attrib->args.getCount(); - if (attrib->args.getCount() != 1) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute expects single parameter"); - return; - } - - Expr* expr = attrib->args[0]; - - auto stringLitExpr = as(expr); - if (!stringLitExpr) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute parameter expecting to be a string "); - return; - } - - m_stream->emit("["); - m_stream->emit(name); - m_stream->emit("(\""); - m_stream->emit(stringLitExpr->value); - m_stream->emit("\")]\n"); -} - -void CLikeSourceEmitter::emitAttributeSingleInt(const char* name, FuncDecl* entryPoint, Attribute* attrib) -{ - assert(attrib); - - attrib->args.getCount(); - if (attrib->args.getCount() != 1) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute expects single parameter"); - return; - } - - Expr* expr = attrib->args[0]; - - auto intLitExpr = as(expr); - if (!intLitExpr) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute expects an int"); - return; - } - - m_stream->emit("["); - m_stream->emit(name); - m_stream->emit("("); - m_stream->emit(intLitExpr->value); - m_stream->emit(")]\n"); -} - -void CLikeSourceEmitter::emitFuncDeclPatchConstantFuncAttribute(IRFunc* irFunc, FuncDecl* entryPoint, PatchConstantFuncAttribute* attrib) -{ - SLANG_UNUSED(attrib); - - auto irPatchFunc = irFunc->findDecoration(); - assert(irPatchFunc); - if (!irPatchFunc) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Unable to find [patchConstantFunc(...)] decoration"); - return; - } - - const String irName = getIRName(irPatchFunc->getFunc()); - - m_stream->emit("[patchconstantfunc(\""); - m_stream->emit(irName); - m_stream->emit("\")]\n"); -} - -void CLikeSourceEmitter::emitIREntryPointAttributes_HLSL(IRFunc* irFunc, EntryPointLayout* entryPointLayout) -{ - auto profile = m_context->effectiveProfile; - auto stage = entryPointLayout->profile.GetStage(); - - if(profile.getFamily() == ProfileFamily::DX) - { - if(profile.GetVersion() >= ProfileVersion::DX_6_1 ) - { - char const* stageName = getStageName(stage); - if(stageName) - { - m_stream->emit("[shader(\""); - m_stream->emit(stageName); - m_stream->emit("\")]"); - } - } - } - - switch (stage) - { - case Stage::Compute: - { - static const UInt kAxisCount = 3; - UInt sizeAlongAxis[kAxisCount]; - - // TODO: this is kind of gross because we are using a public - // reflection API function, rather than some kind of internal - // utility it forwards to... - spReflectionEntryPoint_getComputeThreadGroupSize( - (SlangReflectionEntryPoint*)entryPointLayout, - kAxisCount, - &sizeAlongAxis[0]); - - m_stream->emit("[numthreads("); - for (int ii = 0; ii < 3; ++ii) - { - if (ii != 0) m_stream->emit(", "); - m_stream->emit(sizeAlongAxis[ii]); - } - m_stream->emit(")]\n"); - } - break; - case Stage::Geometry: - { - if (auto attrib = entryPointLayout->entryPoint->FindModifier()) - { - m_stream->emit("[maxvertexcount("); - m_stream->emit(attrib->value); - m_stream->emit(")]\n"); - } - if (auto attrib = entryPointLayout->entryPoint->FindModifier()) - { - m_stream->emit("[instance("); - m_stream->emit(attrib->value); - m_stream->emit(")]\n"); - } - break; - } - case Stage::Domain: - { - FuncDecl* entryPoint = entryPointLayout->entryPoint; - /* [domain("isoline")] */ - if (auto attrib = entryPoint->FindModifier()) - { - emitAttributeSingleString("domain", entryPoint, attrib); - } - - break; - } - case Stage::Hull: - { - // Lists these are only attributes for hull shader - // https://docs.microsoft.com/en-us/windows/desktop/direct3d11/direct3d-11-advanced-stages-hull-shader-design - - FuncDecl* entryPoint = entryPointLayout->entryPoint; - - /* [domain("isoline")] */ - if (auto attrib = entryPoint->FindModifier()) - { - emitAttributeSingleString("domain", entryPoint, attrib); - } - /* [domain("partitioning")] */ - if (auto attrib = entryPoint->FindModifier()) - { - emitAttributeSingleString("partitioning", entryPoint, attrib); - } - /* [outputtopology("line")] */ - if (auto attrib = entryPoint->FindModifier()) - { - emitAttributeSingleString("outputtopology", entryPoint, attrib); - } - /* [outputcontrolpoints(4)] */ - if (auto attrib = entryPoint->FindModifier()) - { - emitAttributeSingleInt("outputcontrolpoints", entryPoint, attrib); - } - /* [patchconstantfunc("HSConst")] */ - if (auto attrib = entryPoint->FindModifier()) - { - emitFuncDeclPatchConstantFuncAttribute(irFunc, entryPoint, attrib); - } - - break; - } - case Stage::Pixel: - { - if (irFunc->findDecoration()) - { - m_stream->emit("[earlydepthstencil]\n"); - } - break; - } - // TODO: There are other stages that will need this kind of handling. - default: - break; - } -} - -void CLikeSourceEmitter::emitIREntryPointAttributes_GLSL(IRFunc* irFunc, EntryPointLayout* entryPointLayout) -{ - auto profile = entryPointLayout->profile; - auto stage = profile.GetStage(); - - switch (stage) - { - case Stage::Compute: - { - static const UInt kAxisCount = 3; - UInt sizeAlongAxis[kAxisCount]; - - // TODO: this is kind of gross because we are using a public - // reflection API function, rather than some kind of internal - // utility it forwards to... - spReflectionEntryPoint_getComputeThreadGroupSize( - (SlangReflectionEntryPoint*)entryPointLayout, - kAxisCount, - &sizeAlongAxis[0]); - - m_stream->emit("layout("); - char const* axes[] = { "x", "y", "z" }; - for (int ii = 0; ii < 3; ++ii) - { - if (ii != 0) m_stream->emit(", "); - m_stream->emit("local_size_"); - m_stream->emit(axes[ii]); - m_stream->emit(" = "); - m_stream->emit(sizeAlongAxis[ii]); - } - m_stream->emit(") in;"); - } - break; - case Stage::Geometry: - { - if (auto attrib = entryPointLayout->entryPoint->FindModifier()) - { - m_stream->emit("layout(max_vertices = "); - m_stream->emit(attrib->value); - m_stream->emit(") out;\n"); - } - if (auto attrib = entryPointLayout->entryPoint->FindModifier()) - { - m_stream->emit("layout(invocations = "); - m_stream->emit(attrib->value); - m_stream->emit(") in;\n"); - } - - for(auto pp : entryPointLayout->entryPoint->GetParameters()) - { - if(auto inputPrimitiveTypeModifier = pp->FindModifier()) - { - if(as(inputPrimitiveTypeModifier)) - { - m_stream->emit("layout(triangles) in;\n"); - } - else if(as(inputPrimitiveTypeModifier)) - { - m_stream->emit("layout(lines) in;\n"); - } - else if(as(inputPrimitiveTypeModifier)) - { - m_stream->emit("layout(lines_adjacency) in;\n"); - } - else if(as(inputPrimitiveTypeModifier)) - { - m_stream->emit("layout(points) in;\n"); - } - else if(as(inputPrimitiveTypeModifier)) - { - m_stream->emit("layout(triangles_adjacency) in;\n"); - } - } - - if(auto outputStreamType = as(pp->type)) - { - if(as(outputStreamType)) - { - m_stream->emit("layout(triangle_strip) out;\n"); - } - else if(as(outputStreamType)) - { - m_stream->emit("layout(line_strip) out;\n"); - } - else if(as(outputStreamType)) - { - m_stream->emit("layout(points) out;\n"); - } - } - } - - - } - break; - case Stage::Pixel: - { - if (irFunc->findDecoration()) - { - // https://www.khronos.org/opengl/wiki/Early_Fragment_Test - m_stream->emit("layout(early_fragment_tests) in;\n"); - } - break; - } - // TODO: There are other stages that will need this kind of handling. - default: - break; - } -} - -void CLikeSourceEmitter::emitIREntryPointAttributes(IRFunc* irFunc, EntryPointLayout* entryPointLayout) -{ - switch(getTarget()) - { - case CodeGenTarget::HLSL: - emitIREntryPointAttributes_HLSL(irFunc, entryPointLayout); - break; - - case CodeGenTarget::GLSL: - emitIREntryPointAttributes_GLSL(irFunc, entryPointLayout); - break; - } -} - -void CLikeSourceEmitter::emitPhiVarDecls(IRFunc* func) -{ - // We will skip the first block, since its parameters are - // the parameters of the whole function. - auto bb = func->getFirstBlock(); - if (!bb) - return; - bb = bb->getNextBlock(); - - for (; bb; bb = bb->getNextBlock()) - { - for (auto pp = bb->getFirstParam(); pp; pp = pp->getNextParam()) - { - emitIRTempModifiers(pp); - emitIRType(pp->getFullType(), getIRName(pp)); - m_stream->emit(";\n"); - } - } -} - -/// Emit high-level statements for the body of a function. -void CLikeSourceEmitter::emitIRFunctionBody(IRGlobalValueWithCode* code) -{ - // Compute a structured region tree that can represent - // the control flow of our function. - // - RefPtr regionTree = generateRegionTreeForFunc( - code, - m_context->getSink()); - - // Now that we've computed the region tree, we have - // an opportunity to perform some last-minute transformations - // on the code to make sure it follows our rules. - // - // TODO: it would be better to do these transformations earlier, - // so that we can, e.g., dump the final IR code *before* emission - // starts, but that gets a bit complicated because we also want - // to have the region tree available without having to recompute it. - // - // For now we are just going to do things the expedient way, but - // eventually we should allow an IR module to have side-band - // storage for derived structures like the region tree (and logic - // for invalidating them when a transformation would break them). - // - fixValueScoping(regionTree); - - // Now emit high-level code from that structured region tree. - // - emitRegionTree(regionTree); -} - -void CLikeSourceEmitter::emitIRSimpleFunc(IRFunc* func) -{ - auto resultType = func->getResultType(); - - // Deal with decorations that need - // to be emitted as attributes - auto entryPointLayout = asEntryPoint(func); - if (entryPointLayout) - { - emitIREntryPointAttributes(func, entryPointLayout); - } - - const CodeGenTarget target = getTarget(); - - auto name = getIRFuncName(func); - - emitType(resultType, name); - - m_stream->emit("("); - auto firstParam = func->getFirstParam(); - for( auto pp = firstParam; pp; pp = pp->getNextParam()) - { - if(pp != firstParam) - m_stream->emit(", "); - - auto paramName = getIRName(pp); - auto paramType = pp->getDataType(); - - if (target == CodeGenTarget::HLSL) - { - if (auto layoutDecor = pp->findDecoration()) - { - Layout* layout = layoutDecor->getLayout(); - VarLayout* varLayout = as(layout); - - if (varLayout) - { - auto var = varLayout->getVariable(); - - if (auto primTypeModifier = var->FindModifier()) - { - if (as(primTypeModifier)) - m_stream->emit("triangle "); - else if (as(primTypeModifier)) - m_stream->emit("point "); - else if (as(primTypeModifier)) - m_stream->emit("line "); - else if (as(primTypeModifier)) - m_stream->emit("lineadj "); - else if (as(primTypeModifier)) - m_stream->emit("triangleadj "); - } - } - } - } - - emitIRParamType(paramType, paramName); - - emitIRSemantics(pp); - } - m_stream->emit(")"); - - emitIRSemantics(func); - - // TODO: encode declaration vs. definition - if(isDefinition(func)) - { - m_stream->emit("\n{\n"); - m_stream->indent(); - - // HACK: forward-declare all the local variables needed for the - // parameters of non-entry blocks. - emitPhiVarDecls(func); - - // Need to emit the operations in the blocks of the function - emitIRFunctionBody(func); - - m_stream->dedent(); - m_stream->emit("}\n\n"); - } - else - { - m_stream->emit(";\n\n"); - } -} - -void CLikeSourceEmitter::emitIRParamType(IRType* type, String const& name) -{ - // An `out` or `inout` parameter will have been - // encoded as a parameter of pointer type, so - // we need to decode that here. - // - if( auto outType = as(type)) - { - m_stream->emit("out "); - type = outType->getValueType(); - } - else if( auto inOutType = as(type)) - { - m_stream->emit("inout "); - type = inOutType->getValueType(); - } - else if( auto refType = as(type)) - { - // Note: There is no HLSL/GLSL equivalent for by-reference parameters, - // so we don't actually expect to encounter these in user code. - m_stream->emit("inout "); - type = inOutType->getValueType(); - } - - emitIRType(type, name); -} - -IRInst* CLikeSourceEmitter::getSpecializedValue(IRSpecialize* specInst) -{ - auto base = specInst->getBase(); - auto baseGeneric = as(base); - if (!baseGeneric) - return base; - - auto lastBlock = baseGeneric->getLastBlock(); - if (!lastBlock) - return base; - - auto returnInst = as(lastBlock->getTerminator()); - if (!returnInst) - return base; - - return returnInst->getVal(); -} - -void CLikeSourceEmitter::emitIRFuncDecl(IRFunc* func) -{ - // We don't want to emit declarations for operations - // that only appear in the IR as stand-ins for built-in - // operations on that target. - if (isTargetIntrinsic(func)) - return; - - // Finally, don't emit a declaration for an entry point, - // because it might need meta-data attributes attached - // to it, and the HLSL compiler will get upset if the - // forward declaration doesn't *also* have those - // attributes. - if(asEntryPoint(func)) - return; - - - // A function declaration doesn't have any IR basic blocks, - // and as a result it *also* doesn't have the IR `param` instructions, - // so we need to emit a declaration entirely from the type. - - auto funcType = func->getDataType(); - auto resultType = func->getResultType(); - - auto name = getIRFuncName(func); - - emitIRType(resultType, name); - - m_stream->emit("("); - auto paramCount = funcType->getParamCount(); - for(UInt pp = 0; pp < paramCount; ++pp) - { - if(pp != 0) - m_stream->emit(", "); - - String paramName; - paramName.append("_"); - paramName.append(Int32(pp)); - auto paramType = funcType->getParamType(pp); - - emitIRParamType(paramType, paramName); - } - m_stream->emit(");\n\n"); -} - -EntryPointLayout* CLikeSourceEmitter::getEntryPointLayout(IRFunc* func) -{ - if( auto layoutDecoration = func->findDecoration() ) - { - return as(layoutDecoration->getLayout()); - } - return nullptr; -} - -EntryPointLayout* CLikeSourceEmitter::asEntryPoint(IRFunc* func) -{ - if (auto layoutDecoration = func->findDecoration()) - { - if (auto entryPointLayout = as(layoutDecoration->getLayout())) - { - return entryPointLayout; - } - } - - return nullptr; -} - -bool CLikeSourceEmitter::isTargetIntrinsic(IRFunc* func) -{ - // For now we do this in an overly simplistic - // fashion: we say that *any* function declaration - // (rather then definition) must be an intrinsic: - return !isDefinition(func); -} - -IRFunc* CLikeSourceEmitter::asTargetIntrinsic(IRInst* value) -{ - if(!value) - return nullptr; - - while (auto specInst = as(value)) - { - value = getSpecializedValue(specInst); - } - - if(value->op != kIROp_Func) - return nullptr; - - IRFunc* func = (IRFunc*) value; - if(!isTargetIntrinsic(func)) - return nullptr; - - return func; -} - -void CLikeSourceEmitter::emitIRFunc(IRFunc* func) -{ - if(!isDefinition(func)) - { - // This is just a function declaration, - // and so we want to emit it as such. - // (Or maybe not emit it at all). - - // We do not emit the declaration for - // functions that appear to be intrinsics/builtins - // in the target language. - if (isTargetIntrinsic(func)) - return; - - emitIRFuncDecl(func); - } - else - { - // The common case is that what we - // have is just an ordinary function, - // and we can emit it as such. - emitIRSimpleFunc(func); - } -} - -void CLikeSourceEmitter::emitIRStruct(IRStructType* structType) -{ - // If the selected `struct` type is actually an intrinsic - // on our target, then we don't want to emit anything at all. - if(auto intrinsicDecoration = findTargetIntrinsicDecoration(structType)) - { - return; - } - - m_stream->emit("struct "); - m_stream->emit(getIRName(structType)); - m_stream->emit("\n{\n"); - m_stream->indent(); - - for(auto ff : structType->getFields()) - { - auto fieldKey = ff->getKey(); - auto fieldType = ff->getFieldType(); - - // Filter out fields with `void` type that might - // have been introduced by legalization. - if(as(fieldType)) - continue; - - // Note: GLSL doesn't support interpolation modifiers on `struct` fields - if( getTarget() != CodeGenTarget::GLSL ) - { - emitInterpolationModifiers(fieldKey, fieldType, nullptr); - } - - emitIRType(fieldType, getIRName(fieldKey)); - emitIRSemantics(fieldKey); - m_stream->emit(";\n"); - } - - m_stream->dedent(); - m_stream->emit("};\n\n"); -} - -void CLikeSourceEmitter::emitIRMatrixLayoutModifiers(VarLayout* layout) -{ - // When a variable has a matrix type, we want to emit an explicit - // layout qualifier based on what the layout has been computed to be. - // - - auto typeLayout = layout->typeLayout; - while(auto arrayTypeLayout = as(typeLayout)) - typeLayout = arrayTypeLayout->elementTypeLayout; - - if (auto matrixTypeLayout = typeLayout.as()) - { - auto target = getTarget(); - - switch (target) - { - case CodeGenTarget::HLSL: - switch (matrixTypeLayout->mode) - { - case kMatrixLayoutMode_ColumnMajor: - m_stream->emit("column_major "); - break; - - case kMatrixLayoutMode_RowMajor: - m_stream->emit("row_major "); - break; - } - break; - - case CodeGenTarget::GLSL: - // Reminder: the meaning of row/column major layout - // in our semantics is the *opposite* of what GLSL - // calls them, because what they call "columns" - // are what we call "rows." - // - switch (matrixTypeLayout->mode) - { - case kMatrixLayoutMode_ColumnMajor: - m_stream->emit("layout(row_major)\n"); - break; - - case kMatrixLayoutMode_RowMajor: - m_stream->emit("layout(column_major)\n"); - break; - } - break; - - default: - break; - } - - } -} - -void CLikeSourceEmitter::maybeEmitGLSLFlatModifier(IRType* valueType) -{ - auto tt = valueType; - if(auto vecType = as(tt)) - tt = vecType->getElementType(); - if(auto vecType = as(tt)) - tt = vecType->getElementType(); - - switch(tt->op) - { - default: - break; - - case kIROp_IntType: - case kIROp_UIntType: - case kIROp_UInt64Type: - m_stream->emit("flat "); - break; - } -} - -void CLikeSourceEmitter::emitInterpolationModifiers(IRInst* varInst, IRType* valueType, VarLayout* layout) -{ - bool isGLSL = (getTarget() == CodeGenTarget::GLSL); - bool anyModifiers = false; - - for(auto dd : varInst->getDecorations()) - { - if(dd->op != kIROp_InterpolationModeDecoration) - continue; - - auto decoration = (IRInterpolationModeDecoration*)dd; - auto mode = decoration->getMode(); - - switch(mode) - { - case IRInterpolationMode::NoInterpolation: - anyModifiers = true; - m_stream->emit(isGLSL ? "flat " : "nointerpolation "); - break; - - case IRInterpolationMode::NoPerspective: - anyModifiers = true; - m_stream->emit("noperspective "); - break; - - case IRInterpolationMode::Linear: - anyModifiers = true; - m_stream->emit(isGLSL ? "smooth " : "linear "); - break; - - case IRInterpolationMode::Sample: - anyModifiers = true; - m_stream->emit("sample "); - break; - - case IRInterpolationMode::Centroid: - anyModifiers = true; - m_stream->emit("centroid "); - break; - - default: - break; - } - } - - // If the user didn't explicitly qualify a varying - // with integer type, then we need to explicitly - // add the `flat` modifier for GLSL. - if(!anyModifiers && isGLSL) - { - // Only emit a default `flat` for fragment - // stage varying inputs. - // - // TODO: double-check that this works for - // signature matching even if the producing - // stage didn't use `flat`. - // - // If this ends up being a problem we can instead - // output everything with `flat` except for - // fragment *outputs* (and maybe vertex inputs). - // - if(layout && layout->stage == Stage::Fragment - && layout->FindResourceInfo(LayoutResourceKind::VaryingInput)) - { - maybeEmitGLSLFlatModifier(valueType); - } - } -} - -UInt CLikeSourceEmitter::getRayPayloadLocation(IRInst* inst) -{ - auto& map = m_context->mapIRValueToRayPayloadLocation; - UInt value = 0; - if(map.TryGetValue(inst, value)) - return value; - - value = map.Count(); - map.Add(inst, value); - return value; -} - -UInt CLikeSourceEmitter::getCallablePayloadLocation(IRInst* inst) -{ - auto& map = m_context->mapIRValueToCallablePayloadLocation; - UInt value = 0; - if(map.TryGetValue(inst, value)) - return value; - - value = map.Count(); - map.Add(inst, value); - return value; -} - -void CLikeSourceEmitter::emitGLSLImageFormatModifier(IRInst* var, IRTextureType* resourceType) -{ - // If the user specified a format manually, using `[format(...)]`, - // then we will respect that format and emit a matching `layout` modifier. - // - if(auto formatDecoration = var->findDecoration()) - { - auto format = formatDecoration->getFormat(); - if(format == ImageFormat::unknown) - { - // If the user explicitly opts out of having a format, then - // the output shader will require the extension to support - // load/store from format-less images. - // - // TODO: We should have a validation somewhere in the compiler - // that atomic operations are only allowed on images with - // explicit formats (and then only on specific formats). - // This is really an argument that format should be part of - // the image *type* (with a "base type" for images with - // unknown format). - // - requireGLSLExtension("GL_EXT_shader_image_load_formatted"); - } - else - { - // If there is an explicit format specified, then we - // should emit a `layout` modifier using the GLSL name - // for the format. - // - m_stream->emit("layout("); - m_stream->emit(getGLSLNameForImageFormat(format)); - m_stream->emit(")\n"); - } - - // No matter what, if an explicit `[format(...)]` was given, - // then we don't need to emit anything else. - // - return; - } - - - // When no explicit format is specified, we need to either - // emit the image as having an unknown format, or else infer - // a format from the type. - // - // For now our default behavior is to infer (so that unmodified - // HLSL input is more likely to generate valid SPIR-V that - // runs anywhere), but we provide a flag to opt into - // treating images without explicit formats as having - // unknown format. - // - if(this->m_context->compileRequest->useUnknownImageFormatAsDefault) - { - requireGLSLExtension("GL_EXT_shader_image_load_formatted"); - return; - } - - // At this point we have a resource type like `RWTexture2D` - // and we want to infer a reasonable format from the element - // type `X` that was specified. - // - // E.g., if `X` is `float` then we can infer a format like `r32f`, - // and so forth. The catch of course is that it is possible to - // specify a shader parameter with a type like `RWTexture2D` but - // provide an image at runtime with a format like `rgba8`, so - // this inference is never guaranteed to give perfect results. - // - // If users don't like our inferred result, they need to use a - // `[format(...)]` attribute to manually specify what they want. - // - // TODO: We should consider whether we can expand the space of - // allowed types for `X` in `RWTexture2D` to include special - // pseudo-types that act just like, e.g., `float4`, but come - // with attached/implied format information. - // - auto elementType = resourceType->getElementType(); - Int vectorWidth = 1; - if(auto elementVecType = as(elementType)) - { - if(auto intLitVal = as(elementVecType->getElementCount())) - { - vectorWidth = (Int) intLitVal->getValue(); - } - else - { - vectorWidth = 0; - } - elementType = elementVecType->getElementType(); - } - if(auto elementBasicType = as(elementType)) - { - m_stream->emit("layout("); - switch(vectorWidth) - { - default: m_stream->emit("rgba"); break; - - case 3: - { - // TODO: GLSL doesn't support 3-component formats so for now we are going to - // default to rgba - // - // The SPIR-V spec (https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.pdf) - // section 3.11 on Image Formats it does not list rgbf32. - // - // It seems SPIR-V can support having an image with an unknown-at-compile-time - // format, so long as the underlying API supports it. Ideally this would mean that we can - // just drop all these qualifiers when emitting GLSL for Vulkan targets. - // - // This raises the question of what to do more long term. For Vulkan hopefully we can just - // drop the layout. For OpenGL targets it would seem reasonable to have well-defined rules - // for inferring the format (and just document that 3-component formats map to 4-component formats, - // but that shouldn't matter because the API wouldn't let the user allocate those 3-component formats anyway), - // and add an attribute for specifying the format manually if you really want to override our - // inference (e.g., to specify r11fg11fb10f). - - m_stream->emit("rgba"); - //Emit("rgb"); - break; - } - - case 2: m_stream->emit("rg"); break; - case 1: m_stream->emit("r"); break; - } - switch(elementBasicType->getBaseType()) - { - default: - case BaseType::Float: m_stream->emit("32f"); break; - case BaseType::Half: m_stream->emit("16f"); break; - case BaseType::UInt: m_stream->emit("32ui"); break; - case BaseType::Int: m_stream->emit("32i"); break; - - // TODO: Here are formats that are available in GLSL, - // but that are not handled by the above cases. - // - // r11f_g11f_b10f - // - // rgba16 - // rgb10_a2 - // rgba8 - // rg16 - // rg8 - // r16 - // r8 - // - // rgba16_snorm - // rgba8_snorm - // rg16_snorm - // rg8_snorm - // r16_snorm - // r8_snorm - // - // rgba16i - // rgba8i - // rg16i - // rg8i - // r16i - // r8i - // - // rgba16ui - // rgb10_a2ui - // rgba8ui - // rg16ui - // rg8ui - // r16ui - // r8ui - } - m_stream->emit(")\n"); - } -} - - /// Emit modifiers that should apply even for a declaration of an SSA temporary. -void CLikeSourceEmitter::emitIRTempModifiers(IRInst* temp) -{ - if(temp->findDecoration()) - { - m_stream->emit("precise "); - } -} - -void CLikeSourceEmitter::emitIRVarModifiers(VarLayout* layout,IRInst* varDecl, IRType* varType) -{ - // Deal with Vulkan raytracing layout stuff *before* we - // do the check for whether `layout` is null, because - // the payload won't automatically get a layout applied - // (it isn't part of the user-visible interface...) - // - if(varDecl->findDecoration()) - { - m_stream->emit("layout(location = "); - m_stream->emit(getRayPayloadLocation(varDecl)); - m_stream->emit(")\n"); - m_stream->emit("rayPayloadNV\n"); - } - if(varDecl->findDecoration()) - { - m_stream->emit("layout(location = "); - m_stream->emit(getCallablePayloadLocation(varDecl)); - m_stream->emit(")\n"); - m_stream->emit("callableDataNV\n"); - } - - if(varDecl->findDecoration()) - { - m_stream->emit("hitAttributeNV\n"); - } - - if(varDecl->findDecoration()) - { - switch(getTarget()) - { - default: - break; - - case CodeGenTarget::HLSL: - m_stream->emit("globallycoherent\n"); - break; - - case CodeGenTarget::GLSL: - m_stream->emit("coherent\n"); - break; - } - } - - emitIRTempModifiers(varDecl); - - if (!layout) - return; - - emitIRMatrixLayoutModifiers(layout); - - // As a special case, if we are emitting a GLSL declaration - // for an HLSL `RWTexture*` then we need to emit a `format` layout qualifier. - if(getTarget() == CodeGenTarget::GLSL) - { - if(auto resourceType = as(unwrapArray(varType))) - { - switch(resourceType->getAccess()) - { - case SLANG_RESOURCE_ACCESS_READ_WRITE: - case SLANG_RESOURCE_ACCESS_RASTER_ORDERED: - { - emitGLSLImageFormatModifier(varDecl, resourceType); - } - break; - - default: - break; - } - } - } - - if(layout->FindResourceInfo(LayoutResourceKind::VaryingInput) - || layout->FindResourceInfo(LayoutResourceKind::VaryingOutput)) - { - emitInterpolationModifiers(varDecl, varType, layout); - } - - if (getTarget() == CodeGenTarget::GLSL) - { - // Layout-related modifiers need to come before the declaration, - // so deal with them here. - emitGLSLLayoutQualifiers(layout, nullptr); - - // try to emit an appropriate leading qualifier - for (auto rr : layout->resourceInfos) - { - switch (rr.kind) - { - case LayoutResourceKind::Uniform: - case LayoutResourceKind::ShaderResource: - case LayoutResourceKind::DescriptorTableSlot: - m_stream->emit("uniform "); - break; - - case LayoutResourceKind::VaryingInput: - { - m_stream->emit("in "); - } - break; - - case LayoutResourceKind::VaryingOutput: - { - m_stream->emit("out "); - } - break; - - case LayoutResourceKind::RayPayload: - { - m_stream->emit("rayPayloadInNV "); - } - break; - - case LayoutResourceKind::CallablePayload: - { - m_stream->emit("callableDataInNV "); - } - break; - - case LayoutResourceKind::HitAttributes: - { - m_stream->emit("hitAttributeNV "); - } - break; - - default: - continue; - } - - break; - } - } -} - -void CLikeSourceEmitter::emitHLSLParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type) -{ - if(as(type)) - { - m_stream->emit("tbuffer "); - } - else - { - m_stream->emit("cbuffer "); - } - m_stream->emit(getIRName(varDecl)); - - auto varLayout = getVarLayout(varDecl); - SLANG_RELEASE_ASSERT(varLayout); - - EmitVarChain blockChain(varLayout); - - EmitVarChain containerChain = blockChain; - EmitVarChain elementChain = blockChain; - - auto typeLayout = varLayout->typeLayout; - if( auto parameterGroupTypeLayout = as(typeLayout) ) - { - containerChain = EmitVarChain(parameterGroupTypeLayout->containerVarLayout, &blockChain); - elementChain = EmitVarChain(parameterGroupTypeLayout->elementVarLayout, &blockChain); - - typeLayout = parameterGroupTypeLayout->elementVarLayout->typeLayout; - } - - emitHLSLRegisterSemantic(LayoutResourceKind::ConstantBuffer, &containerChain); - - m_stream->emit("\n{\n"); - m_stream->indent(); - - auto elementType = type->getElementType(); - - emitIRType(elementType, getIRName(varDecl)); - m_stream->emit(";\n"); - - m_stream->dedent(); - m_stream->emit("}\n"); -} - -void CLikeSourceEmitter::emitArrayBrackets(IRType* inType) -{ - // A declaration may require zero, one, or - // more array brackets. When writing out array - // brackets from left to right, they represent - // the structure of the type from the "outside" - // in (that is, if we have a 5-element array of - // 3-element arrays we should output `[5][3]`), - // because of C-style declarator rules. - // - // This conveniently means that we can print - // out all the array brackets with a looping - // rather than a recursive structure. - // - // We will peel the input type like an onion, - // looking at one layer at a time until we - // reach a non-array type in the middle. - // - IRType* type = inType; - for(;;) - { - if(auto arrayType = as(type)) - { - m_stream->emit("["); - emitVal(arrayType->getElementCount(), getInfo(EmitOp::General)); - m_stream->emit("]"); - - // Continue looping on the next layer in. - // - type = arrayType->getElementType(); - } - else if(auto unsizedArrayType = as(type)) - { - m_stream->emit("[]"); - - // Continue looping on the next layer in. - // - type = unsizedArrayType->getElementType(); - } - else - { - // This layer wasn't an array, so we are done. - // - return; - } - } -} - - -void CLikeSourceEmitter::emitGLSLParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type) -{ - auto varLayout = getVarLayout(varDecl); - SLANG_RELEASE_ASSERT(varLayout); - - EmitVarChain blockChain(varLayout); - - EmitVarChain containerChain = blockChain; - EmitVarChain elementChain = blockChain; - - auto typeLayout = varLayout->typeLayout->unwrapArray(); - if( auto parameterGroupTypeLayout = as(typeLayout) ) - { - containerChain = EmitVarChain(parameterGroupTypeLayout->containerVarLayout, &blockChain); - elementChain = EmitVarChain(parameterGroupTypeLayout->elementVarLayout, &blockChain); - - typeLayout = parameterGroupTypeLayout->elementVarLayout->typeLayout; - } - - /* - With resources backed by 'buffer' on glsl, we want to output 'readonly' if that is a good match - for the underlying type. If uniform it's implicit it's readonly - - Here this only happens with isShaderRecord which is a 'constant buffer' (ie implicitly readonly) - or IRGLSLShaderStorageBufferType which is read write. - */ - - emitGLSLLayoutQualifier(LayoutResourceKind::DescriptorTableSlot, &containerChain); - emitGLSLLayoutQualifier(LayoutResourceKind::PushConstantBuffer, &containerChain); - bool isShaderRecord = emitGLSLLayoutQualifier(LayoutResourceKind::ShaderRecord, &containerChain); - - if( isShaderRecord ) - { - // TODO: A shader record in vk can be potentially read-write. Currently slang doesn't support write access - // and readonly buffer generates SPIRV validation error. - m_stream->emit("buffer "); - } - else if(as(type)) - { - // Is writable - m_stream->emit("layout(std430) buffer "); - } - // TODO: what to do with HLSL `tbuffer` style buffers? - else - { - // uniform is implicitly read only - m_stream->emit("layout(std140) uniform "); - } - - // Generate a dummy name for the block - m_stream->emit("_S"); - m_stream->emit(m_context->uniqueIDCounter++); - - m_stream->emit("\n{\n"); - m_stream->indent(); - - auto elementType = type->getElementType(); - - emitIRType(elementType, "_data"); - m_stream->emit(";\n"); - - m_stream->dedent(); - m_stream->emit("} "); - - m_stream->emit(getIRName(varDecl)); - - // If the underlying variable was an array (or array of arrays, etc.) - // we need to emit all those array brackets here. - emitArrayBrackets(varDecl->getDataType()); - - m_stream->emit(";\n"); -} - -void CLikeSourceEmitter::emitIRParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type) -{ - switch (getTarget()) - { - case CodeGenTarget::HLSL: - emitHLSLParameterGroup(varDecl, type); - break; - - case CodeGenTarget::GLSL: - emitGLSLParameterGroup(varDecl, type); - break; - } -} - -void CLikeSourceEmitter::emitIRVar(IRVar* varDecl) -{ - auto allocatedType = varDecl->getDataType(); - auto varType = allocatedType->getValueType(); -// auto addressSpace = allocatedType->getAddressSpace(); - -#if 0 - switch( varType->op ) - { - case kIROp_ConstantBufferType: - case kIROp_TextureBufferType: - emitIRParameterGroup(ctx, varDecl, (IRUniformBufferType*) varType); - return; - - default: - break; - } -#endif - - // Need to emit appropriate modifiers here. - - auto layout = getVarLayout(varDecl); - - emitIRVarModifiers(layout, varDecl, varType); - -#if 0 - switch (addressSpace) - { - default: - break; - - case kIRAddressSpace_GroupShared: - emit("groupshared "); - break; - } -#endif - emitIRRateQualifiers(varDecl); - - emitIRType(varType, getIRName(varDecl)); - - emitIRSemantics(varDecl); - - emitIRLayoutSemantics(varDecl); - - m_stream->emit(";\n"); -} - -void CLikeSourceEmitter::emitIRStructuredBuffer_GLSL(IRGlobalParam* varDecl, IRHLSLStructuredBufferTypeBase* structuredBufferType) -{ - // Shader storage buffer is an OpenGL 430 feature - // - // TODO: we should require either the extension or the version... - requireGLSLVersion(430); - - m_stream->emit("layout(std430"); - - auto layout = getVarLayout(varDecl); - if (layout) - { - LayoutResourceKind kind = LayoutResourceKind::DescriptorTableSlot; - EmitVarChain chain(layout); - - const UInt index = getBindingOffset(&chain, kind); - const UInt space = getBindingSpace(&chain, kind); - - m_stream->emit(", binding = "); - m_stream->emit(index); - if (space) - { - m_stream->emit(", set = "); - m_stream->emit(space); - } - } - - m_stream->emit(") "); - - /* - If the output type is a buffer, and we can determine it is only readonly we can prefix before - buffer with 'readonly' - - The actual structuredBufferType could be - - HLSLStructuredBufferType - This is unambiguously read only - HLSLRWStructuredBufferType - Read write - HLSLRasterizerOrderedStructuredBufferType - Allows read/write access - HLSLAppendStructuredBufferType - Write - HLSLConsumeStructuredBufferType - TODO (JS): Its possible that this can be readonly, but we currently don't support on GLSL - */ - - if (as(structuredBufferType)) - { - m_stream->emit("readonly "); - } - - m_stream->emit("buffer "); - - // Generate a dummy name for the block - m_stream->emit("_S"); - m_stream->emit(m_context->uniqueIDCounter++); - - m_stream->emit(" {\n"); - m_stream->indent(); - - - auto elementType = structuredBufferType->getElementType(); - emitIRType(elementType, "_data[]"); - m_stream->emit(";\n"); - - m_stream->dedent(); - m_stream->emit("} "); - - m_stream->emit(getIRName(varDecl)); - emitArrayBrackets(varDecl->getDataType()); - - m_stream->emit(";\n"); -} - -void CLikeSourceEmitter::emitIRByteAddressBuffer_GLSL(IRGlobalParam* varDecl, IRByteAddressBufferTypeBase* byteAddressBufferType) -{ - // TODO: A lot of this logic is copy-pasted from `emitIRStructuredBuffer_GLSL`. - // It might be worthwhile to share the common code to avoid regressions sneaking - // in when one or the other, but not both, gets updated. - - // Shader storage buffer is an OpenGL 430 feature - // - // TODO: we should require either the extension or the version... - requireGLSLVersion(430); - - m_stream->emit("layout(std430"); - - auto layout = getVarLayout(varDecl); - if (layout) - { - LayoutResourceKind kind = LayoutResourceKind::DescriptorTableSlot; - EmitVarChain chain(layout); - - const UInt index = getBindingOffset(&chain, kind); - const UInt space = getBindingSpace(&chain, kind); - - m_stream->emit(", binding = "); - m_stream-> emit(index); - if (space) - { - m_stream->emit(", set = "); - m_stream->emit(space); - } - } - - m_stream->emit(") "); - - /* - If the output type is a buffer, and we can determine it is only readonly we can prefix before - buffer with 'readonly' - - HLSLByteAddressBufferType - This is unambiguously read only - HLSLRWByteAddressBufferType - Read write - HLSLRasterizerOrderedByteAddressBufferType - Allows read/write access - */ - - if (as(byteAddressBufferType)) - { - m_stream->emit("readonly "); - } - - m_stream->emit("buffer "); - - // Generate a dummy name for the block - m_stream->emit("_S"); - m_stream->emit(m_context->uniqueIDCounter++); - m_stream->emit("\n{\n"); - m_stream->indent(); - - m_stream->emit("uint _data[];\n"); - - m_stream->dedent(); - m_stream->emit("} "); - - m_stream->emit(getIRName(varDecl)); - emitArrayBrackets(varDecl->getDataType()); - - m_stream->emit(";\n"); -} - -void CLikeSourceEmitter::emitIRGlobalVar(IRGlobalVar* varDecl) -{ - auto allocatedType = varDecl->getDataType(); - auto varType = allocatedType->getValueType(); - - String initFuncName; - if (varDecl->getFirstBlock()) - { - // A global variable with code means it has an initializer - // associated with it. Eventually we'd like to emit that - // initializer directly as an expression here, but for - // now we'll emit it as a separate function. - - initFuncName = getIRName(varDecl); - initFuncName.append("_init"); - - m_stream->emit("\n"); - emitIRType(varType, initFuncName); - m_stream->emit("()\n{\n"); - m_stream->indent(); - emitIRFunctionBody(varDecl); - m_stream->dedent(); - m_stream->emit("}\n"); - } - - // An ordinary global variable won't have a layout - // associated with it, since it is not a shader - // parameter. - // - SLANG_ASSERT(!getVarLayout(varDecl)); - VarLayout* layout = nullptr; - - // An ordinary global variable (which is not a - // shader parameter) may need special - // modifiers to indicate it as such. - // - switch (getTarget()) - { - case CodeGenTarget::HLSL: - // HLSL requires the `static` modifier on any - // global variables; otherwise they are assumed - // to be uniforms. - m_stream->emit("static "); - break; - - default: - break; - } - - emitIRVarModifiers(layout, varDecl, varType); - - emitIRRateQualifiers(varDecl); - emitIRType(varType, getIRName(varDecl)); - - // TODO: These shouldn't be needed for ordinary - // global variables. - // - emitIRSemantics(varDecl); - emitIRLayoutSemantics(varDecl); - - if (varDecl->getFirstBlock()) - { - m_stream->emit(" = "); - m_stream->emit(initFuncName); - m_stream->emit("()"); - } - - m_stream->emit(";\n\n"); -} - -void CLikeSourceEmitter::emitIRGlobalParam(IRGlobalParam* varDecl) -{ - auto rawType = varDecl->getDataType(); - - auto varType = rawType; - if( auto outType = as(varType) ) - { - varType = outType->getValueType(); - } - if (as(varType)) - return; - - // When a global shader parameter represents a "parameter group" - // (either a constant buffer or a parameter block with non-resource - // data in it), we will prefer to emit it as an ordinary `cbuffer` - // declaration or `uniform` block, even when emitting HLSL for - // D3D profiles that support the explicit `ConstantBuffer` type. - // - // Alternatively, we could make this choice based on profile, and - // prefer `ConstantBuffer` on profiles that support it and/or when - // the input code used that syntax. - // - if (auto paramBlockType = as(varType)) - { - emitIRParameterGroup(varDecl, paramBlockType); - return; - } - - if(getTarget() == CodeGenTarget::GLSL) - { - // There are a number of types that are (or can be) - // "first-class" in D3D HLSL, but are second-class in GLSL in - // that they require explicit global declarations for each value/object, - // and don't support declaration as ordinary variables. - // - // This includes constant buffers (`uniform` blocks) and well as - // structured and byte-address buffers (both mapping to `buffer` blocks). - // - // We intercept these types, and arrays thereof, to produce the required - // global declarations. This assumes that earlier "legalization" passes - // already performed the work of pulling fields with these types out of - // aggregates. - // - // Note: this also assumes that these types are not used as function - // parameters/results, local variables, etc. Additional legalization - // steps are required to guarantee these conditions. - // - if (auto paramBlockType = as(unwrapArray(varType))) - { - emitGLSLParameterGroup(varDecl, paramBlockType); - return; - } - if( auto structuredBufferType = as(unwrapArray(varType)) ) - { - emitIRStructuredBuffer_GLSL(varDecl, structuredBufferType); - return; - } - if( auto byteAddressBufferType = as(unwrapArray(varType)) ) - { - emitIRByteAddressBuffer_GLSL(varDecl, byteAddressBufferType); - return; - } - - // We want to skip the declaration of any system-value variables - // when outputting GLSL (well, except in the case where they - // actually *require* redeclaration...). - // - // Note: these won't be variables the user declare explicitly - // in their code, but rather variables that we generated as - // part of legalizing the varying input/output signature of - // an entry point for GL/Vulkan. - // - // TODO: This could be handled more robustly by attaching an - // appropriate decoration to these variables to indicate their - // purpose. - // - if(auto linkageDecoration = varDecl->findDecoration()) - { - if(linkageDecoration->getMangledName().startsWith("gl_")) - { - // The variable represents an OpenGL system value, - // so we will assume that it doesn't need to be declared. - // - // TODO: handle case where we *should* declare the variable. - return; - } - } - - // When emitting unbounded-size resource arrays with GLSL we need - // to use the `GL_EXT_nonuniform_qualifier` extension to ensure - // that they are not treated as "implicitly-sized arrays" which - // are arrays that have a fixed size that just isn't specified - // at the declaration site (instead being inferred from use sites). - // - // While the extension primarily introduces the `nonuniformEXT` - // qualifier that we use to implement `NonUniformResourceIndex`, - // it also changes the GLSL language semantics around (resource) array - // declarations that don't specify a size. - // - if( as(varType) ) - { - if(isResourceType(unwrapArray(varType))) - { - requireGLSLExtension("GL_EXT_nonuniform_qualifier"); - } - } - } - - // Need to emit appropriate modifiers here. - - // We expect/require all shader parameters to - // have some kind of layout information associated with them. - // - auto layout = getVarLayout(varDecl); - SLANG_ASSERT(layout); - - emitIRVarModifiers(layout, varDecl, varType); - - emitIRRateQualifiers(varDecl); - emitIRType(varType, getIRName(varDecl)); - - emitIRSemantics(varDecl); - - emitIRLayoutSemantics(varDecl); - - // A shader parameter cannot have an initializer, - // so we do need to consider emitting one here. - - m_stream->emit(";\n\n"); -} - - -void CLikeSourceEmitter::emitIRGlobalConstantInitializer(IRGlobalConstant* valDecl) -{ - // We expect to see only a single block - auto block = valDecl->getFirstBlock(); - SLANG_RELEASE_ASSERT(block); - SLANG_RELEASE_ASSERT(!block->getNextBlock()); - - // We expect the terminator to be a `return` - // instruction with a value. - auto returnInst = (IRReturnVal*) block->getLastDecorationOrChild(); - SLANG_RELEASE_ASSERT(returnInst->op == kIROp_ReturnVal); - - // We will emit the value in the `GlobalConstant` mode, which - // more or less says to fold all instructions into their use - // sites, so that we end up with a single expression tree even - // in cases that would otherwise trip up our analysis. - // - // Note: We are emitting the value as an *operand* here instead - // of directly calling `emitIRInstExpr` because we need to handle - // cases where the value might *need* to emit as a named referenced - // (e.g., when it names another constant directly). - // - emitIROperand(returnInst->getVal(), IREmitMode::GlobalConstant, getInfo(EmitOp::General)); -} - -void CLikeSourceEmitter::emitIRGlobalConstant(IRGlobalConstant* valDecl) -{ - auto valType = valDecl->getDataType(); - - if( getTarget() != CodeGenTarget::GLSL ) - { - m_stream->emit("static "); - } - m_stream->emit("const "); - emitIRRateQualifiers(valDecl); - emitIRType(valType, getIRName(valDecl)); - - if (valDecl->getFirstBlock()) - { - // There is an initializer (which we expect for - // any global constant...). - - m_stream->emit(" = "); - - // We need to emit the entire initializer as - // a single expression. - emitIRGlobalConstantInitializer(valDecl); - } - - - m_stream->emit(";\n"); -} - -void CLikeSourceEmitter::emitIRGlobalInst(IRInst* inst) -{ - m_stream->advanceToSourceLocation(inst->sourceLoc); - - switch(inst->op) - { - case kIROp_Func: - emitIRFunc((IRFunc*) inst); - break; - - case kIROp_GlobalVar: - emitIRGlobalVar((IRGlobalVar*) inst); - break; - - case kIROp_GlobalParam: - emitIRGlobalParam((IRGlobalParam*) inst); - break; - - case kIROp_GlobalConstant: - emitIRGlobalConstant((IRGlobalConstant*) inst); - break; - - case kIROp_Var: - emitIRVar((IRVar*) inst); - break; - - case kIROp_StructType: - emitIRStruct(cast(inst)); - break; - - default: - break; - } -} - -void CLikeSourceEmitter::ensureInstOperand(ComputeEmitActionsContext* ctx, IRInst* inst, EmitAction::Level requiredLevel) -{ - if(!inst) return; - - if(inst->getParent() == ctx->moduleInst) - { - ensureGlobalInst(ctx, inst, requiredLevel); - } -} - -void CLikeSourceEmitter::ensureInstOperandsRec(ComputeEmitActionsContext* ctx, IRInst* inst) -{ - ensureInstOperand(ctx, inst->getFullType()); - - UInt operandCount = inst->operandCount; - for(UInt ii = 0; ii < operandCount; ++ii) - { - // TODO: there are some special cases we can add here, - // to avoid outputting full definitions in cases that - // can get by with forward declarations. - // - // For example, true pointer types should (in principle) - // only need the type they point to to be forward-declared. - // Similarly, a `call` instruction only needs the callee - // to be forward-declared, etc. - - ensureInstOperand(ctx, inst->getOperand(ii)); - } - - for(auto child : inst->getDecorationsAndChildren()) - { - ensureInstOperandsRec(ctx, child); - } -} - -void CLikeSourceEmitter::ensureGlobalInst(ComputeEmitActionsContext* ctx, IRInst* inst, EmitAction::Level requiredLevel) -{ - // Skip certain instructions, since they - // don't affect output. - switch(inst->op) - { - case kIROp_WitnessTable: - case kIROp_Generic: - return; - - default: - break; - } - - // Have we already processed this instruction? - EmitAction::Level existingLevel; - if(ctx->mapInstToLevel.TryGetValue(inst, existingLevel)) - { - // If we've already emitted it suitably, - // then don't worry about it. - if(existingLevel >= requiredLevel) - return; - } - - EmitAction action; - action.level = requiredLevel; - action.inst = inst; - - if(requiredLevel == EmitAction::Level::Definition) - { - if(ctx->openInsts.Contains(inst)) - { - SLANG_UNEXPECTED("circularity during codegen"); - return; - } - - ctx->openInsts.Add(inst); - - ensureInstOperandsRec(ctx, inst); - - ctx->openInsts.Remove(inst); - } - - ctx->mapInstToLevel[inst] = requiredLevel; - ctx->actions->add(action); -} - -void CLikeSourceEmitter::computeIREmitActions(IRModule* module, List& ioActions) -{ - ComputeEmitActionsContext ctx; - ctx.moduleInst = module->getModuleInst(); - ctx.actions = &ioActions; - - for(auto inst : module->getGlobalInsts()) - { - if( as(inst) ) - { - // Don't emit a type unless it is actually used. - continue; - } - - ensureGlobalInst(&ctx, inst, EmitAction::Level::Definition); - } -} - -void CLikeSourceEmitter::executeIREmitActions(List const& actions) -{ - for(auto action : actions) - { - switch(action.level) - { - case EmitAction::Level::ForwardDeclaration: - emitIRFuncDecl(cast(action.inst)); - break; - - case EmitAction::Level::Definition: - emitIRGlobalInst(action.inst); - break; - } - } -} - -void CLikeSourceEmitter::emitIRModule(IRModule* module) -{ - // The IR will usually come in an order that respects - // dependencies between global declarations, but this - // isn't guaranteed, so we need to be careful about - // the order in which we emit things. - - List actions; - - computeIREmitActions(module, actions); - executeIREmitActions(actions); -} - -} // namespace Slang diff --git a/source/slang/slang-c-like-source-emitter.h b/source/slang/slang-c-like-source-emitter.h deleted file mode 100644 index 4fcbb0b1c..000000000 --- a/source/slang/slang-c-like-source-emitter.h +++ /dev/null @@ -1,379 +0,0 @@ -// slang-c-like-source-emitter.h -#ifndef SLANG_C_LIKE_SOURCE_EMITTER_H_INCLUDED -#define SLANG_C_LIKE_SOURCE_EMITTER_H_INCLUDED - -#include "../core/slang-basic.h" - -#include "slang-compiler.h" - -#include "slang-emit-context.h" -#include "slang-extension-usage-tracker.h" -#include "slang-emit-precedence.h" - -#include "slang-ir.h" -#include "slang-ir-insts.h" -#include "slang-ir-restructure.h" - -namespace Slang -{ - -struct CLikeSourceEmitter -{ - enum class BuiltInCOp - { - Splat, //< Splat a single value to all values of a vector or matrix type - Init, //< Initialize with parameters (must match the type) - }; - - typedef unsigned int ESemanticMask; - enum - { - kESemanticMask_None = 0, - kESemanticMask_NoPackOffset = 1 << 0, - kESemanticMask_Default = kESemanticMask_NoPackOffset, - }; - - // Hack to allow IR emit for global constant to override behavior - enum class IREmitMode - { - Default, - GlobalConstant, - }; - - struct EmitVarChain; - struct IRDeclaratorInfo; - struct EDeclarator; - struct ComputeEmitActionsContext; - - // An action to be performed during code emit. - struct EmitAction - { - enum Level - { - ForwardDeclaration, - Definition, - }; - Level level; - IRInst* inst; - }; - - /// Ctor - CLikeSourceEmitter(EmitContext* context); - - /// Get the source manager - SourceManager* getSourceManager() { return m_context->getSourceManager(); } - - /// Get the diagnostic sink - DiagnosticSink* getSink() { return m_context->getSink();} - - // - // Types - // - - void emitDeclarator(EDeclarator* declarator); - - void emitGLSLTypePrefix(IRType* type, bool promoteHalfToFloat = false); - - void emitHLSLTextureType(IRTextureTypeBase* texType); - - void emitGLSLTextureOrTextureSamplerType(IRTextureTypeBase* type, char const* baseName); - - void emitGLSLTextureType(IRTextureType* texType); - - void emitGLSLTextureSamplerType(IRTextureSamplerType* type); - - void emitGLSLImageType(IRGLSLImageType* type); - - void emitTextureType(IRTextureType* texType); - - void emitTextureSamplerType(IRTextureSamplerType* type); - void emitImageType(IRGLSLImageType* type); - - void emitVectorTypeName(IRType* elementType, IRIntegerValue elementCount); - - void emitVectorTypeImpl(IRVectorType* vecType); - - void emitMatrixTypeImpl(IRMatrixType* matType); - - void emitSamplerStateType(IRSamplerStateTypeBase* samplerStateType); - - void emitStructuredBufferType(IRHLSLStructuredBufferTypeBase* type); - - void emitUntypedBufferType(IRUntypedBufferResourceType* type); - - void emitSimpleTypeImpl(IRType* type); - - void emitArrayTypeImpl(IRArrayType* arrayType, EDeclarator* declarator); - - void emitUnsizedArrayTypeImpl(IRUnsizedArrayType* arrayType, EDeclarator* declarator); - - void emitTypeImpl(IRType* type, EDeclarator* declarator); - - void emitType(IRType* type, const SourceLoc& typeLoc, Name* name, const SourceLoc& nameLoc); - void emitType(IRType* type, Name* name); - void emitType(IRType* type, String const& name); - void emitType(IRType* type); - - // - // Expressions - // - - bool maybeEmitParens(EmitOpInfo& outerPrec, EmitOpInfo prec); - - void maybeCloseParens(bool needClose); - - bool isTargetIntrinsicModifierApplicable(String const& targetName); - - void emitType(IRType* type, Name* name, SourceLoc const& nameLoc); - - void emitType(IRType* type, NameLoc const& nameAndLoc); - - bool isTargetIntrinsicModifierApplicable(IRTargetIntrinsicDecoration* decoration); - - void emitStringLiteral(const String& value); - - void requireGLSLExtension(const String& name); - - void requireGLSLVersion(ProfileVersion version); - void requireGLSLVersion(int version); - void setSampleRateFlag(); - - void doSampleRateInputCheck(Name* name); - - void emitVal(IRInst* val, const EmitOpInfo& outerPrec); - - UInt getBindingOffset(EmitVarChain* chain, LayoutResourceKind kind); - UInt getBindingSpace(EmitVarChain* chain, LayoutResourceKind kind); - - // Emit a single `register` semantic, as appropriate for a given resource-type-specific layout info - // Keyword to use in the uniform case (`register` for globals, `packoffset` inside a `cbuffer`) - void emitHLSLRegisterSemantic(LayoutResourceKind kind, EmitVarChain* chain, char const* uniformSemanticSpelling = "register"); - - // Emit all the `register` semantics that are appropriate for a particular variable layout - void emitHLSLRegisterSemantics(EmitVarChain* chain, char const* uniformSemanticSpelling = "register"); - void emitHLSLRegisterSemantics(VarLayout* varLayout, char const* uniformSemanticSpelling = "register"); - - void emitHLSLParameterGroupFieldLayoutSemantics(EmitVarChain* chain); - - void emitHLSLParameterGroupFieldLayoutSemantics(RefPtr fieldLayout, EmitVarChain* inChain); - - bool emitGLSLLayoutQualifier(LayoutResourceKind kind, EmitVarChain* chain); - - void emitGLSLLayoutQualifiers(RefPtr layout, EmitVarChain* inChain, LayoutResourceKind filter = LayoutResourceKind::None); - - void emitGLSLVersionDirective(); - - void emitGLSLPreprocessorDirectives(); - - /// Emit directives to control overall layout computation for the emitted code. - void emitLayoutDirectives(TargetRequest* targetReq); - - // Utility code for generating unique IDs as needed - // during the emit process (e.g., for declarations - // that didn't originally have names, but now need to). - UInt allocateUniqueID(); - - // IR-level emit logic - - UInt getID(IRInst* value); - - /// "Scrub" a name so that it complies with restrictions of the target language. - String scrubName(const String& name); - - String generateIRName(IRInst* inst); - String getIRName(IRInst* inst); - - void emitDeclarator(IRDeclaratorInfo* declarator); - void emitIRSimpleValue(IRInst* inst); - - CodeGenTarget getTarget(); - - bool shouldFoldIRInstIntoUseSites(IRInst* inst, IREmitMode mode); - - void emitIROperand(IRInst* inst, IREmitMode mode, EmitOpInfo const& outerPrec); - - void emitIRArgs(IRInst* inst, IREmitMode mode); - - void emitIRType(IRType* type, String const& name); - - void emitIRType(IRType* type, Name* name); - - void emitIRType(IRType* type); - - void emitIRRateQualifiers(IRRate* rate); - - void emitIRRateQualifiers(IRInst* value); - - void emitIRInstResultDecl(IRInst* inst); - - IRTargetIntrinsicDecoration* findTargetIntrinsicDecoration(IRInst* inst); - - // Check if the string being used to define a target intrinsic - // is an "ordinary" name, such that we can simply emit a call - // to the new name with the arguments of the old operation. - static bool isOrdinaryName(const String& name); - - void emitTargetIntrinsicCallExpr( - IRCall* inst, - IRFunc* /* func */, - IRTargetIntrinsicDecoration* targetIntrinsic, - IREmitMode mode, - EmitOpInfo const& inOuterPrec); - - void emitIntrinsicCallExpr( - IRCall* inst, - IRFunc* func, - IREmitMode mode, - EmitOpInfo const& inOuterPrec); - - void emitIRCallExpr(IRCall* inst, IREmitMode mode, EmitOpInfo outerPrec); - - void emitNot(IRInst* inst, IREmitMode mode, EmitOpInfo& ioOuterPrec, bool* outNeedClose); - - void emitComparison(IRInst* inst, IREmitMode mode, EmitOpInfo& ioOuterPrec, const EmitOpInfo& opPrec, bool* needCloseOut); - - void emitIRInstExpr(IRInst* inst, IREmitMode mode, EmitOpInfo const& inOuterPrec); - - BaseType extractBaseType(IRType* inType); - - void emitIRInst(IRInst* inst, IREmitMode mode); - - void emitIRInstImpl(IRInst* inst, IREmitMode mode); - - void emitIRSemantics(VarLayout* varLayout); - - void emitIRSemantics(IRInst* inst); - - VarLayout* getVarLayout(IRInst* var); - - void emitIRLayoutSemantics(IRInst* inst, char const* uniformSemanticSpelling = "register"); - - // When we are about to traverse an edge from one block to another, - // we need to emit the assignments that conceptually occur "along" - // the edge. In traditional SSA these are the phi nodes in the - // target block, while in our representation these use the arguments - // to the branch instruction to fill in the parameters of the target. - void emitPhiVarAssignments(UInt argCount, IRUse* args, IRBlock* targetBlock); - - /// Emit high-level language statements from a structured region. - void emitRegion(Region* inRegion); - - /// Emit high-level language statements from a structured region tree. - void emitRegionTree(RegionTree* regionTree); - - // Is an IR function a definition? (otherwise it is a declaration) - bool isDefinition(IRFunc* func); - - String getIRFuncName(IRFunc* func); - - void emitAttributeSingleString(const char* name, FuncDecl* entryPoint, Attribute* attrib); - - void emitAttributeSingleInt(const char* name, FuncDecl* entryPoint, Attribute* attrib); - - void emitFuncDeclPatchConstantFuncAttribute(IRFunc* irFunc, FuncDecl* entryPoint, PatchConstantFuncAttribute* attrib); - - void emitIREntryPointAttributes_HLSL(IRFunc* irFunc, EntryPointLayout* entryPointLayout); - - void emitIREntryPointAttributes_GLSL(IRFunc* irFunc, EntryPointLayout* entryPointLayout); - - void emitIREntryPointAttributes(IRFunc* irFunc, EntryPointLayout* entryPointLayout); - - void emitPhiVarDecls(IRFunc* func); - - /// Emit high-level statements for the body of a function. - void emitIRFunctionBody(IRGlobalValueWithCode* code); - - void emitIRSimpleFunc(IRFunc* func); - - void emitIRParamType(IRType* type, String const& name); - - IRInst* getSpecializedValue(IRSpecialize* specInst); - - void emitIRFuncDecl(IRFunc* func); - - EntryPointLayout* getEntryPointLayout(IRFunc* func); - - EntryPointLayout* asEntryPoint(IRFunc* func); - - // Detect if the given IR function represents a - // declaration of an intrinsic/builtin for the - // current code-generation target. - bool isTargetIntrinsic(IRFunc* func); - - // Check whether a given value names a target intrinsic, - // and return the IR function representing the intrinsic - // if it does. - IRFunc* asTargetIntrinsic(IRInst* value); - - void emitIRFunc(IRFunc* func); - - void emitIRStruct(IRStructType* structType); - - void emitIRMatrixLayoutModifiers(VarLayout* layout); - - // Emit the `flat` qualifier if the underlying type - // of the variable is an integer type. - void maybeEmitGLSLFlatModifier(IRType* valueType); - - void emitInterpolationModifiers(IRInst* varInst, IRType* valueType, VarLayout* layout); - - UInt getRayPayloadLocation(IRInst* inst); - - UInt getCallablePayloadLocation(IRInst* inst); - - void emitGLSLImageFormatModifier(IRInst* var, IRTextureType* resourceType); - - /// Emit modifiers that should apply even for a declaration of an SSA temporary. - void emitIRTempModifiers(IRInst* temp); - - void emitIRVarModifiers(VarLayout* layout, IRInst* varDecl, IRType* varType); - - void emitHLSLParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type); - - /// Emit the array brackets that go on the end of a declaration of the given type. - void emitArrayBrackets(IRType* inType); - - void emitGLSLParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type); - - void emitIRParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type); - - void emitIRVar(IRVar* varDecl); - - void emitIRStructuredBuffer_GLSL(IRGlobalParam* varDecl, IRHLSLStructuredBufferTypeBase* structuredBufferType); - - void emitIRByteAddressBuffer_GLSL(IRGlobalParam* varDecl, IRByteAddressBufferTypeBase* byteAddressBufferType); - - void emitIRGlobalVar(IRGlobalVar* varDecl); - void emitIRGlobalParam(IRGlobalParam* varDecl); - void emitIRGlobalConstantInitializer(IRGlobalConstant* valDecl); - - void emitIRGlobalConstant(IRGlobalConstant* valDecl); - - void emitIRGlobalInst(IRInst* inst); - - void ensureInstOperand(ComputeEmitActionsContext* ctx, IRInst* inst, EmitAction::Level requiredLevel = EmitAction::Level::Definition); - - void ensureInstOperandsRec(ComputeEmitActionsContext* ctx, IRInst* inst); - - void ensureGlobalInst(ComputeEmitActionsContext* ctx, IRInst* inst, EmitAction::Level requiredLevel); - - void computeIREmitActions(IRModule* module, List& ioActions); - - void executeIREmitActions(List const& actions); - void emitIRModule(IRModule* module); - - protected: - - void _requireHalf(); - void _emitCVecType(IROp op, Int size); - void _emitCMatType(IROp op, IRIntegerValue rowCount, IRIntegerValue colCount); - - void _emitCFunc(BuiltInCOp cop, IRType* type); - void _maybeEmitGLSLCast(IRType* castType, IRInst* inst, IREmitMode mode); - - EmitContext* m_context; - SourceStream* m_stream; -}; - -} -#endif diff --git a/source/slang/slang-emit-c-like.cpp b/source/slang/slang-emit-c-like.cpp new file mode 100644 index 000000000..67bf891a4 --- /dev/null +++ b/source/slang/slang-emit-c-like.cpp @@ -0,0 +1,5848 @@ +// slang-emit-c-like.cpp +#include "slang-emit-c-like.h" + +#include "../core/slang-writer.h" +#include "slang-ir-bind-existentials.h" +#include "slang-ir-dce.h" +#include "slang-ir-entry-point-uniforms.h" +#include "slang-ir-glsl-legalize.h" + +#include "slang-ir-link.h" +#include "slang-ir-restructure-scoping.h" +#include "slang-ir-specialize.h" +#include "slang-ir-specialize-resources.h" +#include "slang-ir-ssa.h" +#include "slang-ir-union.h" +#include "slang-ir-validate.h" +#include "slang-legalize-types.h" +#include "slang-lower-to-ir.h" +#include "slang-mangle.h" +#include "slang-name.h" +#include "slang-syntax.h" +#include "slang-type-layout.h" +#include "slang-visitor.h" + +#include "slang-emit-source-writer.h" +#include "slang-mangled-lexer.h" + +#include + +namespace Slang { + +// represents a declarator for use in emitting types +struct CLikeSourceEmitter::EDeclarator +{ + enum class Flavor + { + name, + Array, + UnsizedArray, + }; + Flavor flavor; + EDeclarator* next = nullptr; + + // Used for `Flavor::name` + Name* name; + SourceLoc loc; + + // Used for `Flavor::Array` + IRInst* elementCount; +}; + +struct CLikeSourceEmitter::IRDeclaratorInfo +{ + enum class Flavor + { + Simple, + Ptr, + Array, + }; + + Flavor flavor; + IRDeclaratorInfo* next; + union + { + String const* name; + IRInst* elementCount; + }; +}; + +// A chain of variables to use for emitting semantic/layout info +struct CLikeSourceEmitter::EmitVarChain +{ + VarLayout* varLayout; + EmitVarChain* next; + + EmitVarChain() + : varLayout(0) + , next(0) + {} + + EmitVarChain(VarLayout* varLayout) + : varLayout(varLayout) + , next(0) + {} + + EmitVarChain(VarLayout* varLayout, EmitVarChain* next) + : varLayout(varLayout) + , next(next) + {} +}; + +struct CLikeSourceEmitter::ComputeEmitActionsContext +{ + IRInst* moduleInst; + HashSet openInsts; + Dictionary mapInstToLevel; + List* actions; +}; + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!! CLikeSourceEmitter !!!!!!!!!!!!!!!!!!!!!!!!!! */ + +/* static */CLikeSourceEmitter::SourceStyle CLikeSourceEmitter::getSourceStyle(CodeGenTarget target) +{ + switch (target) + { + default: + case CodeGenTarget::Unknown: + case CodeGenTarget::None: + { + return SourceStyle::Unknown; + } + case CodeGenTarget::GLSL: + case CodeGenTarget::GLSL_Vulkan: + case CodeGenTarget::GLSL_Vulkan_OneDesc: + { + return SourceStyle::GLSL; + } + case CodeGenTarget::HLSL: + { + return SourceStyle::HLSL; + } + case CodeGenTarget::SPIRV: + case CodeGenTarget::SPIRVAssembly: + case CodeGenTarget::DXBytecode: + case CodeGenTarget::DXBytecodeAssembly: + case CodeGenTarget::DXIL: + case CodeGenTarget::DXILAssembly: + { + return SourceStyle::Unknown; + } + case CodeGenTarget::CSource: + { + return SourceStyle::C; + } + case CodeGenTarget::CPPSource: + { + return SourceStyle::CPP; + } + } +} + +CLikeSourceEmitter::CLikeSourceEmitter(const CInfo& cinfo) +{ + m_writer = cinfo.sourceWriter; + m_sourceStyle = getSourceStyle(cinfo.target); + SLANG_ASSERT(m_sourceStyle != SourceStyle::Unknown); + + m_target = cinfo.target; + + m_compileRequest = cinfo.compileRequest; + m_entryPoint = cinfo.entryPoint; + m_effectiveProfile = cinfo.effectiveProfile; + + m_entryPointLayout = cinfo.entryPointLayout; + + m_programLayout = cinfo.programLayout; + m_globalStructLayout = cinfo.globalStructLayout; +} + +// +// Types +// + +void CLikeSourceEmitter::emitDeclarator(EDeclarator* declarator) +{ + if (!declarator) return; + + m_writer->emit(" "); + + switch (declarator->flavor) + { + case EDeclarator::Flavor::name: + m_writer->emitName(declarator->name, declarator->loc); + break; + + case EDeclarator::Flavor::Array: + emitDeclarator(declarator->next); + m_writer->emit("["); + if(auto elementCount = declarator->elementCount) + { + emitVal(elementCount, getInfo(EmitOp::General)); + } + m_writer->emit("]"); + break; + + case EDeclarator::Flavor::UnsizedArray: + emitDeclarator(declarator->next); + m_writer->emit("[]"); + break; + + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unknown declarator flavor"); + break; + } +} + +void CLikeSourceEmitter::emitGLSLTypePrefix(IRType* type, bool promoteHalfToFloat) +{ + switch (type->op) + { + case kIROp_FloatType: + // no prefix + break; + + case kIROp_Int8Type: m_writer->emit("i8"); break; + case kIROp_Int16Type: m_writer->emit("i16"); break; + case kIROp_IntType: m_writer->emit("i"); break; + case kIROp_Int64Type: m_writer->emit("i64"); break; + + case kIROp_UInt8Type: m_writer->emit("u8"); break; + case kIROp_UInt16Type: m_writer->emit("u16"); break; + case kIROp_UIntType: m_writer->emit("u"); break; + case kIROp_UInt64Type: m_writer->emit("u64"); break; + + case kIROp_BoolType: m_writer->emit("b"); break; + + case kIROp_HalfType: + { + _requireHalf(); + if (promoteHalfToFloat) + { + // no prefix + } + else + { + m_writer->emit("f16"); + } + break; + } + case kIROp_DoubleType: m_writer->emit("d"); break; + + case kIROp_VectorType: + emitGLSLTypePrefix(cast(type)->getElementType(), promoteHalfToFloat); + break; + + case kIROp_MatrixType: + emitGLSLTypePrefix(cast(type)->getElementType(), promoteHalfToFloat); + break; + + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled GLSL type prefix"); + break; + } +} + +void CLikeSourceEmitter::emitHLSLTextureType(IRTextureTypeBase* texType) +{ + switch(texType->getAccess()) + { + case SLANG_RESOURCE_ACCESS_READ: + break; + + case SLANG_RESOURCE_ACCESS_READ_WRITE: + m_writer->emit("RW"); + break; + + case SLANG_RESOURCE_ACCESS_RASTER_ORDERED: + m_writer->emit("RasterizerOrdered"); + break; + + case SLANG_RESOURCE_ACCESS_APPEND: + m_writer->emit("Append"); + break; + + case SLANG_RESOURCE_ACCESS_CONSUME: + m_writer->emit("Consume"); + break; + + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled resource access mode"); + break; + } + + switch (texType->GetBaseShape()) + { + case TextureFlavor::Shape::Shape1D: m_writer->emit("Texture1D"); break; + case TextureFlavor::Shape::Shape2D: m_writer->emit("Texture2D"); break; + case TextureFlavor::Shape::Shape3D: m_writer->emit("Texture3D"); break; + case TextureFlavor::Shape::ShapeCube: m_writer->emit("TextureCube"); break; + case TextureFlavor::Shape::ShapeBuffer: m_writer->emit("Buffer"); break; + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled resource shape"); + break; + } + + if (texType->isMultisample()) + { + m_writer->emit("MS"); + } + if (texType->isArray()) + { + m_writer->emit("Array"); + } + m_writer->emit("<"); + emitType(texType->getElementType()); + m_writer->emit(" >"); +} + +void CLikeSourceEmitter::emitGLSLTextureOrTextureSamplerType(IRTextureTypeBase* type, char const* baseName) +{ + if (type->getElementType()->op == kIROp_HalfType) + { + // Texture access is always as float types if half is specified + + } + else + { + emitGLSLTypePrefix(type->getElementType(), true); + } + + m_writer->emit(baseName); + switch (type->GetBaseShape()) + { + case TextureFlavor::Shape::Shape1D: m_writer->emit("1D"); break; + case TextureFlavor::Shape::Shape2D: m_writer->emit("2D"); break; + case TextureFlavor::Shape::Shape3D: m_writer->emit("3D"); break; + case TextureFlavor::Shape::ShapeCube: m_writer->emit("Cube"); break; + case TextureFlavor::Shape::ShapeBuffer: m_writer->emit("Buffer"); break; + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled resource shape"); + break; + } + + if (type->isMultisample()) + { + m_writer->emit("MS"); + } + if (type->isArray()) + { + m_writer->emit("Array"); + } +} + +void CLikeSourceEmitter::emitGLSLTextureType( + IRTextureType* texType) +{ + switch(texType->getAccess()) + { + case SLANG_RESOURCE_ACCESS_READ_WRITE: + case SLANG_RESOURCE_ACCESS_RASTER_ORDERED: + emitGLSLTextureOrTextureSamplerType(texType, "image"); + break; + + default: + emitGLSLTextureOrTextureSamplerType(texType, "texture"); + break; + } +} + +void CLikeSourceEmitter::emitGLSLTextureSamplerType(IRTextureSamplerType* type) +{ + emitGLSLTextureOrTextureSamplerType(type, "sampler"); +} + +void CLikeSourceEmitter::emitGLSLImageType(IRGLSLImageType* type) +{ + emitGLSLTextureOrTextureSamplerType(type, "image"); +} + +void CLikeSourceEmitter::emitTextureType(IRTextureType* texType) +{ + switch(getSourceStyle()) + { + case SourceStyle::HLSL: + emitHLSLTextureType(texType); + break; + + case SourceStyle::GLSL: + emitGLSLTextureType(texType); + break; + + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target"); + break; + } +} + +void CLikeSourceEmitter::emitTextureSamplerType(IRTextureSamplerType* type) +{ + switch(getSourceStyle()) + { + case SourceStyle::GLSL: + emitGLSLTextureSamplerType(type); + break; + + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "this target should see combined texture-sampler types"); + break; + } +} + +void CLikeSourceEmitter::emitImageType(IRGLSLImageType* type) +{ + switch(getSourceStyle()) + { + case SourceStyle::HLSL: + emitHLSLTextureType(type); + break; + + case SourceStyle::GLSL: + emitGLSLImageType(type); + break; + + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "this target should see GLSL image types"); + break; + } +} + +static IROp _getCType(IROp op) +{ + switch (op) + { + case kIROp_VoidType: + case kIROp_BoolType: + { + return op; + } + case kIROp_Int8Type: + case kIROp_Int16Type: + case kIROp_IntType: + case kIROp_UInt8Type: + case kIROp_UInt16Type: + case kIROp_UIntType: + { + // Promote all these to Int + return kIROp_IntType; + } + case kIROp_Int64Type: + case kIROp_UInt64Type: + { + // Promote all these to Int16, we can just vary the call to make these work + return kIROp_Int64Type; + } + case kIROp_DoubleType: + { + return kIROp_DoubleType; + } + case kIROp_HalfType: + case kIROp_FloatType: + { + // Promote both to float + return kIROp_FloatType; + } + default: + { + SLANG_ASSERT(!"Unhandled type"); + return kIROp_undefined; + } + } +} + +static UnownedStringSlice _getCTypeVecPostFix(IROp op) +{ + switch (op) + { + case kIROp_BoolType: return UnownedStringSlice::fromLiteral("B"); + case kIROp_IntType: return UnownedStringSlice::fromLiteral("I"); + case kIROp_FloatType: return UnownedStringSlice::fromLiteral("F"); + case kIROp_Int64Type: return UnownedStringSlice::fromLiteral("I64"); + case kIROp_DoubleType: return UnownedStringSlice::fromLiteral("F64"); + default: return UnownedStringSlice::fromLiteral("?"); + } +} + +#if 0 +static UnownedStringSlice _getCTypeName(IROp op) +{ + switch (op) + { + case kIROp_BoolType: return UnownedStringSlice::fromLiteral("Bool"); + case kIROp_IntType: return UnownedStringSlice::fromLiteral("I32"); + case kIROp_FloatType: return UnownedStringSlice::fromLiteral("F32"); + case kIROp_Int64Type: return UnownedStringSlice::fromLiteral("I64"); + case kIROp_DoubleType: return UnownedStringSlice::fromLiteral("F64"); + default: return UnownedStringSlice::fromLiteral("?"); + } +} +#endif + +void CLikeSourceEmitter::_emitCVecType(IROp op, Int size) +{ + m_writer->emit("Vec"); + const UnownedStringSlice postFix = _getCTypeVecPostFix(_getCType(op)); + m_writer->emit(postFix); + if (postFix.size() > 1) + { + m_writer->emit("_"); + } + m_writer->emit(size); +} + +void CLikeSourceEmitter::_emitCMatType(IROp op, IRIntegerValue rowCount, IRIntegerValue colCount) +{ + m_writer->emit("Mat"); + const UnownedStringSlice postFix = _getCTypeVecPostFix(_getCType(op)); + m_writer->emit(postFix); + if (postFix.size() > 1) + { + m_writer->emit("_"); + } + m_writer->emit(rowCount); + m_writer->emit(colCount); +} + +void CLikeSourceEmitter::_emitCFunc(BuiltInCOp cop, IRType* type) +{ + _emitSimpleType(type); + m_writer->emit("_"); + + switch (cop) + { + case BuiltInCOp::Init: m_writer->emit("init"); + case BuiltInCOp::Splat: m_writer->emit("splat"); break; + } +} + +void CLikeSourceEmitter::emitVectorTypeName(IRType* elementType, IRIntegerValue elementCount) +{ + switch(getSourceStyle()) + { + case SourceStyle::GLSL: + { + if (elementCount > 1) + { + emitGLSLTypePrefix(elementType); + m_writer->emit("vec"); + m_writer->emit(elementCount); + } + else + { + _emitSimpleType(elementType); + } + } + break; + + case SourceStyle::HLSL: + // TODO(tfoley): should really emit these with sugar + m_writer->emit("vector<"); + emitType(elementType); + m_writer->emit(","); + m_writer->emit(elementCount); + m_writer->emit(">"); + break; + + case SourceStyle::C: + case SourceStyle::CPP: + _emitCVecType(elementType->op, Int(elementCount)); + break; + + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target"); + break; + } +} + +void CLikeSourceEmitter::_emitVectorType(IRVectorType* vecType) +{ + IRInst* elementCountInst = vecType->getElementCount(); + if (elementCountInst->op != kIROp_IntLit) + { + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "Expecting an integral size for vector size"); + return; + } + + const IRConstant* irConst = (const IRConstant*)elementCountInst; + const IRIntegerValue elementCount = irConst->value.intVal; + if (elementCount <= 0) + { + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "Vector size must be greater than 0"); + return; + } + + auto* elementType = vecType->getElementType(); + + emitVectorTypeName(elementType, elementCount); +} + +void CLikeSourceEmitter::_emitMatrixType(IRMatrixType* matType) +{ + switch(getSourceStyle()) + { + case SourceStyle::GLSL: + { + emitGLSLTypePrefix(matType->getElementType()); + m_writer->emit("mat"); + emitVal(matType->getRowCount(), getInfo(EmitOp::General)); + // TODO(tfoley): only emit the next bit + // for non-square matrix + m_writer->emit("x"); + emitVal(matType->getColumnCount(), getInfo(EmitOp::General)); + } + break; + + case SourceStyle::HLSL: + // TODO(tfoley): should really emit these with sugar + m_writer->emit("matrix<"); + emitType(matType->getElementType()); + m_writer->emit(","); + emitVal(matType->getRowCount(), getInfo(EmitOp::General)); + m_writer->emit(","); + emitVal(matType->getColumnCount(), getInfo(EmitOp::General)); + m_writer->emit("> "); + break; + + case SourceStyle::CPP: + case SourceStyle::C: + { + const auto rowCount = static_cast(matType->getRowCount())->value.intVal; + const auto colCount = static_cast(matType->getColumnCount())->value.intVal; + + _emitCMatType(matType->getElementType()->op, rowCount, colCount); + break; + } + + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target"); + break; + } +} + +void CLikeSourceEmitter::emitSamplerStateType(IRSamplerStateTypeBase* samplerStateType) +{ + switch(getSourceStyle()) + { + case SourceStyle::HLSL: + default: + switch (samplerStateType->op) + { + case kIROp_SamplerStateType: m_writer->emit("SamplerState"); break; + case kIROp_SamplerComparisonStateType: m_writer->emit("SamplerComparisonState"); break; + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled sampler state flavor"); + break; + } + break; + + case SourceStyle::GLSL: + switch (samplerStateType->op) + { + case kIROp_SamplerStateType: m_writer->emit("sampler"); break; + case kIROp_SamplerComparisonStateType: m_writer->emit("samplerShadow"); break; + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled sampler state flavor"); + break; + } + break; + break; + } +} + +void CLikeSourceEmitter::emitStructuredBufferType(IRHLSLStructuredBufferTypeBase* type) +{ + switch(getSourceStyle()) + { + case SourceStyle::HLSL: + default: + { + switch (type->op) + { + case kIROp_HLSLStructuredBufferType: m_writer->emit("StructuredBuffer"); break; + case kIROp_HLSLRWStructuredBufferType: m_writer->emit("RWStructuredBuffer"); break; + case kIROp_HLSLRasterizerOrderedStructuredBufferType: m_writer->emit("RasterizerOrderedStructuredBuffer"); break; + case kIROp_HLSLAppendStructuredBufferType: m_writer->emit("AppendStructuredBuffer"); break; + case kIROp_HLSLConsumeStructuredBufferType: m_writer->emit("ConsumeStructuredBuffer"); break; + + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled structured buffer type"); + break; + } + + m_writer->emit("<"); + emitType(type->getElementType()); + m_writer->emit(" >"); + } + break; + + case SourceStyle::GLSL: + // TODO: We desugar global variables with structured-buffer type into GLSL + // `buffer` declarations, but we don't currently handle structured-buffer types + // in other contexts (e.g., as function parameters). The simplest thing to do + // would be to emit a `StructuredBuffer` as `Foo[]` and `RWStructuredBuffer` + // as `in out Foo[]`, but that is starting to get into the realm of transformations + // that should really be handled during legalization, rather than during emission. + // + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "structured buffer type used unexpectedly"); + break; + } +} + +void CLikeSourceEmitter::emitUntypedBufferType(IRUntypedBufferResourceType* type) +{ + switch(getSourceStyle()) + { + case SourceStyle::HLSL: + default: + { + switch (type->op) + { + case kIROp_HLSLByteAddressBufferType: m_writer->emit("ByteAddressBuffer"); break; + case kIROp_HLSLRWByteAddressBufferType: m_writer->emit("RWByteAddressBuffer"); break; + case kIROp_HLSLRasterizerOrderedByteAddressBufferType: m_writer->emit("RasterizerOrderedByteAddressBuffer"); break; + case kIROp_RaytracingAccelerationStructureType: m_writer->emit("RaytracingAccelerationStructure"); break; + + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled buffer type"); + break; + } + } + break; + + case SourceStyle::GLSL: + { + switch (type->op) + { + case kIROp_RaytracingAccelerationStructureType: + requireGLSLExtension("GL_NV_ray_tracing"); + m_writer->emit("accelerationStructureNV"); + break; + + // TODO: These "translations" are obviously wrong for GLSL. + case kIROp_HLSLByteAddressBufferType: m_writer->emit("ByteAddressBuffer"); break; + case kIROp_HLSLRWByteAddressBufferType: m_writer->emit("RWByteAddressBuffer"); break; + case kIROp_HLSLRasterizerOrderedByteAddressBufferType: m_writer->emit("RasterizerOrderedByteAddressBuffer"); break; + + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled buffer type"); + break; + } + } + break; + } +} + +void CLikeSourceEmitter::_requireHalf() +{ + if (getSourceStyle() == SourceStyle::GLSL) + { + m_glslExtensionTracker.requireHalfExtension(); + } +} + +void CLikeSourceEmitter::_emitSimpleType(IRType* type) +{ + switch (type->op) + { + default: + break; + + case kIROp_VoidType: m_writer->emit("void"); return; + case kIROp_BoolType: m_writer->emit("bool"); return; + + case kIROp_Int8Type: m_writer->emit("int8_t"); return; + case kIROp_Int16Type: m_writer->emit("int16_t"); return; + case kIROp_IntType: m_writer->emit("int"); return; + case kIROp_Int64Type: m_writer->emit("int64_t"); return; + + case kIROp_UInt8Type: m_writer->emit("uint8_t"); return; + case kIROp_UInt16Type: m_writer->emit("uint16_t"); return; + case kIROp_UIntType: m_writer->emit("uint"); return; + case kIROp_UInt64Type: m_writer->emit("uint64_t"); return; + + case kIROp_HalfType: + { + _requireHalf(); + if (getSourceStyle() == SourceStyle::GLSL) + { + m_writer->emit("float16_t"); + } + else + { + m_writer->emit("half"); + } + return; + } + case kIROp_FloatType: m_writer->emit("float"); return; + case kIROp_DoubleType: m_writer->emit("double"); return; + + case kIROp_VectorType: + _emitVectorType((IRVectorType*)type); + return; + + case kIROp_MatrixType: + _emitMatrixType((IRMatrixType*)type); + return; + + case kIROp_SamplerStateType: + case kIROp_SamplerComparisonStateType: + emitSamplerStateType(cast(type)); + return; + + case kIROp_StructType: + m_writer->emit(getIRName(type)); + return; + } + + // TODO: Ideally the following should be data-driven, + // based on meta-data attached to the definitions of + // each of these IR opcodes. + + if (auto texType = as(type)) + { + emitTextureType(texType); + return; + } + else if (auto textureSamplerType = as(type)) + { + emitTextureSamplerType(textureSamplerType); + return; + } + else if (auto imageType = as(type)) + { + emitImageType(imageType); + return; + } + else if (auto structuredBufferType = as(type)) + { + emitStructuredBufferType(structuredBufferType); + return; + } + else if(auto untypedBufferType = as(type)) + { + emitUntypedBufferType(untypedBufferType); + return; + } + + // HACK: As a fallback for HLSL targets, assume that the name of the + // instruction being used is the same as the name of the HLSL type. + if(getSourceStyle() == SourceStyle::HLSL) + { + auto opInfo = getIROpInfo(type->op); + m_writer->emit(opInfo.name); + UInt operandCount = type->getOperandCount(); + if(operandCount) + { + m_writer->emit("<"); + for(UInt ii = 0; ii < operandCount; ++ii) + { + if(ii != 0) m_writer->emit(", "); + emitVal(type->getOperand(ii), getInfo(EmitOp::General)); + } + m_writer->emit(" >"); + } + + return; + } + + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled type"); +} + +void CLikeSourceEmitter::_emitArrayType(IRArrayType* arrayType, EDeclarator* declarator) +{ + EDeclarator arrayDeclarator; + arrayDeclarator.flavor = EDeclarator::Flavor::Array; + arrayDeclarator.next = declarator; + arrayDeclarator.elementCount = arrayType->getElementCount(); + + _emitType(arrayType->getElementType(), &arrayDeclarator); +} + +void CLikeSourceEmitter::_emitUnsizedArrayType(IRUnsizedArrayType* arrayType, EDeclarator* declarator) +{ + EDeclarator arrayDeclarator; + arrayDeclarator.flavor = EDeclarator::Flavor::UnsizedArray; + arrayDeclarator.next = declarator; + + _emitType(arrayType->getElementType(), &arrayDeclarator); +} + +void CLikeSourceEmitter::_emitType(IRType* type, EDeclarator* declarator) +{ + switch (type->op) + { + default: + _emitSimpleType(type); + emitDeclarator(declarator); + break; + + case kIROp_RateQualifiedType: + { + auto rateQualifiedType = cast(type); + _emitType(rateQualifiedType->getValueType(), declarator); + } + break; + + case kIROp_ArrayType: + _emitArrayType(cast(type), declarator); + break; + + case kIROp_UnsizedArrayType: + _emitUnsizedArrayType(cast(type), declarator); + break; + } + +} + +void CLikeSourceEmitter::emitType( + IRType* type, + SourceLoc const& typeLoc, + Name* name, + SourceLoc const& nameLoc) +{ + m_writer->advanceToSourceLocation(typeLoc); + + EDeclarator nameDeclarator; + nameDeclarator.flavor = EDeclarator::Flavor::name; + nameDeclarator.name = name; + nameDeclarator.loc = nameLoc; + _emitType(type, &nameDeclarator); +} + +void CLikeSourceEmitter::emitType(IRType* type, Name* name) +{ + emitType(type, SourceLoc(), name, SourceLoc()); +} + +void CLikeSourceEmitter::emitType(IRType* type, const String& name) +{ + // HACK: the rest of the code wants a `Name`, + // so we'll create one for a bit... + Name tempName; + tempName.text = name; + + emitType(type, SourceLoc(), &tempName, SourceLoc()); +} + + +void CLikeSourceEmitter::emitType(IRType* type) +{ + _emitType(type, nullptr); +} + +// +// Expressions +// + +bool CLikeSourceEmitter::maybeEmitParens(EmitOpInfo& outerPrec, EmitOpInfo prec) +{ + bool needParens = (prec.leftPrecedence <= outerPrec.leftPrecedence) + || (prec.rightPrecedence <= outerPrec.rightPrecedence); + + if (needParens) + { + m_writer->emit("("); + + outerPrec = getInfo(EmitOp::None); + } + return needParens; +} + +void CLikeSourceEmitter::maybeCloseParens(bool needClose) +{ + if(needClose) m_writer->emit(")"); +} + +bool CLikeSourceEmitter::isTargetIntrinsicModifierApplicable(const String& targetName) +{ + switch(getSourceStyle()) + { + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target"); + return false; + + case SourceStyle::C: return targetName == "c"; + case SourceStyle::CPP: return targetName == "cpp"; + case SourceStyle::GLSL: return targetName == "glsl"; + case SourceStyle::HLSL: return targetName == "hlsl"; + } +} + +void CLikeSourceEmitter::emitType(IRType* type, Name* name, SourceLoc const& nameLoc) +{ + emitType( + type, + SourceLoc(), + name, + nameLoc); +} + +void CLikeSourceEmitter::emitType(IRType* type, NameLoc const& nameAndLoc) +{ + emitType(type, nameAndLoc.name, nameAndLoc.loc); +} + +bool CLikeSourceEmitter::isTargetIntrinsicModifierApplicable( + IRTargetIntrinsicDecoration* decoration) +{ + auto targetName = String(decoration->getTargetName()); + + // If no target name was specified, then the modifier implicitly + // applies to all targets. + if(targetName.getLength() == 0) + return true; + + return isTargetIntrinsicModifierApplicable(targetName); +} + +void CLikeSourceEmitter::emitStringLiteral( + String const& value) +{ + m_writer->emit("\""); + for (auto c : value) + { + // TODO: This needs a more complete implementation, + // especially if we want to support Unicode. + + char buffer[] = { c, 0 }; + switch (c) + { + default: + m_writer->emit(buffer); + break; + + case '\"': m_writer->emit("\\\""); + case '\'': m_writer->emit("\\\'"); + case '\\': m_writer->emit("\\\\"); + case '\n': m_writer->emit("\\n"); + case '\r': m_writer->emit("\\r"); + case '\t': m_writer->emit("\\t"); + } + } + m_writer->emit("\""); +} + +void CLikeSourceEmitter::requireGLSLExtension(String const& name) +{ + m_glslExtensionTracker.requireExtension(name); +} + +void CLikeSourceEmitter::requireGLSLVersion(ProfileVersion version) +{ + if (getSourceStyle() != SourceStyle::GLSL) + return; + + m_glslExtensionTracker.requireVersion(version); +} + +void CLikeSourceEmitter::requireGLSLVersion(int version) +{ + switch (version) + { +#define CASE(NUMBER) \ + case NUMBER: requireGLSLVersion(ProfileVersion::GLSL_##NUMBER); break + + CASE(110); + CASE(120); + CASE(130); + CASE(140); + CASE(150); + CASE(330); + CASE(400); + CASE(410); + CASE(420); + CASE(430); + CASE(440); + CASE(450); + +#undef CASE + } +} + +void CLikeSourceEmitter::setSampleRateFlag() +{ + m_entryPointLayout->flags |= EntryPointLayout::Flag::usesAnySampleRateInput; +} + +void CLikeSourceEmitter::doSampleRateInputCheck(Name* name) +{ + auto text = getText(name); + if (text == "gl_SampleID") + { + setSampleRateFlag(); + } +} + +void CLikeSourceEmitter::emitVal(IRInst* val, EmitOpInfo const& outerPrec) +{ + if(auto type = as(val)) + { + emitType(type); + } + else + { + emitIRInstExpr(val, IREmitMode::Default, outerPrec); + } +} + +UInt CLikeSourceEmitter::getBindingOffset(EmitVarChain* chain, LayoutResourceKind kind) +{ + UInt offset = 0; + for(auto cc = chain; cc; cc = cc->next) + { + if(auto resInfo = cc->varLayout->FindResourceInfo(kind)) + { + offset += resInfo->index; + } + } + return offset; +} + +UInt CLikeSourceEmitter::getBindingSpace(EmitVarChain* chain, LayoutResourceKind kind) +{ + UInt space = 0; + for(auto cc = chain; cc; cc = cc->next) + { + auto varLayout = cc->varLayout; + if(auto resInfo = varLayout->FindResourceInfo(kind)) + { + space += resInfo->space; + } + if(auto resInfo = varLayout->FindResourceInfo(LayoutResourceKind::RegisterSpace)) + { + space += resInfo->index; + } + } + return space; +} + +void CLikeSourceEmitter::emitHLSLRegisterSemantic(LayoutResourceKind kind, EmitVarChain* chain, char const* uniformSemanticSpelling) +{ + if(!chain) + return; + if(!chain->varLayout->FindResourceInfo(kind)) + return; + + UInt index = getBindingOffset(chain, kind); + UInt space = getBindingSpace(chain, kind); + + switch(kind) + { + case LayoutResourceKind::Uniform: + { + UInt offset = index; + + // The HLSL `c` register space is logically grouped in 16-byte registers, + // while we try to traffic in byte offsets. That means we need to pick + // a register number, based on the starting offset in 16-byte register + // units, and then a "component" within that register, based on 4-byte + // offsets from there. We cannot support more fine-grained offsets than that. + + m_writer->emit(" : "); + m_writer->emit(uniformSemanticSpelling); + m_writer->emit("(c"); + + // Size of a logical `c` register in bytes + auto registerSize = 16; + + // Size of each component of a logical `c` register, in bytes + auto componentSize = 4; + + size_t startRegister = offset / registerSize; + m_writer->emit(int(startRegister)); + + size_t byteOffsetInRegister = offset % registerSize; + + // If this field doesn't start on an even register boundary, + // then we need to emit additional information to pick the + // right component to start from + if (byteOffsetInRegister != 0) + { + // The value had better occupy a whole number of components. + SLANG_RELEASE_ASSERT(byteOffsetInRegister % componentSize == 0); + + size_t startComponent = byteOffsetInRegister / componentSize; + + static const char* kComponentNames[] = {"x", "y", "z", "w"}; + m_writer->emit("."); + m_writer->emit(kComponentNames[startComponent]); + } + m_writer->emit(")"); + } + break; + + case LayoutResourceKind::RegisterSpace: + case LayoutResourceKind::GenericResource: + case LayoutResourceKind::ExistentialTypeParam: + case LayoutResourceKind::ExistentialObjectParam: + // ignore + break; + default: + { + m_writer->emit(" : register("); + switch( kind ) + { + case LayoutResourceKind::ConstantBuffer: + m_writer->emit("b"); + break; + case LayoutResourceKind::ShaderResource: + m_writer->emit("t"); + break; + case LayoutResourceKind::UnorderedAccess: + m_writer->emit("u"); + break; + case LayoutResourceKind::SamplerState: + m_writer->emit("s"); + break; + default: + SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled HLSL register type"); + break; + } + m_writer->emit(index); + if(space) + { + m_writer->emit(", space"); + m_writer->emit(space); + } + m_writer->emit(")"); + } + } +} + +void CLikeSourceEmitter::emitHLSLRegisterSemantics(EmitVarChain* chain, char const* uniformSemanticSpelling) +{ + if (!chain) return; + + auto layout = chain->varLayout; + + switch( getSourceStyle()) + { + default: + return; + + case SourceStyle::HLSL: + break; + } + + for( auto rr : layout->resourceInfos ) + { + emitHLSLRegisterSemantic(rr.kind, chain, uniformSemanticSpelling); + } +} + +void CLikeSourceEmitter::emitHLSLRegisterSemantics(VarLayout* varLayout, char const* uniformSemanticSpelling) +{ + if(!varLayout) + return; + + EmitVarChain chain(varLayout); + emitHLSLRegisterSemantics(&chain, uniformSemanticSpelling); +} + +void CLikeSourceEmitter::emitHLSLParameterGroupFieldLayoutSemantics(EmitVarChain* chain) +{ + if(!chain) + return; + + auto layout = chain->varLayout; + for( auto rr : layout->resourceInfos ) + { + emitHLSLRegisterSemantic(rr.kind, chain, "packoffset"); + } +} + + +void CLikeSourceEmitter::emitHLSLParameterGroupFieldLayoutSemantics(RefPtr fieldLayout, EmitVarChain* inChain) +{ + EmitVarChain chain(fieldLayout, inChain); + emitHLSLParameterGroupFieldLayoutSemantics(&chain); +} + +bool CLikeSourceEmitter::emitGLSLLayoutQualifier(LayoutResourceKind kind, EmitVarChain* chain) +{ + if(!chain) + return false; + if(!chain->varLayout->FindResourceInfo(kind)) + return false; + + UInt index = getBindingOffset(chain, kind); + UInt space = getBindingSpace(chain, kind); + switch(kind) + { + case LayoutResourceKind::Uniform: + { + // Explicit offsets require a GLSL extension (which + // is not universally supported, it seems) or a new + // enough GLSL version (which we don't want to + // universally require), so for right now we + // won't actually output explicit offsets for uniform + // shader parameters. + // + // TODO: We should fix this so that we skip any + // extra work for parameters that are laid out as + // expected by the default rules, but do *something* + // for parameters that need non-default layout. + // + // Using the `GL_ARB_enhanced_layouts` feature is one + // option, but we should also be able to do some + // things by introducing padding into the declaration + // (padding insertion would probably be best done at + // the IR level). + bool useExplicitOffsets = false; + if (useExplicitOffsets) + { + requireGLSLExtension("GL_ARB_enhanced_layouts"); + + m_writer->emit("layout(offset = "); + m_writer->emit(index); + m_writer->emit(")\n"); + } + } + break; + + case LayoutResourceKind::VertexInput: + case LayoutResourceKind::FragmentOutput: + m_writer->emit("layout(location = "); + m_writer->emit(index); + m_writer->emit(")\n"); + break; + + case LayoutResourceKind::SpecializationConstant: + m_writer->emit("layout(constant_id = "); + m_writer->emit(index); + m_writer->emit(")\n"); + break; + + case LayoutResourceKind::ConstantBuffer: + case LayoutResourceKind::ShaderResource: + case LayoutResourceKind::UnorderedAccess: + case LayoutResourceKind::SamplerState: + case LayoutResourceKind::DescriptorTableSlot: + m_writer->emit("layout(binding = "); + m_writer->emit(index); + if(space) + { + m_writer->emit(", set = "); + m_writer->emit(space); + } + m_writer->emit(")\n"); + break; + + case LayoutResourceKind::PushConstantBuffer: + m_writer->emit("layout(push_constant)\n"); + break; + case LayoutResourceKind::ShaderRecord: + m_writer->emit("layout(shaderRecordNV)\n"); + break; + + } + return true; +} + +void CLikeSourceEmitter::emitGLSLLayoutQualifiers(RefPtr layout, EmitVarChain* inChain, LayoutResourceKind filter) +{ + if(!layout) return; + + switch( getSourceStyle()) + { + default: + return; + + case SourceStyle::GLSL: + break; + } + + EmitVarChain chain(layout, inChain); + + for( auto info : layout->resourceInfos ) + { + // Skip info that doesn't match our filter + if (filter != LayoutResourceKind::None + && filter != info.kind) + { + continue; + } + + emitGLSLLayoutQualifier(info.kind, &chain); + } +} + +void CLikeSourceEmitter::emitGLSLVersionDirective() +{ + auto effectiveProfile = m_effectiveProfile; + if(effectiveProfile.getFamily() == ProfileFamily::GLSL) + { + requireGLSLVersion(effectiveProfile.GetVersion()); + } + + // HACK: We aren't picking GLSL versions carefully right now, + // and so we might end up only requiring the initial 1.10 version, + // even though even basic functionality needs a higher version. + // + // For now, we'll work around this by just setting the minimum required + // version to a high one: + // + // TODO: Either correctly compute a minimum required version, or require + // the user to specify a version as part of the target. + m_glslExtensionTracker.requireVersion(ProfileVersion::GLSL_450); + + auto requiredProfileVersion = m_glslExtensionTracker.getRequiredProfileVersion(); + switch (requiredProfileVersion) + { +#define CASE(TAG, VALUE) \ + case ProfileVersion::TAG: m_writer->emit("#version " #VALUE "\n"); return + + CASE(GLSL_110, 110); + CASE(GLSL_120, 120); + CASE(GLSL_130, 130); + CASE(GLSL_140, 140); + CASE(GLSL_150, 150); + CASE(GLSL_330, 330); + CASE(GLSL_400, 400); + CASE(GLSL_410, 410); + CASE(GLSL_420, 420); + CASE(GLSL_430, 430); + CASE(GLSL_440, 440); + CASE(GLSL_450, 450); + CASE(GLSL_460, 460); +#undef CASE + + default: + break; + } + + // No information is available for us to guess a profile, + // so it seems like we need to pick one out of thin air. + // + // Ideally we should infer a minimum required version based + // on the constructs we have seen used in the user's code + // + // For now we just fall back to a reasonably recent version. + + m_writer->emit("#version 420\n"); +} + +void CLikeSourceEmitter::emitGLSLPreprocessorDirectives() +{ + switch(getSourceStyle()) + { + // Don't emit this stuff unless we are targeting GLSL + default: + return; + + case SourceStyle::GLSL: + break; + } + + emitGLSLVersionDirective(); +} + +void CLikeSourceEmitter::emitLayoutDirectives(TargetRequest* targetReq) +{ + // We are going to emit the target-language-specific directives + // needed to get the default matrix layout to match what was requested + // for the given target. + // + // Note: we do not rely on the defaults for the target language, + // because a user could take the HLSL/GLSL generated by Slang and pass + // it to another compiler with non-default options specified on + // the command line, leading to all kinds of trouble. + // + // TODO: We need an approach to "global" layout directives that will work + // in the presence of multiple modules. If modules A and B were each + // compiled with different assumptions about how layout is performed, + // then types/variables defined in those modules should be emitted in + // a way that is consistent with that layout... + + auto matrixLayoutMode = targetReq->getDefaultMatrixLayoutMode(); + + switch(getSourceStyle()) + { + default: + return; + + case SourceStyle::GLSL: + // Reminder: the meaning of row/column major layout + // in our semantics is the *opposite* of what GLSL + // calls them, because what they call "columns" + // are what we call "rows." + // + switch(matrixLayoutMode) + { + case kMatrixLayoutMode_RowMajor: + default: + m_writer->emit("layout(column_major) uniform;\n"); + m_writer->emit("layout(column_major) buffer;\n"); + break; + + case kMatrixLayoutMode_ColumnMajor: + m_writer->emit("layout(row_major) uniform;\n"); + m_writer->emit("layout(row_major) buffer;\n"); + break; + } + break; + + case SourceStyle::HLSL: + switch(matrixLayoutMode) + { + case kMatrixLayoutMode_RowMajor: + default: + m_writer->emit("#pragma pack_matrix(row_major)\n"); + break; + + case kMatrixLayoutMode_ColumnMajor: + m_writer->emit("#pragma pack_matrix(column_major)\n"); + break; + } + break; + } +} + +UInt CLikeSourceEmitter::allocateUniqueID() +{ + return m_uniqueIDCounter++; +} + +// IR-level emit logic + +UInt CLikeSourceEmitter::getID(IRInst* value) +{ + auto& mapIRValueToID = m_mapIRValueToID; + + UInt id = 0; + if (mapIRValueToID.TryGetValue(value, id)) + return id; + + id = allocateUniqueID(); + mapIRValueToID.Add(value, id); + return id; +} + +/// "Scrub" a name so that it complies with restrictions of the target language. +String CLikeSourceEmitter::scrubName(const String& name) +{ + // We will use a plain `U` as a dummy character to insert + // whenever we need to insert things to make a string into + // valid name. + // + char const* dummyChar = "U"; + + // Special case a name that is the empty string, just in case. + if(name.getLength() == 0) + return dummyChar; + + // Otherwise, we are going to walk over the name byte by byte + // and write some legal characters to the output as we go. + StringBuilder sb; + + if(getSourceStyle() == SourceStyle::GLSL) + { + // GLSL reserverse all names that start with `gl_`, + // so if we are in danger of collision, then make + // our name start with a dummy character instead. + if(name.startsWith("gl_")) + { + sb.append(dummyChar); + } + } + + // We will also detect user-defined names that + // might overlap with our convention for mangled names, + // to avoid an possible collision. + if(name.startsWith("_S")) + { + sb.Append(dummyChar); + } + + // TODO: This is where we might want to consult + // a dictionary of reserved words for the chosen target + // + // if(isReservedWord(name)) { sb.Append(dummyChar); } + // + + // We need to track the previous byte in + // order to detect consecutive underscores for GLSL. + int prevChar = -1; + + for(auto c : name) + { + // We will treat a dot character just like an underscore + // for the purposes of producing a scrubbed name, so + // that we translate `SomeType.someMethod` into + // `SomeType_someMethod`. + // + // By handling this case at the top of this loop, we + // ensure that a `.`-turned-`_` is handled just like + // a `_` in the original name, and will be properly + // scrubbed for GLSL output. + // + if(c == '.') + { + c = '_'; + } + + if(((c >= 'a') && (c <= 'z')) + || ((c >= 'A') && (c <= 'Z'))) + { + // Ordinary ASCII alphabetic characters are assumed + // to always be okay. + } + else if((c >= '0') && (c <= '9')) + { + // We don't want to allow a digit as the first + // byte in a name, since the result wouldn't + // be a valid identifier in many target languages. + if(prevChar == -1) + { + sb.append(dummyChar); + } + } + else if(c == '_') + { + // We will collapse any consecutive sequence of `_` + // characters into a single one (this means that + // some names that were unique in the original + // code might not resolve to unique names after + // scrubbing, but that was true in general). + + if(prevChar == '_') + { + // Skip this underscore, so we don't output + // more than one in a row. + continue; + } + } + else + { + // If we run into a character that wouldn't normally + // be allowed in an identifier, we need to translate + // it into something that *is* valid. + // + // Our solution for now will be very clumsy: we will + // emit `x` and then the hexadecimal version of + // the byte we were given. + sb.append("x"); + sb.append(uint32_t((unsigned char) c), 16); + + // We don't want to apply the default handling below, + // so skip to the top of the loop now. + prevChar = c; + continue; + } + + sb.append(c); + prevChar = c; + } + + return sb.ProduceString(); +} + +String CLikeSourceEmitter::generateIRName(IRInst* inst) +{ + // If the instruction names something + // that should be emitted as a target intrinsic, + // then use that name instead. + if(auto intrinsicDecoration = findTargetIntrinsicDecoration(inst)) + { + return String(intrinsicDecoration->getDefinition()); + } + + // If we have a name hint on the instruction, then we will try to use that + // to provide the actual name in the output code. + // + // We need to be careful that the name follows the rules of the target language, + // so there is a "scrubbing" step that needs to be applied here. + // + // We also need to make sure that the name won't collide with other declarations + // that might have the same name hint applied, so we will still unique + // them by appending the numeric ID of the instruction. + // + // TODO: Find cases where we can drop the suffix safely. + // + // TODO: When we start having to handle symbols with external linkage for + // things like DXIL libraries, we will need to *not* use the friendly + // names for stuff that should be link-able. + // + if(auto nameHintDecoration = inst->findDecoration()) + { + // The name we output will basically be: + // + // _ + // + // Except that we will "scrub" the name hint first, + // and we will omit the underscore if the (scrubbed) + // name hint already ends with one. + // + + String nameHint = nameHintDecoration->getName(); + nameHint = scrubName(nameHint); + + StringBuilder sb; + sb.append(nameHint); + + // Avoid introducing a double underscore + if(!nameHint.endsWith("_")) + { + sb.append("_"); + } + + String key = sb.ProduceString(); + UInt count = 0; + m_uniqueNameCounters.TryGetValue(key, count); + + m_uniqueNameCounters[key] = count+1; + + sb.append(Int32(count)); + return sb.ProduceString(); + } + + // If the instruction has a mangled name, then emit using that. + if(auto linkageDecoration = inst->findDecoration()) + { + return linkageDecoration->getMangledName(); + } + + // Otherwise fall back to a construct temporary name + // for the instruction. + StringBuilder sb; + sb << "_S"; + sb << Int32(getID(inst)); + + return sb.ProduceString(); +} + +String CLikeSourceEmitter::getIRName(IRInst* inst) +{ + String name; + if(!m_mapInstToName.TryGetValue(inst, name)) + { + name = generateIRName(inst); + m_mapInstToName.Add(inst, name); + } + return name; +} +void CLikeSourceEmitter::emitDeclarator(IRDeclaratorInfo* declarator) +{ + if(!declarator) + return; + + switch( declarator->flavor ) + { + case IRDeclaratorInfo::Flavor::Simple: + m_writer->emit(" "); + m_writer->emit(*declarator->name); + break; + + case IRDeclaratorInfo::Flavor::Ptr: + m_writer->emit("*"); + emitDeclarator(declarator->next); + break; + + case IRDeclaratorInfo::Flavor::Array: + emitDeclarator(declarator->next); + m_writer->emit("["); + emitIROperand(declarator->elementCount, IREmitMode::Default, getInfo(EmitOp::General)); + m_writer->emit("]"); + break; + } +} + +void CLikeSourceEmitter::emitIRSimpleValue(IRInst* inst) +{ + switch(inst->op) + { + case kIROp_IntLit: + m_writer->emit(((IRConstant*) inst)->value.intVal); + break; + + case kIROp_FloatLit: + m_writer->emit(((IRConstant*) inst)->value.floatVal); + break; + + case kIROp_BoolLit: + { + bool val = ((IRConstant*)inst)->value.intVal != 0; + m_writer->emit(val ? "true" : "false"); + } + break; + + default: + SLANG_UNIMPLEMENTED_X("val case for emit"); + break; + } + +} + +bool CLikeSourceEmitter::shouldFoldIRInstIntoUseSites(IRInst* inst, IREmitMode mode) +{ + // Certain opcodes should never/always be folded in + switch( inst->op ) + { + default: + break; + + // Never fold these in, because they represent declarations + // + case kIROp_Var: + case kIROp_GlobalVar: + case kIROp_GlobalConstant: + case kIROp_GlobalParam: + case kIROp_Param: + case kIROp_Func: + return false; + + // Always fold these in, because they are trivial + // + case kIROp_IntLit: + case kIROp_FloatLit: + case kIROp_BoolLit: + return true; + + // Always fold these in, because their results + // cannot be represented in the type system of + // our current targets. + // + // TODO: when we add C/C++ as an optional target, + // we could consider lowering insts that result + // in pointers directly. + // + case kIROp_FieldAddress: + case kIROp_getElementPtr: + case kIROp_Specialize: + return true; + } + + // Always fold when we are inside a global constant initializer + if (mode == IREmitMode::GlobalConstant) + return true; + + switch( inst->op ) + { + default: + break; + + // HACK: don't fold these in because we currently lower + // them to initializer lists, which aren't allowed in + // general expression contexts. + // + // Note: we are doing this check *after* the check for `GlobalConstant` + // mode, because otherwise we'd fail to emit initializer lists in + // the main place where we want/need them. + // + case kIROp_makeStruct: + case kIROp_makeArray: + return false; + + } + + // Instructions with specific result *types* will usually + // want to be folded in, because they aren't allowed as types + // for temporary variables. + auto type = inst->getDataType(); + + // Unwrap any layers of array-ness from the type, so that + // we can look at the underlying data type, in case we + // should *never* expose a value of that type + while (auto arrayType = as(type)) + { + type = arrayType->getElementType(); + } + + // Don't allow temporaries of pointer types to be created. + if(as(type)) + { + return true; + } + + // First we check for uniform parameter groups, + // because a `cbuffer` or GLSL `uniform` block + // does not have a first-class type that we can + // pass around. + // + // TODO: We need to ensure that type legalization + // cleans up cases where we use a parameter group + // or parameter block type as a function parameter... + // + if(as(type)) + { + // TODO: we need to be careful here, because + // HLSL shader model 6 allows these as explicit + // types. + return true; + } + // + // The stream-output and patch types need to be handled + // too, because they are not really first class (especially + // not in GLSL, but they also seem to confuse the HLSL + // compiler when they get used as temporaries). + // + else if (as(type)) + { + return true; + } + else if (as(type)) + { + return true; + } + + + // GLSL doesn't allow texture/resource types to + // be used as first-class values, so we need + // to fold them into their use sites in all cases + if (getSourceStyle() == SourceStyle::GLSL) + { + if(as(type)) + { + return true; + } + else if(as(type)) + { + return true; + } + else if(as(type)) + { + return true; + } + else if(as(type)) + { + return true; + } + } + + // If the instruction is at global scope, then it might represent + // a constant (e.g., the value of an enum case). + // + if(as(inst->getParent())) + { + if(!inst->mightHaveSideEffects()) + return true; + } + + // Having dealt with all of the cases where we *must* fold things + // above, we can now deal with the more general cases where we + // *should not* fold things. + + // Don't fold something with no users: + if(!inst->hasUses()) + return false; + + // Don't fold something that has multiple users: + if(inst->hasMoreThanOneUse()) + return false; + + // Don't fold something that might have side effects: + if(inst->mightHaveSideEffects()) + return false; + + // Don't fold instructions that are marked `[precise]`. + // This could in principle be extended to any other + // decorations that affect the semantics of an instruction + // in ways that require a temporary to be introduced. + // + if(inst->findDecoration()) + return false; + + // Okay, at this point we know our instruction must have a single use. + auto use = inst->firstUse; + SLANG_ASSERT(use); + SLANG_ASSERT(!use->nextUse); + + auto user = use->getUser(); + + // We'd like to figure out if it is safe to fold our instruction into `user` + + // First, let's make sure they are in the same block/parent: + if(inst->getParent() != user->getParent()) + return false; + + // Now let's look at all the instructions between this instruction + // and the user. If any of them might have side effects, then lets + // bail out now. + for(auto ii = inst->getNextInst(); ii != user; ii = ii->getNextInst()) + { + if(!ii) + { + // We somehow reached the end of the block without finding + // the user, which doesn't make sense if uses dominate + // defs. Let's just play it safe and bail out. + return false; + } + + if(ii->mightHaveSideEffects()) + return false; + } + + // Okay, if we reach this point then the user comes later in + // the same block, and there are no instructions with side + // effects in between, so it seems safe to fold things in. + return true; +} + +void CLikeSourceEmitter::emitIROperand(IRInst* inst, IREmitMode mode, EmitOpInfo const& outerPrec) +{ + if( shouldFoldIRInstIntoUseSites(inst, mode) ) + { + emitIRInstExpr(inst, mode, outerPrec); + return; + } + + switch(inst->op) + { + case 0: // nothing yet + default: + m_writer->emit(getIRName(inst)); + break; + } +} + +void CLikeSourceEmitter::emitIRArgs(IRInst* inst, IREmitMode mode) +{ + UInt argCount = inst->getOperandCount(); + IRUse* args = inst->getOperands(); + + m_writer->emit("("); + for(UInt aa = 0; aa < argCount; ++aa) + { + if(aa != 0) m_writer->emit(", "); + emitIROperand(args[aa].get(), mode, getInfo(EmitOp::General)); + } + m_writer->emit(")"); +} + +void CLikeSourceEmitter::emitIRType(IRType* type, String const& name) +{ + emitType(type, name); +} + +void CLikeSourceEmitter::emitIRType(IRType* type, Name* name) +{ + emitType(type, name); +} + +void CLikeSourceEmitter::emitIRType(IRType* type) +{ + emitType(type); +} + +void CLikeSourceEmitter::emitIRRateQualifiers(IRRate* rate) +{ + if(!rate) return; + + if(as(rate)) + { + switch( getSourceStyle() ) + { + case SourceStyle::GLSL: + m_writer->emit("const "); + break; + + default: + break; + } + } + + if (as(rate)) + { + switch(getSourceStyle()) + { + case SourceStyle::HLSL: + m_writer->emit("groupshared "); + break; + + case SourceStyle::GLSL: + m_writer->emit("shared "); + break; + + default: + break; + } + } +} + +void CLikeSourceEmitter::emitIRRateQualifiers(IRInst* value) +{ + emitIRRateQualifiers(value->getRate()); +} + +void CLikeSourceEmitter::emitIRInstResultDecl(IRInst* inst) +{ + auto type = inst->getDataType(); + if(!type) + return; + + if (as(type)) + return; + + emitIRTempModifiers(inst); + + emitIRRateQualifiers(inst); + + emitIRType(type, getIRName(inst)); + m_writer->emit(" = "); +} + +IRTargetIntrinsicDecoration* CLikeSourceEmitter::findTargetIntrinsicDecoration(IRInst* inst) +{ + for(auto dd : inst->getDecorations()) + { + if (dd->op != kIROp_TargetIntrinsicDecoration) + continue; + + auto targetIntrinsic = (IRTargetIntrinsicDecoration*)dd; + if (isTargetIntrinsicModifierApplicable(targetIntrinsic)) + return targetIntrinsic; + } + + return nullptr; +} + +/* static */bool CLikeSourceEmitter::isOrdinaryName(String const& name) +{ + char const* cursor = name.begin(); + char const* end = name.end(); + + while(cursor != end) + { + int c = *cursor++; + if( (c >= 'a') && (c <= 'z') ) continue; + if( (c >= 'A') && (c <= 'Z') ) continue; + if( c == '_' ) continue; + + return false; + } + return true; +} + +void CLikeSourceEmitter::emitTargetIntrinsicCallExpr( + IRCall* inst, + IRFunc* /* func */, + IRTargetIntrinsicDecoration* targetIntrinsic, + IREmitMode mode, + EmitOpInfo const& inOuterPrec) +{ + auto outerPrec = inOuterPrec; + + IRUse* args = inst->getOperands(); + Index argCount = inst->getOperandCount(); + + // First operand was the function to be called + args++; + argCount--; + + auto name = String(targetIntrinsic->getDefinition()); + + if(isOrdinaryName(name)) + { + // Simple case: it is just an ordinary name, so we call it like a builtin. + auto prec = getInfo(EmitOp::Postfix); + bool needClose = maybeEmitParens(outerPrec, prec); + + m_writer->emit(name); + m_writer->emit("("); + for (Index aa = 0; aa < argCount; ++aa) + { + if (aa != 0) m_writer->emit(", "); + emitIROperand(args[aa].get(), mode, getInfo(EmitOp::General)); + } + m_writer->emit(")"); + + maybeCloseParens(needClose); + return; + } + else + { + int openParenCount = 0; + + const auto returnType = inst->getDataType(); + + // If it returns void -> then we don't need parenthesis + if (as(returnType) == nullptr) + { + m_writer->emit("("); + openParenCount++; + } + + // General case: we are going to emit some more complex text. + + char const* cursor = name.begin(); + char const* end = name.end(); + while(cursor != end) + { + char c = *cursor++; + if( c != '$' ) + { + // Not an escape sequence + m_writer->emitRawTextSpan(&c, &c+1); + continue; + } + + SLANG_RELEASE_ASSERT(cursor != end); + + char d = *cursor++; + + switch (d) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + { + // Simple case: emit one of the direct arguments to the call + Index argIndex = d - '0'; + SLANG_RELEASE_ASSERT((0 <= argIndex) && (argIndex < argCount)); + m_writer->emit("("); + emitIROperand(args[argIndex].get(), mode, getInfo(EmitOp::General)); + m_writer->emit(")"); + } + break; + + case 'p': + { + // If we are calling a D3D texturing operation in the form t.Foo(s, ...), + // then this form will pair up the t and s arguments as needed for a GLSL + // texturing operation. + SLANG_RELEASE_ASSERT(argCount >= 2); + + auto textureArg = args[0].get(); + auto samplerArg = args[1].get(); + + if (auto baseTextureType = as(textureArg->getDataType())) + { + emitGLSLTextureOrTextureSamplerType(baseTextureType, "sampler"); + + if (auto samplerType = as(samplerArg->getDataType())) + { + if (as(samplerType)) + { + m_writer->emit("Shadow"); + } + } + + m_writer->emit("("); + emitIROperand(textureArg, mode, getInfo(EmitOp::General)); + m_writer->emit(","); + emitIROperand(samplerArg, mode, getInfo(EmitOp::General)); + m_writer->emit(")"); + } + else + { + SLANG_UNEXPECTED("bad format in intrinsic definition"); + } + } + break; + + case 'c': + { + // When doing texture access in glsl the result may need to be cast. + // In particular if the underlying texture is 'half' based, glsl only accesses (read/write) + // as float. So we need to cast to a half type on output. + // When storing into a texture it is still the case the value written must be half - but + // we don't need to do any casting there as half is coerced to float without a problem. + SLANG_RELEASE_ASSERT(argCount >= 1); + + auto textureArg = args[0].get(); + if (auto baseTextureType = as(textureArg->getDataType())) + { + auto elementType = baseTextureType->getElementType(); + IRBasicType* underlyingType = nullptr; + if (auto basicType = as(elementType)) + { + underlyingType = basicType; + } + else if (auto vectorType = as(elementType)) + { + underlyingType = as(vectorType->getElementType()); + } + + // We only need to output a cast if the underlying type is half. + if (underlyingType && underlyingType->op == kIROp_HalfType) + { + _emitSimpleType(elementType); + m_writer->emit("("); + openParenCount++; + } + } + } + break; + + case 'z': + { + // If we are calling a D3D texturing operation in the form t.Foo(s, ...), + // where `t` is a `Texture*`, then this is the step where we try to + // properly swizzle the output of the equivalent GLSL call into the right + // shape. + SLANG_RELEASE_ASSERT(argCount >= 1); + + auto textureArg = args[0].get(); + if (auto baseTextureType = as(textureArg->getDataType())) + { + auto elementType = baseTextureType->getElementType(); + if (auto basicType = as(elementType)) + { + // A scalar result is expected + m_writer->emit(".x"); + } + else if (auto vectorType = as(elementType)) + { + // A vector result is expected + auto elementCount = GetIntVal(vectorType->getElementCount()); + + if (elementCount < 4) + { + char const* swiz[] = { "", ".x", ".xy", ".xyz", "" }; + m_writer->emit(swiz[elementCount]); + } + } + else + { + // What other cases are possible? + } + } + else + { + SLANG_UNEXPECTED("bad format in intrinsic definition"); + } + } + break; + + case 'N': + { + // Extract the element count from a vector argument so that + // we can use it in the constructed expression. + + SLANG_RELEASE_ASSERT(*cursor >= '0' && *cursor <= '9'); + Index argIndex = (*cursor++) - '0'; + SLANG_RELEASE_ASSERT(argCount > argIndex); + + auto vectorArg = args[argIndex].get(); + if (auto vectorType = as(vectorArg->getDataType())) + { + auto elementCount = GetIntVal(vectorType->getElementCount()); + m_writer->emit(elementCount); + } + else + { + SLANG_UNEXPECTED("bad format in intrinsic definition"); + } + } + break; + + case 'V': + { + // Take an argument of some scalar/vector type and pad + // it out to a 4-vector with the same element type + // (this is the inverse of `$z`). + // + SLANG_RELEASE_ASSERT(*cursor >= '0' && *cursor <= '9'); + Index argIndex = (*cursor++) - '0'; + SLANG_RELEASE_ASSERT(argCount > argIndex); + + auto arg = args[argIndex].get(); + IRIntegerValue elementCount = 1; + IRType* elementType = arg->getDataType(); + if (auto vectorType = as(elementType)) + { + elementCount = GetIntVal(vectorType->getElementCount()); + elementType = vectorType->getElementType(); + } + + if(elementCount == 4) + { + // In the simple case, the operand is already a 4-vector, + // so we can just emit it as-is. + emitIROperand(arg, mode, getInfo(EmitOp::General)); + } + else + { + // Otherwise, we need to construct a 4-vector from the + // value we have, padding it out with zero elements as + // needed. + // + emitVectorTypeName(elementType, 4); + m_writer->emit("("); + emitIROperand(arg, mode, getInfo(EmitOp::General)); + for(IRIntegerValue ii = elementCount; ii < 4; ++ii) + { + m_writer->emit(", "); + if(getSourceStyle() == SourceStyle::GLSL) + { + _emitSimpleType(elementType); + m_writer->emit("(0)"); + } + else + { + m_writer->emit("0"); + } + } + m_writer->emit(")"); + } + } + break; + + case 'a': + { + // We have an operation that needs to lower to either + // `atomic*` or `imageAtomic*` for GLSL, depending on + // whether its first operand is a subscript into an + // array. This `$a` is the first `a` in `atomic`, + // so we will replace it accordingly. + // + // TODO: This distinction should be made earlier, + // with the front-end picking the right overload + // based on the "address space" of the argument. + + Index argIndex = 0; + SLANG_RELEASE_ASSERT(argCount > argIndex); + + auto arg = args[argIndex].get(); + if(arg->op == kIROp_ImageSubscript) + { + m_writer->emit("imageA"); + } + else + { + m_writer->emit("a"); + } + } + break; + + case 'A': + { + // We have an operand that represents the destination + // of an atomic operation in GLSL, and it should + // be lowered based on whether it is an ordinary l-value, + // or an image subscript. In the image subscript case + // this operand will turn into multiple arguments + // to the `imageAtomic*` function. + // + + Index argIndex = 0; + SLANG_RELEASE_ASSERT(argCount > argIndex); + + auto arg = args[argIndex].get(); + if(arg->op == kIROp_ImageSubscript) + { + if(getSourceStyle() == SourceStyle::GLSL) + { + // TODO: we don't handle the multisample + // case correctly here, where the last + // component of the image coordinate needs + // to be broken out into its own argument. + // + m_writer->emit("("); + emitIROperand(arg->getOperand(0), mode, getInfo(EmitOp::General)); + m_writer->emit("), "); + + // The coordinate argument will have been computed + // as a `vector` because that is how the + // HLSL image subscript operations are defined. + // In contrast, the GLSL `imageAtomic*` operations + // expect `vector` coordinates, so we + // hill hackily insert the conversion here as + // part of the intrinsic op. + // + auto coords = arg->getOperand(1); + auto coordsType = coords->getDataType(); + + auto coordsVecType = as(coordsType); + IRIntegerValue elementCount = 1; + if(coordsVecType) + { + coordsType = coordsVecType->getElementType(); + elementCount = GetIntVal(coordsVecType->getElementCount()); + } + + SLANG_ASSERT(coordsType->op == kIROp_UIntType); + + if (elementCount > 1) + { + m_writer->emit("ivec"); + m_writer->emit(elementCount); + } + else + { + m_writer->emit("int"); + } + + m_writer->emit("("); + emitIROperand(arg->getOperand(1), mode, getInfo(EmitOp::General)); + m_writer->emit(")"); + } + else + { + m_writer->emit("("); + emitIROperand(arg, mode, getInfo(EmitOp::General)); + m_writer->emit(")"); + } + } + else + { + m_writer->emit("("); + emitIROperand(arg, mode, getInfo(EmitOp::General)); + m_writer->emit(")"); + } + } + break; + + // We will use the `$X` case as a prefix for + // special logic needed when cross-compiling ray-tracing + // shaders. + case 'X': + { + SLANG_RELEASE_ASSERT(*cursor); + switch(*cursor++) + { + case 'P': + { + // The `$XP` case handles looking up + // the associated `location` for a variable + // used as the argument ray payload at a + // trace call site. + + Index argIndex = 0; + SLANG_RELEASE_ASSERT(argCount > argIndex); + auto arg = args[argIndex].get(); + auto argLoad = as(arg); + SLANG_RELEASE_ASSERT(argLoad); + auto argVar = argLoad->getOperand(0); + m_writer->emit(getRayPayloadLocation(argVar)); + } + break; + + case 'C': + { + // The `$XC` case handles looking up + // the associated `location` for a variable + // used as the argument callable payload at a + // call site. + + Index argIndex = 0; + SLANG_RELEASE_ASSERT(argCount > argIndex); + auto arg = args[argIndex].get(); + auto argLoad = as(arg); + SLANG_RELEASE_ASSERT(argLoad); + auto argVar = argLoad->getOperand(0); + m_writer->emit(getCallablePayloadLocation(argVar)); + } + break; + + case 'T': + { + // The `$XT` case handles selecting between + // the `gl_HitTNV` and `gl_RayTmaxNV` builtins, + // based on what stage we are using: + switch( m_entryPoint->getStage() ) + { + default: + m_writer->emit("gl_RayTmaxNV"); + break; + + case Stage::AnyHit: + case Stage::ClosestHit: + m_writer->emit("gl_HitTNV"); + break; + } + } + break; + + default: + SLANG_RELEASE_ASSERT(false); + break; + } + } + break; + + default: + SLANG_UNEXPECTED("bad format in intrinsic definition"); + break; + } + } + + // Close any remaining open parens + for (; openParenCount > 0; --openParenCount) + { + m_writer->emit(")"); + } + } +} + +void CLikeSourceEmitter::emitIntrinsicCallExpr( + IRCall* inst, + IRFunc* func, + IREmitMode mode, + EmitOpInfo const& inOuterPrec) +{ + auto outerPrec = inOuterPrec; + bool needClose = false; + + // For a call with N arguments, the instruction will + // have N+1 operands. We will start consuming operands + // starting at the index 1. + UInt operandCount = inst->getOperandCount(); + UInt argCount = operandCount - 1; + UInt operandIndex = 1; + + + // + if (auto targetIntrinsicDecoration = findTargetIntrinsicDecoration(func)) + { + emitTargetIntrinsicCallExpr( + inst, + func, + targetIntrinsicDecoration, + mode, + outerPrec); + return; + } + + // Our current strategy for dealing with intrinsic + // calls is to "un-mangle" the mangled name, in + // order to figure out what the user was originally + // calling. This is a bit messy, and there might + // be better strategies (including just stuffing + // a pointer to the original decl onto the callee). + + // If the intrinsic the user is calling is a generic, + // then the mangled name will have been set on the + // outer-most generic, and not on the leaf value + // (which is `func` above), so we need to walk + // upwards to find it. + // + IRInst* valueForName = func; + for(;;) + { + auto parentBlock = as(valueForName->parent); + if(!parentBlock) + break; + + auto parentGeneric = as(parentBlock->parent); + if(!parentGeneric) + break; + + valueForName = parentGeneric; + } + + // If we reach this point, we are assuming that the value + // has some kind of linkage, and thus a mangled name. + // + auto linkageDecoration = valueForName->findDecoration(); + SLANG_ASSERT(linkageDecoration); + auto mangledName = String(linkageDecoration->getMangledName()); + + + // We will use the `MangledLexer` to + // help us split the original name into its pieces. + MangledLexer lexer(mangledName); + + // We'll read through the qualified name of the + // symbol (e.g., `Texture2D.Sample`) and then + // only keep the last segment of the name (e.g., + // the `Sample` part). + auto name = lexer.readSimpleName(); + + // We will special-case some names here, that + // represent callable declarations that aren't + // ordinary functions, and thus may use different + // syntax. + if(name == "operator[]") + { + // The user is invoking a built-in subscript operator + + auto prec = getInfo(EmitOp::Postfix); + needClose = maybeEmitParens(outerPrec, prec); + + emitIROperand(inst->getOperand(operandIndex++), mode, leftSide(outerPrec, prec)); + m_writer->emit("["); + emitIROperand(inst->getOperand(operandIndex++), mode, getInfo(EmitOp::General)); + m_writer->emit("]"); + + if(operandIndex < operandCount) + { + m_writer->emit(" = "); + emitIROperand(inst->getOperand(operandIndex++), mode, getInfo(EmitOp::General)); + } + + maybeCloseParens(needClose); + return; + } + + auto prec = getInfo(EmitOp::Postfix); + needClose = maybeEmitParens(outerPrec, prec); + + // The mangled function name currently records + // the number of explicit parameters, and thus + // doesn't include the implicit `this` parameter. + // We can compare the argument and parameter counts + // to figure out whether we have a member function call. + UInt paramCount = lexer.readParamCount(); + + if(argCount != paramCount) + { + // Looks like a member function call + emitIROperand(inst->getOperand(operandIndex), mode, leftSide(outerPrec, prec)); + m_writer->emit("."); + operandIndex++; + } + // fixing issue #602 for GLSL sign function: https://github.com/shader-slang/slang/issues/602 + bool glslSignFix = getSourceStyle() == SourceStyle::GLSL && name == "sign"; + if (glslSignFix) + { + if (auto vectorType = as(inst->getDataType())) + { + m_writer->emit("ivec"); + m_writer->emit(as(vectorType->getElementCount())->value.intVal); + m_writer->emit("("); + } + else if (auto scalarType = as(inst->getDataType())) + { + m_writer->emit("int("); + } + else + glslSignFix = false; + } + m_writer->emit(name); + m_writer->emit("("); + bool first = true; + for(; operandIndex < operandCount; ++operandIndex ) + { + if(!first) m_writer->emit(", "); + emitIROperand(inst->getOperand(operandIndex), mode, getInfo(EmitOp::General)); + first = false; + } + m_writer->emit(")"); + if (glslSignFix) + m_writer->emit(")"); + maybeCloseParens(needClose); +} + +void CLikeSourceEmitter::emitIRCallExpr(IRCall* inst, IREmitMode mode, EmitOpInfo outerPrec) +{ + auto funcValue = inst->getOperand(0); + + // Does this function declare any requirements on GLSL version or + // extensions, which should affect our output? + if(getSourceStyle() == SourceStyle::GLSL) + { + auto decoratedValue = funcValue; + while (auto specInst = as(decoratedValue)) + { + decoratedValue = getSpecializedValue(specInst); + } + + for( auto decoration : decoratedValue->getDecorations() ) + { + switch(decoration->op) + { + default: + break; + + case kIROp_RequireGLSLExtensionDecoration: + requireGLSLExtension(String(((IRRequireGLSLExtensionDecoration*)decoration)->getExtensionName())); + break; + + case kIROp_RequireGLSLVersionDecoration: + requireGLSLVersion(int(((IRRequireGLSLVersionDecoration*)decoration)->getLanguageVersion())); + break; + } + } + } + + // We want to detect any call to an intrinsic operation, + // that we can emit it directly without mangling, etc. + if(auto irFunc = asTargetIntrinsic(funcValue)) + { + emitIntrinsicCallExpr(inst, irFunc, mode, outerPrec); + } + else + { + auto prec = getInfo(EmitOp::Postfix); + bool needClose = maybeEmitParens(outerPrec, prec); + + emitIROperand(funcValue, mode, leftSide(outerPrec, prec)); + m_writer->emit("("); + UInt argCount = inst->getOperandCount(); + for( UInt aa = 1; aa < argCount; ++aa ) + { + auto operand = inst->getOperand(aa); + if (as(operand->getDataType())) + continue; + if(aa != 1) m_writer->emit(", "); + emitIROperand(inst->getOperand(aa), mode, getInfo(EmitOp::General)); + } + m_writer->emit(")"); + + maybeCloseParens(needClose); + } +} + +static const char* _getGLSLVectorCompareFunctionName(IROp op) +{ + // Glsl vector comparisons use functions... + // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/equal.xhtml + + switch (op) + { + case kIROp_Eql: return "equal"; + case kIROp_Neq: return "notEqual"; + case kIROp_Greater: return "greaterThan"; + case kIROp_Less: return "lessThan"; + case kIROp_Geq: return "greaterThanEqual"; + case kIROp_Leq: return "lessThanEqual"; + default: return nullptr; + } +} + +void CLikeSourceEmitter::_maybeEmitGLSLCast(IRType* castType, IRInst* inst, IREmitMode mode) +{ + // Wrap in cast if a cast type is specified + if (castType) + { + emitIRType(castType); + m_writer->emit("("); + + // Emit the operand + emitIROperand(inst, mode, getInfo(EmitOp::General)); + + m_writer->emit(")"); + } + else + { + // Emit the operand + emitIROperand(inst, mode, getInfo(EmitOp::General)); + } +} + +void CLikeSourceEmitter::emitNot(IRInst* inst, IREmitMode mode, EmitOpInfo& ioOuterPrec, bool* outNeedClose) +{ + IRInst* operand = inst->getOperand(0); + + if (getSourceStyle() == SourceStyle::GLSL) + { + if (auto vectorType = as(operand->getDataType())) + { + // Handle as a function call + auto prec = getInfo(EmitOp::Postfix); + *outNeedClose = maybeEmitParens(ioOuterPrec, prec); + + m_writer->emit("not("); + emitIROperand(operand, mode, getInfo(EmitOp::General)); + m_writer->emit(")"); + return; + } + } + + auto prec = getInfo(EmitOp::Prefix); + *outNeedClose = maybeEmitParens(ioOuterPrec, prec); + + m_writer->emit("!"); + emitIROperand(operand, mode, rightSide(prec, ioOuterPrec)); +} + + +void CLikeSourceEmitter::emitComparison(IRInst* inst, IREmitMode mode, EmitOpInfo& ioOuterPrec, const EmitOpInfo& opPrec, bool* needCloseOut) +{ + if (getSourceStyle() == SourceStyle::GLSL) + { + IRInst* left = inst->getOperand(0); + IRInst* right = inst->getOperand(1); + + auto leftVectorType = as(left->getDataType()); + auto rightVectorType = as(right->getDataType()); + + // If either side is a vector handle as a vector + if (leftVectorType || rightVectorType) + { + const char* funcName = _getGLSLVectorCompareFunctionName(inst->op); + SLANG_ASSERT(funcName); + + // Determine the vector type + const auto vecType = leftVectorType ? leftVectorType : rightVectorType; + + // Handle as a function call + auto prec = getInfo(EmitOp::Postfix); + *needCloseOut = maybeEmitParens(ioOuterPrec, prec); + + m_writer->emit(funcName); + m_writer->emit("("); + _maybeEmitGLSLCast((leftVectorType ? nullptr : vecType), left, mode); + m_writer->emit(","); + _maybeEmitGLSLCast((rightVectorType ? nullptr : vecType), right, mode); + m_writer->emit(")"); + + return; + } + } + + *needCloseOut = maybeEmitParens(ioOuterPrec, opPrec); + + emitIROperand(inst->getOperand(0), mode, leftSide(ioOuterPrec, opPrec)); + m_writer->emit(" "); + m_writer->emit(opPrec.op); + m_writer->emit(" "); + emitIROperand(inst->getOperand(1), mode, rightSide(ioOuterPrec, opPrec)); +} + + +void CLikeSourceEmitter::emitIRInstExpr(IRInst* inst, IREmitMode mode, const EmitOpInfo& inOuterPrec) +{ + EmitOpInfo outerPrec = inOuterPrec; + bool needClose = false; + switch(inst->op) + { + case kIROp_IntLit: + case kIROp_FloatLit: + case kIROp_BoolLit: + emitIRSimpleValue(inst); + break; + + case kIROp_Construct: + case kIROp_makeVector: + case kIROp_MakeMatrix: + // Simple constructor call + + switch (getSourceStyle()) + { + case SourceStyle::HLSL: + { + if (inst->getOperandCount() == 1) + { + auto prec = getInfo(EmitOp::Prefix); + needClose = maybeEmitParens(outerPrec, prec); + + // Need to emit as cast for HLSL + m_writer->emit("("); + emitIRType(inst->getDataType()); + m_writer->emit(") "); + emitIROperand(inst->getOperand(0), mode, rightSide(outerPrec, prec)); + break; + } + /* fallthru*/ + } + case SourceStyle::GLSL: + { + emitIRType(inst->getDataType()); + emitIRArgs(inst, mode); + break; + } + case SourceStyle::CPP: + case SourceStyle::C: + { + if (inst->getOperandCount() == 1) + { + _emitCFunc(BuiltInCOp::Splat, inst->getDataType()); + emitIRArgs(inst, mode); + } + else + { + _emitCFunc(BuiltInCOp::Init, inst->getDataType()); + emitIRArgs(inst, mode); + } + break; + } + } + break; + case kIROp_constructVectorFromScalar: + + // Simple constructor call + if( getSourceStyle() == SourceStyle::HLSL ) + { + auto prec = getInfo(EmitOp::Prefix); + needClose = maybeEmitParens(outerPrec, prec); + + m_writer->emit("("); + emitIRType(inst->getDataType()); + m_writer->emit(")"); + + emitIROperand(inst->getOperand(0), mode, rightSide(outerPrec,prec)); + } + else + { + auto prec = getInfo(EmitOp::Postfix); + needClose = maybeEmitParens(outerPrec, prec); + + emitIRType(inst->getDataType()); + m_writer->emit("("); + emitIROperand(inst->getOperand(0), mode, getInfo(EmitOp::General)); + m_writer->emit(")"); + } + break; + + case kIROp_FieldExtract: + { + // Extract field from aggregate + + IRFieldExtract* fieldExtract = (IRFieldExtract*) inst; + + auto prec = getInfo(EmitOp::Postfix); + needClose = maybeEmitParens(outerPrec, prec); + + auto base = fieldExtract->getBase(); + emitIROperand(base, mode, leftSide(outerPrec, prec)); + m_writer->emit("."); + if(getSourceStyle() == SourceStyle::GLSL + && as(base->getDataType())) + { + m_writer->emit("_data."); + } + m_writer->emit(getIRName(fieldExtract->getField())); + } + break; + + case kIROp_FieldAddress: + { + // Extract field "address" from aggregate + + IRFieldAddress* ii = (IRFieldAddress*) inst; + + auto prec = getInfo(EmitOp::Postfix); + needClose = maybeEmitParens(outerPrec, prec); + + auto base = ii->getBase(); + emitIROperand(base, mode, leftSide(outerPrec, prec)); + m_writer->emit("."); + if(getSourceStyle() == SourceStyle::GLSL + && as(base->getDataType())) + { + m_writer->emit("_data."); + } + m_writer->emit(getIRName(ii->getField())); + } + break; + + +#define CASE_COMPARE(OPCODE, PREC, OP) \ + case OPCODE: \ + emitComparison(inst, mode, outerPrec, getInfo(EmitOp::PREC), &needClose); \ + break + +#define CASE(OPCODE, PREC, OP) \ + case OPCODE: \ + needClose = maybeEmitParens(outerPrec, getInfo(EmitOp::PREC)); \ + emitIROperand(inst->getOperand(0), mode, leftSide(outerPrec, getInfo(EmitOp::PREC))); \ + m_writer->emit(" " #OP " "); \ + emitIROperand(inst->getOperand(1), mode, rightSide(outerPrec, getInfo(EmitOp::PREC))); \ + break + + CASE(kIROp_Add, Add, +); + CASE(kIROp_Sub, Sub, -); + CASE(kIROp_Div, Div, /); + CASE(kIROp_Mod, Mod, %); + + CASE(kIROp_Lsh, Lsh, <<); + CASE(kIROp_Rsh, Rsh, >>); + + // TODO: Need to pull out component-wise + // comparison cases for matrices/vectors + CASE_COMPARE(kIROp_Eql, Eql, ==); + CASE_COMPARE(kIROp_Neq, Neq, !=); + CASE_COMPARE(kIROp_Greater, Greater, >); + CASE_COMPARE(kIROp_Less, Less, <); + CASE_COMPARE(kIROp_Geq, Geq, >=); + CASE_COMPARE(kIROp_Leq, Leq, <=); + + CASE(kIROp_BitXor, BitXor, ^); + + CASE(kIROp_And, And, &&); + CASE(kIROp_Or, Or, ||); + +#undef CASE + + // Component-wise multiplication needs to be special cased, + // because GLSL uses infix `*` to express inner product + // when working with matrices. + case kIROp_Mul: + // Are we targetting GLSL, and are both operands matrices? + if(getSourceStyle() == SourceStyle::GLSL + && as(inst->getOperand(0)->getDataType()) + && as(inst->getOperand(1)->getDataType())) + { + m_writer->emit("matrixCompMult("); + emitIROperand(inst->getOperand(0), mode, getInfo(EmitOp::General)); + m_writer->emit(", "); + emitIROperand(inst->getOperand(1), mode, getInfo(EmitOp::General)); + m_writer->emit(")"); + } + else + { + // Default handling is to just rely on infix + // `operator*`. + auto prec = getInfo(EmitOp::Mul); + needClose = maybeEmitParens(outerPrec, prec); + emitIROperand(inst->getOperand(0), mode, leftSide(outerPrec, prec)); + m_writer->emit(" * "); + emitIROperand(inst->getOperand(1), mode, rightSide(prec, outerPrec)); + } + break; + + case kIROp_Not: + { + emitNot(inst, mode, outerPrec, &needClose); + } + break; + + case kIROp_Neg: + { + auto prec = getInfo(EmitOp::Prefix); + needClose = maybeEmitParens(outerPrec, prec); + + m_writer->emit("-"); + emitIROperand(inst->getOperand(0), mode, rightSide(prec, outerPrec)); + } + break; + + case kIROp_BitNot: + { + auto prec = getInfo(EmitOp::Prefix); + needClose = maybeEmitParens(outerPrec, prec); + + if (as(inst->getDataType())) + { + m_writer->emit("!"); + } + else + { + m_writer->emit("~"); + } + emitIROperand(inst->getOperand(0), mode, rightSide(prec, outerPrec)); + } + break; + + case kIROp_BitAnd: + { + auto prec = getInfo(EmitOp::BitAnd); + needClose = maybeEmitParens(outerPrec, prec); + + // TODO: handle a bitwise And of a vector of bools by casting to + // a uvec and performing the bitwise operation + + emitIROperand(inst->getOperand(0), mode, leftSide(outerPrec, prec)); + + // Are we targetting GLSL, and are both operands scalar bools? + // In that case convert the operation to a logical And + if (getSourceStyle() == SourceStyle::GLSL + && as(inst->getOperand(0)->getDataType()) + && as(inst->getOperand(1)->getDataType())) + { + m_writer->emit("&&"); + } + else + { + m_writer->emit("&"); + } + + emitIROperand(inst->getOperand(1), mode, rightSide(outerPrec, prec)); + } + break; + + case kIROp_BitOr: + { + auto prec = getInfo(EmitOp::BitOr); + needClose = maybeEmitParens(outerPrec, prec); + + // TODO: handle a bitwise Or of a vector of bools by casting to + // a uvec and performing the bitwise operation + + emitIROperand(inst->getOperand(0), mode, leftSide(outerPrec, prec)); + + // Are we targetting GLSL, and are both operands scalar bools? + // In that case convert the operation to a logical Or + if (getSourceStyle() == SourceStyle::GLSL + && as(inst->getOperand(0)->getDataType()) + && as(inst->getOperand(1)->getDataType())) + { + m_writer->emit("||"); + } + else + { + m_writer->emit("|"); + } + + emitIROperand(inst->getOperand(1), mode, rightSide(outerPrec, prec)); + } + break; + + case kIROp_Load: + { + auto base = inst->getOperand(0); + emitIROperand(base, mode, outerPrec); + if(getSourceStyle() == SourceStyle::GLSL + && as(base->getDataType())) + { + m_writer->emit("._data"); + } + } + break; + + case kIROp_Store: + { + auto prec = getInfo(EmitOp::Assign); + needClose = maybeEmitParens(outerPrec, prec); + + emitIROperand(inst->getOperand(0), mode, leftSide(outerPrec, prec)); + m_writer->emit(" = "); + emitIROperand(inst->getOperand(1), mode, rightSide(prec, outerPrec)); + } + break; + + case kIROp_Call: + { + emitIRCallExpr((IRCall*)inst, mode, outerPrec); + } + break; + + case kIROp_GroupMemoryBarrierWithGroupSync: + m_writer->emit("GroupMemoryBarrierWithGroupSync()"); + break; + + case kIROp_getElement: + case kIROp_getElementPtr: + case kIROp_ImageSubscript: + // HACK: deal with translation of GLSL geometry shader input arrays. + if(auto decoration = inst->getOperand(0)->findDecoration()) + { + auto prec = getInfo(EmitOp::Postfix); + needClose = maybeEmitParens(outerPrec, prec); + + m_writer->emit(decoration->getOuterArrayName()); + m_writer->emit("["); + emitIROperand(inst->getOperand(1), mode, getInfo(EmitOp::General)); + m_writer->emit("]."); + emitIROperand(inst->getOperand(0), mode, rightSide(prec, outerPrec)); + break; + } + else + { + auto prec = getInfo(EmitOp::Postfix); + needClose = maybeEmitParens(outerPrec, prec); + + emitIROperand( inst->getOperand(0), mode, leftSide(outerPrec, prec)); + m_writer->emit("["); + emitIROperand(inst->getOperand(1), mode, getInfo(EmitOp::General)); + m_writer->emit("]"); + } + break; + + case kIROp_Mul_Vector_Matrix: + case kIROp_Mul_Matrix_Vector: + case kIROp_Mul_Matrix_Matrix: + if(getSourceStyle() == SourceStyle::GLSL) + { + // GLSL expresses inner-product multiplications + // with the ordinary infix `*` operator. + // + // Note that the order of the operands is reversed + // compared to HLSL (and Slang's internal representation) + // because the notion of what is a "row" vs. a "column" + // is reversed between HLSL/Slang and GLSL. + // + auto prec = getInfo(EmitOp::Mul); + needClose = maybeEmitParens(outerPrec, prec); + + emitIROperand(inst->getOperand(1), mode, leftSide(outerPrec, prec)); + m_writer->emit(" * "); + emitIROperand(inst->getOperand(0), mode, rightSide(prec, outerPrec)); + } + else + { + m_writer->emit("mul("); + emitIROperand(inst->getOperand(0), mode, getInfo(EmitOp::General)); + m_writer->emit(", "); + emitIROperand(inst->getOperand(1), mode, getInfo(EmitOp::General)); + m_writer->emit(")"); + } + break; + + case kIROp_swizzle: + { + auto prec = getInfo(EmitOp::Postfix); + needClose = maybeEmitParens(outerPrec, prec); + + auto ii = (IRSwizzle*)inst; + emitIROperand(ii->getBase(), mode, leftSide(outerPrec, prec)); + m_writer->emit("."); + const Index elementCount = Index(ii->getElementCount()); + for (Index ee = 0; ee < elementCount; ++ee) + { + IRInst* irElementIndex = ii->getElementIndex(ee); + SLANG_RELEASE_ASSERT(irElementIndex->op == kIROp_IntLit); + IRConstant* irConst = (IRConstant*)irElementIndex; + + UInt elementIndex = (UInt)irConst->value.intVal; + SLANG_RELEASE_ASSERT(elementIndex < 4); + + char const* kComponents[] = { "x", "y", "z", "w" }; + m_writer->emit(kComponents[elementIndex]); + } + } + break; + + case kIROp_Specialize: + { + emitIROperand(inst->getOperand(0), mode, outerPrec); + } + break; + + case kIROp_Select: + { + if (getSourceStyle() == SourceStyle::GLSL && + inst->getOperand(0)->getDataType()->op != kIROp_BoolType) + { + // For GLSL, emit a call to `mix` if condition is a vector + m_writer->emit("mix("); + emitIROperand(inst->getOperand(2), mode, leftSide(getInfo(EmitOp::General), getInfo(EmitOp::General))); + m_writer->emit(", "); + emitIROperand(inst->getOperand(1), mode, leftSide(getInfo(EmitOp::General), getInfo(EmitOp::General))); + m_writer->emit(", "); + emitIROperand(inst->getOperand(0), mode, leftSide(getInfo(EmitOp::General), getInfo(EmitOp::General))); + m_writer->emit(")"); + } + else + { + auto prec = getInfo(EmitOp::Conditional); + needClose = maybeEmitParens(outerPrec, prec); + + emitIROperand(inst->getOperand(0), mode, leftSide(outerPrec, prec)); + m_writer->emit(" ? "); + emitIROperand(inst->getOperand(1), mode, prec); + m_writer->emit(" : "); + emitIROperand(inst->getOperand(2), mode, rightSide(prec, outerPrec)); + } + } + break; + + case kIROp_Param: + m_writer->emit(getIRName(inst)); + break; + + case kIROp_makeArray: + case kIROp_makeStruct: + { + // TODO: initializer-list syntax may not always + // be appropriate, depending on the context + // of the expression. + + m_writer->emit("{ "); + UInt argCount = inst->getOperandCount(); + for (UInt aa = 0; aa < argCount; ++aa) + { + if (aa != 0) m_writer->emit(", "); + emitIROperand(inst->getOperand(aa), mode, getInfo(EmitOp::General)); + } + m_writer->emit(" }"); + } + break; + + case kIROp_BitCast: + { + // TODO: we can simplify the logic for arbitrary bitcasts + // by always bitcasting the source to a `uint*` type (if it + // isn't already) and then bitcasting that to the destination + // type (if it isn't already `uint*`. + // + // For now we are assuming the source type is *already* + // a `uint*` type of the appropriate size. + // +// auto fromType = extractBaseType(inst->getOperand(0)->getDataType()); + auto toType = extractBaseType(inst->getDataType()); + switch(getSourceStyle()) + { + case SourceStyle::GLSL: + switch(toType) + { + default: + m_writer->emit("/* unhandled */"); + break; + + case BaseType::UInt: + break; + + case BaseType::Int: + emitIRType(inst->getDataType()); + break; + + case BaseType::Float: + m_writer->emit("uintBitsToFloat("); + break; + } + break; + + case SourceStyle::HLSL: + switch(toType) + { + default: + m_writer->emit("/* unhandled */"); + break; + + case BaseType::UInt: + break; + case BaseType::Int: + m_writer->emit("("); + emitIRType(inst->getDataType()); + m_writer->emit(")"); + break; + case BaseType::Float: + m_writer->emit("asfloat"); + break; + } + break; + + + default: + SLANG_UNEXPECTED("unhandled codegen target"); + break; + } + + m_writer->emit("("); + emitIROperand(inst->getOperand(0), mode, getInfo(EmitOp::General)); + m_writer->emit(")"); + } + break; + + default: + m_writer->emit("/* unhandled */"); + break; + } + maybeCloseParens(needClose); +} + +BaseType CLikeSourceEmitter::extractBaseType(IRType* inType) +{ + auto type = inType; + for(;;) + { + if(auto irBaseType = as(type)) + { + return irBaseType->getBaseType(); + } + else if(auto vecType = as(type)) + { + type = vecType->getElementType(); + continue; + } + else + { + return BaseType::Void; + } + } +} + +void CLikeSourceEmitter::emitIRInst(IRInst* inst, IREmitMode mode) +{ + try + { + _emitIRInst(inst, mode); + } + // Don't emit any context message for an explicit `AbortCompilationException` + // because it should only happen when an error is already emitted. + catch(AbortCompilationException&) { throw; } + catch(...) + { + noteInternalErrorLoc(inst->sourceLoc); + throw; + } +} + +void CLikeSourceEmitter::_emitIRInst(IRInst* inst, IREmitMode mode) +{ + if (shouldFoldIRInstIntoUseSites(inst, mode)) + { + return; + } + + m_writer->advanceToSourceLocation(inst->sourceLoc); + + switch(inst->op) + { + default: + emitIRInstResultDecl(inst); + emitIRInstExpr(inst, mode, getInfo(EmitOp::General)); + m_writer->emit(";\n"); + break; + + case kIROp_undefined: + { + auto type = inst->getDataType(); + emitIRType(type, getIRName(inst)); + m_writer->emit(";\n"); + } + break; + + case kIROp_Var: + { + auto ptrType = cast(inst->getDataType()); + auto valType = ptrType->getValueType(); + + auto name = getIRName(inst); + emitIRRateQualifiers(inst); + emitIRType(valType, name); + m_writer->emit(";\n"); + } + break; + + case kIROp_Param: + // Don't emit parameters, since they are declared as part of the function. + break; + + case kIROp_FieldAddress: + // skip during code emit, since it should be + // folded into use site(s) + break; + + case kIROp_ReturnVoid: + m_writer->emit("return;\n"); + break; + + case kIROp_ReturnVal: + m_writer->emit("return "); + emitIROperand(((IRReturnVal*) inst)->getVal(), mode, getInfo(EmitOp::General)); + m_writer->emit(";\n"); + break; + + case kIROp_discard: + m_writer->emit("discard;\n"); + break; + + case kIROp_swizzleSet: + { + auto ii = (IRSwizzleSet*)inst; + emitIRInstResultDecl(inst); + emitIROperand(inst->getOperand(0), mode, getInfo(EmitOp::General)); + m_writer->emit(";\n"); + + auto subscriptOuter = getInfo(EmitOp::General); + auto subscriptPrec = getInfo(EmitOp::Postfix); + bool needCloseSubscript = maybeEmitParens(subscriptOuter, subscriptPrec); + + emitIROperand(inst, mode, leftSide(subscriptOuter, subscriptPrec)); + m_writer->emit("."); + UInt elementCount = ii->getElementCount(); + for (UInt ee = 0; ee < elementCount; ++ee) + { + IRInst* irElementIndex = ii->getElementIndex(ee); + SLANG_RELEASE_ASSERT(irElementIndex->op == kIROp_IntLit); + IRConstant* irConst = (IRConstant*)irElementIndex; + + UInt elementIndex = (UInt)irConst->value.intVal; + SLANG_RELEASE_ASSERT(elementIndex < 4); + + char const* kComponents[] = { "x", "y", "z", "w" }; + m_writer->emit(kComponents[elementIndex]); + } + maybeCloseParens(needCloseSubscript); + + m_writer->emit(" = "); + emitIROperand(inst->getOperand(1), mode, getInfo(EmitOp::General)); + m_writer->emit(";\n"); + } + break; + + case kIROp_SwizzledStore: + { + auto subscriptOuter = getInfo(EmitOp::General); + auto subscriptPrec = getInfo(EmitOp::Postfix); + bool needCloseSubscript = maybeEmitParens(subscriptOuter, subscriptPrec); + + + auto ii = cast(inst); + emitIROperand(ii->getDest(), mode, leftSide(subscriptOuter, subscriptPrec)); + m_writer->emit("."); + UInt elementCount = ii->getElementCount(); + for (UInt ee = 0; ee < elementCount; ++ee) + { + IRInst* irElementIndex = ii->getElementIndex(ee); + SLANG_RELEASE_ASSERT(irElementIndex->op == kIROp_IntLit); + IRConstant* irConst = (IRConstant*)irElementIndex; + + UInt elementIndex = (UInt)irConst->value.intVal; + SLANG_RELEASE_ASSERT(elementIndex < 4); + + char const* kComponents[] = { "x", "y", "z", "w" }; + m_writer->emit(kComponents[elementIndex]); + } + maybeCloseParens(needCloseSubscript); + + m_writer->emit(" = "); + emitIROperand(ii->getSource(), mode, getInfo(EmitOp::General)); + m_writer->emit(";\n"); + } + break; + } +} + +void CLikeSourceEmitter::emitIRSemantics(VarLayout* varLayout) +{ + if(varLayout->flags & VarLayoutFlag::HasSemantic) + { + m_writer->emit(" : "); + m_writer->emit(varLayout->semanticName); + if(varLayout->semanticIndex) + { + m_writer->emit(varLayout->semanticIndex); + } + } +} + +void CLikeSourceEmitter::emitIRSemantics(IRInst* inst) +{ + // Don't emit semantics if we aren't translating down to HLSL + switch (getSourceStyle()) + { + case SourceStyle::HLSL: + break; + + default: + return; + } + + if (auto semanticDecoration = inst->findDecoration()) + { + m_writer->emit(" : "); + m_writer->emit(semanticDecoration->getSemanticName()); + return; + } + + if(auto layoutDecoration = inst->findDecoration()) + { + auto layout = layoutDecoration->getLayout(); + if(auto varLayout = as(layout)) + { + emitIRSemantics(varLayout); + } + else if (auto entryPointLayout = as(layout)) + { + if(auto resultLayout = entryPointLayout->resultLayout) + { + emitIRSemantics(resultLayout); + } + } + } +} + +VarLayout* CLikeSourceEmitter::getVarLayout(IRInst* var) +{ + auto decoration = var->findDecoration(); + if (!decoration) + return nullptr; + + return (VarLayout*) decoration->getLayout(); +} + +void CLikeSourceEmitter::emitIRLayoutSemantics(IRInst* inst, char const* uniformSemanticSpelling) +{ + auto layout = getVarLayout(inst); + if (layout) + { + emitHLSLRegisterSemantics(layout, uniformSemanticSpelling); + } +} + +void CLikeSourceEmitter::emitPhiVarAssignments(UInt argCount, IRUse* args, IRBlock* targetBlock) +{ + UInt argCounter = 0; + for (auto pp = targetBlock->getFirstParam(); pp; pp = pp->getNextParam()) + { + UInt argIndex = argCounter++; + + if (argIndex >= argCount) + { + SLANG_UNEXPECTED("not enough arguments for branch"); + break; + } + + IRInst* arg = args[argIndex].get(); + + auto outerPrec = getInfo(EmitOp::General); + auto prec = getInfo(EmitOp::Assign); + + emitIROperand(pp, IREmitMode::Default, leftSide(outerPrec, prec)); + m_writer->emit(" = "); + emitIROperand(arg, IREmitMode::Default, rightSide(prec, outerPrec)); + m_writer->emit(";\n"); + } +} + +void CLikeSourceEmitter::emitRegion(Region* inRegion) +{ + // We will use a loop so that we can process sequential (simple) + // regions iteratively rather than recursively. + // This is effectively an emulation of tail recursion. + Region* region = inRegion; + while(region) + { + // What flavor of region are we trying to emit? + switch(region->getFlavor()) + { + case Region::Flavor::Simple: + { + // A simple region consists of a basic block followed + // by another region. + // + auto simpleRegion = (SimpleRegion*) region; + + // We start by outputting all of the non-terminator + // instructions in the block. + // + auto block = simpleRegion->block; + auto terminator = block->getTerminator(); + for (auto inst = block->getFirstInst(); inst != terminator; inst = inst->getNextInst()) + { + emitIRInst(inst, IREmitMode::Default); + } + + // Next we have to deal with the terminator instruction + // itself. In many cases, the terminator will have been + // turned into a block of its own, but certain cases + // of terminators are simple enough that we just fold + // them into the current block. + // + m_writer->advanceToSourceLocation(terminator->sourceLoc); + switch(terminator->op) + { + default: + // Don't do anything with the terminator, and assume + // its behavior has been folded into the next region. + break; + + case kIROp_ReturnVal: + case kIROp_ReturnVoid: + case kIROp_discard: + // For extremely simple terminators, we just handle + // them here, so that we don't have to allocate + // separate `Region`s for them. + emitIRInst(terminator, IREmitMode::Default); + break; + + // We will also handle any unconditional branches + // here, since they may have arguments to pass + // to the target block (our encoding of SSA + // "phi" operations). + // + // TODO: A better approach would be to move out of SSA + // as an IR pass, and introduce explicit variables to + // replace any "phi nodes." This would avoid possible + // complications if we ever end up in the bad case where + // one of the block arguments on a branch is also + // a parameter of the target block, so that the order + // of operations is important. + // + case kIROp_unconditionalBranch: + { + auto t = (IRUnconditionalBranch*)terminator; + UInt argCount = t->getOperandCount(); + static const UInt kFixedArgCount = 1; + emitPhiVarAssignments( + argCount - kFixedArgCount, + t->getOperands() + kFixedArgCount, + t->getTargetBlock()); + } + break; + case kIROp_loop: + { + auto t = (IRLoop*) terminator; + UInt argCount = t->getOperandCount(); + static const UInt kFixedArgCount = 3; + emitPhiVarAssignments( + argCount - kFixedArgCount, + t->getOperands() + kFixedArgCount, + t->getTargetBlock()); + + } + break; + } + + // If the terminator required a full region to represent + // its behavior in a structured form, then we will move + // along to that region now. + // + // We do this iteratively rather than recursively, by + // jumping back to the top of our loop with a new + // value for `region`. + // + region = simpleRegion->nextRegion; + continue; + } + + // Break and continue regions are trivial to handle, as long as we + // don't need to consider multi-level break/continue (which we + // don't for now). + case Region::Flavor::Break: + m_writer->emit("break;\n"); + break; + case Region::Flavor::Continue: + m_writer->emit("continue;\n"); + break; + + case Region::Flavor::If: + { + auto ifRegion = (IfRegion*) region; + + // TODO: consider simplifying the code in + // the case where `ifRegion == null` + // so that we output `if(!condition) { elseRegion }` + // instead of the current `if(condition) {} else { elseRegion }` + + m_writer->emit("if("); + emitIROperand(ifRegion->condition, IREmitMode::Default, getInfo(EmitOp::General)); + m_writer->emit(")\n{\n"); + m_writer->indent(); + emitRegion(ifRegion->thenRegion); + m_writer->dedent(); + m_writer->emit("}\n"); + + // Don't emit the `else` region if it would be empty + // + if(auto elseRegion = ifRegion->elseRegion) + { + m_writer->emit("else\n{\n"); + m_writer->indent(); + emitRegion(elseRegion); + m_writer->dedent(); + m_writer->emit("}\n"); + } + + // Continue with the region after the `if`. + // + // TODO: consider just constructing a `SimpleRegion` + // around an `IfRegion` to handle this sequencing, + // rather than making `IfRegion` serve as both a + // conditional and a sequence. + // + region = ifRegion->nextRegion; + continue; + } + break; + + case Region::Flavor::Loop: + { + auto loopRegion = (LoopRegion*) region; + auto loopInst = loopRegion->loopInst; + + // If the user applied an explicit decoration to the loop, + // to control its unrolling behavior, then pass that + // along in the output code (if the target language + // supports the semantics of the decoration). + // + if (auto loopControlDecoration = loopInst->findDecoration()) + { + switch (loopControlDecoration->getMode()) + { + case kIRLoopControl_Unroll: + // Note: loop unrolling control is only available in HLSL, not GLSL + if(getSourceStyle() == SourceStyle::HLSL) + { + m_writer->emit("[unroll]\n"); + } + break; + + default: + break; + } + } + + m_writer->emit("for(;;)\n{\n"); + m_writer->indent(); + emitRegion(loopRegion->body); + m_writer->dedent(); + m_writer->emit("}\n"); + + // Continue with the region after the loop + region = loopRegion->nextRegion; + continue; + } + + case Region::Flavor::Switch: + { + auto switchRegion = (SwitchRegion*) region; + + // Emit the start of our statement. + m_writer->emit("switch("); + emitIROperand(switchRegion->condition, IREmitMode::Default, getInfo(EmitOp::General)); + m_writer->emit(")\n{\n"); + + auto defaultCase = switchRegion->defaultCase; + for(auto currentCase : switchRegion->cases) + { + for(auto caseVal : currentCase->values) + { + m_writer->emit("case "); + emitIROperand(caseVal, IREmitMode::Default, getInfo(EmitOp::General)); + m_writer->emit(":\n"); + } + if(currentCase.Ptr() == defaultCase) + { + m_writer->emit("default:\n"); + } + + m_writer->indent(); + m_writer->emit("{\n"); + m_writer->indent(); + emitRegion(currentCase->body); + m_writer->dedent(); + m_writer->emit("}\n"); + m_writer->dedent(); + } + + m_writer->emit("}\n"); + + // Continue with the region after the `switch` + region = switchRegion->nextRegion; + continue; + } + break; + } + break; + } +} + +/// Emit high-level language statements from a structured region tree. +void CLikeSourceEmitter::emitRegionTree(RegionTree* regionTree) +{ + emitRegion(regionTree->rootRegion); +} + +bool CLikeSourceEmitter::isDefinition(IRFunc* func) +{ + // For now, we use a simple approach: a function is + // a definition if it has any blocks, and a declaration otherwise. + return func->getFirstBlock() != nullptr; +} + +String CLikeSourceEmitter::getIRFuncName(IRFunc* func) +{ + if (auto entryPointLayout = asEntryPoint(func)) + { + // GLSL will always need to use `main` as the + // name for an entry-point function, but other + // targets should try to use the original name. + // + // TODO: always use `main`, and have any code + // that wraps this know to use `main` instead + // of the original entry-point name... + // + if (getSourceStyle() != SourceStyle::GLSL) + { + return getText(entryPointLayout->entryPoint->getName()); + } + + // + + return "main"; + } + else + { + return getIRName(func); + } +} + +void CLikeSourceEmitter::emitAttributeSingleString(const char* name, FuncDecl* entryPoint, Attribute* attrib) +{ + assert(attrib); + + attrib->args.getCount(); + if (attrib->args.getCount() != 1) + { + SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute expects single parameter"); + return; + } + + Expr* expr = attrib->args[0]; + + auto stringLitExpr = as(expr); + if (!stringLitExpr) + { + SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute parameter expecting to be a string "); + return; + } + + m_writer->emit("["); + m_writer->emit(name); + m_writer->emit("(\""); + m_writer->emit(stringLitExpr->value); + m_writer->emit("\")]\n"); +} + +void CLikeSourceEmitter::emitAttributeSingleInt(const char* name, FuncDecl* entryPoint, Attribute* attrib) +{ + assert(attrib); + + attrib->args.getCount(); + if (attrib->args.getCount() != 1) + { + SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute expects single parameter"); + return; + } + + Expr* expr = attrib->args[0]; + + auto intLitExpr = as(expr); + if (!intLitExpr) + { + SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute expects an int"); + return; + } + + m_writer->emit("["); + m_writer->emit(name); + m_writer->emit("("); + m_writer->emit(intLitExpr->value); + m_writer->emit(")]\n"); +} + +void CLikeSourceEmitter::emitFuncDeclPatchConstantFuncAttribute(IRFunc* irFunc, FuncDecl* entryPoint, PatchConstantFuncAttribute* attrib) +{ + SLANG_UNUSED(attrib); + + auto irPatchFunc = irFunc->findDecoration(); + assert(irPatchFunc); + if (!irPatchFunc) + { + SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Unable to find [patchConstantFunc(...)] decoration"); + return; + } + + const String irName = getIRName(irPatchFunc->getFunc()); + + m_writer->emit("[patchconstantfunc(\""); + m_writer->emit(irName); + m_writer->emit("\")]\n"); +} + +void CLikeSourceEmitter::emitIREntryPointAttributes_HLSL(IRFunc* irFunc, EntryPointLayout* entryPointLayout) +{ + auto profile = m_effectiveProfile; + auto stage = entryPointLayout->profile.GetStage(); + + if(profile.getFamily() == ProfileFamily::DX) + { + if(profile.GetVersion() >= ProfileVersion::DX_6_1 ) + { + char const* stageName = getStageName(stage); + if(stageName) + { + m_writer->emit("[shader(\""); + m_writer->emit(stageName); + m_writer->emit("\")]"); + } + } + } + + switch (stage) + { + case Stage::Compute: + { + static const UInt kAxisCount = 3; + UInt sizeAlongAxis[kAxisCount]; + + // TODO: this is kind of gross because we are using a public + // reflection API function, rather than some kind of internal + // utility it forwards to... + spReflectionEntryPoint_getComputeThreadGroupSize( + (SlangReflectionEntryPoint*)entryPointLayout, + kAxisCount, + &sizeAlongAxis[0]); + + m_writer->emit("[numthreads("); + for (int ii = 0; ii < 3; ++ii) + { + if (ii != 0) m_writer->emit(", "); + m_writer->emit(sizeAlongAxis[ii]); + } + m_writer->emit(")]\n"); + } + break; + case Stage::Geometry: + { + if (auto attrib = entryPointLayout->entryPoint->FindModifier()) + { + m_writer->emit("[maxvertexcount("); + m_writer->emit(attrib->value); + m_writer->emit(")]\n"); + } + if (auto attrib = entryPointLayout->entryPoint->FindModifier()) + { + m_writer->emit("[instance("); + m_writer->emit(attrib->value); + m_writer->emit(")]\n"); + } + break; + } + case Stage::Domain: + { + FuncDecl* entryPoint = entryPointLayout->entryPoint; + /* [domain("isoline")] */ + if (auto attrib = entryPoint->FindModifier()) + { + emitAttributeSingleString("domain", entryPoint, attrib); + } + + break; + } + case Stage::Hull: + { + // Lists these are only attributes for hull shader + // https://docs.microsoft.com/en-us/windows/desktop/direct3d11/direct3d-11-advanced-stages-hull-shader-design + + FuncDecl* entryPoint = entryPointLayout->entryPoint; + + /* [domain("isoline")] */ + if (auto attrib = entryPoint->FindModifier()) + { + emitAttributeSingleString("domain", entryPoint, attrib); + } + /* [domain("partitioning")] */ + if (auto attrib = entryPoint->FindModifier()) + { + emitAttributeSingleString("partitioning", entryPoint, attrib); + } + /* [outputtopology("line")] */ + if (auto attrib = entryPoint->FindModifier()) + { + emitAttributeSingleString("outputtopology", entryPoint, attrib); + } + /* [outputcontrolpoints(4)] */ + if (auto attrib = entryPoint->FindModifier()) + { + emitAttributeSingleInt("outputcontrolpoints", entryPoint, attrib); + } + /* [patchconstantfunc("HSConst")] */ + if (auto attrib = entryPoint->FindModifier()) + { + emitFuncDeclPatchConstantFuncAttribute(irFunc, entryPoint, attrib); + } + + break; + } + case Stage::Pixel: + { + if (irFunc->findDecoration()) + { + m_writer->emit("[earlydepthstencil]\n"); + } + break; + } + // TODO: There are other stages that will need this kind of handling. + default: + break; + } +} + +void CLikeSourceEmitter::emitIREntryPointAttributes_GLSL(IRFunc* irFunc, EntryPointLayout* entryPointLayout) +{ + auto profile = entryPointLayout->profile; + auto stage = profile.GetStage(); + + switch (stage) + { + case Stage::Compute: + { + static const UInt kAxisCount = 3; + UInt sizeAlongAxis[kAxisCount]; + + // TODO: this is kind of gross because we are using a public + // reflection API function, rather than some kind of internal + // utility it forwards to... + spReflectionEntryPoint_getComputeThreadGroupSize( + (SlangReflectionEntryPoint*)entryPointLayout, + kAxisCount, + &sizeAlongAxis[0]); + + m_writer->emit("layout("); + char const* axes[] = { "x", "y", "z" }; + for (int ii = 0; ii < 3; ++ii) + { + if (ii != 0) m_writer->emit(", "); + m_writer->emit("local_size_"); + m_writer->emit(axes[ii]); + m_writer->emit(" = "); + m_writer->emit(sizeAlongAxis[ii]); + } + m_writer->emit(") in;"); + } + break; + case Stage::Geometry: + { + if (auto attrib = entryPointLayout->entryPoint->FindModifier()) + { + m_writer->emit("layout(max_vertices = "); + m_writer->emit(attrib->value); + m_writer->emit(") out;\n"); + } + if (auto attrib = entryPointLayout->entryPoint->FindModifier()) + { + m_writer->emit("layout(invocations = "); + m_writer->emit(attrib->value); + m_writer->emit(") in;\n"); + } + + for(auto pp : entryPointLayout->entryPoint->GetParameters()) + { + if(auto inputPrimitiveTypeModifier = pp->FindModifier()) + { + if(as(inputPrimitiveTypeModifier)) + { + m_writer->emit("layout(triangles) in;\n"); + } + else if(as(inputPrimitiveTypeModifier)) + { + m_writer->emit("layout(lines) in;\n"); + } + else if(as(inputPrimitiveTypeModifier)) + { + m_writer->emit("layout(lines_adjacency) in;\n"); + } + else if(as(inputPrimitiveTypeModifier)) + { + m_writer->emit("layout(points) in;\n"); + } + else if(as(inputPrimitiveTypeModifier)) + { + m_writer->emit("layout(triangles_adjacency) in;\n"); + } + } + + if(auto outputStreamType = as(pp->type)) + { + if(as(outputStreamType)) + { + m_writer->emit("layout(triangle_strip) out;\n"); + } + else if(as(outputStreamType)) + { + m_writer->emit("layout(line_strip) out;\n"); + } + else if(as(outputStreamType)) + { + m_writer->emit("layout(points) out;\n"); + } + } + } + + + } + break; + case Stage::Pixel: + { + if (irFunc->findDecoration()) + { + // https://www.khronos.org/opengl/wiki/Early_Fragment_Test + m_writer->emit("layout(early_fragment_tests) in;\n"); + } + break; + } + // TODO: There are other stages that will need this kind of handling. + default: + break; + } +} + +void CLikeSourceEmitter::emitIREntryPointAttributes(IRFunc* irFunc, EntryPointLayout* entryPointLayout) +{ + switch(getSourceStyle()) + { + case SourceStyle::HLSL: + emitIREntryPointAttributes_HLSL(irFunc, entryPointLayout); + break; + + case SourceStyle::GLSL: + emitIREntryPointAttributes_GLSL(irFunc, entryPointLayout); + break; + } +} + +void CLikeSourceEmitter::emitPhiVarDecls(IRFunc* func) +{ + // We will skip the first block, since its parameters are + // the parameters of the whole function. + auto bb = func->getFirstBlock(); + if (!bb) + return; + bb = bb->getNextBlock(); + + for (; bb; bb = bb->getNextBlock()) + { + for (auto pp = bb->getFirstParam(); pp; pp = pp->getNextParam()) + { + emitIRTempModifiers(pp); + emitIRType(pp->getFullType(), getIRName(pp)); + m_writer->emit(";\n"); + } + } +} + +/// Emit high-level statements for the body of a function. +void CLikeSourceEmitter::emitIRFunctionBody(IRGlobalValueWithCode* code) +{ + // Compute a structured region tree that can represent + // the control flow of our function. + // + RefPtr regionTree = generateRegionTreeForFunc(code, getSink()); + + // Now that we've computed the region tree, we have + // an opportunity to perform some last-minute transformations + // on the code to make sure it follows our rules. + // + // TODO: it would be better to do these transformations earlier, + // so that we can, e.g., dump the final IR code *before* emission + // starts, but that gets a bit complicated because we also want + // to have the region tree available without having to recompute it. + // + // For now we are just going to do things the expedient way, but + // eventually we should allow an IR module to have side-band + // storage for derived structures like the region tree (and logic + // for invalidating them when a transformation would break them). + // + fixValueScoping(regionTree); + + // Now emit high-level code from that structured region tree. + // + emitRegionTree(regionTree); +} + +void CLikeSourceEmitter::emitIRSimpleFunc(IRFunc* func) +{ + auto resultType = func->getResultType(); + + // Deal with decorations that need + // to be emitted as attributes + auto entryPointLayout = asEntryPoint(func); + if (entryPointLayout) + { + emitIREntryPointAttributes(func, entryPointLayout); + } + + auto name = getIRFuncName(func); + + emitType(resultType, name); + + m_writer->emit("("); + auto firstParam = func->getFirstParam(); + for( auto pp = firstParam; pp; pp = pp->getNextParam()) + { + if(pp != firstParam) + m_writer->emit(", "); + + auto paramName = getIRName(pp); + auto paramType = pp->getDataType(); + + if (getSourceStyle() == SourceStyle::HLSL) + { + if (auto layoutDecor = pp->findDecoration()) + { + Layout* layout = layoutDecor->getLayout(); + VarLayout* varLayout = as(layout); + + if (varLayout) + { + auto var = varLayout->getVariable(); + + if (auto primTypeModifier = var->FindModifier()) + { + if (as(primTypeModifier)) + m_writer->emit("triangle "); + else if (as(primTypeModifier)) + m_writer->emit("point "); + else if (as(primTypeModifier)) + m_writer->emit("line "); + else if (as(primTypeModifier)) + m_writer->emit("lineadj "); + else if (as(primTypeModifier)) + m_writer->emit("triangleadj "); + } + } + } + } + + emitIRParamType(paramType, paramName); + + emitIRSemantics(pp); + } + m_writer->emit(")"); + + emitIRSemantics(func); + + // TODO: encode declaration vs. definition + if(isDefinition(func)) + { + m_writer->emit("\n{\n"); + m_writer->indent(); + + // HACK: forward-declare all the local variables needed for the + // parameters of non-entry blocks. + emitPhiVarDecls(func); + + // Need to emit the operations in the blocks of the function + emitIRFunctionBody(func); + + m_writer->dedent(); + m_writer->emit("}\n\n"); + } + else + { + m_writer->emit(";\n\n"); + } +} + +void CLikeSourceEmitter::emitIRParamType(IRType* type, String const& name) +{ + // An `out` or `inout` parameter will have been + // encoded as a parameter of pointer type, so + // we need to decode that here. + // + if( auto outType = as(type)) + { + m_writer->emit("out "); + type = outType->getValueType(); + } + else if( auto inOutType = as(type)) + { + m_writer->emit("inout "); + type = inOutType->getValueType(); + } + else if( auto refType = as(type)) + { + // Note: There is no HLSL/GLSL equivalent for by-reference parameters, + // so we don't actually expect to encounter these in user code. + m_writer->emit("inout "); + type = inOutType->getValueType(); + } + + emitIRType(type, name); +} + +IRInst* CLikeSourceEmitter::getSpecializedValue(IRSpecialize* specInst) +{ + auto base = specInst->getBase(); + auto baseGeneric = as(base); + if (!baseGeneric) + return base; + + auto lastBlock = baseGeneric->getLastBlock(); + if (!lastBlock) + return base; + + auto returnInst = as(lastBlock->getTerminator()); + if (!returnInst) + return base; + + return returnInst->getVal(); +} + +void CLikeSourceEmitter::emitIRFuncDecl(IRFunc* func) +{ + // We don't want to emit declarations for operations + // that only appear in the IR as stand-ins for built-in + // operations on that target. + if (isTargetIntrinsic(func)) + return; + + // Finally, don't emit a declaration for an entry point, + // because it might need meta-data attributes attached + // to it, and the HLSL compiler will get upset if the + // forward declaration doesn't *also* have those + // attributes. + if(asEntryPoint(func)) + return; + + + // A function declaration doesn't have any IR basic blocks, + // and as a result it *also* doesn't have the IR `param` instructions, + // so we need to emit a declaration entirely from the type. + + auto funcType = func->getDataType(); + auto resultType = func->getResultType(); + + auto name = getIRFuncName(func); + + emitIRType(resultType, name); + + m_writer->emit("("); + auto paramCount = funcType->getParamCount(); + for(UInt pp = 0; pp < paramCount; ++pp) + { + if(pp != 0) + m_writer->emit(", "); + + String paramName; + paramName.append("_"); + paramName.append(Int32(pp)); + auto paramType = funcType->getParamType(pp); + + emitIRParamType(paramType, paramName); + } + m_writer->emit(");\n\n"); +} + +EntryPointLayout* CLikeSourceEmitter::getEntryPointLayout(IRFunc* func) +{ + if( auto layoutDecoration = func->findDecoration() ) + { + return as(layoutDecoration->getLayout()); + } + return nullptr; +} + +EntryPointLayout* CLikeSourceEmitter::asEntryPoint(IRFunc* func) +{ + if (auto layoutDecoration = func->findDecoration()) + { + if (auto entryPointLayout = as(layoutDecoration->getLayout())) + { + return entryPointLayout; + } + } + + return nullptr; +} + +bool CLikeSourceEmitter::isTargetIntrinsic(IRFunc* func) +{ + // For now we do this in an overly simplistic + // fashion: we say that *any* function declaration + // (rather then definition) must be an intrinsic: + return !isDefinition(func); +} + +IRFunc* CLikeSourceEmitter::asTargetIntrinsic(IRInst* value) +{ + if(!value) + return nullptr; + + while (auto specInst = as(value)) + { + value = getSpecializedValue(specInst); + } + + if(value->op != kIROp_Func) + return nullptr; + + IRFunc* func = (IRFunc*) value; + if(!isTargetIntrinsic(func)) + return nullptr; + + return func; +} + +void CLikeSourceEmitter::emitIRFunc(IRFunc* func) +{ + if(!isDefinition(func)) + { + // This is just a function declaration, + // and so we want to emit it as such. + // (Or maybe not emit it at all). + + // We do not emit the declaration for + // functions that appear to be intrinsics/builtins + // in the target language. + if (isTargetIntrinsic(func)) + return; + + emitIRFuncDecl(func); + } + else + { + // The common case is that what we + // have is just an ordinary function, + // and we can emit it as such. + emitIRSimpleFunc(func); + } +} + +void CLikeSourceEmitter::emitIRStruct(IRStructType* structType) +{ + // If the selected `struct` type is actually an intrinsic + // on our target, then we don't want to emit anything at all. + if(auto intrinsicDecoration = findTargetIntrinsicDecoration(structType)) + { + return; + } + + m_writer->emit("struct "); + m_writer->emit(getIRName(structType)); + m_writer->emit("\n{\n"); + m_writer->indent(); + + for(auto ff : structType->getFields()) + { + auto fieldKey = ff->getKey(); + auto fieldType = ff->getFieldType(); + + // Filter out fields with `void` type that might + // have been introduced by legalization. + if(as(fieldType)) + continue; + + // Note: GLSL doesn't support interpolation modifiers on `struct` fields + if( getSourceStyle() != SourceStyle::GLSL ) + { + emitInterpolationModifiers(fieldKey, fieldType, nullptr); + } + + emitIRType(fieldType, getIRName(fieldKey)); + emitIRSemantics(fieldKey); + m_writer->emit(";\n"); + } + + m_writer->dedent(); + m_writer->emit("};\n\n"); +} + +void CLikeSourceEmitter::emitIRMatrixLayoutModifiers(VarLayout* layout) +{ + // When a variable has a matrix type, we want to emit an explicit + // layout qualifier based on what the layout has been computed to be. + // + + auto typeLayout = layout->typeLayout; + while(auto arrayTypeLayout = as(typeLayout)) + typeLayout = arrayTypeLayout->elementTypeLayout; + + if (auto matrixTypeLayout = typeLayout.as()) + { + switch (getSourceStyle()) + { + case SourceStyle::HLSL: + switch (matrixTypeLayout->mode) + { + case kMatrixLayoutMode_ColumnMajor: + m_writer->emit("column_major "); + break; + + case kMatrixLayoutMode_RowMajor: + m_writer->emit("row_major "); + break; + } + break; + + case SourceStyle::GLSL: + // Reminder: the meaning of row/column major layout + // in our semantics is the *opposite* of what GLSL + // calls them, because what they call "columns" + // are what we call "rows." + // + switch (matrixTypeLayout->mode) + { + case kMatrixLayoutMode_ColumnMajor: + m_writer->emit("layout(row_major)\n"); + break; + + case kMatrixLayoutMode_RowMajor: + m_writer->emit("layout(column_major)\n"); + break; + } + break; + + default: + break; + } + + } +} + +void CLikeSourceEmitter::maybeEmitGLSLFlatModifier(IRType* valueType) +{ + auto tt = valueType; + if(auto vecType = as(tt)) + tt = vecType->getElementType(); + if(auto vecType = as(tt)) + tt = vecType->getElementType(); + + switch(tt->op) + { + default: + break; + + case kIROp_IntType: + case kIROp_UIntType: + case kIROp_UInt64Type: + m_writer->emit("flat "); + break; + } +} + +void CLikeSourceEmitter::emitInterpolationModifiers(IRInst* varInst, IRType* valueType, VarLayout* layout) +{ + bool isGLSL = (getSourceStyle() == SourceStyle::GLSL); + bool anyModifiers = false; + + for(auto dd : varInst->getDecorations()) + { + if(dd->op != kIROp_InterpolationModeDecoration) + continue; + + auto decoration = (IRInterpolationModeDecoration*)dd; + auto mode = decoration->getMode(); + + switch(mode) + { + case IRInterpolationMode::NoInterpolation: + anyModifiers = true; + m_writer->emit(isGLSL ? "flat " : "nointerpolation "); + break; + + case IRInterpolationMode::NoPerspective: + anyModifiers = true; + m_writer->emit("noperspective "); + break; + + case IRInterpolationMode::Linear: + anyModifiers = true; + m_writer->emit(isGLSL ? "smooth " : "linear "); + break; + + case IRInterpolationMode::Sample: + anyModifiers = true; + m_writer->emit("sample "); + break; + + case IRInterpolationMode::Centroid: + anyModifiers = true; + m_writer->emit("centroid "); + break; + + default: + break; + } + } + + // If the user didn't explicitly qualify a varying + // with integer type, then we need to explicitly + // add the `flat` modifier for GLSL. + if(!anyModifiers && isGLSL) + { + // Only emit a default `flat` for fragment + // stage varying inputs. + // + // TODO: double-check that this works for + // signature matching even if the producing + // stage didn't use `flat`. + // + // If this ends up being a problem we can instead + // output everything with `flat` except for + // fragment *outputs* (and maybe vertex inputs). + // + if(layout && layout->stage == Stage::Fragment + && layout->FindResourceInfo(LayoutResourceKind::VaryingInput)) + { + maybeEmitGLSLFlatModifier(valueType); + } + } +} + +UInt CLikeSourceEmitter::getRayPayloadLocation(IRInst* inst) +{ + auto& map = m_mapIRValueToRayPayloadLocation; + UInt value = 0; + if(map.TryGetValue(inst, value)) + return value; + + value = map.Count(); + map.Add(inst, value); + return value; +} + +UInt CLikeSourceEmitter::getCallablePayloadLocation(IRInst* inst) +{ + auto& map = m_mapIRValueToCallablePayloadLocation; + UInt value = 0; + if(map.TryGetValue(inst, value)) + return value; + + value = map.Count(); + map.Add(inst, value); + return value; +} + +void CLikeSourceEmitter::emitGLSLImageFormatModifier(IRInst* var, IRTextureType* resourceType) +{ + // If the user specified a format manually, using `[format(...)]`, + // then we will respect that format and emit a matching `layout` modifier. + // + if(auto formatDecoration = var->findDecoration()) + { + auto format = formatDecoration->getFormat(); + if(format == ImageFormat::unknown) + { + // If the user explicitly opts out of having a format, then + // the output shader will require the extension to support + // load/store from format-less images. + // + // TODO: We should have a validation somewhere in the compiler + // that atomic operations are only allowed on images with + // explicit formats (and then only on specific formats). + // This is really an argument that format should be part of + // the image *type* (with a "base type" for images with + // unknown format). + // + requireGLSLExtension("GL_EXT_shader_image_load_formatted"); + } + else + { + // If there is an explicit format specified, then we + // should emit a `layout` modifier using the GLSL name + // for the format. + // + m_writer->emit("layout("); + m_writer->emit(getGLSLNameForImageFormat(format)); + m_writer->emit(")\n"); + } + + // No matter what, if an explicit `[format(...)]` was given, + // then we don't need to emit anything else. + // + return; + } + + + // When no explicit format is specified, we need to either + // emit the image as having an unknown format, or else infer + // a format from the type. + // + // For now our default behavior is to infer (so that unmodified + // HLSL input is more likely to generate valid SPIR-V that + // runs anywhere), but we provide a flag to opt into + // treating images without explicit formats as having + // unknown format. + // + if(m_compileRequest->useUnknownImageFormatAsDefault) + { + requireGLSLExtension("GL_EXT_shader_image_load_formatted"); + return; + } + + // At this point we have a resource type like `RWTexture2D` + // and we want to infer a reasonable format from the element + // type `X` that was specified. + // + // E.g., if `X` is `float` then we can infer a format like `r32f`, + // and so forth. The catch of course is that it is possible to + // specify a shader parameter with a type like `RWTexture2D` but + // provide an image at runtime with a format like `rgba8`, so + // this inference is never guaranteed to give perfect results. + // + // If users don't like our inferred result, they need to use a + // `[format(...)]` attribute to manually specify what they want. + // + // TODO: We should consider whether we can expand the space of + // allowed types for `X` in `RWTexture2D` to include special + // pseudo-types that act just like, e.g., `float4`, but come + // with attached/implied format information. + // + auto elementType = resourceType->getElementType(); + Int vectorWidth = 1; + if(auto elementVecType = as(elementType)) + { + if(auto intLitVal = as(elementVecType->getElementCount())) + { + vectorWidth = (Int) intLitVal->getValue(); + } + else + { + vectorWidth = 0; + } + elementType = elementVecType->getElementType(); + } + if(auto elementBasicType = as(elementType)) + { + m_writer->emit("layout("); + switch(vectorWidth) + { + default: m_writer->emit("rgba"); break; + + case 3: + { + // TODO: GLSL doesn't support 3-component formats so for now we are going to + // default to rgba + // + // The SPIR-V spec (https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.pdf) + // section 3.11 on Image Formats it does not list rgbf32. + // + // It seems SPIR-V can support having an image with an unknown-at-compile-time + // format, so long as the underlying API supports it. Ideally this would mean that we can + // just drop all these qualifiers when emitting GLSL for Vulkan targets. + // + // This raises the question of what to do more long term. For Vulkan hopefully we can just + // drop the layout. For OpenGL targets it would seem reasonable to have well-defined rules + // for inferring the format (and just document that 3-component formats map to 4-component formats, + // but that shouldn't matter because the API wouldn't let the user allocate those 3-component formats anyway), + // and add an attribute for specifying the format manually if you really want to override our + // inference (e.g., to specify r11fg11fb10f). + + m_writer->emit("rgba"); + //Emit("rgb"); + break; + } + + case 2: m_writer->emit("rg"); break; + case 1: m_writer->emit("r"); break; + } + switch(elementBasicType->getBaseType()) + { + default: + case BaseType::Float: m_writer->emit("32f"); break; + case BaseType::Half: m_writer->emit("16f"); break; + case BaseType::UInt: m_writer->emit("32ui"); break; + case BaseType::Int: m_writer->emit("32i"); break; + + // TODO: Here are formats that are available in GLSL, + // but that are not handled by the above cases. + // + // r11f_g11f_b10f + // + // rgba16 + // rgb10_a2 + // rgba8 + // rg16 + // rg8 + // r16 + // r8 + // + // rgba16_snorm + // rgba8_snorm + // rg16_snorm + // rg8_snorm + // r16_snorm + // r8_snorm + // + // rgba16i + // rgba8i + // rg16i + // rg8i + // r16i + // r8i + // + // rgba16ui + // rgb10_a2ui + // rgba8ui + // rg16ui + // rg8ui + // r16ui + // r8ui + } + m_writer->emit(")\n"); + } +} + + /// Emit modifiers that should apply even for a declaration of an SSA temporary. +void CLikeSourceEmitter::emitIRTempModifiers(IRInst* temp) +{ + if(temp->findDecoration()) + { + m_writer->emit("precise "); + } +} + +void CLikeSourceEmitter::emitIRVarModifiers(VarLayout* layout,IRInst* varDecl, IRType* varType) +{ + // Deal with Vulkan raytracing layout stuff *before* we + // do the check for whether `layout` is null, because + // the payload won't automatically get a layout applied + // (it isn't part of the user-visible interface...) + // + if(varDecl->findDecoration()) + { + m_writer->emit("layout(location = "); + m_writer->emit(getRayPayloadLocation(varDecl)); + m_writer->emit(")\n"); + m_writer->emit("rayPayloadNV\n"); + } + if(varDecl->findDecoration()) + { + m_writer->emit("layout(location = "); + m_writer->emit(getCallablePayloadLocation(varDecl)); + m_writer->emit(")\n"); + m_writer->emit("callableDataNV\n"); + } + + if(varDecl->findDecoration()) + { + m_writer->emit("hitAttributeNV\n"); + } + + if(varDecl->findDecoration()) + { + switch(getSourceStyle()) + { + default: + break; + + case SourceStyle::HLSL: + m_writer->emit("globallycoherent\n"); + break; + + case SourceStyle::GLSL: + m_writer->emit("coherent\n"); + break; + } + } + + emitIRTempModifiers(varDecl); + + if (!layout) + return; + + emitIRMatrixLayoutModifiers(layout); + + // As a special case, if we are emitting a GLSL declaration + // for an HLSL `RWTexture*` then we need to emit a `format` layout qualifier. + if(getSourceStyle() == SourceStyle::GLSL) + { + if(auto resourceType = as(unwrapArray(varType))) + { + switch(resourceType->getAccess()) + { + case SLANG_RESOURCE_ACCESS_READ_WRITE: + case SLANG_RESOURCE_ACCESS_RASTER_ORDERED: + { + emitGLSLImageFormatModifier(varDecl, resourceType); + } + break; + + default: + break; + } + } + } + + if(layout->FindResourceInfo(LayoutResourceKind::VaryingInput) + || layout->FindResourceInfo(LayoutResourceKind::VaryingOutput)) + { + emitInterpolationModifiers(varDecl, varType, layout); + } + + if (getSourceStyle() == SourceStyle::GLSL) + { + // Layout-related modifiers need to come before the declaration, + // so deal with them here. + emitGLSLLayoutQualifiers(layout, nullptr); + + // try to emit an appropriate leading qualifier + for (auto rr : layout->resourceInfos) + { + switch (rr.kind) + { + case LayoutResourceKind::Uniform: + case LayoutResourceKind::ShaderResource: + case LayoutResourceKind::DescriptorTableSlot: + m_writer->emit("uniform "); + break; + + case LayoutResourceKind::VaryingInput: + { + m_writer->emit("in "); + } + break; + + case LayoutResourceKind::VaryingOutput: + { + m_writer->emit("out "); + } + break; + + case LayoutResourceKind::RayPayload: + { + m_writer->emit("rayPayloadInNV "); + } + break; + + case LayoutResourceKind::CallablePayload: + { + m_writer->emit("callableDataInNV "); + } + break; + + case LayoutResourceKind::HitAttributes: + { + m_writer->emit("hitAttributeNV "); + } + break; + + default: + continue; + } + + break; + } + } +} + +void CLikeSourceEmitter::emitHLSLParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type) +{ + if(as(type)) + { + m_writer->emit("tbuffer "); + } + else + { + m_writer->emit("cbuffer "); + } + m_writer->emit(getIRName(varDecl)); + + auto varLayout = getVarLayout(varDecl); + SLANG_RELEASE_ASSERT(varLayout); + + EmitVarChain blockChain(varLayout); + + EmitVarChain containerChain = blockChain; + EmitVarChain elementChain = blockChain; + + auto typeLayout = varLayout->typeLayout; + if( auto parameterGroupTypeLayout = as(typeLayout) ) + { + containerChain = EmitVarChain(parameterGroupTypeLayout->containerVarLayout, &blockChain); + elementChain = EmitVarChain(parameterGroupTypeLayout->elementVarLayout, &blockChain); + + typeLayout = parameterGroupTypeLayout->elementVarLayout->typeLayout; + } + + emitHLSLRegisterSemantic(LayoutResourceKind::ConstantBuffer, &containerChain); + + m_writer->emit("\n{\n"); + m_writer->indent(); + + auto elementType = type->getElementType(); + + emitIRType(elementType, getIRName(varDecl)); + m_writer->emit(";\n"); + + m_writer->dedent(); + m_writer->emit("}\n"); +} + +void CLikeSourceEmitter::emitArrayBrackets(IRType* inType) +{ + // A declaration may require zero, one, or + // more array brackets. When writing out array + // brackets from left to right, they represent + // the structure of the type from the "outside" + // in (that is, if we have a 5-element array of + // 3-element arrays we should output `[5][3]`), + // because of C-style declarator rules. + // + // This conveniently means that we can print + // out all the array brackets with a looping + // rather than a recursive structure. + // + // We will peel the input type like an onion, + // looking at one layer at a time until we + // reach a non-array type in the middle. + // + IRType* type = inType; + for(;;) + { + if(auto arrayType = as(type)) + { + m_writer->emit("["); + emitVal(arrayType->getElementCount(), getInfo(EmitOp::General)); + m_writer->emit("]"); + + // Continue looping on the next layer in. + // + type = arrayType->getElementType(); + } + else if(auto unsizedArrayType = as(type)) + { + m_writer->emit("[]"); + + // Continue looping on the next layer in. + // + type = unsizedArrayType->getElementType(); + } + else + { + // This layer wasn't an array, so we are done. + // + return; + } + } +} + + +void CLikeSourceEmitter::emitGLSLParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type) +{ + auto varLayout = getVarLayout(varDecl); + SLANG_RELEASE_ASSERT(varLayout); + + EmitVarChain blockChain(varLayout); + + EmitVarChain containerChain = blockChain; + EmitVarChain elementChain = blockChain; + + auto typeLayout = varLayout->typeLayout->unwrapArray(); + if( auto parameterGroupTypeLayout = as(typeLayout) ) + { + containerChain = EmitVarChain(parameterGroupTypeLayout->containerVarLayout, &blockChain); + elementChain = EmitVarChain(parameterGroupTypeLayout->elementVarLayout, &blockChain); + + typeLayout = parameterGroupTypeLayout->elementVarLayout->typeLayout; + } + + /* + With resources backed by 'buffer' on glsl, we want to output 'readonly' if that is a good match + for the underlying type. If uniform it's implicit it's readonly + + Here this only happens with isShaderRecord which is a 'constant buffer' (ie implicitly readonly) + or IRGLSLShaderStorageBufferType which is read write. + */ + + emitGLSLLayoutQualifier(LayoutResourceKind::DescriptorTableSlot, &containerChain); + emitGLSLLayoutQualifier(LayoutResourceKind::PushConstantBuffer, &containerChain); + bool isShaderRecord = emitGLSLLayoutQualifier(LayoutResourceKind::ShaderRecord, &containerChain); + + if( isShaderRecord ) + { + // TODO: A shader record in vk can be potentially read-write. Currently slang doesn't support write access + // and readonly buffer generates SPIRV validation error. + m_writer->emit("buffer "); + } + else if(as(type)) + { + // Is writable + m_writer->emit("layout(std430) buffer "); + } + // TODO: what to do with HLSL `tbuffer` style buffers? + else + { + // uniform is implicitly read only + m_writer->emit("layout(std140) uniform "); + } + + // Generate a dummy name for the block + m_writer->emit("_S"); + m_writer->emit(m_uniqueIDCounter++); + + m_writer->emit("\n{\n"); + m_writer->indent(); + + auto elementType = type->getElementType(); + + emitIRType(elementType, "_data"); + m_writer->emit(";\n"); + + m_writer->dedent(); + m_writer->emit("} "); + + m_writer->emit(getIRName(varDecl)); + + // If the underlying variable was an array (or array of arrays, etc.) + // we need to emit all those array brackets here. + emitArrayBrackets(varDecl->getDataType()); + + m_writer->emit(";\n"); +} + +void CLikeSourceEmitter::emitIRParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type) +{ + switch (getSourceStyle()) + { + case SourceStyle::HLSL: + emitHLSLParameterGroup(varDecl, type); + break; + + case SourceStyle::GLSL: + emitGLSLParameterGroup(varDecl, type); + break; + } +} + +void CLikeSourceEmitter::emitIRVar(IRVar* varDecl) +{ + auto allocatedType = varDecl->getDataType(); + auto varType = allocatedType->getValueType(); +// auto addressSpace = allocatedType->getAddressSpace(); + +#if 0 + switch( varType->op ) + { + case kIROp_ConstantBufferType: + case kIROp_TextureBufferType: + emitIRParameterGroup(ctx, varDecl, (IRUniformBufferType*) varType); + return; + + default: + break; + } +#endif + + // Need to emit appropriate modifiers here. + + auto layout = getVarLayout(varDecl); + + emitIRVarModifiers(layout, varDecl, varType); + +#if 0 + switch (addressSpace) + { + default: + break; + + case kIRAddressSpace_GroupShared: + emit("groupshared "); + break; + } +#endif + emitIRRateQualifiers(varDecl); + + emitIRType(varType, getIRName(varDecl)); + + emitIRSemantics(varDecl); + + emitIRLayoutSemantics(varDecl); + + m_writer->emit(";\n"); +} + +void CLikeSourceEmitter::emitIRStructuredBuffer_GLSL(IRGlobalParam* varDecl, IRHLSLStructuredBufferTypeBase* structuredBufferType) +{ + // Shader storage buffer is an OpenGL 430 feature + // + // TODO: we should require either the extension or the version... + requireGLSLVersion(430); + + m_writer->emit("layout(std430"); + + auto layout = getVarLayout(varDecl); + if (layout) + { + LayoutResourceKind kind = LayoutResourceKind::DescriptorTableSlot; + EmitVarChain chain(layout); + + const UInt index = getBindingOffset(&chain, kind); + const UInt space = getBindingSpace(&chain, kind); + + m_writer->emit(", binding = "); + m_writer->emit(index); + if (space) + { + m_writer->emit(", set = "); + m_writer->emit(space); + } + } + + m_writer->emit(") "); + + /* + If the output type is a buffer, and we can determine it is only readonly we can prefix before + buffer with 'readonly' + + The actual structuredBufferType could be + + HLSLStructuredBufferType - This is unambiguously read only + HLSLRWStructuredBufferType - Read write + HLSLRasterizerOrderedStructuredBufferType - Allows read/write access + HLSLAppendStructuredBufferType - Write + HLSLConsumeStructuredBufferType - TODO (JS): Its possible that this can be readonly, but we currently don't support on GLSL + */ + + if (as(structuredBufferType)) + { + m_writer->emit("readonly "); + } + + m_writer->emit("buffer "); + + // Generate a dummy name for the block + m_writer->emit("_S"); + m_writer->emit(m_uniqueIDCounter++); + + m_writer->emit(" {\n"); + m_writer->indent(); + + + auto elementType = structuredBufferType->getElementType(); + emitIRType(elementType, "_data[]"); + m_writer->emit(";\n"); + + m_writer->dedent(); + m_writer->emit("} "); + + m_writer->emit(getIRName(varDecl)); + emitArrayBrackets(varDecl->getDataType()); + + m_writer->emit(";\n"); +} + +void CLikeSourceEmitter::emitIRByteAddressBuffer_GLSL(IRGlobalParam* varDecl, IRByteAddressBufferTypeBase* byteAddressBufferType) +{ + // TODO: A lot of this logic is copy-pasted from `emitIRStructuredBuffer_GLSL`. + // It might be worthwhile to share the common code to avoid regressions sneaking + // in when one or the other, but not both, gets updated. + + // Shader storage buffer is an OpenGL 430 feature + // + // TODO: we should require either the extension or the version... + requireGLSLVersion(430); + + m_writer->emit("layout(std430"); + + auto layout = getVarLayout(varDecl); + if (layout) + { + LayoutResourceKind kind = LayoutResourceKind::DescriptorTableSlot; + EmitVarChain chain(layout); + + const UInt index = getBindingOffset(&chain, kind); + const UInt space = getBindingSpace(&chain, kind); + + m_writer->emit(", binding = "); + m_writer-> emit(index); + if (space) + { + m_writer->emit(", set = "); + m_writer->emit(space); + } + } + + m_writer->emit(") "); + + /* + If the output type is a buffer, and we can determine it is only readonly we can prefix before + buffer with 'readonly' + + HLSLByteAddressBufferType - This is unambiguously read only + HLSLRWByteAddressBufferType - Read write + HLSLRasterizerOrderedByteAddressBufferType - Allows read/write access + */ + + if (as(byteAddressBufferType)) + { + m_writer->emit("readonly "); + } + + m_writer->emit("buffer "); + + // Generate a dummy name for the block + m_writer->emit("_S"); + m_writer->emit(m_uniqueIDCounter++); + m_writer->emit("\n{\n"); + m_writer->indent(); + + m_writer->emit("uint _data[];\n"); + + m_writer->dedent(); + m_writer->emit("} "); + + m_writer->emit(getIRName(varDecl)); + emitArrayBrackets(varDecl->getDataType()); + + m_writer->emit(";\n"); +} + +void CLikeSourceEmitter::emitIRGlobalVar(IRGlobalVar* varDecl) +{ + auto allocatedType = varDecl->getDataType(); + auto varType = allocatedType->getValueType(); + + String initFuncName; + if (varDecl->getFirstBlock()) + { + // A global variable with code means it has an initializer + // associated with it. Eventually we'd like to emit that + // initializer directly as an expression here, but for + // now we'll emit it as a separate function. + + initFuncName = getIRName(varDecl); + initFuncName.append("_init"); + + m_writer->emit("\n"); + emitIRType(varType, initFuncName); + m_writer->emit("()\n{\n"); + m_writer->indent(); + emitIRFunctionBody(varDecl); + m_writer->dedent(); + m_writer->emit("}\n"); + } + + // An ordinary global variable won't have a layout + // associated with it, since it is not a shader + // parameter. + // + SLANG_ASSERT(!getVarLayout(varDecl)); + VarLayout* layout = nullptr; + + // An ordinary global variable (which is not a + // shader parameter) may need special + // modifiers to indicate it as such. + // + switch (getSourceStyle()) + { + case SourceStyle::HLSL: + // HLSL requires the `static` modifier on any + // global variables; otherwise they are assumed + // to be uniforms. + m_writer->emit("static "); + break; + + default: + break; + } + + emitIRVarModifiers(layout, varDecl, varType); + + emitIRRateQualifiers(varDecl); + emitIRType(varType, getIRName(varDecl)); + + // TODO: These shouldn't be needed for ordinary + // global variables. + // + emitIRSemantics(varDecl); + emitIRLayoutSemantics(varDecl); + + if (varDecl->getFirstBlock()) + { + m_writer->emit(" = "); + m_writer->emit(initFuncName); + m_writer->emit("()"); + } + + m_writer->emit(";\n\n"); +} + +void CLikeSourceEmitter::emitIRGlobalParam(IRGlobalParam* varDecl) +{ + auto rawType = varDecl->getDataType(); + + auto varType = rawType; + if( auto outType = as(varType) ) + { + varType = outType->getValueType(); + } + if (as(varType)) + return; + + // When a global shader parameter represents a "parameter group" + // (either a constant buffer or a parameter block with non-resource + // data in it), we will prefer to emit it as an ordinary `cbuffer` + // declaration or `uniform` block, even when emitting HLSL for + // D3D profiles that support the explicit `ConstantBuffer` type. + // + // Alternatively, we could make this choice based on profile, and + // prefer `ConstantBuffer` on profiles that support it and/or when + // the input code used that syntax. + // + if (auto paramBlockType = as(varType)) + { + emitIRParameterGroup(varDecl, paramBlockType); + return; + } + + if(getSourceStyle() == SourceStyle::GLSL) + { + // There are a number of types that are (or can be) + // "first-class" in D3D HLSL, but are second-class in GLSL in + // that they require explicit global declarations for each value/object, + // and don't support declaration as ordinary variables. + // + // This includes constant buffers (`uniform` blocks) and well as + // structured and byte-address buffers (both mapping to `buffer` blocks). + // + // We intercept these types, and arrays thereof, to produce the required + // global declarations. This assumes that earlier "legalization" passes + // already performed the work of pulling fields with these types out of + // aggregates. + // + // Note: this also assumes that these types are not used as function + // parameters/results, local variables, etc. Additional legalization + // steps are required to guarantee these conditions. + // + if (auto paramBlockType = as(unwrapArray(varType))) + { + emitGLSLParameterGroup(varDecl, paramBlockType); + return; + } + if( auto structuredBufferType = as(unwrapArray(varType)) ) + { + emitIRStructuredBuffer_GLSL(varDecl, structuredBufferType); + return; + } + if( auto byteAddressBufferType = as(unwrapArray(varType)) ) + { + emitIRByteAddressBuffer_GLSL(varDecl, byteAddressBufferType); + return; + } + + // We want to skip the declaration of any system-value variables + // when outputting GLSL (well, except in the case where they + // actually *require* redeclaration...). + // + // Note: these won't be variables the user declare explicitly + // in their code, but rather variables that we generated as + // part of legalizing the varying input/output signature of + // an entry point for GL/Vulkan. + // + // TODO: This could be handled more robustly by attaching an + // appropriate decoration to these variables to indicate their + // purpose. + // + if(auto linkageDecoration = varDecl->findDecoration()) + { + if(linkageDecoration->getMangledName().startsWith("gl_")) + { + // The variable represents an OpenGL system value, + // so we will assume that it doesn't need to be declared. + // + // TODO: handle case where we *should* declare the variable. + return; + } + } + + // When emitting unbounded-size resource arrays with GLSL we need + // to use the `GL_EXT_nonuniform_qualifier` extension to ensure + // that they are not treated as "implicitly-sized arrays" which + // are arrays that have a fixed size that just isn't specified + // at the declaration site (instead being inferred from use sites). + // + // While the extension primarily introduces the `nonuniformEXT` + // qualifier that we use to implement `NonUniformResourceIndex`, + // it also changes the GLSL language semantics around (resource) array + // declarations that don't specify a size. + // + if( as(varType) ) + { + if(isResourceType(unwrapArray(varType))) + { + requireGLSLExtension("GL_EXT_nonuniform_qualifier"); + } + } + } + + // Need to emit appropriate modifiers here. + + // We expect/require all shader parameters to + // have some kind of layout information associated with them. + // + auto layout = getVarLayout(varDecl); + SLANG_ASSERT(layout); + + emitIRVarModifiers(layout, varDecl, varType); + + emitIRRateQualifiers(varDecl); + emitIRType(varType, getIRName(varDecl)); + + emitIRSemantics(varDecl); + + emitIRLayoutSemantics(varDecl); + + // A shader parameter cannot have an initializer, + // so we do need to consider emitting one here. + + m_writer->emit(";\n\n"); +} + + +void CLikeSourceEmitter::emitIRGlobalConstantInitializer(IRGlobalConstant* valDecl) +{ + // We expect to see only a single block + auto block = valDecl->getFirstBlock(); + SLANG_RELEASE_ASSERT(block); + SLANG_RELEASE_ASSERT(!block->getNextBlock()); + + // We expect the terminator to be a `return` + // instruction with a value. + auto returnInst = (IRReturnVal*) block->getLastDecorationOrChild(); + SLANG_RELEASE_ASSERT(returnInst->op == kIROp_ReturnVal); + + // We will emit the value in the `GlobalConstant` mode, which + // more or less says to fold all instructions into their use + // sites, so that we end up with a single expression tree even + // in cases that would otherwise trip up our analysis. + // + // Note: We are emitting the value as an *operand* here instead + // of directly calling `emitIRInstExpr` because we need to handle + // cases where the value might *need* to emit as a named referenced + // (e.g., when it names another constant directly). + // + emitIROperand(returnInst->getVal(), IREmitMode::GlobalConstant, getInfo(EmitOp::General)); +} + +void CLikeSourceEmitter::emitIRGlobalConstant(IRGlobalConstant* valDecl) +{ + auto valType = valDecl->getDataType(); + + if( getSourceStyle() != SourceStyle::GLSL ) + { + m_writer->emit("static "); + } + m_writer->emit("const "); + emitIRRateQualifiers(valDecl); + emitIRType(valType, getIRName(valDecl)); + + if (valDecl->getFirstBlock()) + { + // There is an initializer (which we expect for + // any global constant...). + + m_writer->emit(" = "); + + // We need to emit the entire initializer as + // a single expression. + emitIRGlobalConstantInitializer(valDecl); + } + + + m_writer->emit(";\n"); +} + +void CLikeSourceEmitter::emitIRGlobalInst(IRInst* inst) +{ + m_writer->advanceToSourceLocation(inst->sourceLoc); + + switch(inst->op) + { + case kIROp_Func: + emitIRFunc((IRFunc*) inst); + break; + + case kIROp_GlobalVar: + emitIRGlobalVar((IRGlobalVar*) inst); + break; + + case kIROp_GlobalParam: + emitIRGlobalParam((IRGlobalParam*) inst); + break; + + case kIROp_GlobalConstant: + emitIRGlobalConstant((IRGlobalConstant*) inst); + break; + + case kIROp_Var: + emitIRVar((IRVar*) inst); + break; + + case kIROp_StructType: + emitIRStruct(cast(inst)); + break; + + default: + break; + } +} + +void CLikeSourceEmitter::ensureInstOperand(ComputeEmitActionsContext* ctx, IRInst* inst, EmitAction::Level requiredLevel) +{ + if(!inst) return; + + if(inst->getParent() == ctx->moduleInst) + { + ensureGlobalInst(ctx, inst, requiredLevel); + } +} + +void CLikeSourceEmitter::ensureInstOperandsRec(ComputeEmitActionsContext* ctx, IRInst* inst) +{ + ensureInstOperand(ctx, inst->getFullType()); + + UInt operandCount = inst->operandCount; + for(UInt ii = 0; ii < operandCount; ++ii) + { + // TODO: there are some special cases we can add here, + // to avoid outputting full definitions in cases that + // can get by with forward declarations. + // + // For example, true pointer types should (in principle) + // only need the type they point to to be forward-declared. + // Similarly, a `call` instruction only needs the callee + // to be forward-declared, etc. + + ensureInstOperand(ctx, inst->getOperand(ii)); + } + + for(auto child : inst->getDecorationsAndChildren()) + { + ensureInstOperandsRec(ctx, child); + } +} + +void CLikeSourceEmitter::ensureGlobalInst(ComputeEmitActionsContext* ctx, IRInst* inst, EmitAction::Level requiredLevel) +{ + // Skip certain instructions, since they + // don't affect output. + switch(inst->op) + { + case kIROp_WitnessTable: + case kIROp_Generic: + return; + + default: + break; + } + + // Have we already processed this instruction? + EmitAction::Level existingLevel; + if(ctx->mapInstToLevel.TryGetValue(inst, existingLevel)) + { + // If we've already emitted it suitably, + // then don't worry about it. + if(existingLevel >= requiredLevel) + return; + } + + EmitAction action; + action.level = requiredLevel; + action.inst = inst; + + if(requiredLevel == EmitAction::Level::Definition) + { + if(ctx->openInsts.Contains(inst)) + { + SLANG_UNEXPECTED("circularity during codegen"); + return; + } + + ctx->openInsts.Add(inst); + + ensureInstOperandsRec(ctx, inst); + + ctx->openInsts.Remove(inst); + } + + ctx->mapInstToLevel[inst] = requiredLevel; + ctx->actions->add(action); +} + +void CLikeSourceEmitter::computeIREmitActions(IRModule* module, List& ioActions) +{ + ComputeEmitActionsContext ctx; + ctx.moduleInst = module->getModuleInst(); + ctx.actions = &ioActions; + + for(auto inst : module->getGlobalInsts()) + { + if( as(inst) ) + { + // Don't emit a type unless it is actually used. + continue; + } + + ensureGlobalInst(&ctx, inst, EmitAction::Level::Definition); + } +} + +void CLikeSourceEmitter::executeIREmitActions(List const& actions) +{ + for(auto action : actions) + { + switch(action.level) + { + case EmitAction::Level::ForwardDeclaration: + emitIRFuncDecl(cast(action.inst)); + break; + + case EmitAction::Level::Definition: + emitIRGlobalInst(action.inst); + break; + } + } +} + +void CLikeSourceEmitter::emitIRModule(IRModule* module) +{ + // The IR will usually come in an order that respects + // dependencies between global declarations, but this + // isn't guaranteed, so we need to be careful about + // the order in which we emit things. + + List actions; + + computeIREmitActions(module, actions); + executeIREmitActions(actions); +} + +} // namespace Slang diff --git a/source/slang/slang-emit-c-like.h b/source/slang/slang-emit-c-like.h new file mode 100644 index 000000000..f74850b35 --- /dev/null +++ b/source/slang/slang-emit-c-like.h @@ -0,0 +1,473 @@ +// slang-emit-c-like.h +#ifndef SLANG_EMIT_C_LIKE_H +#define SLANG_EMIT_C_LIKE_H + +#include "../core/slang-basic.h" + +#include "slang-compiler.h" + +#include "slang-emit-glsl-extension-tracker.h" +#include "slang-emit-precedence.h" +#include "slang-emit-source-writer.h" + +#include "slang-ir.h" +#include "slang-ir-insts.h" +#include "slang-ir-restructure.h" + +namespace Slang +{ + +class CLikeSourceEmitter +{ +public: + struct CInfo + { + BackEndCompileRequest* compileRequest = nullptr; + // The target language we want to generate code for + CodeGenTarget target = CodeGenTarget::Unknown; + // The entry point we are being asked to compile + EntryPoint* entryPoint = nullptr; + // The "effective" profile that is being used to emit code, + // combining information from the target and entry point. + Profile effectiveProfile = Profile::RawEnum::Unknown; + + SourceWriter* sourceWriter = nullptr; + // The layout for the entry point + EntryPointLayout* entryPointLayout = nullptr; + + ProgramLayout* programLayout = nullptr; + // We track the original global-scope layout so that we can + // find layout information for `import`ed parameters. + // + // TODO: This will probably change if we represent imports + // explicitly in the layout data. + StructTypeLayout* globalStructLayout = nullptr; + }; + + /// To simplify cases + enum class SourceStyle + { + Unknown, + GLSL, + HLSL, + C, + CPP, + CountOf, + }; + enum class BuiltInCOp + { + Splat, //< Splat a single value to all values of a vector or matrix type + Init, //< Initialize with parameters (must match the type) + }; + + typedef unsigned int ESemanticMask; + enum + { + kESemanticMask_None = 0, + kESemanticMask_NoPackOffset = 1 << 0, + kESemanticMask_Default = kESemanticMask_NoPackOffset, + }; + + // Hack to allow IR emit for global constant to override behavior + enum class IREmitMode + { + Default, + GlobalConstant, + }; + + struct EmitVarChain; + struct IRDeclaratorInfo; + struct EDeclarator; + struct ComputeEmitActionsContext; + + // An action to be performed during code emit. + struct EmitAction + { + enum Level + { + ForwardDeclaration, + Definition, + }; + Level level; + IRInst* inst; + }; + + /// Ctor + CLikeSourceEmitter(const CInfo& cinfo); + + /// Get the source manager + SourceManager* getSourceManager() { return m_compileRequest->getSourceManager(); } + + /// Get the diagnostic sink + DiagnosticSink* getSink() { return m_compileRequest->getSink();} + LineDirectiveMode getLineDirectiveMode() { return m_compileRequest->getLineDirectiveMode(); } + + /// Get the code gen target + CodeGenTarget getTarget() { return m_target; } + /// Get the source style + SLANG_FORCE_INLINE SourceStyle getSourceStyle() const { return m_sourceStyle; } + + void noteInternalErrorLoc(SourceLoc loc) { return getSink()->noteInternalErrorLoc(loc); } + + GLSLExtensionTracker* getGLSLExtensionTracker() { return &m_glslExtensionTracker; } + + // + // Types + // + + void emitDeclarator(EDeclarator* declarator); + + void emitGLSLTypePrefix(IRType* type, bool promoteHalfToFloat = false); + + void emitHLSLTextureType(IRTextureTypeBase* texType); + + void emitGLSLTextureOrTextureSamplerType(IRTextureTypeBase* type, char const* baseName); + + void emitGLSLTextureType(IRTextureType* texType); + + void emitGLSLTextureSamplerType(IRTextureSamplerType* type); + + void emitGLSLImageType(IRGLSLImageType* type); + + void emitTextureType(IRTextureType* texType); + + void emitTextureSamplerType(IRTextureSamplerType* type); + void emitImageType(IRGLSLImageType* type); + + void emitVectorTypeName(IRType* elementType, IRIntegerValue elementCount); + + void emitSamplerStateType(IRSamplerStateTypeBase* samplerStateType); + + void emitStructuredBufferType(IRHLSLStructuredBufferTypeBase* type); + + void emitUntypedBufferType(IRUntypedBufferResourceType* type); + + void emitType(IRType* type, const SourceLoc& typeLoc, Name* name, const SourceLoc& nameLoc); + void emitType(IRType* type, Name* name); + void emitType(IRType* type, String const& name); + void emitType(IRType* type); + + // + // Expressions + // + + bool maybeEmitParens(EmitOpInfo& outerPrec, EmitOpInfo prec); + + void maybeCloseParens(bool needClose); + + bool isTargetIntrinsicModifierApplicable(String const& targetName); + + void emitType(IRType* type, Name* name, SourceLoc const& nameLoc); + + void emitType(IRType* type, NameLoc const& nameAndLoc); + + bool isTargetIntrinsicModifierApplicable(IRTargetIntrinsicDecoration* decoration); + + void emitStringLiteral(const String& value); + + void requireGLSLExtension(const String& name); + + void requireGLSLVersion(ProfileVersion version); + void requireGLSLVersion(int version); + void setSampleRateFlag(); + + void doSampleRateInputCheck(Name* name); + + void emitVal(IRInst* val, const EmitOpInfo& outerPrec); + + UInt getBindingOffset(EmitVarChain* chain, LayoutResourceKind kind); + UInt getBindingSpace(EmitVarChain* chain, LayoutResourceKind kind); + + // Emit a single `register` semantic, as appropriate for a given resource-type-specific layout info + // Keyword to use in the uniform case (`register` for globals, `packoffset` inside a `cbuffer`) + void emitHLSLRegisterSemantic(LayoutResourceKind kind, EmitVarChain* chain, char const* uniformSemanticSpelling = "register"); + + // Emit all the `register` semantics that are appropriate for a particular variable layout + void emitHLSLRegisterSemantics(EmitVarChain* chain, char const* uniformSemanticSpelling = "register"); + void emitHLSLRegisterSemantics(VarLayout* varLayout, char const* uniformSemanticSpelling = "register"); + + void emitHLSLParameterGroupFieldLayoutSemantics(EmitVarChain* chain); + + void emitHLSLParameterGroupFieldLayoutSemantics(RefPtr fieldLayout, EmitVarChain* inChain); + + bool emitGLSLLayoutQualifier(LayoutResourceKind kind, EmitVarChain* chain); + + void emitGLSLLayoutQualifiers(RefPtr layout, EmitVarChain* inChain, LayoutResourceKind filter = LayoutResourceKind::None); + + void emitGLSLVersionDirective(); + + void emitGLSLPreprocessorDirectives(); + + /// Emit directives to control overall layout computation for the emitted code. + void emitLayoutDirectives(TargetRequest* targetReq); + + // Utility code for generating unique IDs as needed + // during the emit process (e.g., for declarations + // that didn't originally have names, but now need to). + UInt allocateUniqueID(); + + // IR-level emit logic + + UInt getID(IRInst* value); + + /// "Scrub" a name so that it complies with restrictions of the target language. + String scrubName(const String& name); + + String generateIRName(IRInst* inst); + String getIRName(IRInst* inst); + + void emitDeclarator(IRDeclaratorInfo* declarator); + void emitIRSimpleValue(IRInst* inst); + + bool shouldFoldIRInstIntoUseSites(IRInst* inst, IREmitMode mode); + + void emitIROperand(IRInst* inst, IREmitMode mode, EmitOpInfo const& outerPrec); + + void emitIRArgs(IRInst* inst, IREmitMode mode); + + void emitIRType(IRType* type, String const& name); + + void emitIRType(IRType* type, Name* name); + + void emitIRType(IRType* type); + + void emitIRRateQualifiers(IRRate* rate); + + void emitIRRateQualifiers(IRInst* value); + + void emitIRInstResultDecl(IRInst* inst); + + IRTargetIntrinsicDecoration* findTargetIntrinsicDecoration(IRInst* inst); + + // Check if the string being used to define a target intrinsic + // is an "ordinary" name, such that we can simply emit a call + // to the new name with the arguments of the old operation. + static bool isOrdinaryName(const String& name); + + void emitTargetIntrinsicCallExpr( + IRCall* inst, + IRFunc* /* func */, + IRTargetIntrinsicDecoration* targetIntrinsic, + IREmitMode mode, + EmitOpInfo const& inOuterPrec); + + void emitIntrinsicCallExpr( + IRCall* inst, + IRFunc* func, + IREmitMode mode, + EmitOpInfo const& inOuterPrec); + + void emitIRCallExpr(IRCall* inst, IREmitMode mode, EmitOpInfo outerPrec); + + void emitNot(IRInst* inst, IREmitMode mode, EmitOpInfo& ioOuterPrec, bool* outNeedClose); + + void emitComparison(IRInst* inst, IREmitMode mode, EmitOpInfo& ioOuterPrec, const EmitOpInfo& opPrec, bool* needCloseOut); + + void emitIRInstExpr(IRInst* inst, IREmitMode mode, EmitOpInfo const& inOuterPrec); + + BaseType extractBaseType(IRType* inType); + + void emitIRInst(IRInst* inst, IREmitMode mode); + + void emitIRSemantics(VarLayout* varLayout); + + void emitIRSemantics(IRInst* inst); + + VarLayout* getVarLayout(IRInst* var); + + void emitIRLayoutSemantics(IRInst* inst, char const* uniformSemanticSpelling = "register"); + + // When we are about to traverse an edge from one block to another, + // we need to emit the assignments that conceptually occur "along" + // the edge. In traditional SSA these are the phi nodes in the + // target block, while in our representation these use the arguments + // to the branch instruction to fill in the parameters of the target. + void emitPhiVarAssignments(UInt argCount, IRUse* args, IRBlock* targetBlock); + + /// Emit high-level language statements from a structured region. + void emitRegion(Region* inRegion); + + /// Emit high-level language statements from a structured region tree. + void emitRegionTree(RegionTree* regionTree); + + // Is an IR function a definition? (otherwise it is a declaration) + bool isDefinition(IRFunc* func); + + String getIRFuncName(IRFunc* func); + + void emitAttributeSingleString(const char* name, FuncDecl* entryPoint, Attribute* attrib); + + void emitAttributeSingleInt(const char* name, FuncDecl* entryPoint, Attribute* attrib); + + void emitFuncDeclPatchConstantFuncAttribute(IRFunc* irFunc, FuncDecl* entryPoint, PatchConstantFuncAttribute* attrib); + + void emitIREntryPointAttributes_HLSL(IRFunc* irFunc, EntryPointLayout* entryPointLayout); + + void emitIREntryPointAttributes_GLSL(IRFunc* irFunc, EntryPointLayout* entryPointLayout); + + void emitIREntryPointAttributes(IRFunc* irFunc, EntryPointLayout* entryPointLayout); + + void emitPhiVarDecls(IRFunc* func); + + /// Emit high-level statements for the body of a function. + void emitIRFunctionBody(IRGlobalValueWithCode* code); + + void emitIRSimpleFunc(IRFunc* func); + + void emitIRParamType(IRType* type, String const& name); + + IRInst* getSpecializedValue(IRSpecialize* specInst); + + void emitIRFuncDecl(IRFunc* func); + + EntryPointLayout* getEntryPointLayout(IRFunc* func); + + EntryPointLayout* asEntryPoint(IRFunc* func); + + // Detect if the given IR function represents a + // declaration of an intrinsic/builtin for the + // current code-generation target. + bool isTargetIntrinsic(IRFunc* func); + + // Check whether a given value names a target intrinsic, + // and return the IR function representing the intrinsic + // if it does. + IRFunc* asTargetIntrinsic(IRInst* value); + + void emitIRFunc(IRFunc* func); + + void emitIRStruct(IRStructType* structType); + + void emitIRMatrixLayoutModifiers(VarLayout* layout); + + // Emit the `flat` qualifier if the underlying type + // of the variable is an integer type. + void maybeEmitGLSLFlatModifier(IRType* valueType); + + void emitInterpolationModifiers(IRInst* varInst, IRType* valueType, VarLayout* layout); + + UInt getRayPayloadLocation(IRInst* inst); + + UInt getCallablePayloadLocation(IRInst* inst); + + void emitGLSLImageFormatModifier(IRInst* var, IRTextureType* resourceType); + + /// Emit modifiers that should apply even for a declaration of an SSA temporary. + void emitIRTempModifiers(IRInst* temp); + + void emitIRVarModifiers(VarLayout* layout, IRInst* varDecl, IRType* varType); + + void emitHLSLParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type); + + /// Emit the array brackets that go on the end of a declaration of the given type. + void emitArrayBrackets(IRType* inType); + + void emitGLSLParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type); + + void emitIRParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type); + + void emitIRVar(IRVar* varDecl); + + void emitIRStructuredBuffer_GLSL(IRGlobalParam* varDecl, IRHLSLStructuredBufferTypeBase* structuredBufferType); + + void emitIRByteAddressBuffer_GLSL(IRGlobalParam* varDecl, IRByteAddressBufferTypeBase* byteAddressBufferType); + + void emitIRGlobalVar(IRGlobalVar* varDecl); + void emitIRGlobalParam(IRGlobalParam* varDecl); + void emitIRGlobalConstantInitializer(IRGlobalConstant* valDecl); + + void emitIRGlobalConstant(IRGlobalConstant* valDecl); + + void emitIRGlobalInst(IRInst* inst); + + void ensureInstOperand(ComputeEmitActionsContext* ctx, IRInst* inst, EmitAction::Level requiredLevel = EmitAction::Level::Definition); + + void ensureInstOperandsRec(ComputeEmitActionsContext* ctx, IRInst* inst); + + void ensureGlobalInst(ComputeEmitActionsContext* ctx, IRInst* inst, EmitAction::Level requiredLevel); + + void computeIREmitActions(IRModule* module, List& ioActions); + + void executeIREmitActions(List const& actions); + void emitIRModule(IRModule* module); + + /// Gets a source style for a target. Returns Unknown if not a known target + static SourceStyle getSourceStyle(CodeGenTarget target); + + protected: + + void _emitSimpleType(IRType* type); + void _emitArrayType(IRArrayType* arrayType, EDeclarator* declarator); + void _emitUnsizedArrayType(IRUnsizedArrayType* arrayType, EDeclarator* declarator); + void _emitType(IRType* type, EDeclarator* declarator); + void _emitIRInst(IRInst* inst, IREmitMode mode); + void _emitVectorType(IRVectorType* vecType); + void _emitMatrixType(IRMatrixType* matType); + + void _requireHalf(); + void _emitCVecType(IROp op, Int size); + void _emitCMatType(IROp op, IRIntegerValue rowCount, IRIntegerValue colCount); + + void _emitCFunc(BuiltInCOp cop, IRType* type); + void _maybeEmitGLSLCast(IRType* castType, IRInst* inst, IREmitMode mode); + + + BackEndCompileRequest* m_compileRequest = nullptr; + + // The entry point we are being asked to compile + EntryPoint* m_entryPoint; + + // The layout for the entry point + EntryPointLayout* m_entryPointLayout; + + // The target language we want to generate code for + CodeGenTarget m_target; + // Source style - a simplification of the more nuanced m_target + SourceStyle m_sourceStyle; + + // Where source is written to + SourceWriter* m_writer; + + // We only want to emit each `import`ed module one time, so + // we maintain a set of already-emitted modules. + HashSet m_modulesAlreadyEmitted; + + // We track the original global-scope layout so that we can + // find layout information for `import`ed parameters. + // + // TODO: This will probably change if we represent imports + // explicitly in the layout data. + StructTypeLayout* m_globalStructLayout; + + ProgramLayout* m_programLayout; + + ModuleDecl* m_program; + + GLSLExtensionTracker m_glslExtensionTracker; + + UInt m_uniqueIDCounter = 1; + Dictionary m_mapIRValueToID; + Dictionary m_mapDeclToID; + + HashSet m_irDeclsVisited; + + HashSet m_irTupleTypes; + + // The "effective" profile that is being used to emit code, + // combining information from the target and entry point. + Profile m_effectiveProfile; + + // Map a string name to the number of times we have seen this + // name used so far during code emission. + Dictionary m_uniqueNameCounters; + + // Map an IR instruction to the name that we've decided + // to use for it when emitting code. + Dictionary m_mapInstToName; + + Dictionary m_mapIRValueToRayPayloadLocation; + Dictionary m_mapIRValueToCallablePayloadLocation; +}; + +} +#endif diff --git a/source/slang/slang-emit-context.cpp b/source/slang/slang-emit-context.cpp deleted file mode 100644 index b330b86f9..000000000 --- a/source/slang/slang-emit-context.cpp +++ /dev/null @@ -1,7 +0,0 @@ -// slang-emit-context.cpp -#include "slang-emit-context.h" - -namespace Slang { - - -} // namespace Slang diff --git a/source/slang/slang-emit-context.h b/source/slang/slang-emit-context.h deleted file mode 100644 index 337d0434f..000000000 --- a/source/slang/slang-emit-context.h +++ /dev/null @@ -1,79 +0,0 @@ -// slang-emit-context.h -#ifndef SLANG_EMIT_CONTEXT_H_INCLUDED -#define SLANG_EMIT_CONTEXT_H_INCLUDED - -#include "../core/slang-basic.h" - -#include "slang-compiler.h" -#include "slang-type-layout.h" -#include "slang-source-stream.h" -#include "slang-extension-usage-tracker.h" - -namespace Slang -{ - -// Shared state for an entire emit session -struct EmitContext -{ - DiagnosticSink* getSink() { return compileRequest->getSink(); } - LineDirectiveMode getLineDirectiveMode() { return compileRequest->getLineDirectiveMode(); } - SourceManager* getSourceManager() { return compileRequest->getSourceManager(); } - void noteInternalErrorLoc(SourceLoc loc) { return getSink()->noteInternalErrorLoc(loc); } - - BackEndCompileRequest* compileRequest = nullptr; - - // The entry point we are being asked to compile - EntryPoint* entryPoint; - - // The layout for the entry point - EntryPointLayout* entryPointLayout; - - // The target language we want to generate code for - CodeGenTarget target; - - // Where source is written to - SourceStream* stream; - - // We only want to emit each `import`ed module one time, so - // we maintain a set of already-emitted modules. - HashSet modulesAlreadyEmitted; - - // We track the original global-scope layout so that we can - // find layout information for `import`ed parameters. - // - // TODO: This will probably change if we represent imports - // explicitly in the layout data. - StructTypeLayout* globalStructLayout; - - ProgramLayout* programLayout; - - ModuleDecl* program; - - ExtensionUsageTracker extensionUsageTracker; - - UInt uniqueIDCounter = 1; - Dictionary mapIRValueToID; - Dictionary mapDeclToID; - - HashSet irDeclsVisited; - - HashSet irTupleTypes; - - // The "effective" profile that is being used to emit code, - // combining information from the target and entry point. - Profile effectiveProfile; - - // Map a string name to the number of times we have seen this - // name used so far during code emission. - Dictionary uniqueNameCounters; - - // Map an IR instruction to the name that we've decided - // to use for it when emitting code. - Dictionary mapInstToName; - - Dictionary mapIRValueToRayPayloadLocation; - Dictionary mapIRValueToCallablePayloadLocation; -}; - -} -#endif diff --git a/source/slang/slang-emit-glsl-extension-tracker.cpp b/source/slang/slang-emit-glsl-extension-tracker.cpp new file mode 100644 index 000000000..c162f2f46 --- /dev/null +++ b/source/slang/slang-emit-glsl-extension-tracker.cpp @@ -0,0 +1,44 @@ +// slang-emit-glsl-extension-tracker.cpp +#include "slang-emit-glsl-extension-tracker.h" + +namespace Slang { + +void GLSLExtensionTracker::requireExtension(const String& name) +{ + if (m_extensionsRequired.Contains(name)) + return; + + StringBuilder& sb = m_extensionRequireLines; + + sb.append("#extension "); + sb.append(name); + sb.append(" : require\n"); + + m_extensionsRequired.Add(name); +} + +void GLSLExtensionTracker::requireVersion(ProfileVersion version) +{ + // Check if this profile is newer + if ((UInt)version > (UInt)m_profileVersion) + { + m_profileVersion = version; + } +} + +void GLSLExtensionTracker::requireHalfExtension() +{ + if (!m_hasHalfExtension) + { + // https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_shader_16bit_storage.txt + requireExtension("GL_EXT_shader_16bit_storage"); + + // https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_shader_explicit_arithmetic_types.txt + requireExtension("GL_EXT_shader_explicit_arithmetic_types"); + + m_hasHalfExtension = true; + } +} + + +} // namespace Slang diff --git a/source/slang/slang-emit-glsl-extension-tracker.h b/source/slang/slang-emit-glsl-extension-tracker.h new file mode 100644 index 000000000..3ba226fbc --- /dev/null +++ b/source/slang/slang-emit-glsl-extension-tracker.h @@ -0,0 +1,34 @@ +// slang-emit-glsl-extension-tracker.h +#ifndef SLANG_EMIT_GLSL_EXTENSION_TRACKER_H +#define SLANG_EMIT_GLSL_EXTENSION_TRACKER_H + +#include "../core/slang-basic.h" + +#include "slang-compiler.h" + +namespace Slang +{ + +class GLSLExtensionTracker +{ +public: + + void requireExtension(const String& name); + void requireVersion(ProfileVersion version); + void requireHalfExtension(); + + ProfileVersion getRequiredProfileVersion() const { return m_profileVersion; } + const StringBuilder& getExtensionRequireLines() const { return m_extensionRequireLines; } + +protected: + // Record the GLSL extensions we have already emitted a `#extension` for + HashSet m_extensionsRequired; + StringBuilder m_extensionRequireLines; + + ProfileVersion m_profileVersion = ProfileVersion::GLSL_110; + + bool m_hasHalfExtension = false; +}; + +} +#endif diff --git a/source/slang/slang-emit-source-writer.cpp b/source/slang/slang-emit-source-writer.cpp new file mode 100644 index 000000000..051e1ba0b --- /dev/null +++ b/source/slang/slang-emit-source-writer.cpp @@ -0,0 +1,396 @@ +// slang-emit-source-writer.cpp +#include "slang-emit-source-writer.h" + +// Disable warnings about sprintf +#ifdef _WIN32 +# pragma warning(disable:4996) +#endif + +// Note: using C++ stdio just to get a locale-independent +// way to format floating-point values. +// +// TODO: Go ahead and implement the Dragon4 algorithm so +// that we can print floating-point values to arbitrary +// precision as needed. +#include + +namespace Slang { + +SourceWriter::SourceWriter(SourceManager* sourceManager, LineDirectiveMode lineDirectiveMode) +{ + m_lineDirectiveMode = lineDirectiveMode; + this->m_sourceManager = sourceManager; +} + +String SourceWriter::getContentAndClear() +{ + String content(getContent()); + clearContent(); + return content; +} + +void SourceWriter::emitRawTextSpan(char const* textBegin, char const* textEnd) +{ + // TODO(tfoley): Need to make "corelib" not use `int` for pointer-sized things... + auto len = textEnd - textBegin; + m_builder.Append(textBegin, len); +} + +void SourceWriter::emitRawText(char const* text) +{ + emitRawTextSpan(text, text + strlen(text)); +} + +void SourceWriter::_emitTextSpan(char const* textBegin, char const* textEnd) +{ + // Don't change anything given an empty string + if (textBegin == textEnd) + return; + + // If the source location has changed in a way that required update, + // do it now! + _flushSourceLocationChange(); + + // Note: we don't want to emit indentation on a line that is empty. + // The logic in `Emit(textBegin, textEnd)` below will have broken + // the text into lines, so we can simply check if a line consists + // of just a newline. + if (m_isAtStartOfLine && *textBegin != '\n') + { + // We are about to emit text (other than a newline) + // at the start of a line, so we will emit the proper + // amount of indentation to keep things looking nice. + m_isAtStartOfLine = false; + for (Int ii = 0; ii < m_indentLevel; ++ii) + { + char const* indentString = " "; + size_t indentStringSize = strlen(indentString); + emitRawTextSpan(indentString, indentString + indentStringSize); + + // We will also update our tracking location, just in + // case other logic needs it. + // + // TODO: We may need to have a switch that controls whether + // we are in "pretty-printing" mode or "follow the locations + // in the original code" mode. + m_loc.column += indentStringSize; + } + } + + // Emit the raw text + emitRawTextSpan(textBegin, textEnd); + + // Update our logical position + auto len = int(textEnd - textBegin); + m_loc.column += len; +} + +void SourceWriter::indent() +{ + m_indentLevel++; +} + +void SourceWriter::dedent() +{ + m_indentLevel--; +} + +void SourceWriter::emit(char const* textBegin, char const* textEnd) +{ + char const* spanBegin = textBegin; + char const* spanEnd = spanBegin; + for (;;) + { + if (spanEnd == textEnd) + { + // We have a whole range of text waiting to be flushed + _emitTextSpan(spanBegin, spanEnd); + return; + } + + auto c = *spanEnd++; + + if (c == '\n') + { + // At the end of a line, we need to update our tracking + // information on code positions + _emitTextSpan(spanBegin, spanEnd); + m_loc.line++; + m_loc.column = 1; + m_isAtStartOfLine = true; + + // Start a new span for emit purposes + spanBegin = spanEnd; + } + } +} + +void SourceWriter::emit(char const* text) +{ + emit(text, text + strlen(text)); +} + +void SourceWriter::emit(const String& text) +{ + emit(text.begin(), text.end()); +} + +void SourceWriter::emit(const UnownedStringSlice& text) +{ + emit(text.begin(), text.end()); +} + +void SourceWriter::emit(Name* name) +{ + emit(getText(name)); +} + +void SourceWriter::emit(const NameLoc& nameAndLoc) +{ + advanceToSourceLocation(nameAndLoc.loc); + emit(getText(nameAndLoc.name)); +} + +void SourceWriter::emitName(Name* name, const SourceLoc& locIn) +{ + advanceToSourceLocation(locIn); + emit(name); +} + +void SourceWriter::emitName(const NameLoc& nameAndLoc) +{ + emitName(nameAndLoc.name, nameAndLoc.loc); +} + +void SourceWriter::emitName(Name* name) +{ + emitName(name, SourceLoc()); +} + +void SourceWriter::emit(IntegerLiteralValue value) +{ + char buffer[32]; + sprintf(buffer, "%lld", (long long int)value); + emit(buffer); +} + +void SourceWriter::emit(UInt value) +{ + char buffer[32]; + sprintf(buffer, "%llu", (unsigned long long)(value)); + emit(buffer); +} + +void SourceWriter::emit(int value) +{ + char buffer[16]; + sprintf(buffer, "%d", value); + emit(buffer); +} + +void SourceWriter::emit(double value) +{ + // There are a few different requirements here that we need to deal with: + // + // 1) We need to print something that is valid syntax in the target language + // (this means that hex floats are off the table for now) + // + // 2) We need our printing to be independent of the current global locale in C, + // so that we don't depend on the application leaving it as the default, + // and we also don't revert any changes they make. + // (this means that `sprintf` and friends are off the table) + // + // 3) We need to be sure that floating-point literals specified by the user will + // "round-trip" and turn into the same value when parsed back in. This means + // that we need to print a reasonable number of digits of precision. + // + // For right now, the easiest option that can balance these is to use + // the C++ standard library `iostream`s, because they support an explicit locale, + // and can (hopefully) print floating-point numbers accurately. + // + // Eventually, the right move here would be to implement proper floating-point + // number formatting ourselves, but that would require extensive testing to + // make sure we get it right. + + std::ostringstream stream; + stream.imbue(std::locale::classic()); + stream.setf(std::ios::fixed, std::ios::floatfield); + stream.precision(20); + stream << value; + + emit(stream.str().c_str()); +} + +void SourceWriter::advanceToSourceLocation(const SourceLoc& sourceLocation) +{ + advanceToSourceLocation(getSourceManager()->getHumaneLoc(sourceLocation)); +} + +void SourceWriter::advanceToSourceLocation(const HumaneSourceLoc& sourceLocation) +{ + // Skip invalid locations + if (sourceLocation.line <= 0) + return; + + m_needToUpdateSourceLocation = true; + m_nextSourceLocation = sourceLocation; +} + +void SourceWriter::_flushSourceLocationChange() +{ + if (!m_needToUpdateSourceLocation) + return; + + // Note: the order matters here, because trying to update + // the source location may involve outputting text that + // advances the location, and outputting text is what + // triggers this flush operation. + m_needToUpdateSourceLocation = false; + _emitLineDirectiveIfNeeded(m_nextSourceLocation); +} + +void SourceWriter::_emitLineDirectiveAndUpdateSourceLocation(const HumaneSourceLoc& sourceLocation) +{ + _emitLineDirective(sourceLocation); + + HumaneSourceLoc newLoc = sourceLocation; + newLoc.column = 1; + + m_loc = newLoc; +} + +void SourceWriter::_emitLineDirectiveIfNeeded(const HumaneSourceLoc& sourceLocation) +{ + // Don't do any of this work if the user has requested that we + // not emit line directives. + auto mode = getLineDirectiveMode(); + switch (mode) + { + case LineDirectiveMode::None: + return; + + case LineDirectiveMode::Default: + default: + break; + } + + // Ignore invalid source locations + if (sourceLocation.line <= 0) + return; + + // If we are currently emitting code at a source location with + // a differnet file or line, *or* if the source location is + // somehow later on the line than what we want to emit, + // then we need to emit a new `#line` directive. + if (sourceLocation.pathInfo.foundPath != m_loc.pathInfo.foundPath + || sourceLocation.line != m_loc.line + || sourceLocation.column < m_loc.column) + { + // Special case: if we are in the same file, and within a small number + // of lines of the target location, then go ahead and output newlines + // to get us caught up. + enum { kSmallLineCount = 3 }; + auto lineDiff = sourceLocation.line - m_loc.line; + if (sourceLocation.pathInfo.foundPath == m_loc.pathInfo.foundPath + && sourceLocation.line > m_loc.line + && lineDiff <= kSmallLineCount) + { + for (int ii = 0; ii < lineDiff; ++ii) + { + emit("\n"); + } + SLANG_RELEASE_ASSERT(sourceLocation.line == m_loc.line); + } + else + { + // Go ahead and output a `#line` directive to get us caught up + _emitLineDirectiveAndUpdateSourceLocation(sourceLocation); + } + } +} + +void SourceWriter::_emitLineDirective(const HumaneSourceLoc& sourceLocation) +{ + emitRawText("\n#line "); + + char buffer[16]; + sprintf(buffer, "%llu", (unsigned long long)sourceLocation.line); + emitRawText(buffer); + + // Only emit the path part of a `#line` directive if needed + if (sourceLocation.pathInfo.foundPath != m_loc.pathInfo.foundPath) + { + emitRawText(" "); + + auto mode = getLineDirectiveMode(); + switch (mode) + { + default: + case LineDirectiveMode::None: + SLANG_UNEXPECTED("should not be trying to emit '#line' directive"); + return; + case LineDirectiveMode::GLSL: + { + auto path = sourceLocation.pathInfo.foundPath; + + // GLSL doesn't support the traditional form of a `#line` directive without + // an extension. Rather than depend on that extension we will output + // a directive in the traditional GLSL fashion. + // + // TODO: Add some kind of configuration where we require the appropriate + // extension and then emit a traditional line directive. + + int id = 0; + if (!m_mapGLSLSourcePathToID.TryGetValue(path, id)) + { + id = m_glslSourceIDCount++; + m_mapGLSLSourcePathToID.Add(path, id); + } + + sprintf(buffer, "%d", id); + emitRawText(buffer); + break; + } + case LineDirectiveMode::Default: + case LineDirectiveMode::Standard: + { + // The simple case is to emit the path for the current source + // location. We need to be a little bit careful with this, + // because the path might include backslash characters if we + // are on Windows, and we want to canonicalize those over + // to forward slashes. + // + // TODO: Canonicalization like this should be done centrally + // in a module that tracks source files. + + emitRawText("\""); + const auto& path = sourceLocation.pathInfo.foundPath; + for (auto c : path) + { + char charBuffer[] = { c, 0 }; + switch (c) + { + default: + emitRawText(charBuffer); + break; + + // The incoming file path might use `/` and/or `\\` as + // a directory separator. We want to canonicalize this. + // + // TODO: should probably canonicalize paths to not use backslash somewhere else + // in the compilation pipeline... + case '\\': + emitRawText("/"); + break; + } + } + emitRawText("\""); + break; + } + } + } + + emitRawText("\n"); +} + +} // namespace Slang diff --git a/source/slang/slang-emit-source-writer.h b/source/slang/slang-emit-source-writer.h new file mode 100644 index 000000000..4d18ea8fe --- /dev/null +++ b/source/slang/slang-emit-source-writer.h @@ -0,0 +1,113 @@ +// slang-source-stream.h +#ifndef SLANG_EMIT_SOURCE_WRITER_H +#define SLANG_EMIT_SOURCE_WRITER_H + +#include "../core/slang-basic.h" + +#include "slang-compiler.h" + +namespace Slang +{ + +/* Class that encapsulates a stream of source. Facilities provided... + +* Management of the buffer that holds the source content as it is constructed +* output line directives + + Supports GLSL as well as C/CPP/HLSL style directives +* Support for line indention */ +class SourceWriter +{ +public: + /// Emits without span without any extra processing + void emitRawTextSpan(char const* textBegin, char const* textEnd); + void emitRawText(char const* text); + + /// Emit different types into the stream + void emit(char const* textBegin, char const* textEnd); + void emit(char const* text); + void emit(const String& text); + void emit(const UnownedStringSlice& text); + void emit(Name* name); + void emit(const NameLoc& nameAndLoc); + void emit(IntegerLiteralValue value); + void emit(UInt value); + void emit(int value); + void emit(double value); + + /// Emit names (doing so can also advance to a new source location) + void emitName(const NameLoc& nameAndLoc); + void emitName(Name* name, const SourceLoc& loc); + void emitName(Name* name); + + /// Indent the text + void indent(); + /// Dedent (the opposite of indenting) the text + void dedent(); + + /// Move the current source location to that specified + void advanceToSourceLocation(const SourceLoc& sourceLocation); + void advanceToSourceLocation(const HumaneSourceLoc& sourceLocation); + + /// Get the content as a string + String getContent() { return m_builder.ProduceString(); } + /// Clear the content + void clearContent() { m_builder.Clear(); } + /// Get the content as a string and clear the internal representation + String getContentAndClear(); + + /// Get the line directive mode used + LineDirectiveMode getLineDirectiveMode() const { return m_lineDirectiveMode; } + /// Get the source manager user + SourceManager* getSourceManager() const { return m_sourceManager; } + + /// Ctor + SourceWriter(SourceManager* sourceManager, LineDirectiveMode lineDirectiveMode); + +protected: + void _emitTextSpan(char const* textBegin, char const* textEnd); + void _flushSourceLocationChange(); + + // Emit a `#line` directive to the output, and also + // ensure that source location tracking information + // is correct based on the directive we just output. + void _emitLineDirectiveAndUpdateSourceLocation(const HumaneSourceLoc& sourceLocation); + + void _emitLineDirectiveIfNeeded(const HumaneSourceLoc& sourceLocation); + + // Emit a `#line` directive to the output. + // Doesn't update state of source-location tracking. + void _emitLineDirective(const HumaneSourceLoc& sourceLocation); + + // The string of code we've built so far. + // TODO(JS): We could store the text in chunks, and then only sew together into one buffer + // when we are done. Doing so would not require copies/reallocs until the full buffer has been + // produced. A downside to doing this is that it won't be so simple to debug by trying to + // look at the current contents of the buffer + StringBuilder m_builder; + + // Current source position for tracking purposes... + HumaneSourceLoc m_loc; + HumaneSourceLoc m_nextSourceLocation; + bool m_needToUpdateSourceLocation = false; + + // Are we at the start of a line, so that we should indent + // before writing any other text? + bool m_isAtStartOfLine = true; + + // How far are we indented? + Int m_indentLevel = 0; + + SourceManager* m_sourceManager = nullptr; + + // For GLSL output, we can't emit traditional `#line` directives + // with a file path in them, so we maintain a map that associates + // each path with a unique integer, and then we output those + // instead. + Dictionary m_mapGLSLSourcePathToID; + int m_glslSourceIDCount = 0; + + LineDirectiveMode m_lineDirectiveMode; +}; + +} +#endif diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp index 67b57660a..3cacb2d90 100644 --- a/source/slang/slang-emit.cpp +++ b/source/slang/slang-emit.cpp @@ -23,10 +23,9 @@ #include "slang-type-layout.h" #include "slang-visitor.h" -#include "slang-source-stream.h" -#include "slang-emit-context.h" +#include "slang-emit-source-writer.h" -#include "slang-c-like-source-emitter.h" +#include "slang-emit-c-like.h" #include @@ -172,32 +171,31 @@ String emitEntryPoint( lineDirectiveMode = LineDirectiveMode::GLSL; } - SourceStream sourceStream(compileRequest->getSourceManager(), lineDirectiveMode ); + SourceWriter sourceWriter(compileRequest->getSourceManager(), lineDirectiveMode ); - EmitContext emitContext; - emitContext.compileRequest = compileRequest; - emitContext.target = target; - emitContext.entryPoint = entryPoint; - emitContext.effectiveProfile = getEffectiveProfile(entryPoint, targetRequest); - emitContext.stream = &sourceStream; + CLikeSourceEmitter::CInfo cinfo; + + cinfo.compileRequest = compileRequest; + cinfo.target = target; + cinfo.entryPoint = entryPoint; + cinfo.effectiveProfile = getEffectiveProfile(entryPoint, targetRequest); + cinfo.sourceWriter = &sourceWriter; if (entryPoint && programLayout) { - emitContext.entryPointLayout = findEntryPointLayout( - programLayout, - entryPoint); + cinfo.entryPointLayout = findEntryPointLayout(programLayout, entryPoint); } - emitContext.programLayout = programLayout; + cinfo.programLayout = programLayout; // Layout information for the global scope is either an ordinary // `struct` in the common case, or a constant buffer in the case // where there were global-scope uniforms. StructTypeLayout* globalStructLayout = programLayout ? getGlobalStructLayout(programLayout) : nullptr; - emitContext.globalStructLayout = globalStructLayout; + cinfo.globalStructLayout = globalStructLayout; - CLikeSourceEmitter sourceEmitter(&emitContext); + CLikeSourceEmitter sourceEmitter(cinfo); { auto session = targetRequest->getSession(); @@ -423,7 +421,7 @@ String emitEntryPoint( irModule, irEntryPoint, compileRequest->getSink(), - &emitContext.extensionUsageTracker); + sourceEmitter.getGLSLExtensionTracker()); #if 0 dumpIRIfEnabled(compileRequest, irModule, "GLSL LEGALIZED"); @@ -476,14 +474,14 @@ String emitEntryPoint( case Stage::RayGeneration: if( target == CodeGenTarget::GLSL ) { - emitContext.extensionUsageTracker.requireGLSLExtension("GL_NV_ray_tracing"); - emitContext.extensionUsageTracker.requireGLSLVersion(ProfileVersion::GLSL_460); + sourceEmitter.getGLSLExtensionTracker()->requireExtension("GL_NV_ray_tracing"); + sourceEmitter.getGLSLExtensionTracker()->requireVersion(ProfileVersion::GLSL_460); } break; } - String code = sourceStream.getContent(); - sourceStream.clearContent(); + String code = sourceWriter.getContent(); + sourceWriter.clearContent(); // Now that we've emitted the code for all the declarations in the file, // it is time to stitch together the final output. @@ -493,12 +491,12 @@ String emitEntryPoint( sourceEmitter.emitLayoutDirectives(targetRequest); - String prefix = sourceStream.getContent(); + String prefix = sourceWriter.getContent(); StringBuilder finalResultBuilder; finalResultBuilder << prefix; - finalResultBuilder << emitContext.extensionUsageTracker.getGLSLExtensionRequireLines(); + finalResultBuilder << sourceEmitter.getGLSLExtensionTracker()->getExtensionRequireLines(); finalResultBuilder << code; diff --git a/source/slang/slang-extension-usage-tracker.cpp b/source/slang/slang-extension-usage-tracker.cpp deleted file mode 100644 index f679e15db..000000000 --- a/source/slang/slang-extension-usage-tracker.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// slang-extension-usage-tracker.cpp -#include "slang-extension-usage-tracker.h" - -namespace Slang { - -void ExtensionUsageTracker::requireGLSLExtension(const String& name) -{ - if (m_glslExtensionsRequired.Contains(name)) - return; - - StringBuilder& sb = m_glslExtensionRequireLines; - - sb.append("#extension "); - sb.append(name); - sb.append(" : require\n"); - - m_glslExtensionsRequired.Add(name); -} - -void ExtensionUsageTracker::requireGLSLVersion(ProfileVersion version) -{ - // Check if this profile is newer - if ((UInt)version > (UInt)m_glslProfileVersion) - { - m_glslProfileVersion = version; - } -} - -void ExtensionUsageTracker::requireGLSLHalfExtension() -{ - if (!m_hasGLSLHalfExtension) - { - // https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_shader_16bit_storage.txt - requireGLSLExtension("GL_EXT_shader_16bit_storage"); - - // https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_shader_explicit_arithmetic_types.txt - requireGLSLExtension("GL_EXT_shader_explicit_arithmetic_types"); - - m_hasGLSLHalfExtension = true; - } -} - - -} // namespace Slang diff --git a/source/slang/slang-extension-usage-tracker.h b/source/slang/slang-extension-usage-tracker.h deleted file mode 100644 index d17a7a6a1..000000000 --- a/source/slang/slang-extension-usage-tracker.h +++ /dev/null @@ -1,34 +0,0 @@ -// slang-extension-usage-tracker.h -#ifndef SLANG_EXTENSION_USAGE_TRACKER_H_INCLUDED -#define SLANG_EXTENSION_USAGE_TRACKER_H_INCLUDED - -#include "../core/slang-basic.h" - -#include "slang-compiler.h" - -namespace Slang -{ - -class ExtensionUsageTracker -{ -public: - - void requireGLSLExtension(const String& name); - void requireGLSLVersion(ProfileVersion version); - void requireGLSLHalfExtension(); - - ProfileVersion getRequiredGLSLProfileVersion() const { return m_glslProfileVersion; } - const StringBuilder& getGLSLExtensionRequireLines() const { return m_glslExtensionRequireLines; } - -protected: - // Record the GLSL extensions we have already emitted a `#extension` for - HashSet m_glslExtensionsRequired; - StringBuilder m_glslExtensionRequireLines; - - ProfileVersion m_glslProfileVersion = ProfileVersion::GLSL_110; - - bool m_hasGLSLHalfExtension = false; -}; - -} -#endif diff --git a/source/slang/slang-ir-glsl-legalize.cpp b/source/slang/slang-ir-glsl-legalize.cpp index 1e42cba62..c78b84cac 100644 --- a/source/slang/slang-ir-glsl-legalize.cpp +++ b/source/slang/slang-ir-glsl-legalize.cpp @@ -4,7 +4,7 @@ #include "slang-ir.h" #include "slang-ir-insts.h" -#include "slang-extension-usage-tracker.h" +#include "slang-emit-glsl-extension-tracker.h" namespace Slang { @@ -179,18 +179,18 @@ struct GLSLSystemValueInfo struct GLSLLegalizationContext { Session* session; - ExtensionUsageTracker* extensionUsageTracker; + GLSLExtensionTracker* glslExtensionTracker; DiagnosticSink* sink; Stage stage; void requireGLSLExtension(String const& name) { - extensionUsageTracker->requireGLSLExtension(name); + glslExtensionTracker->requireExtension(name); } void requireGLSLVersion(ProfileVersion version) { - extensionUsageTracker->requireGLSLVersion(version); + glslExtensionTracker->requireVersion(version); } Stage getStage() @@ -1511,7 +1511,7 @@ void legalizeEntryPointForGLSL( IRModule* module, IRFunc* func, DiagnosticSink* sink, - ExtensionUsageTracker* extensionUsageTracker) + GLSLExtensionTracker* glslExtensionTracker) { auto layoutDecoration = func->findDecoration(); SLANG_ASSERT(layoutDecoration); @@ -1523,7 +1523,7 @@ void legalizeEntryPointForGLSL( context.session = session; context.stage = entryPointLayout->profile.GetStage(); context.sink = sink; - context.extensionUsageTracker = extensionUsageTracker; + context.glslExtensionTracker = glslExtensionTracker; Stage stage = entryPointLayout->profile.GetStage(); diff --git a/source/slang/slang-ir-glsl-legalize.h b/source/slang/slang-ir-glsl-legalize.h index 3694005b3..e525f2791 100644 --- a/source/slang/slang-ir-glsl-legalize.h +++ b/source/slang/slang-ir-glsl-legalize.h @@ -7,7 +7,7 @@ namespace Slang class DiagnosticSink; class Session; -class ExtensionUsageTracker; +class GLSLExtensionTracker; struct IRFunc; struct IRModule; @@ -17,6 +17,6 @@ void legalizeEntryPointForGLSL( IRModule* module, IRFunc* func, DiagnosticSink* sink, - ExtensionUsageTracker* extensionUsageTracker); + GLSLExtensionTracker* glslExtensionTracker); } diff --git a/source/slang/slang-source-stream.cpp b/source/slang/slang-source-stream.cpp deleted file mode 100644 index e561176ae..000000000 --- a/source/slang/slang-source-stream.cpp +++ /dev/null @@ -1,396 +0,0 @@ -// emit.cpp -#include "slang-source-stream.h" - -// Disable warnings about sprintf -#ifdef _WIN32 -# pragma warning(disable:4996) -#endif - -// Note: using C++ stdio just to get a locale-independent -// way to format floating-point values. -// -// TODO: Go ahead and implement the Dragon4 algorithm so -// that we can print floating-point values to arbitrary -// precision as needed. -#include - -namespace Slang { - -SourceStream::SourceStream(SourceManager* sourceManager, LineDirectiveMode lineDirectiveMode) -{ - m_lineDirectiveMode = lineDirectiveMode; - this->m_sourceManager = sourceManager; -} - -String SourceStream::getContentAndClear() -{ - String content(getContent()); - clearContent(); - return content; -} - -void SourceStream::emitRawTextSpan(char const* textBegin, char const* textEnd) -{ - // TODO(tfoley): Need to make "corelib" not use `int` for pointer-sized things... - auto len = textEnd - textBegin; - m_builder.Append(textBegin, len); -} - -void SourceStream::emitRawText(char const* text) -{ - emitRawTextSpan(text, text + strlen(text)); -} - -void SourceStream::_emitTextSpan(char const* textBegin, char const* textEnd) -{ - // Don't change anything given an empty string - if (textBegin == textEnd) - return; - - // If the source location has changed in a way that required update, - // do it now! - _flushSourceLocationChange(); - - // Note: we don't want to emit indentation on a line that is empty. - // The logic in `Emit(textBegin, textEnd)` below will have broken - // the text into lines, so we can simply check if a line consists - // of just a newline. - if (m_isAtStartOfLine && *textBegin != '\n') - { - // We are about to emit text (other than a newline) - // at the start of a line, so we will emit the proper - // amount of indentation to keep things looking nice. - m_isAtStartOfLine = false; - for (Int ii = 0; ii < m_indentLevel; ++ii) - { - char const* indentString = " "; - size_t indentStringSize = strlen(indentString); - emitRawTextSpan(indentString, indentString + indentStringSize); - - // We will also update our tracking location, just in - // case other logic needs it. - // - // TODO: We may need to have a switch that controls whether - // we are in "pretty-printing" mode or "follow the locations - // in the original code" mode. - m_loc.column += indentStringSize; - } - } - - // Emit the raw text - emitRawTextSpan(textBegin, textEnd); - - // Update our logical position - auto len = int(textEnd - textBegin); - m_loc.column += len; -} - -void SourceStream::indent() -{ - m_indentLevel++; -} - -void SourceStream::dedent() -{ - m_indentLevel--; -} - -void SourceStream::emit(char const* textBegin, char const* textEnd) -{ - char const* spanBegin = textBegin; - char const* spanEnd = spanBegin; - for (;;) - { - if (spanEnd == textEnd) - { - // We have a whole range of text waiting to be flushed - _emitTextSpan(spanBegin, spanEnd); - return; - } - - auto c = *spanEnd++; - - if (c == '\n') - { - // At the end of a line, we need to update our tracking - // information on code positions - _emitTextSpan(spanBegin, spanEnd); - m_loc.line++; - m_loc.column = 1; - m_isAtStartOfLine = true; - - // Start a new span for emit purposes - spanBegin = spanEnd; - } - } -} - -void SourceStream::emit(char const* text) -{ - emit(text, text + strlen(text)); -} - -void SourceStream::emit(const String& text) -{ - emit(text.begin(), text.end()); -} - -void SourceStream::emit(const UnownedStringSlice& text) -{ - emit(text.begin(), text.end()); -} - -void SourceStream::emit(Name* name) -{ - emit(getText(name)); -} - -void SourceStream::emit(const NameLoc& nameAndLoc) -{ - advanceToSourceLocation(nameAndLoc.loc); - emit(getText(nameAndLoc.name)); -} - -void SourceStream::emitName(Name* name, const SourceLoc& locIn) -{ - advanceToSourceLocation(locIn); - emit(name); -} - -void SourceStream::emitName(const NameLoc& nameAndLoc) -{ - emitName(nameAndLoc.name, nameAndLoc.loc); -} - -void SourceStream::emitName(Name* name) -{ - emitName(name, SourceLoc()); -} - -void SourceStream::emit(IntegerLiteralValue value) -{ - char buffer[32]; - sprintf(buffer, "%lld", (long long int)value); - emit(buffer); -} - -void SourceStream::emit(UInt value) -{ - char buffer[32]; - sprintf(buffer, "%llu", (unsigned long long)(value)); - emit(buffer); -} - -void SourceStream::emit(int value) -{ - char buffer[16]; - sprintf(buffer, "%d", value); - emit(buffer); -} - -void SourceStream::emit(double value) -{ - // There are a few different requirements here that we need to deal with: - // - // 1) We need to print something that is valid syntax in the target language - // (this means that hex floats are off the table for now) - // - // 2) We need our printing to be independent of the current global locale in C, - // so that we don't depend on the application leaving it as the default, - // and we also don't revert any changes they make. - // (this means that `sprintf` and friends are off the table) - // - // 3) We need to be sure that floating-point literals specified by the user will - // "round-trip" and turn into the same value when parsed back in. This means - // that we need to print a reasonable number of digits of precision. - // - // For right now, the easiest option that can balance these is to use - // the C++ standard library `iostream`s, because they support an explicit locale, - // and can (hopefully) print floating-point numbers accurately. - // - // Eventually, the right move here would be to implement proper floating-point - // number formatting ourselves, but that would require extensive testing to - // make sure we get it right. - - std::ostringstream stream; - stream.imbue(std::locale::classic()); - stream.setf(std::ios::fixed, std::ios::floatfield); - stream.precision(20); - stream << value; - - emit(stream.str().c_str()); -} - -void SourceStream::advanceToSourceLocation(const SourceLoc& sourceLocation) -{ - advanceToSourceLocation(getSourceManager()->getHumaneLoc(sourceLocation)); -} - -void SourceStream::advanceToSourceLocation(const HumaneSourceLoc& sourceLocation) -{ - // Skip invalid locations - if (sourceLocation.line <= 0) - return; - - m_needToUpdateSourceLocation = true; - m_nextSourceLocation = sourceLocation; -} - -void SourceStream::_flushSourceLocationChange() -{ - if (!m_needToUpdateSourceLocation) - return; - - // Note: the order matters here, because trying to update - // the source location may involve outputting text that - // advances the location, and outputting text is what - // triggers this flush operation. - m_needToUpdateSourceLocation = false; - _emitLineDirectiveIfNeeded(m_nextSourceLocation); -} - -void SourceStream::_emitLineDirectiveAndUpdateSourceLocation(const HumaneSourceLoc& sourceLocation) -{ - _emitLineDirective(sourceLocation); - - HumaneSourceLoc newLoc = sourceLocation; - newLoc.column = 1; - - m_loc = newLoc; -} - -void SourceStream::_emitLineDirectiveIfNeeded(const HumaneSourceLoc& sourceLocation) -{ - // Don't do any of this work if the user has requested that we - // not emit line directives. - auto mode = getLineDirectiveMode(); - switch (mode) - { - case LineDirectiveMode::None: - return; - - case LineDirectiveMode::Default: - default: - break; - } - - // Ignore invalid source locations - if (sourceLocation.line <= 0) - return; - - // If we are currently emitting code at a source location with - // a differnet file or line, *or* if the source location is - // somehow later on the line than what we want to emit, - // then we need to emit a new `#line` directive. - if (sourceLocation.pathInfo.foundPath != m_loc.pathInfo.foundPath - || sourceLocation.line != m_loc.line - || sourceLocation.column < m_loc.column) - { - // Special case: if we are in the same file, and within a small number - // of lines of the target location, then go ahead and output newlines - // to get us caught up. - enum { kSmallLineCount = 3 }; - auto lineDiff = sourceLocation.line - m_loc.line; - if (sourceLocation.pathInfo.foundPath == m_loc.pathInfo.foundPath - && sourceLocation.line > m_loc.line - && lineDiff <= kSmallLineCount) - { - for (int ii = 0; ii < lineDiff; ++ii) - { - emit("\n"); - } - SLANG_RELEASE_ASSERT(sourceLocation.line == m_loc.line); - } - else - { - // Go ahead and output a `#line` directive to get us caught up - _emitLineDirectiveAndUpdateSourceLocation(sourceLocation); - } - } -} - -void SourceStream::_emitLineDirective(const HumaneSourceLoc& sourceLocation) -{ - emitRawText("\n#line "); - - char buffer[16]; - sprintf(buffer, "%llu", (unsigned long long)sourceLocation.line); - emitRawText(buffer); - - // Only emit the path part of a `#line` directive if needed - if (sourceLocation.pathInfo.foundPath != m_loc.pathInfo.foundPath) - { - emitRawText(" "); - - auto mode = getLineDirectiveMode(); - switch (mode) - { - default: - case LineDirectiveMode::None: - SLANG_UNEXPECTED("should not be trying to emit '#line' directive"); - return; - case LineDirectiveMode::GLSL: - { - auto path = sourceLocation.pathInfo.foundPath; - - // GLSL doesn't support the traditional form of a `#line` directive without - // an extension. Rather than depend on that extension we will output - // a directive in the traditional GLSL fashion. - // - // TODO: Add some kind of configuration where we require the appropriate - // extension and then emit a traditional line directive. - - int id = 0; - if (!m_mapGLSLSourcePathToID.TryGetValue(path, id)) - { - id = m_glslSourceIDCount++; - m_mapGLSLSourcePathToID.Add(path, id); - } - - sprintf(buffer, "%d", id); - emitRawText(buffer); - break; - } - case LineDirectiveMode::Default: - case LineDirectiveMode::Standard: - { - // The simple case is to emit the path for the current source - // location. We need to be a little bit careful with this, - // because the path might include backslash characters if we - // are on Windows, and we want to canonicalize those over - // to forward slashes. - // - // TODO: Canonicalization like this should be done centrally - // in a module that tracks source files. - - emitRawText("\""); - const auto& path = sourceLocation.pathInfo.foundPath; - for (auto c : path) - { - char charBuffer[] = { c, 0 }; - switch (c) - { - default: - emitRawText(charBuffer); - break; - - // The incoming file path might use `/` and/or `\\` as - // a directory separator. We want to canonicalize this. - // - // TODO: should probably canonicalize paths to not use backslash somewhere else - // in the compilation pipeline... - case '\\': - emitRawText("/"); - break; - } - } - emitRawText("\""); - break; - } - } - } - - emitRawText("\n"); -} - -} // namespace Slang diff --git a/source/slang/slang-source-stream.h b/source/slang/slang-source-stream.h deleted file mode 100644 index e6b7507c0..000000000 --- a/source/slang/slang-source-stream.h +++ /dev/null @@ -1,113 +0,0 @@ -// slang-source-stream.h -#ifndef SLANG_SOURCE_STREAM_H_INCLUDED -#define SLANG_SOURCE_STREAM_H_INCLUDED - -#include "../core/slang-basic.h" - -#include "slang-compiler.h" - -namespace Slang -{ - -/* Class that encapsulates a stream of source. Facilities provided... - -* Management of the buffer that holds the source content as it is constructed -* output line directives - + Supports GLSL as well as C/CPP/HLSL style directives -* Support for line indention */ -class SourceStream -{ -public: - /// Emits without span without any extra processing - void emitRawTextSpan(char const* textBegin, char const* textEnd); - void emitRawText(char const* text); - - /// Emit different types into the stream - void emit(char const* textBegin, char const* textEnd); - void emit(char const* text); - void emit(const String& text); - void emit(const UnownedStringSlice& text); - void emit(Name* name); - void emit(const NameLoc& nameAndLoc); - void emit(IntegerLiteralValue value); - void emit(UInt value); - void emit(int value); - void emit(double value); - - /// Emit names (doing so can also advance to a new source location) - void emitName(const NameLoc& nameAndLoc); - void emitName(Name* name, const SourceLoc& loc); - void emitName(Name* name); - - /// Indent the text - void indent(); - /// Dedent (the opposite of indenting) the text - void dedent(); - - /// Move the current source location to that specified - void advanceToSourceLocation(const SourceLoc& sourceLocation); - void advanceToSourceLocation(const HumaneSourceLoc& sourceLocation); - - /// Get the content as a string - String getContent() { return m_builder.ProduceString(); } - /// Clear the content - void clearContent() { m_builder.Clear(); } - /// Get the content as a string and clear the internal representation - String getContentAndClear(); - - /// Get the line directive mode used - LineDirectiveMode getLineDirectiveMode() const { return m_lineDirectiveMode; } - /// Get the source manager user - SourceManager* getSourceManager() const { return m_sourceManager; } - - /// Ctor - SourceStream(SourceManager* sourceManager, LineDirectiveMode lineDirectiveMode); - -protected: - void _emitTextSpan(char const* textBegin, char const* textEnd); - void _flushSourceLocationChange(); - - // Emit a `#line` directive to the output, and also - // ensure that source location tracking information - // is correct based on the directive we just output. - void _emitLineDirectiveAndUpdateSourceLocation(const HumaneSourceLoc& sourceLocation); - - void _emitLineDirectiveIfNeeded(const HumaneSourceLoc& sourceLocation); - - // Emit a `#line` directive to the output. - // Doesn't update state of source-location tracking. - void _emitLineDirective(const HumaneSourceLoc& sourceLocation); - - // The string of code we've built so far. - // TODO(JS): We could store the text in chunks, and then only sew together into one buffer - // when we are done. Doing so would not require copies/reallocs until the full buffer has been - // produced. A downside to doing this is that it won't be so simple to debug by trying to - // look at the current contents of the buffer - StringBuilder m_builder; - - // Current source position for tracking purposes... - HumaneSourceLoc m_loc; - HumaneSourceLoc m_nextSourceLocation; - bool m_needToUpdateSourceLocation = false; - - // Are we at the start of a line, so that we should indent - // before writing any other text? - bool m_isAtStartOfLine = true; - - // How far are we indented? - Int m_indentLevel = 0; - - SourceManager* m_sourceManager = nullptr; - - // For GLSL output, we can't emit traditional `#line` directives - // with a file path in them, so we maintain a map that associates - // each path with a unique integer, and then we output those - // instead. - Dictionary m_mapGLSLSourcePathToID; - int m_glslSourceIDCount = 0; - - LineDirectiveMode m_lineDirectiveMode; -}; - -} -#endif diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj index 3f10a36e1..3b302fbcb 100644 --- a/source/slang/slang.vcxproj +++ b/source/slang/slang.vcxproj @@ -174,17 +174,17 @@ - - + + + - @@ -225,7 +225,6 @@ - @@ -240,15 +239,15 @@ - - + + + - @@ -284,7 +283,6 @@ - diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters index 7103749c0..1edcba640 100644 --- a/source/slang/slang.vcxproj.filters +++ b/source/slang/slang.vcxproj.filters @@ -21,9 +21,6 @@ Header Files - - Header Files - Header Files @@ -39,19 +36,22 @@ Header Files - + + Header Files + + Header Files Header Files - + Header Files - + Header Files - + Header Files @@ -174,9 +174,6 @@ Header Files - - Header Files - Header Files @@ -215,9 +212,6 @@ - - Source Files - Source Files @@ -230,16 +224,19 @@ Source Files - + + Source Files + + Source Files Source Files - + Source Files - + Source Files @@ -347,9 +344,6 @@ Source Files - - Source Files - Source Files -- cgit v1.2.3