diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2019-05-31 13:17:34 -0400 |
|---|---|---|
| committer | Tim Foley <tfoleyNV@users.noreply.github.com> | 2019-05-31 10:17:34 -0700 |
| commit | b81ff3ef968d1cc4e954b31a1812b3c391d17b02 (patch) | |
| tree | d9669f736c3be30c569b1c0dbd0abfaca6e85a0c | |
| parent | d4924f5fc67f56b60d11381bf77d21bc01eb8763 (diff) | |
WIP: Support for other source target language (#971)
* WIP: Setting up C/Cpp source compilation targets.
* WIP: Emitting C/CPP.
* WIP: Split out SourceSink, and use it for source output on emit.
* SourceSink -> SourceStream
* * Made SourceStream use m_ prefixing of members.
* Make all methods use lower camel
* Removed methods from SourceStream interface that are not used externally (use _ prefixing)
* Improvements to documentation
* EmitContext is now effectively empty, so just use SharedEmitContext as EmitContext.
* SharedEmitContext -> EmitContext
* Methods to LowerCamel in emit.cpp
* Split out EmitContext and ExtensionUsageTracker into separate files.
* Split out EmitVisitor into slang-c-like-source-emitter files.
* EmitVisitor -> CLikeSourceEmitter
* Tidy up around CLikeSourceEmitter - simplify header.
* Small tidy up - removing repeated comments that are in header.
* Remove EmitContext paramter threading.
* Small tidy up.
Use prefixed macros for slang-c-like-source-emitter.h
* Small tidy up in slang-c-like-source-emitter.cpp
* First pass at splitting out UnmangleContext.
* MangledNameParser -> MangledLexer.
* WIP making EmitOp (EOp) enum available outside of cpp
* Generating EmitOpInfo from macro.
* Split out emit precedence handling.
Don't use kOp_ style anymore, just use an array indexed by EmitOp.
* Disable C simple test for now.
* Keep g++/clang happy with token pasting.
* Fix win32 narrowing warning.
27 files changed, 7506 insertions, 6938 deletions
@@ -505,6 +505,8 @@ extern "C" SLANG_DXBC_ASM, SLANG_DXIL, SLANG_DXIL_ASM, + SLANG_C_SOURCE, ///< The C language + SLANG_CPP_SOURCE, ///< The C++ language }; /* A "container format" describes the way that the outputs diff --git a/source/slang/compiler.cpp b/source/slang/compiler.cpp index 83a579345..22c1d4cd8 100644 --- a/source/slang/compiler.cpp +++ b/source/slang/compiler.cpp @@ -365,6 +365,12 @@ namespace Slang { return PassThroughMode::dxc; } + case CodeGenTarget::CPPSource: + case CodeGenTarget::CSource: + { + // Don't need an external compiler to output C and C++ code + return PassThroughMode::None; + } default: break; } @@ -1051,6 +1057,17 @@ SlangResult dissassembleDXILUsingDXC( } break; + case CodeGenTarget::CPPSource: + case CodeGenTarget::CSource: + { + return emitEntryPoint( + compileRequest, + entryPoint, + target, + targetReq); + } + break; + #if SLANG_ENABLE_DXBC_SUPPORT case CodeGenTarget::DXBytecode: { diff --git a/source/slang/compiler.h b/source/slang/compiler.h index 9b7e06be0..5d9e47aee 100644 --- a/source/slang/compiler.h +++ b/source/slang/compiler.h @@ -55,6 +55,8 @@ namespace Slang DXBytecodeAssembly = SLANG_DXBC_ASM, DXIL = SLANG_DXIL, DXILAssembly = SLANG_DXIL_ASM, + CSource = SLANG_C_SOURCE, + CPPSource = SLANG_CPP_SOURCE, }; enum class ContainerFormat diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp index 0d5e51747..d90415d89 100644 --- a/source/slang/emit.cpp +++ b/source/slang/emit.cpp @@ -23,6907 +23,21 @@ #include "type-layout.h" #include "visitor.h" -#include <assert.h> +#include "slang-source-stream.h" +#include "slang-emit-context.h" -// Note: using C++ stdio just to get a locale-independent -// way to format floating-point values. -// -// TODO: Go ahead and implement teh Dragon4 algorithm so -// that we can print floating-point values to arbitrary -// precision as needed. -#include <sstream> +#include "slang-c-like-source-emitter.h" -#ifdef _WIN32 -#include <d3dcompiler.h> -#pragma warning(disable:4996) -#endif +#include <assert.h> namespace Slang { -struct ExtensionUsageTracker -{ - // Record the GLSL extnsions we have already emitted a `#extension` for - HashSet<String> glslExtensionsRequired; - StringBuilder glslExtensionRequireLines; - - ProfileVersion profileVersion = ProfileVersion::GLSL_110; - - bool hasHalfExtension = false; -}; - -void requireGLSLExtension( - ExtensionUsageTracker* tracker, - String const& name) -{ - if (tracker->glslExtensionsRequired.Contains(name)) - return; - - StringBuilder& sb = tracker->glslExtensionRequireLines; - - sb.append("#extension "); - sb.append(name); - sb.append(" : require\n"); - - tracker->glslExtensionsRequired.Add(name); -} - -void requireGLSLVersionImpl( - ExtensionUsageTracker* tracker, - ProfileVersion version) -{ - // Check if this profile is newer - if ((UInt)version > (UInt)tracker->profileVersion) - { - tracker->profileVersion = version; - } -} - -void requireGLSLHalfExtension(ExtensionUsageTracker* tracker) -{ - if (!tracker->hasHalfExtension) - { - // https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_shader_16bit_storage.txt - requireGLSLExtension(tracker, "GL_EXT_shader_16bit_storage"); - - // https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_shader_explicit_arithmetic_types.txt - requireGLSLExtension(tracker, "GL_EXT_shader_explicit_arithmetic_types"); - - tracker->hasHalfExtension = true; - } -} - - -// Shared state for an entire emit session -struct SharedEmitContext +enum class BuiltInCOp { - 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; - - // The final code generation target - // - // For example, `target` might be `GLSL`, while `finalTarget` might be `SPIRV` - CodeGenTarget finalTarget; - - // The string of code we've built so far - StringBuilder sb; - - // Current source position for tracking purposes... - HumaneSourceLoc loc; - HumaneSourceLoc nextSourceLocation; - bool needToUpdateSourceLocation; - - // 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<String, int> mapGLSLSourcePathToID; - int glslSourceIDCount = 0; - - // We only want to emit each `import`ed module one time, so - // we maintain a set of already-emitted modules. - HashSet<ModuleDecl*> 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<IRInst*, UInt> mapIRValueToID; - Dictionary<Decl*, UInt> mapDeclToID; - - HashSet<String> irDeclsVisited; - - HashSet<String> irTupleTypes; - - // The "effective" profile that is being used to emit code, - // combining information from the target and entry point. - Profile effectiveProfile; - - - // Are we at the start of a line, so that we should indent - // before writing any other text? - bool isAtStartOfLine = true; - - // How far are we indented? - Int indentLevel = 0; - - // Map a string name to the number of times we have seen this - // name used so far during code emission. - Dictionary<String, UInt> uniqueNameCounters; - - // Map an IR instruction to the name that we've decided - // to use for it when emitting code. - Dictionary<IRInst*, String> mapInstToName; - - DiagnosticSink* getSink() { return compileRequest->getSink(); } - - Dictionary<IRInst*, UInt> mapIRValueToRayPayloadLocation; - Dictionary<IRInst*, UInt> mapIRValueToCallablePayloadLocation; + Splat, //< Splat a single value to all values of a vector or matrix type + Init, //< Initialize with parameters (must match the type) }; -struct EmitContext -{ - // The shared context that is in effect - SharedEmitContext* shared; - - DiagnosticSink* getSink() { return shared->getSink(); } - - LineDirectiveMode getLineDirectiveMode() { return shared->compileRequest->getLineDirectiveMode(); } - SourceManager* getSourceManager() { return shared->compileRequest->getSourceManager(); } - void noteInternalErrorLoc(SourceLoc loc) { return getSink()->noteInternalErrorLoc(loc); } -}; - -// - -enum EPrecedence -{ -#define LEFT(NAME) \ - kEPrecedence_##NAME##_Left, \ - kEPrecedence_##NAME##_Right - -#define RIGHT(NAME) \ - kEPrecedence_##NAME##_Right, \ - kEPrecedence_##NAME##_Left - -#define NONASSOC(NAME) \ - kEPrecedence_##NAME##_Left, \ - kEPrecedence_##NAME##_Right = kEPrecedence_##NAME##_Left - - NONASSOC(None), - LEFT(Comma), - - NONASSOC(General), - - RIGHT(Assign), - - RIGHT(Conditional), - - LEFT(Or), - LEFT(And), - LEFT(BitOr), - LEFT(BitXor), - LEFT(BitAnd), - - LEFT(Equality), - LEFT(Relational), - LEFT(Shift), - LEFT(Additive), - LEFT(Multiplicative), - RIGHT(Prefix), - LEFT(Postfix), - NONASSOC(Atomic), -}; - -// Info on an op for emit purposes -struct EOpInfo -{ - char const* op; - EPrecedence leftPrecedence; - EPrecedence rightPrecedence; -}; - -#define OP(NAME, TEXT, PREC) \ -static const EOpInfo kEOp_##NAME = { TEXT, kEPrecedence_##PREC##_Left, kEPrecedence_##PREC##_Right, } - -OP(None, "", None); - -OP(Comma, ",", Comma); - -OP(General, "", General); - -OP(Assign, "=", Assign); -OP(AddAssign, "+=", Assign); -OP(SubAssign, "-=", Assign); -OP(MulAssign, "*=", Assign); -OP(DivAssign, "/=", Assign); -OP(ModAssign, "%=", Assign); -OP(LshAssign, "<<=", Assign); -OP(RshAssign, ">>=", Assign); -OP(OrAssign, "|=", Assign); -OP(AndAssign, "&=", Assign); -OP(XorAssign, "^=", Assign); - -OP(Conditional, "?:", Conditional); - -OP(Or, "||", Or); -OP(And, "&&", And); -OP(BitOr, "|", BitOr); -OP(BitXor, "^", BitXor); -OP(BitAnd, "&", BitAnd); - -OP(Eql, "==", Equality); -OP(Neq, "!=", Equality); - -OP(Less, "<", Relational); -OP(Greater, ">", Relational); -OP(Leq, "<=", Relational); -OP(Geq, ">=", Relational); - -OP(Lsh, "<<", Shift); -OP(Rsh, ">>", Shift); - -OP(Add, "+", Additive); -OP(Sub, "-", Additive); - -OP(Mul, "*", Multiplicative); -OP(Div, "/", Multiplicative); -OP(Mod, "%", Multiplicative); - -OP(Prefix, "", Prefix); -OP(Postfix, "", Postfix); -OP(Atomic, "", Atomic); - -#undef OP - -// Table to allow data-driven lookup of an op based on its -// name (to assist when outputting unchecked operator calls) -static EOpInfo const* const kInfixOpInfos[] = -{ - &kEOp_Comma, - &kEOp_Assign, - &kEOp_AddAssign, - &kEOp_SubAssign, - &kEOp_MulAssign, - &kEOp_DivAssign, - &kEOp_ModAssign, - &kEOp_LshAssign, - &kEOp_RshAssign, - &kEOp_OrAssign, - &kEOp_AndAssign, - &kEOp_XorAssign, - &kEOp_Or, - &kEOp_And, - &kEOp_BitOr, - &kEOp_BitXor, - &kEOp_BitAnd, - &kEOp_Eql, - &kEOp_Neq, - &kEOp_Less, - &kEOp_Greater, - &kEOp_Leq, - &kEOp_Geq, - &kEOp_Lsh, - &kEOp_Rsh, - &kEOp_Add, - &kEOp_Sub, - &kEOp_Mul, - &kEOp_Div, - &kEOp_Mod, -}; - -// - -// represents a declarator for use in emitting types -struct 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 EmitVisitor -{ - EmitContext* context; - EmitVisitor(EmitContext* context) - : context(context) - {} - - // Low-level emit logic - - void emitRawTextSpan(char const* textBegin, char const* textEnd) - { - // TODO(tfoley): Need to make "corelib" not use `int` for pointer-sized things... - auto len = textEnd - textBegin; - context->shared->sb.Append(textBegin, len); - } - - void emitRawText(char const* text) - { - emitRawTextSpan(text, text + strlen(text)); - } - - void 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(context->shared->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. - context->shared->isAtStartOfLine = false; - for(Int ii = 0; ii < context->shared->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. - context->shared->loc.column += indentStringSize; - } - } - - // Emit the raw text - emitRawTextSpan(textBegin, textEnd); - - // Update our logical position - auto len = int(textEnd - textBegin); - context->shared->loc.column += len; - } - - void indent() - { - context->shared->indentLevel++; - } - - void dedent() - { - context->shared->indentLevel--; - } - - void 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); - context->shared->loc.line++; - context->shared->loc.column = 1; - context->shared->isAtStartOfLine = true; - - // Start a new span for emit purposes - spanBegin = spanEnd; - } - } - } - - void Emit(char const* text) - { - Emit(text, text + strlen(text)); - } - - void emit(String const& text) - { - Emit(text.begin(), text.end()); - } - - void emit(UnownedStringSlice const& text) - { - Emit(text.begin(), text.end()); - } - - - void emit(Name* name) - { - emit(getText(name)); - } - - void emit(NameLoc const& nameAndLoc) - { - advanceToSourceLocation(nameAndLoc.loc); - emit(getText(nameAndLoc.name)); - } - - void emitName( - Name* name, - SourceLoc const& loc) - { - advanceToSourceLocation(loc); - emit(name); - } - - void emitName(NameLoc const& nameAndLoc) - { - emitName(nameAndLoc.name, nameAndLoc.loc); - } - - void emitName(Name* name) - { - emitName(name, SourceLoc()); - } - - void Emit(IntegerLiteralValue value) - { - char buffer[32]; - sprintf(buffer, "%lld", (long long int)value); - Emit(buffer); - } - - - void Emit(UInt value) - { - char buffer[32]; - sprintf(buffer, "%llu", (unsigned long long)(value)); - Emit(buffer); - } - - void Emit(int value) - { - char buffer[16]; - sprintf(buffer, "%d", value); - Emit(buffer); - } - - void 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()); - } - - - // Emit a `#line` directive to the output. - // Doesn't update state of source-location tracking. - void emitLineDirective( - HumaneSourceLoc const& 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 != context->shared->loc.pathInfo.foundPath) - { - emitRawText(" "); - - bool shouldUseGLSLStyleLineDirective = false; - - auto mode = context->getLineDirectiveMode(); - switch (mode) - { - case LineDirectiveMode::None: - SLANG_UNEXPECTED("should not be trying to emit '#line' directive"); - return; - - case LineDirectiveMode::Default: - default: - // To try to make the default behavior reasonable, we will - // always use C-style line directives (to give the user - // good source locations on error messages from downstream - // compilers) *unless* they requested raw GLSL as the - // output (in which case we want to maximize compatibility - // with downstream tools). - if (context->shared->finalTarget == CodeGenTarget::GLSL) - { - shouldUseGLSLStyleLineDirective = true; - } - break; - - case LineDirectiveMode::Standard: - break; - - case LineDirectiveMode::GLSL: - shouldUseGLSLStyleLineDirective = true; - break; - } - - if(shouldUseGLSLStyleLineDirective) - { - 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(!context->shared->mapGLSLSourcePathToID.TryGetValue(path, id)) - { - id = context->shared->glslSourceIDCount++; - context->shared->mapGLSLSourcePathToID.Add(path, id); - } - - sprintf(buffer, "%d", id); - emitRawText(buffer); - } - else - { - // 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("\""); - } - } - - emitRawText("\n"); - } - - // 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( - HumaneSourceLoc const& sourceLocation) - { - emitLineDirective(sourceLocation); - - HumaneSourceLoc newLoc = sourceLocation; - newLoc.column = 1; - - context->shared->loc = newLoc; - } - - void emitLineDirectiveIfNeeded( - HumaneSourceLoc const& sourceLocation) - { - // Don't do any of this work if the user has requested that we - // not emit line directives. - auto mode = context->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 != context->shared->loc.pathInfo.foundPath - || sourceLocation.line != context->shared->loc.line - || sourceLocation.column < context->shared->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 - context->shared->loc.line; - if(sourceLocation.pathInfo.foundPath == context->shared->loc.pathInfo.foundPath - && sourceLocation.line > context->shared->loc.line - && lineDiff <= kSmallLineCount) - { - for(int ii = 0; ii < lineDiff; ++ii ) - { - Emit("\n"); - } - SLANG_RELEASE_ASSERT(sourceLocation.line == context->shared->loc.line); - } - else - { - // Go ahead and output a `#line` directive to get us caught up - emitLineDirectiveAndUpdateSourceLocation(sourceLocation); - } - } - } - - void advanceToSourceLocation( - HumaneSourceLoc const& sourceLocation) - { - // Skip invalid locations - if(sourceLocation.line <= 0) - return; - - context->shared->needToUpdateSourceLocation = true; - context->shared->nextSourceLocation = sourceLocation; - } - - SourceManager* getSourceManager() - { - return context->getSourceManager(); - } - - void advanceToSourceLocation( - SourceLoc const& sourceLocation) - { - advanceToSourceLocation(getSourceManager()->getHumaneLoc(sourceLocation)); - } - - void flushSourceLocationChange() - { - if(!context->shared->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. - context->shared->needToUpdateSourceLocation = false; - emitLineDirectiveIfNeeded(context->shared->nextSourceLocation); - } - - DiagnosticSink* getSink() - { - return context->getSink(); - } - - // - // Types - // - - void EmitDeclarator(EDeclarator* declarator) - { - if (!declarator) return; - - Emit(" "); - - switch (declarator->flavor) - { - case EDeclarator::Flavor::name: - emitName(declarator->name, declarator->loc); - break; - - case EDeclarator::Flavor::Array: - EmitDeclarator(declarator->next); - Emit("["); - if(auto elementCount = declarator->elementCount) - { - EmitVal(elementCount, kEOp_General); - } - Emit("]"); - break; - - case EDeclarator::Flavor::UnsizedArray: - EmitDeclarator(declarator->next); - Emit("[]"); - break; - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unknown declarator flavor"); - break; - } - } - - void emitGLSLTypePrefix( - IRType* type, - bool promoteHalfToFloat = false) - { - switch (type->op) - { - case kIROp_FloatType: - // no prefix - break; - - case kIROp_Int8Type: Emit("i8"); break; - case kIROp_Int16Type: Emit("i16"); break; - case kIROp_IntType: Emit("i"); break; - case kIROp_Int64Type: Emit("i64"); break; - - case kIROp_UInt8Type: Emit("u8"); break; - case kIROp_UInt16Type: Emit("u16"); break; - case kIROp_UIntType: Emit("u"); break; - case kIROp_UInt64Type: Emit("u64"); break; - - case kIROp_BoolType: Emit("b"); break; - - case kIROp_HalfType: - { - _requireHalf(); - if (promoteHalfToFloat) - { - // no prefix - } - else - { - Emit("f16"); - } - break; - } - case kIROp_DoubleType: Emit("d"); break; - - case kIROp_VectorType: - emitGLSLTypePrefix(cast<IRVectorType>(type)->getElementType(), promoteHalfToFloat); - break; - - case kIROp_MatrixType: - emitGLSLTypePrefix(cast<IRMatrixType>(type)->getElementType(), promoteHalfToFloat); - break; - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled GLSL type prefix"); - break; - } - } - - void emitHLSLTextureType( - IRTextureTypeBase* texType) - { - switch(texType->getAccess()) - { - case SLANG_RESOURCE_ACCESS_READ: - break; - - case SLANG_RESOURCE_ACCESS_READ_WRITE: - Emit("RW"); - break; - - case SLANG_RESOURCE_ACCESS_RASTER_ORDERED: - Emit("RasterizerOrdered"); - break; - - case SLANG_RESOURCE_ACCESS_APPEND: - Emit("Append"); - break; - - case SLANG_RESOURCE_ACCESS_CONSUME: - Emit("Consume"); - break; - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled resource access mode"); - break; - } - - switch (texType->GetBaseShape()) - { - case TextureFlavor::Shape::Shape1D: Emit("Texture1D"); break; - case TextureFlavor::Shape::Shape2D: Emit("Texture2D"); break; - case TextureFlavor::Shape::Shape3D: Emit("Texture3D"); break; - case TextureFlavor::Shape::ShapeCube: Emit("TextureCube"); break; - case TextureFlavor::Shape::ShapeBuffer: Emit("Buffer"); break; - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled resource shape"); - break; - } - - if (texType->isMultisample()) - { - Emit("MS"); - } - if (texType->isArray()) - { - Emit("Array"); - } - Emit("<"); - EmitType(texType->getElementType()); - Emit(" >"); - } - - void 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); - } - - Emit(baseName); - switch (type->GetBaseShape()) - { - case TextureFlavor::Shape::Shape1D: Emit("1D"); break; - case TextureFlavor::Shape::Shape2D: Emit("2D"); break; - case TextureFlavor::Shape::Shape3D: Emit("3D"); break; - case TextureFlavor::Shape::ShapeCube: Emit("Cube"); break; - case TextureFlavor::Shape::ShapeBuffer: Emit("Buffer"); break; - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled resource shape"); - break; - } - - if (type->isMultisample()) - { - Emit("MS"); - } - if (type->isArray()) - { - Emit("Array"); - } - } - - void 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 emitGLSLTextureSamplerType( - IRTextureSamplerType* type) - { - emitGLSLTextureOrTextureSamplerType(type, "sampler"); - } - - void emitGLSLImageType( - IRGLSLImageType* type) - { - emitGLSLTextureOrTextureSamplerType(type, "image"); - } - - void emitTextureType( - IRTextureType* texType) - { - switch(context->shared->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 emitTextureSamplerType( - IRTextureSamplerType* type) - { - switch(context->shared->target) - { - case CodeGenTarget::GLSL: - emitGLSLTextureSamplerType(type); - break; - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "this target should see combined texture-sampler types"); - break; - } - } - - void emitImageType( - IRGLSLImageType* type) - { - switch(context->shared->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; - } - } - - void emitVectorTypeName(IRType* elementType, IRIntegerValue elementCount) - { - switch(context->shared->target) - { - case CodeGenTarget::GLSL: - case CodeGenTarget::GLSL_Vulkan: - case CodeGenTarget::GLSL_Vulkan_OneDesc: - { - if (elementCount > 1) - { - emitGLSLTypePrefix(elementType); - Emit("vec"); - emit(elementCount); - } - else - { - emitSimpleTypeImpl(elementType); - } - } - break; - - case CodeGenTarget::HLSL: - // TODO(tfoley): should really emit these with sugar - Emit("vector<"); - EmitType(elementType); - Emit(","); - emit(elementCount); - Emit(">"); - break; - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target"); - break; - } - } - - void 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 emitMatrixTypeImpl(IRMatrixType* matType) - { - switch(context->shared->target) - { - case CodeGenTarget::GLSL: - case CodeGenTarget::GLSL_Vulkan: - case CodeGenTarget::GLSL_Vulkan_OneDesc: - { - emitGLSLTypePrefix(matType->getElementType()); - Emit("mat"); - EmitVal(matType->getRowCount(), kEOp_General); - // TODO(tfoley): only emit the next bit - // for non-square matrix - Emit("x"); - EmitVal(matType->getColumnCount(), kEOp_General); - } - break; - - case CodeGenTarget::HLSL: - // TODO(tfoley): should really emit these with sugar - Emit("matrix<"); - EmitType(matType->getElementType()); - Emit(","); - EmitVal(matType->getRowCount(), kEOp_General); - Emit(","); - EmitVal(matType->getColumnCount(), kEOp_General); - Emit("> "); - break; - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target"); - break; - } - } - - void emitSamplerStateType(IRSamplerStateTypeBase* samplerStateType) - { - switch(context->shared->target) - { - case CodeGenTarget::HLSL: - default: - switch (samplerStateType->op) - { - case kIROp_SamplerStateType: Emit("SamplerState"); break; - case kIROp_SamplerComparisonStateType: Emit("SamplerComparisonState"); break; - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled sampler state flavor"); - break; - } - break; - - case CodeGenTarget::GLSL: - switch (samplerStateType->op) - { - case kIROp_SamplerStateType: Emit("sampler"); break; - case kIROp_SamplerComparisonStateType: Emit("samplerShadow"); break; - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled sampler state flavor"); - break; - } - break; - break; - } - } - - void emitStructuredBufferType(IRHLSLStructuredBufferTypeBase* type) - { - switch(context->shared->target) - { - case CodeGenTarget::HLSL: - default: - { - switch (type->op) - { - case kIROp_HLSLStructuredBufferType: Emit("StructuredBuffer"); break; - case kIROp_HLSLRWStructuredBufferType: Emit("RWStructuredBuffer"); break; - case kIROp_HLSLRasterizerOrderedStructuredBufferType: Emit("RasterizerOrderedStructuredBuffer"); break; - case kIROp_HLSLAppendStructuredBufferType: Emit("AppendStructuredBuffer"); break; - case kIROp_HLSLConsumeStructuredBufferType: Emit("ConsumeStructuredBuffer"); break; - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled structured buffer type"); - break; - } - - Emit("<"); - EmitType(type->getElementType()); - 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<Foo>` as `Foo[]` and `RWStructuredBuffer<Foo>` - // 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 emitUntypedBufferType(IRUntypedBufferResourceType* type) - { - switch(context->shared->target) - { - case CodeGenTarget::HLSL: - default: - { - switch (type->op) - { - case kIROp_HLSLByteAddressBufferType: Emit("ByteAddressBuffer"); break; - case kIROp_HLSLRWByteAddressBufferType: Emit("RWByteAddressBuffer"); break; - case kIROp_HLSLRasterizerOrderedByteAddressBufferType: Emit("RasterizerOrderedByteAddressBuffer"); break; - case kIROp_RaytracingAccelerationStructureType: 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"); - Emit("accelerationStructureNV"); - break; - - // TODO: These "translations" are obviously wrong for GLSL. - case kIROp_HLSLByteAddressBufferType: Emit("ByteAddressBuffer"); break; - case kIROp_HLSLRWByteAddressBufferType: Emit("RWByteAddressBuffer"); break; - case kIROp_HLSLRasterizerOrderedByteAddressBufferType: Emit("RasterizerOrderedByteAddressBuffer"); break; - - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled buffer type"); - break; - } - } - break; - } - } - - void _requireHalf() - { - if (getTarget(context) == CodeGenTarget::GLSL) - { - requireGLSLHalfExtension(&context->shared->extensionUsageTracker); - } - } - - void emitSimpleTypeImpl(IRType* type) - { - switch (type->op) - { - default: - break; - - case kIROp_VoidType: Emit("void"); return; - case kIROp_BoolType: Emit("bool"); return; - - case kIROp_Int8Type: Emit("int8_t"); return; - case kIROp_Int16Type: Emit("int16_t"); return; - case kIROp_IntType: Emit("int"); return; - case kIROp_Int64Type: Emit("int64_t"); return; - - case kIROp_UInt8Type: Emit("uint8_t"); return; - case kIROp_UInt16Type: Emit("uint16_t"); return; - case kIROp_UIntType: Emit("uint"); return; - case kIROp_UInt64Type: Emit("uint64_t"); return; - - case kIROp_HalfType: - { - _requireHalf(); - if (getTarget(context) == CodeGenTarget::GLSL) - { - Emit("float16_t"); - } - else - { - Emit("half"); - } - return; - } - case kIROp_FloatType: Emit("float"); return; - case kIROp_DoubleType: 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<IRSamplerStateTypeBase>(type)); - return; - - case kIROp_StructType: - 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<IRTextureType>(type)) - { - emitTextureType(texType); - return; - } - else if (auto textureSamplerType = as<IRTextureSamplerType>(type)) - { - emitTextureSamplerType(textureSamplerType); - return; - } - else if (auto imageType = as<IRGLSLImageType>(type)) - { - emitImageType(imageType); - return; - } - else if (auto structuredBufferType = as<IRHLSLStructuredBufferTypeBase>(type)) - { - emitStructuredBufferType(structuredBufferType); - return; - } - else if(auto untypedBufferType = as<IRUntypedBufferResourceType>(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(context->shared->target == CodeGenTarget::HLSL) - { - auto opInfo = getIROpInfo(type->op); - emit(opInfo.name); - UInt operandCount = type->getOperandCount(); - if(operandCount) - { - emit("<"); - for(UInt ii = 0; ii < operandCount; ++ii) - { - if(ii != 0) emit(", "); - EmitVal(type->getOperand(ii), kEOp_General); - } - emit(" >"); - } - - return; - } - - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled type"); - } - - void emitArrayTypeImpl(IRArrayType* arrayType, EDeclarator* declarator) - { - EDeclarator arrayDeclarator; - arrayDeclarator.flavor = EDeclarator::Flavor::Array; - arrayDeclarator.next = declarator; - arrayDeclarator.elementCount = arrayType->getElementCount(); - - emitTypeImpl(arrayType->getElementType(), &arrayDeclarator); - } - - void emitUnsizedArrayTypeImpl(IRUnsizedArrayType* arrayType, EDeclarator* declarator) - { - EDeclarator arrayDeclarator; - arrayDeclarator.flavor = EDeclarator::Flavor::UnsizedArray; - arrayDeclarator.next = declarator; - - emitTypeImpl(arrayType->getElementType(), &arrayDeclarator); - } - - void emitTypeImpl(IRType* type, EDeclarator* declarator) - { - switch (type->op) - { - default: - emitSimpleTypeImpl(type); - EmitDeclarator(declarator); - break; - - case kIROp_RateQualifiedType: - { - auto rateQualifiedType = cast<IRRateQualifiedType>(type); - emitTypeImpl(rateQualifiedType->getValueType(), declarator); - } - break; - - case kIROp_ArrayType: - emitArrayTypeImpl(cast<IRArrayType>(type), declarator); - break; - - case kIROp_UnsizedArrayType: - emitUnsizedArrayTypeImpl(cast<IRUnsizedArrayType>(type), declarator); - break; - } - - } - - void EmitType( - IRType* type, - SourceLoc const& typeLoc, - Name* name, - SourceLoc const& nameLoc) - { - advanceToSourceLocation(typeLoc); - - EDeclarator nameDeclarator; - nameDeclarator.flavor = EDeclarator::Flavor::name; - nameDeclarator.name = name; - nameDeclarator.loc = nameLoc; - emitTypeImpl(type, &nameDeclarator); - } - - void EmitType(IRType* type, Name* name) - { - EmitType(type, SourceLoc(), name, SourceLoc()); - } - - void EmitType(IRType* type, String const& 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 EmitType(IRType* type) - { - emitTypeImpl(type, nullptr); - } - - // - // Expressions - // - - bool maybeEmitParens(EOpInfo& outerPrec, EOpInfo prec) - { - bool needParens = (prec.leftPrecedence <= outerPrec.leftPrecedence) - || (prec.rightPrecedence <= outerPrec.rightPrecedence); - - if (needParens) - { - Emit("("); - - outerPrec = kEOp_None; - } - return needParens; - } - - void maybeCloseParens(bool needClose) - { - if(needClose) Emit(")"); - } - - bool isTargetIntrinsicModifierApplicable( - String const& targetName) - { - switch(context->shared->target) - { - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target"); - return false; - - case CodeGenTarget::GLSL: return targetName == "glsl"; - case CodeGenTarget::HLSL: return targetName == "hlsl"; - } - } - - void EmitType(IRType* type, Name* name, SourceLoc const& nameLoc) - { - EmitType( - type, - SourceLoc(), - name, - nameLoc); - } - - void EmitType(IRType* type, NameLoc const& nameAndLoc) - { - EmitType(type, nameAndLoc.name, nameAndLoc.loc); - } - - bool 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 emitStringLiteral( - String const& value) - { - 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: - emit(buffer); - break; - - case '\"': emit("\\\""); - case '\'': emit("\\\'"); - case '\\': emit("\\\\"); - case '\n': emit("\\n"); - case '\r': emit("\\r"); - case '\t': emit("\\t"); - } - } - emit("\""); - } - - EOpInfo leftSide(EOpInfo const& outerPrec, EOpInfo const& prec) - { - EOpInfo result; - result.op = nullptr; - result.leftPrecedence = outerPrec.leftPrecedence; - result.rightPrecedence = prec.leftPrecedence; - return result; - } - - EOpInfo rightSide(EOpInfo const& prec, EOpInfo const& outerPrec) - { - EOpInfo result; - result.op = nullptr; - result.leftPrecedence = prec.rightPrecedence; - result.rightPrecedence = outerPrec.rightPrecedence; - return result; - } - - void requireGLSLExtension(String const& name) - { - Slang::requireGLSLExtension(&context->shared->extensionUsageTracker, name); - } - - void requireGLSLVersion(ProfileVersion version) - { - if (context->shared->target != CodeGenTarget::GLSL) - return; - - Slang::requireGLSLVersionImpl(&context->shared->extensionUsageTracker, version); - } - - void 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 setSampleRateFlag() - { - context->shared->entryPointLayout->flags |= EntryPointLayout::Flag::usesAnySampleRateInput; - } - - void doSampleRateInputCheck(Name* name) - { - auto text = getText(name); - if (text == "gl_SampleID") - { - setSampleRateFlag(); - } - } - - void EmitVal( - IRInst* val, - EOpInfo const& outerPrec) - { - if(auto type = as<IRType>(val)) - { - EmitType(type); - } - else - { - emitIRInstExpr(context, val, IREmitMode::Default, outerPrec); - } - } - - typedef unsigned int ESemanticMask; - enum - { - kESemanticMask_None = 0, - - kESemanticMask_NoPackOffset = 1 << 0, - - kESemanticMask_Default = kESemanticMask_NoPackOffset, - }; - - // A chain of variables to use for emitting semantic/layout info - struct 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) - {} - }; - - UInt 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 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; - } - - // Emit a single `register` semantic, as appropriate for a given resource-type-specific layout info - void emitHLSLRegisterSemantic( - LayoutResourceKind kind, - EmitVarChain* chain, - // Keyword to use in the uniform case (`register` for globals, `packoffset` inside a `cbuffer`) - char const* uniformSemanticSpelling = "register") - { - 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. - - Emit(" : "); - Emit(uniformSemanticSpelling); - 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; - 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"}; - Emit("."); - Emit(kComponentNames[startComponent]); - } - Emit(")"); - } - break; - - case LayoutResourceKind::RegisterSpace: - case LayoutResourceKind::GenericResource: - case LayoutResourceKind::ExistentialTypeParam: - case LayoutResourceKind::ExistentialObjectParam: - // ignore - break; - default: - { - Emit(" : register("); - switch( kind ) - { - case LayoutResourceKind::ConstantBuffer: - Emit("b"); - break; - case LayoutResourceKind::ShaderResource: - Emit("t"); - break; - case LayoutResourceKind::UnorderedAccess: - Emit("u"); - break; - case LayoutResourceKind::SamplerState: - Emit("s"); - break; - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled HLSL register type"); - break; - } - Emit(index); - if(space) - { - Emit(", space"); - Emit(space); - } - Emit(")"); - } - } - } - - // Emit all the `register` semantics that are appropriate for a particular variable layout - void emitHLSLRegisterSemantics( - EmitVarChain* chain, - char const* uniformSemanticSpelling = "register") - { - if (!chain) return; - - auto layout = chain->varLayout; - - switch( context->shared->target ) - { - default: - return; - - case CodeGenTarget::HLSL: - break; - } - - for( auto rr : layout->resourceInfos ) - { - emitHLSLRegisterSemantic(rr.kind, chain, uniformSemanticSpelling); - } - } - - void emitHLSLRegisterSemantics( - VarLayout* varLayout, - char const* uniformSemanticSpelling = "register") - { - if(!varLayout) - return; - - EmitVarChain chain(varLayout); - emitHLSLRegisterSemantics(&chain, uniformSemanticSpelling); - } - - void emitHLSLParameterGroupFieldLayoutSemantics( - EmitVarChain* chain) - { - if(!chain) - return; - - auto layout = chain->varLayout; - for( auto rr : layout->resourceInfos ) - { - emitHLSLRegisterSemantic(rr.kind, chain, "packoffset"); - } - } - - - void emitHLSLParameterGroupFieldLayoutSemantics( - RefPtr<VarLayout> fieldLayout, - EmitVarChain* inChain) - { - EmitVarChain chain(fieldLayout, inChain); - emitHLSLParameterGroupFieldLayoutSemantics(&chain); - } - - bool 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"); - - Emit("layout(offset = "); - Emit(index); - Emit(")\n"); - } - } - break; - - case LayoutResourceKind::VertexInput: - case LayoutResourceKind::FragmentOutput: - Emit("layout(location = "); - Emit(index); - Emit(")\n"); - break; - - case LayoutResourceKind::SpecializationConstant: - Emit("layout(constant_id = "); - Emit(index); - Emit(")\n"); - break; - - case LayoutResourceKind::ConstantBuffer: - case LayoutResourceKind::ShaderResource: - case LayoutResourceKind::UnorderedAccess: - case LayoutResourceKind::SamplerState: - case LayoutResourceKind::DescriptorTableSlot: - Emit("layout(binding = "); - Emit(index); - if(space) - { - Emit(", set = "); - Emit(space); - } - Emit(")\n"); - break; - - case LayoutResourceKind::PushConstantBuffer: - Emit("layout(push_constant)\n"); - break; - case LayoutResourceKind::ShaderRecord: - Emit("layout(shaderRecordNV)\n"); - break; - - } - return true; - } - - void emitGLSLLayoutQualifiers( - RefPtr<VarLayout> layout, - EmitVarChain* inChain, - LayoutResourceKind filter = LayoutResourceKind::None) - { - if(!layout) return; - - switch( context->shared->target ) - { - 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 emitGLSLVersionDirective() - { - auto effectiveProfile = context->shared->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. - requireGLSLVersionImpl(&context->shared->extensionUsageTracker, ProfileVersion::GLSL_450); - - auto requiredProfileVersion = context->shared->extensionUsageTracker.profileVersion; - switch (requiredProfileVersion) - { -#define CASE(TAG, VALUE) \ - case ProfileVersion::TAG: 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. - - Emit("#version 420\n"); - } - - void emitGLSLPreprocessorDirectives() - { - switch(context->shared->target) - { - // Don't emit this stuff unless we are targetting GLSL - default: - return; - - case CodeGenTarget::GLSL: - break; - } - - emitGLSLVersionDirective(); - } - - /// Emit directives to control overall layout computation for the emitted code. - void 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(context->shared->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: - Emit("layout(column_major) uniform;\n"); - Emit("layout(column_major) buffer;\n"); - break; - - case kMatrixLayoutMode_ColumnMajor: - Emit("layout(row_major) uniform;\n"); - Emit("layout(row_major) buffer;\n"); - break; - } - break; - - case CodeGenTarget::HLSL: - switch(matrixLayoutMode) - { - case kMatrixLayoutMode_RowMajor: - default: - Emit("#pragma pack_matrix(row_major)\n"); - break; - - case kMatrixLayoutMode_ColumnMajor: - Emit("#pragma pack_matrix(column_major)\n"); - break; - } - break; - } - } - - // Utility code for generating unique IDs as needed - // during the emit process (e.g., for declarations - // that didn't origianlly have names, but now need to). - - UInt allocateUniqueID() - { - return context->shared->uniqueIDCounter++; - } - - // IR-level emit logc - - UInt getID(IRInst* value) - { - auto& mapIRValueToID = context->shared->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 scrubName( - String const& 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(context) == 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 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(context, 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<IRNameHintDecoration>()) - { - // The name we output will basically be: - // - // <nameHint>_<uniqueID> - // - // 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; - context->shared->uniqueNameCounters.TryGetValue(key, count); - - context->shared->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<IRLinkageDecoration>()) - { - 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 getIRName( - IRInst* inst) - { - String name; - if(!context->shared->mapInstToName.TryGetValue(inst, name)) - { - name = generateIRName(inst); - context->shared->mapInstToName.Add(inst, name); - } - return name; - } - - struct IRDeclaratorInfo - { - enum class Flavor - { - Simple, - Ptr, - Array, - }; - - Flavor flavor; - IRDeclaratorInfo* next; - union - { - String const* name; - IRInst* elementCount; - }; - }; - - void emitDeclarator( - EmitContext* ctx, - IRDeclaratorInfo* declarator) - { - if(!declarator) - return; - - switch( declarator->flavor ) - { - case IRDeclaratorInfo::Flavor::Simple: - emit(" "); - emit(*declarator->name); - break; - - case IRDeclaratorInfo::Flavor::Ptr: - emit("*"); - emitDeclarator(ctx, declarator->next); - break; - - case IRDeclaratorInfo::Flavor::Array: - emitDeclarator(ctx, declarator->next); - emit("["); - emitIROperand(ctx, declarator->elementCount, IREmitMode::Default, kEOp_General); - emit("]"); - break; - } - } - - void emitIRSimpleValue( - EmitContext* /*context*/, - IRInst* inst) - { - switch(inst->op) - { - case kIROp_IntLit: - emit(((IRConstant*) inst)->value.intVal); - break; - - case kIROp_FloatLit: - Emit(((IRConstant*) inst)->value.floatVal); - break; - - case kIROp_BoolLit: - { - bool val = ((IRConstant*)inst)->value.intVal != 0; - emit(val ? "true" : "false"); - } - break; - - default: - SLANG_UNIMPLEMENTED_X("val case for emit"); - break; - } - - } - - CodeGenTarget getTarget(EmitContext* ctx) - { - return ctx->shared->target; - } - - // Hack to allow IR emit for global constant to override behavior - enum class IREmitMode - { - Default, - GlobalConstant, - }; - - bool shouldFoldIRInstIntoUseSites( - EmitContext* ctx, - 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<IRArrayTypeBase>(type)) - { - type = arrayType->getElementType(); - } - - // Don't allow temporaries of pointer types to be created. - if(as<IRPtrTypeBase>(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<IRUniformParameterGroupType>(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<IRHLSLStreamOutputType>(type)) - { - return true; - } - else if (as<IRHLSLPatchType>(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(ctx) == CodeGenTarget::GLSL) - { - if(as<IRResourceTypeBase>(type)) - { - return true; - } - else if(as<IRHLSLStructuredBufferTypeBase>(type)) - { - return true; - } - else if(as<IRUntypedBufferResourceType>(type)) - { - return true; - } - else if(as<IRSamplerStateTypeBase>(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<IRModuleInst>(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 somethin 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<IRPreciseDecoration>()) - 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 emitIROperand( - EmitContext* ctx, - IRInst* inst, - IREmitMode mode, - EOpInfo const& outerPrec) - { - if( shouldFoldIRInstIntoUseSites(ctx, inst, mode) ) - { - emitIRInstExpr(ctx, inst, mode, outerPrec); - return; - } - - switch(inst->op) - { - case 0: // nothing yet - default: - emit(getIRName(inst)); - break; - } - } - - void emitIRArgs( - EmitContext* ctx, - IRInst* inst, - IREmitMode mode) - { - UInt argCount = inst->getOperandCount(); - IRUse* args = inst->getOperands(); - - emit("("); - for(UInt aa = 0; aa < argCount; ++aa) - { - if(aa != 0) emit(", "); - emitIROperand(ctx, args[aa].get(), mode, kEOp_General); - } - emit(")"); - } - - void emitIRType( - EmitContext* /*context*/, - IRType* type, - String const& name) - { - EmitType(type, name); - } - - void emitIRType( - EmitContext* /*context*/, - IRType* type, - Name* name) - { - EmitType(type, name); - } - - void emitIRType( - EmitContext* /*context*/, - IRType* type) - { - EmitType(type); - } - - void emitIRRateQualifiers( - EmitContext* ctx, - IRRate* rate) - { - if(!rate) return; - - if(as<IRConstExprRate>(rate)) - { - switch( getTarget(ctx) ) - { - case CodeGenTarget::GLSL: - emit("const "); - break; - - default: - break; - } - } - - if (as<IRGroupSharedRate>(rate)) - { - switch( getTarget(ctx) ) - { - case CodeGenTarget::HLSL: - Emit("groupshared "); - break; - - case CodeGenTarget::GLSL: - Emit("shared "); - break; - - default: - break; - } - } - } - - void emitIRRateQualifiers( - EmitContext* ctx, - IRInst* value) - { - emitIRRateQualifiers(ctx, value->getRate()); - } - - - void emitIRInstResultDecl( - EmitContext* ctx, - IRInst* inst) - { - auto type = inst->getDataType(); - if(!type) - return; - - if (as<IRVoidType>(type)) - return; - - emitIRTempModifiers(ctx, inst); - - emitIRRateQualifiers(ctx, inst); - - emitIRType(ctx, type, getIRName(inst)); - emit(" = "); - } - - class UnmangleContext - { - private: - char const* cursor_ = nullptr; - char const* begin_ = nullptr; - char const* end_ = nullptr; - - bool isDigit(char c) - { - return (c >= '0') && (c <= '9'); - } - - char peek() - { - return *cursor_; - } - - char get() - { - return *cursor_++; - } - - void expect(char c) - { - if(peek() == c) - { - get(); - } - else - { - // ERROR! - SLANG_UNEXPECTED("mangled name error"); - } - } - - void expect(char const* str) - { - while(char c = *str++) - expect(c); - } - - public: - UnmangleContext() - {} - - UnmangleContext(String const& str) - : cursor_(str.begin()) - , begin_(str.begin()) - , end_(str.end()) - {} - - // Call at the beginning of a mangled name, - // to strip off the main prefix - void startUnmangling() - { - expect("_S"); - } - - UInt readCount() - { - int c = peek(); - if(!isDigit((char)c)) - { - SLANG_UNEXPECTED("bad name mangling"); - UNREACHABLE_RETURN(0); - } - get(); - - if(c == '0') - return 0; - - UInt count = 0; - for(;;) - { - count = count*10 + c - '0'; - c = peek(); - if(!isDigit((char)c)) - return count; - - get(); - } - } - - void readGenericParam() - { - switch(peek()) - { - case 'T': - case 'C': - get(); - break; - - case 'v': - get(); - readType(); - break; - - default: - SLANG_UNEXPECTED("bad name mangling"); - break; - } - } - - void readGenericParams() - { - expect("g"); - UInt paramCount = readCount(); - for(UInt pp = 0; pp < paramCount; pp++) - { - readGenericParam(); - } - } - - void readSimpleIntVal() - { - int c = peek(); - if(isDigit((char)c)) - { - get(); - } - else - { - readVal(); - } - } - - - UnownedStringSlice readRawStringSegment() - { - // Read the length part - UInt count = readCount(); - if(count > UInt(end_ - cursor_)) - { - SLANG_UNEXPECTED("bad name mangling"); - UNREACHABLE_RETURN(UnownedStringSlice()); - } - - auto result = UnownedStringSlice(cursor_, cursor_ + count); - cursor_ += count; - return result; - } - - void readNamedType() - { - // TODO: handle types with more complicated names - readRawStringSegment(); - } - - void readType() - { - int c = peek(); - switch(c) - { - case 'V': - case 'b': - case 'i': - case 'u': - case 'U': - case 'h': - case 'f': - case 'd': - get(); - break; - - case 'v': - get(); - readSimpleIntVal(); - readType(); - break; - - default: - readNamedType(); - break; - } - } - - void readVal() - { - switch(peek()) - { - case 'k': - get(); - readCount(); - break; - - case 'K': - get(); - readRawStringSegment(); - break; - - default: - readType(); - break; - } - - } - - void readGenericArg() - { - readVal(); - } - - void readGenericArgs() - { - expect("G"); - UInt argCount = readCount(); - for(UInt aa = 0; aa < argCount; aa++) - { - readGenericArg(); - } - } - - void readExtensionSpec() - { - expect("X"); - readType(); - } - - UnownedStringSlice readSimpleName() - { - UnownedStringSlice result; - for(;;) - { - int c = peek(); - - if(c == 'g') - { - readGenericParams(); - continue; - } - else if(c == 'G') - { - readGenericArgs(); - continue; - } - else if(c == 'X') - { - readExtensionSpec(); - continue; - } - - if(!isDigit((char)c)) - return result; - - // Read the length part - UInt count = readCount(); - if(count > UInt(end_ - cursor_)) - { - SLANG_UNEXPECTED("bad name mangling"); - UNREACHABLE_RETURN(result); - } - - result = UnownedStringSlice(cursor_, cursor_ + count); - cursor_ += count; - } - } - - UInt readParamCount() - { - expect("p"); - UInt count = readCount(); - expect("p"); - return count; - } - }; - - IRTargetIntrinsicDecoration* findTargetIntrinsicDecoration( - EmitContext* /* ctx */, - IRInst* inst) - { - for(auto dd : inst->getDecorations()) - { - if (dd->op != kIROp_TargetIntrinsicDecoration) - continue; - - auto targetIntrinsic = (IRTargetIntrinsicDecoration*)dd; - if (isTargetIntrinsicModifierApplicable(targetIntrinsic)) - return targetIntrinsic; - } - - return nullptr; - } - - // 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. - bool 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 emitTargetIntrinsicCallExpr( - EmitContext* ctx, - IRCall* inst, - IRFunc* /* func */, - IRTargetIntrinsicDecoration* targetIntrinsic, - IREmitMode mode, - EOpInfo 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 = kEOp_Postfix; - bool needClose = maybeEmitParens(outerPrec, prec); - - emit(name); - Emit("("); - for (Index aa = 0; aa < argCount; ++aa) - { - if (aa != 0) Emit(", "); - emitIROperand(ctx, args[aa].get(), mode, kEOp_General); - } - 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<IRVoidType>(returnType) == nullptr) - { - 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 - 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)); - Emit("("); - emitIROperand(ctx, args[argIndex].get(), mode, kEOp_General); - 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<IRTextureType>(textureArg->getDataType())) - { - emitGLSLTextureOrTextureSamplerType(baseTextureType, "sampler"); - - if (auto samplerType = as<IRSamplerStateTypeBase>(samplerArg->getDataType())) - { - if (as<IRSamplerComparisonStateType>(samplerType)) - { - Emit("Shadow"); - } - } - - Emit("("); - emitIROperand(ctx, textureArg, mode, kEOp_General); - Emit(","); - emitIROperand(ctx, samplerArg, mode, kEOp_General); - 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<IRTextureType>(textureArg->getDataType())) - { - auto elementType = baseTextureType->getElementType(); - IRBasicType* underlyingType = nullptr; - if (auto basicType = as<IRBasicType>(elementType)) - { - underlyingType = basicType; - } - else if (auto vectorType = as<IRVectorType>(elementType)) - { - underlyingType = as<IRBasicType>(vectorType->getElementType()); - } - - // We only need to output a cast if the underlying type is half. - if (underlyingType && underlyingType->op == kIROp_HalfType) - { - emitSimpleTypeImpl(elementType); - emit("("); - openParenCount++; - } - } - } - break; - - case 'z': - { - // If we are calling a D3D texturing operation in the form t.Foo(s, ...), - // where `t` is a `Texture*<T>`, 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<IRTextureType>(textureArg->getDataType())) - { - auto elementType = baseTextureType->getElementType(); - if (auto basicType = as<IRBasicType>(elementType)) - { - // A scalar result is expected - Emit(".x"); - } - else if (auto vectorType = as<IRVectorType>(elementType)) - { - // A vector result is expected - auto elementCount = GetIntVal(vectorType->getElementCount()); - - if (elementCount < 4) - { - char const* swiz[] = { "", ".x", ".xy", ".xyz", "" }; - 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<IRVectorType>(vectorArg->getDataType())) - { - auto elementCount = GetIntVal(vectorType->getElementCount()); - 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<IRVectorType>(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(ctx, arg, mode, kEOp_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); - Emit("("); - emitIROperand(ctx, arg, mode, kEOp_General); - for(IRIntegerValue ii = elementCount; ii < 4; ++ii) - { - Emit(", "); - if(getTarget(ctx) == CodeGenTarget::GLSL) - { - emitSimpleTypeImpl(elementType); - Emit("(0)"); - } - else - { - Emit("0"); - } - } - 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) - { - Emit("imageA"); - } - else - { - 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(ctx) == 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. - // - Emit("("); - emitIROperand(ctx, arg->getOperand(0), mode, kEOp_General); - Emit("), "); - - // The coordinate argument will have been computed - // as a `vector<uint, N>` because that is how the - // HLSL image subscript operations are defined. - // In contrast, the GLSL `imageAtomic*` operations - // expect `vector<int, N>` 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<IRVectorType>(coordsType); - IRIntegerValue elementCount = 1; - if(coordsVecType) - { - coordsType = coordsVecType->getElementType(); - elementCount = GetIntVal(coordsVecType->getElementCount()); - } - - SLANG_ASSERT(coordsType->op == kIROp_UIntType); - - if (elementCount > 1) - { - Emit("ivec"); - emit(elementCount); - } - else - { - Emit("int"); - } - - Emit("("); - emitIROperand(ctx, arg->getOperand(1), mode, kEOp_General); - Emit(")"); - } - else - { - Emit("("); - emitIROperand(ctx, arg, mode, kEOp_General); - Emit(")"); - } - } - else - { - Emit("("); - emitIROperand(ctx, arg, mode, kEOp_General); - 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<IRLoad>(arg); - SLANG_RELEASE_ASSERT(argLoad); - auto argVar = argLoad->getOperand(0); - Emit(getRayPayloadLocation(ctx, 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<IRLoad>(arg); - SLANG_RELEASE_ASSERT(argLoad); - auto argVar = argLoad->getOperand(0); - Emit(getCallablePayloadLocation(ctx, 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( ctx->shared->entryPoint->getStage() ) - { - default: - Emit("gl_RayTmaxNV"); - break; - - case Stage::AnyHit: - case Stage::ClosestHit: - 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) - { - Emit(")"); - } - } - } - - void emitIntrinsicCallExpr( - EmitContext* ctx, - IRCall* inst, - IRFunc* func, - IREmitMode mode, - EOpInfo 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(ctx, func)) - { - emitTargetIntrinsicCallExpr( - ctx, - 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<IRBlock>(valueForName->parent); - if(!parentBlock) - break; - - auto parentGeneric = as<IRGeneric>(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<IRLinkageDecoration>(); - SLANG_ASSERT(linkageDecoration); - auto mangledName = String(linkageDecoration->getMangledName()); - - - // We will use the `UnmangleContext` utility to - // help us split the original name into its pieces. - UnmangleContext um(mangledName); - um.startUnmangling(); - - // We'll read through the qualified name of the - // symbol (e.g., `Texture2D<T>.Sample`) and then - // only keep the last segment of the name (e.g., - // the `Sample` part). - auto name = um.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 = kEOp_Postfix; - needClose = maybeEmitParens(outerPrec, prec); - - emitIROperand(ctx, inst->getOperand(operandIndex++), mode, leftSide(outerPrec, prec)); - emit("["); - emitIROperand(ctx, inst->getOperand(operandIndex++), mode, kEOp_General); - emit("]"); - - if(operandIndex < operandCount) - { - emit(" = "); - emitIROperand(ctx, inst->getOperand(operandIndex++), mode, kEOp_General); - } - - maybeCloseParens(needClose); - return; - } - - auto prec = kEOp_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 = um.readParamCount(); - - if(argCount != paramCount) - { - // Looks like a member function call - emitIROperand(ctx, inst->getOperand(operandIndex), mode, leftSide(outerPrec, prec)); - emit("."); - operandIndex++; - } - // fixing issue #602 for GLSL sign function: https://github.com/shader-slang/slang/issues/602 - bool glslSignFix = ctx->shared->target == CodeGenTarget::GLSL && name == "sign"; - if (glslSignFix) - { - if (auto vectorType = as<IRVectorType>(inst->getDataType())) - { - emit("ivec"); - emit(as<IRConstant>(vectorType->getElementCount())->value.intVal); - emit("("); - } - else if (auto scalarType = as<IRBasicType>(inst->getDataType())) - { - emit("int("); - } - else - glslSignFix = false; - } - emit(name); - emit("("); - bool first = true; - for(; operandIndex < operandCount; ++operandIndex ) - { - if(!first) emit(", "); - emitIROperand(ctx, inst->getOperand(operandIndex), mode, kEOp_General); - first = false; - } - emit(")"); - if (glslSignFix) - emit(")"); - maybeCloseParens(needClose); - } - - void emitIRCallExpr( - EmitContext* ctx, - IRCall* inst, - IREmitMode mode, - EOpInfo outerPrec) - { - auto funcValue = inst->getOperand(0); - - // Does this function declare any requirements on GLSL version or - // extensions, which should affect our output? - if(getTarget(ctx) == CodeGenTarget::GLSL) - { - auto decoratedValue = funcValue; - while (auto specInst = as<IRSpecialize>(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(ctx, funcValue)) - { - emitIntrinsicCallExpr(ctx, inst, irFunc, mode, outerPrec); - } - else - { - auto prec = kEOp_Postfix; - bool needClose = maybeEmitParens(outerPrec, prec); - - emitIROperand(ctx, funcValue, mode, leftSide(outerPrec, prec)); - emit("("); - UInt argCount = inst->getOperandCount(); - for( UInt aa = 1; aa < argCount; ++aa ) - { - auto operand = inst->getOperand(aa); - if (as<IRVoidType>(operand->getDataType())) - continue; - if(aa != 1) emit(", "); - emitIROperand(ctx, inst->getOperand(aa), mode, kEOp_General); - } - 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 _maybeEmitGLSLCast(EmitContext* ctx, IRType* castType, IRInst* inst, IREmitMode mode) - { - // Wrap in cast if a cast type is specified - if (castType) - { - emitIRType(ctx, castType); - emit("("); - - // Emit the operand - emitIROperand(ctx, inst, mode, kEOp_General); - - emit(")"); - } - else - { - // Emit the operand - emitIROperand(ctx, inst, mode, kEOp_General); - } - } - - void emitNot(EmitContext* ctx, IRInst* inst, IREmitMode mode, EOpInfo& ioOuterPrec, bool* outNeedClose) - { - IRInst* operand = inst->getOperand(0); - - if (getTarget(ctx) == CodeGenTarget::GLSL) - { - if (auto vectorType = as<IRVectorType>(operand->getDataType())) - { - // Handle as a function call - auto prec = kEOp_Postfix; - *outNeedClose = maybeEmitParens(ioOuterPrec, prec); - - emit("not("); - emitIROperand(ctx, operand, mode, kEOp_General); - emit(")"); - return; - } - } - - auto prec = kEOp_Prefix; - *outNeedClose = maybeEmitParens(ioOuterPrec, prec); - - emit("!"); - emitIROperand(ctx, operand, mode, rightSide(prec, ioOuterPrec)); - } - - - void emitComparison(EmitContext* ctx, IRInst* inst, IREmitMode mode, EOpInfo& ioOuterPrec, const EOpInfo& opPrec, bool* needCloseOut) - { - if (getTarget(ctx) == CodeGenTarget::GLSL) - { - IRInst* left = inst->getOperand(0); - IRInst* right = inst->getOperand(1); - - auto leftVectorType = as<IRVectorType>(left->getDataType()); - auto rightVectorType = as<IRVectorType>(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 = kEOp_Postfix; - *needCloseOut = maybeEmitParens(ioOuterPrec, prec); - - emit(funcName); - emit("("); - _maybeEmitGLSLCast(ctx, (leftVectorType ? nullptr : vecType), left, mode); - emit(","); - _maybeEmitGLSLCast(ctx, (rightVectorType ? nullptr : vecType), right, mode); - emit(")"); - - return; - } - } - - *needCloseOut = maybeEmitParens(ioOuterPrec, opPrec); - - emitIROperand(ctx, inst->getOperand(0), mode, leftSide(ioOuterPrec, opPrec)); - emit(" "); - emit(opPrec.op); - emit(" "); - emitIROperand(ctx, inst->getOperand(1), mode, rightSide(ioOuterPrec, opPrec)); - } - - void emitIRInstExpr( - EmitContext* ctx, - IRInst* inst, - IREmitMode mode, - EOpInfo const& inOuterPrec) - { - EOpInfo outerPrec = inOuterPrec; - bool needClose = false; - switch(inst->op) - { - case kIROp_IntLit: - case kIROp_FloatLit: - case kIROp_BoolLit: - emitIRSimpleValue(ctx, inst); - break; - - case kIROp_Construct: - case kIROp_makeVector: - case kIROp_MakeMatrix: - // Simple constructor call - if( inst->getOperandCount() == 1 && getTarget(ctx) == CodeGenTarget::HLSL) - { - auto prec = kEOp_Prefix; - needClose = maybeEmitParens(outerPrec, prec); - - // Need to emit as cast for HLSL - emit("("); - emitIRType(ctx, inst->getDataType()); - emit(") "); - emitIROperand(ctx, inst->getOperand(0), mode, rightSide(outerPrec,prec)); - } - else - { - emitIRType(ctx, inst->getDataType()); - emitIRArgs(ctx, inst, mode); - } - break; - - case kIROp_constructVectorFromScalar: - - // Simple constructor call - if( getTarget(ctx) == CodeGenTarget::HLSL ) - { - auto prec = kEOp_Prefix; - needClose = maybeEmitParens(outerPrec, prec); - - emit("("); - emitIRType(ctx, inst->getDataType()); - emit(")"); - - emitIROperand(ctx, inst->getOperand(0), mode, rightSide(outerPrec,prec)); - } - else - { - auto prec = kEOp_Postfix; - needClose = maybeEmitParens(outerPrec, prec); - - emitIRType(ctx, inst->getDataType()); - emit("("); - emitIROperand(ctx, inst->getOperand(0), mode, kEOp_General); - emit(")"); - } - break; - - case kIROp_FieldExtract: - { - // Extract field from aggregate - - IRFieldExtract* fieldExtract = (IRFieldExtract*) inst; - - auto prec = kEOp_Postfix; - needClose = maybeEmitParens(outerPrec, prec); - - auto base = fieldExtract->getBase(); - emitIROperand(ctx, base, mode, leftSide(outerPrec, prec)); - emit("."); - if(getTarget(ctx) == CodeGenTarget::GLSL - && as<IRUniformParameterGroupType>(base->getDataType())) - { - emit("_data."); - } - emit(getIRName(fieldExtract->getField())); - } - break; - - case kIROp_FieldAddress: - { - // Extract field "address" from aggregate - - IRFieldAddress* ii = (IRFieldAddress*) inst; - - auto prec = kEOp_Postfix; - needClose = maybeEmitParens(outerPrec, prec); - - auto base = ii->getBase(); - emitIROperand(ctx, base, mode, leftSide(outerPrec, prec)); - emit("."); - if(getTarget(ctx) == CodeGenTarget::GLSL - && as<IRUniformParameterGroupType>(base->getDataType())) - { - emit("_data."); - } - emit(getIRName(ii->getField())); - } - break; - - -#define CASE_COMPARE(OPCODE, PREC, OP) \ - case OPCODE: \ - emitComparison(ctx, inst, mode, outerPrec, kEOp_##PREC, &needClose); \ - break - -#define CASE(OPCODE, PREC, OP) \ - case OPCODE: \ - needClose = maybeEmitParens(outerPrec, kEOp_##PREC); \ - emitIROperand(ctx, inst->getOperand(0), mode, leftSide(outerPrec, kEOp_##PREC)); \ - emit(" " #OP " "); \ - emitIROperand(ctx, inst->getOperand(1), mode, rightSide(outerPrec, kEOp_##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 ultiplication 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(ctx) == CodeGenTarget::GLSL - && as<IRMatrixType>(inst->getOperand(0)->getDataType()) - && as<IRMatrixType>(inst->getOperand(1)->getDataType())) - { - emit("matrixCompMult("); - emitIROperand(ctx, inst->getOperand(0), mode, kEOp_General); - emit(", "); - emitIROperand(ctx, inst->getOperand(1), mode, kEOp_General); - emit(")"); - } - else - { - // Default handling is to just rely on infix - // `operator*`. - auto prec = kEOp_Mul; - needClose = maybeEmitParens(outerPrec, prec); - emitIROperand(ctx, inst->getOperand(0), mode, leftSide(outerPrec, prec)); - emit(" * "); - emitIROperand(ctx, inst->getOperand(1), mode, rightSide(prec, outerPrec)); - } - break; - - case kIROp_Not: - { - emitNot(ctx, inst, mode, outerPrec, &needClose); - } - break; - - case kIROp_Neg: - { - auto prec = kEOp_Prefix; - needClose = maybeEmitParens(outerPrec, prec); - - emit("-"); - emitIROperand(ctx, inst->getOperand(0), mode, rightSide(prec, outerPrec)); - } - break; - - case kIROp_BitNot: - { - auto prec = kEOp_Prefix; - needClose = maybeEmitParens(outerPrec, prec); - - if (as<IRBoolType>(inst->getDataType())) - { - emit("!"); - } - else - { - emit("~"); - } - emitIROperand(ctx, inst->getOperand(0), mode, rightSide(prec, outerPrec)); - } - break; - - case kIROp_BitAnd: - { - auto prec = kEOp_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(ctx, 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(ctx) == CodeGenTarget::GLSL - && as<IRBoolType>(inst->getOperand(0)->getDataType()) - && as<IRBoolType>(inst->getOperand(1)->getDataType())) - { - emit("&&"); - } - else - { - emit("&"); - } - - emitIROperand(ctx, inst->getOperand(1), mode, rightSide(outerPrec, prec)); - } - break; - - case kIROp_BitOr: - { - auto prec = kEOp_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(ctx, 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(ctx) == CodeGenTarget::GLSL - && as<IRBoolType>(inst->getOperand(0)->getDataType()) - && as<IRBoolType>(inst->getOperand(1)->getDataType())) - { - emit("||"); - } - else - { - emit("|"); - } - - emitIROperand(ctx, inst->getOperand(1), mode, rightSide(outerPrec, prec)); - } - break; - - case kIROp_Load: - { - auto base = inst->getOperand(0); - emitIROperand(ctx, base, mode, outerPrec); - if(getTarget(ctx) == CodeGenTarget::GLSL - && as<IRUniformParameterGroupType>(base->getDataType())) - { - emit("._data"); - } - } - break; - - case kIROp_Store: - { - auto prec = kEOp_Assign; - needClose = maybeEmitParens(outerPrec, prec); - - emitIROperand(ctx, inst->getOperand(0), mode, leftSide(outerPrec, prec)); - emit(" = "); - emitIROperand(ctx, inst->getOperand(1), mode, rightSide(prec, outerPrec)); - } - break; - - case kIROp_Call: - { - emitIRCallExpr(ctx, (IRCall*)inst, mode, outerPrec); - } - break; - - case kIROp_GroupMemoryBarrierWithGroupSync: - 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<IRGLSLOuterArrayDecoration>()) - { - auto prec = kEOp_Postfix; - needClose = maybeEmitParens(outerPrec, prec); - - emit(decoration->getOuterArrayName()); - emit("["); - emitIROperand(ctx, inst->getOperand(1), mode, kEOp_General); - emit("]."); - emitIROperand(ctx, inst->getOperand(0), mode, rightSide(prec, outerPrec)); - break; - } - else - { - auto prec = kEOp_Postfix; - needClose = maybeEmitParens(outerPrec, prec); - - emitIROperand(ctx, inst->getOperand(0), mode, leftSide(outerPrec, prec)); - emit("["); - emitIROperand(ctx, inst->getOperand(1), mode, kEOp_General); - emit("]"); - } - break; - - case kIROp_Mul_Vector_Matrix: - case kIROp_Mul_Matrix_Vector: - case kIROp_Mul_Matrix_Matrix: - if(getTarget(ctx) == 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 = kEOp_Mul; - needClose = maybeEmitParens(outerPrec, prec); - - emitIROperand(ctx, inst->getOperand(1), mode, leftSide(outerPrec, prec)); - emit(" * "); - emitIROperand(ctx, inst->getOperand(0), mode, rightSide(prec, outerPrec)); - } - else - { - emit("mul("); - emitIROperand(ctx, inst->getOperand(0), mode, kEOp_General); - emit(", "); - emitIROperand(ctx, inst->getOperand(1), mode, kEOp_General); - emit(")"); - } - break; - - case kIROp_swizzle: - { - auto prec = kEOp_Postfix; - needClose = maybeEmitParens(outerPrec, prec); - - auto ii = (IRSwizzle*)inst; - emitIROperand(ctx, ii->getBase(), mode, leftSide(outerPrec, prec)); - 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" }; - emit(kComponents[elementIndex]); - } - } - break; - - case kIROp_Specialize: - { - emitIROperand(ctx, inst->getOperand(0), mode, outerPrec); - } - break; - - case kIROp_Select: - { - if (getTarget(ctx) == CodeGenTarget::GLSL && - inst->getOperand(0)->getDataType()->op != kIROp_BoolType) - { - // For GLSL, emit a call to `mix` if condition is a vector - emit("mix("); - emitIROperand(ctx, inst->getOperand(2), mode, leftSide(kEOp_General, kEOp_General)); - emit(", "); - emitIROperand(ctx, inst->getOperand(1), mode, leftSide(kEOp_General, kEOp_General)); - emit(", "); - emitIROperand(ctx, inst->getOperand(0), mode, leftSide(kEOp_General, kEOp_General)); - emit(")"); - } - else - { - auto prec = kEOp_Conditional; - needClose = maybeEmitParens(outerPrec, prec); - - emitIROperand(ctx, inst->getOperand(0), mode, leftSide(outerPrec, prec)); - emit(" ? "); - emitIROperand(ctx, inst->getOperand(1), mode, prec); - emit(" : "); - emitIROperand(ctx, inst->getOperand(2), mode, rightSide(prec, outerPrec)); - } - } - break; - - case kIROp_Param: - 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. - - emit("{ "); - UInt argCount = inst->getOperandCount(); - for (UInt aa = 0; aa < argCount; ++aa) - { - if (aa != 0) emit(", "); - emitIROperand(ctx, inst->getOperand(aa), mode, kEOp_General); - } - 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(ctx)) - { - case CodeGenTarget::GLSL: - switch(toType) - { - default: - emit("/* unhandled */"); - break; - - case BaseType::UInt: - break; - - case BaseType::Int: - emitIRType(ctx, inst->getDataType()); - break; - - case BaseType::Float: - emit("uintBitsToFloat("); - break; - } - break; - - case CodeGenTarget::HLSL: - switch(toType) - { - default: - emit("/* unhandled */"); - break; - - case BaseType::UInt: - break; - case BaseType::Int: - emit("("); - emitIRType(ctx, inst->getDataType()); - emit(")"); - break; - case BaseType::Float: - emit("asfloat"); - break; - } - break; - - - default: - SLANG_UNEXPECTED("unhandled codegen target"); - break; - } - - emit("("); - emitIROperand(ctx, inst->getOperand(0), mode, kEOp_General); - emit(")"); - } - break; - - default: - emit("/* unhandled */"); - break; - } - maybeCloseParens(needClose); - } - - BaseType extractBaseType(IRType* inType) - { - auto type = inType; - for(;;) - { - if(auto irBaseType = as<IRBasicType>(type)) - { - return irBaseType->getBaseType(); - } - else if(auto vecType = as<IRVectorType>(type)) - { - type = vecType->getElementType(); - continue; - } - else - { - return BaseType::Void; - } - } - } - - void emitIRInst( - EmitContext* ctx, - IRInst* inst, - IREmitMode mode) - { - try - { - emitIRInstImpl(ctx, 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(...) - { - ctx->noteInternalErrorLoc(inst->sourceLoc); - throw; - } - } - - void emitIRInstImpl( - EmitContext* ctx, - IRInst* inst, - IREmitMode mode) - { - if (shouldFoldIRInstIntoUseSites(ctx, inst, mode)) - { - return; - } - - advanceToSourceLocation(inst->sourceLoc); - - switch(inst->op) - { - default: - emitIRInstResultDecl(ctx, inst); - emitIRInstExpr(ctx, inst, mode, kEOp_General); - emit(";\n"); - break; - - case kIROp_undefined: - { - auto type = inst->getDataType(); - emitIRType(ctx, type, getIRName(inst)); - emit(";\n"); - } - break; - - case kIROp_Var: - { - auto ptrType = cast<IRPtrType>(inst->getDataType()); - auto valType = ptrType->getValueType(); - - auto name = getIRName(inst); - emitIRRateQualifiers(ctx, inst); - emitIRType(ctx, valType, name); - 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: - emit("return;\n"); - break; - - case kIROp_ReturnVal: - emit("return "); - emitIROperand(ctx, ((IRReturnVal*) inst)->getVal(), mode, kEOp_General); - emit(";\n"); - break; - - case kIROp_discard: - emit("discard;\n"); - break; - - case kIROp_swizzleSet: - { - auto ii = (IRSwizzleSet*)inst; - emitIRInstResultDecl(ctx, inst); - emitIROperand(ctx, inst->getOperand(0), mode, kEOp_General); - emit(";\n"); - - auto subscriptOuter = kEOp_General; - auto subscriptPrec = kEOp_Postfix; - bool needCloseSubscript = maybeEmitParens(subscriptOuter, subscriptPrec); - - emitIROperand(ctx, inst, mode, leftSide(subscriptOuter, subscriptPrec)); - 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" }; - emit(kComponents[elementIndex]); - } - maybeCloseParens(needCloseSubscript); - - emit(" = "); - emitIROperand(ctx, inst->getOperand(1), mode, kEOp_General); - emit(";\n"); - } - break; - - case kIROp_SwizzledStore: - { - auto subscriptOuter = kEOp_General; - auto subscriptPrec = kEOp_Postfix; - bool needCloseSubscript = maybeEmitParens(subscriptOuter, subscriptPrec); - - - auto ii = cast<IRSwizzledStore>(inst); - emitIROperand(ctx, ii->getDest(), mode, leftSide(subscriptOuter, subscriptPrec)); - 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" }; - emit(kComponents[elementIndex]); - } - maybeCloseParens(needCloseSubscript); - - emit(" = "); - emitIROperand(ctx, ii->getSource(), mode, kEOp_General); - emit(";\n"); - } - break; - } - } - - void emitIRSemantics( - EmitContext*, - VarLayout* varLayout) - { - if(varLayout->flags & VarLayoutFlag::HasSemantic) - { - Emit(" : "); - emit(varLayout->semanticName); - if(varLayout->semanticIndex) - { - Emit(varLayout->semanticIndex); - } - } - } - - void emitIRSemantics( - EmitContext* ctx, - IRInst* inst) - { - // Don't emit semantics if we aren't translating down to HLSL - switch (ctx->shared->target) - { - case CodeGenTarget::HLSL: - break; - - default: - return; - } - - if (auto semanticDecoration = inst->findDecoration<IRSemanticDecoration>()) - { - Emit(" : "); - emit(semanticDecoration->getSemanticName()); - return; - } - - if(auto layoutDecoration = inst->findDecoration<IRLayoutDecoration>()) - { - auto layout = layoutDecoration->getLayout(); - if(auto varLayout = as<VarLayout>(layout)) - { - emitIRSemantics(ctx, varLayout); - } - else if (auto entryPointLayout = as<EntryPointLayout>(layout)) - { - if(auto resultLayout = entryPointLayout->resultLayout) - { - emitIRSemantics(ctx, resultLayout); - } - } - } - } - - VarLayout* getVarLayout( - EmitContext* /*context*/, - IRInst* var) - { - auto decoration = var->findDecoration<IRLayoutDecoration>(); - if (!decoration) - return nullptr; - - return (VarLayout*) decoration->getLayout(); - } - - void emitIRLayoutSemantics( - EmitContext* ctx, - IRInst* inst, - char const* uniformSemanticSpelling = "register") - { - auto layout = getVarLayout(ctx, inst); - if (layout) - { - emitHLSLRegisterSemantics(layout, uniformSemanticSpelling); - } - } - - // 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( - EmitContext* ctx, - 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 = kEOp_General; - auto prec = kEOp_Assign; - - emitIROperand(ctx, pp, IREmitMode::Default, leftSide(outerPrec, prec)); - emit(" = "); - emitIROperand(ctx, arg, IREmitMode::Default, rightSide(prec, outerPrec)); - emit(";\n"); - } - } - - /// Emit high-level language statements from a structrured region. - void emitRegion( - EmitContext* ctx, - 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(ctx, 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. - // - 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(ctx, 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 paremter 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( - ctx, - 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( - ctx, - 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: - emit("break;\n"); - break; - case Region::Flavor::Continue: - 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 }` - - emit("if("); - emitIROperand(ctx, ifRegion->condition, IREmitMode::Default, kEOp_General); - emit(")\n{\n"); - indent(); - emitRegion(ctx, ifRegion->thenRegion); - dedent(); - emit("}\n"); - - // Don't emit the `else` region if it would be empty - // - if(auto elseRegion = ifRegion->elseRegion) - { - emit("else\n{\n"); - indent(); - emitRegion(ctx, elseRegion); - dedent(); - 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<IRLoopControlDecoration>()) - { - switch (loopControlDecoration->getMode()) - { - case kIRLoopControl_Unroll: - // Note: loop unrolling control is only available in HLSL, not GLSL - if(getTarget(ctx) == CodeGenTarget::HLSL) - { - emit("[unroll]\n"); - } - break; - - default: - break; - } - } - - emit("for(;;)\n{\n"); - indent(); - emitRegion(ctx, loopRegion->body); - dedent(); - 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. - emit("switch("); - emitIROperand(ctx, switchRegion->condition, IREmitMode::Default, kEOp_General); - emit(")\n{\n"); - - auto defaultCase = switchRegion->defaultCase; - for(auto currentCase : switchRegion->cases) - { - for(auto caseVal : currentCase->values) - { - emit("case "); - emitIROperand(ctx, caseVal, IREmitMode::Default, kEOp_General); - emit(":\n"); - } - if(currentCase.Ptr() == defaultCase) - { - emit("default:\n"); - } - - indent(); - emit("{\n"); - indent(); - emitRegion(ctx, currentCase->body); - dedent(); - emit("}\n"); - dedent(); - } - - 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 emitRegionTree( - EmitContext* ctx, - RegionTree* regionTree) - { - emitRegion(ctx, regionTree->rootRegion); - } - - // Is an IR function a definition? (otherwise it is a declaration) - bool 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 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(context) != CodeGenTarget::GLSL) - { - return getText(entryPointLayout->entryPoint->getName()); - } - - // - - return "main"; - } - else - { - return getIRName(func); - } - } - - void 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<StringLiteralExpr>(expr); - if (!stringLitExpr) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute parameter expecting to be a string "); - return; - } - - emit("["); - emit(name); - emit("(\""); - emit(stringLitExpr->value); - emit("\")]\n"); - } - - void 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<IntegerLiteralExpr>(expr); - if (!intLitExpr) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute expects an int"); - return; - } - - emit("["); - emit(name); - emit("("); - emit(intLitExpr->value); - emit(")]\n"); - } - - void emitFuncDeclPatchConstantFuncAttribute(IRFunc* irFunc, FuncDecl* entryPoint, PatchConstantFuncAttribute* attrib) - { - SLANG_UNUSED(attrib); - - auto irPatchFunc = irFunc->findDecoration<IRPatchConstantFuncDecoration>(); - assert(irPatchFunc); - if (!irPatchFunc) - { - SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Unable to find [patchConstantFunc(...)] decoration"); - return; - } - - const String irName = getIRName(irPatchFunc->getFunc()); - - emit("[patchconstantfunc(\""); - emit(irName); - emit("\")]\n"); - } - - void emitIREntryPointAttributes_HLSL( - IRFunc* irFunc, - EmitContext* ctx, - EntryPointLayout* entryPointLayout) - { - auto profile = ctx->shared->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) - { - emit("[shader(\""); - emit(stageName); - 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]); - - emit("[numthreads("); - for (int ii = 0; ii < 3; ++ii) - { - if (ii != 0) emit(", "); - Emit(sizeAlongAxis[ii]); - } - emit(")]\n"); - } - break; - case Stage::Geometry: - { - if (auto attrib = entryPointLayout->entryPoint->FindModifier<MaxVertexCountAttribute>()) - { - emit("[maxvertexcount("); - Emit(attrib->value); - emit(")]\n"); - } - if (auto attrib = entryPointLayout->entryPoint->FindModifier<InstanceAttribute>()) - { - emit("[instance("); - Emit(attrib->value); - emit(")]\n"); - } - break; - } - case Stage::Domain: - { - FuncDecl* entryPoint = entryPointLayout->entryPoint; - /* [domain("isoline")] */ - if (auto attrib = entryPoint->FindModifier<DomainAttribute>()) - { - 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<DomainAttribute>()) - { - emitAttributeSingleString("domain", entryPoint, attrib); - } - /* [domain("partitioning")] */ - if (auto attrib = entryPoint->FindModifier<PartitioningAttribute>()) - { - emitAttributeSingleString("partitioning", entryPoint, attrib); - } - /* [outputtopology("line")] */ - if (auto attrib = entryPoint->FindModifier<OutputTopologyAttribute>()) - { - emitAttributeSingleString("outputtopology", entryPoint, attrib); - } - /* [outputcontrolpoints(4)] */ - if (auto attrib = entryPoint->FindModifier<OutputControlPointsAttribute>()) - { - emitAttributeSingleInt("outputcontrolpoints", entryPoint, attrib); - } - /* [patchconstantfunc("HSConst")] */ - if (auto attrib = entryPoint->FindModifier<PatchConstantFuncAttribute>()) - { - emitFuncDeclPatchConstantFuncAttribute(irFunc, entryPoint, attrib); - } - - break; - } - case Stage::Pixel: - { - if (irFunc->findDecoration<IREarlyDepthStencilDecoration>()) - { - emit("[earlydepthstencil]\n"); - } - break; - } - // TODO: There are other stages that will need this kind of handling. - default: - break; - } - } - - void emitIREntryPointAttributes_GLSL( - IRFunc* irFunc, - EmitContext* /*ctx*/, - 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]); - - emit("layout("); - char const* axes[] = { "x", "y", "z" }; - for (int ii = 0; ii < 3; ++ii) - { - if (ii != 0) emit(", "); - emit("local_size_"); - emit(axes[ii]); - emit(" = "); - Emit(sizeAlongAxis[ii]); - } - emit(") in;"); - } - break; - case Stage::Geometry: - { - if (auto attrib = entryPointLayout->entryPoint->FindModifier<MaxVertexCountAttribute>()) - { - emit("layout(max_vertices = "); - Emit(attrib->value); - emit(") out;\n"); - } - if (auto attrib = entryPointLayout->entryPoint->FindModifier<InstanceAttribute>()) - { - emit("layout(invocations = "); - Emit(attrib->value); - emit(") in;\n"); - } - - for(auto pp : entryPointLayout->entryPoint->GetParameters()) - { - if(auto inputPrimitiveTypeModifier = pp->FindModifier<HLSLGeometryShaderInputPrimitiveTypeModifier>()) - { - if(as<HLSLTriangleModifier>(inputPrimitiveTypeModifier)) - { - emit("layout(triangles) in;\n"); - } - else if(as<HLSLLineModifier>(inputPrimitiveTypeModifier)) - { - emit("layout(lines) in;\n"); - } - else if(as<HLSLLineAdjModifier>(inputPrimitiveTypeModifier)) - { - emit("layout(lines_adjacency) in;\n"); - } - else if(as<HLSLPointModifier>(inputPrimitiveTypeModifier)) - { - emit("layout(points) in;\n"); - } - else if(as<HLSLTriangleAdjModifier>(inputPrimitiveTypeModifier)) - { - emit("layout(triangles_adjacency) in;\n"); - } - } - - if(auto outputStreamType = as<HLSLStreamOutputType>(pp->type)) - { - if(as<HLSLTriangleStreamType>(outputStreamType)) - { - emit("layout(triangle_strip) out;\n"); - } - else if(as<HLSLLineStreamType>(outputStreamType)) - { - emit("layout(line_strip) out;\n"); - } - else if(as<HLSLPointStreamType>(outputStreamType)) - { - emit("layout(points) out;\n"); - } - } - } - - - } - break; - case Stage::Pixel: - { - if (irFunc->findDecoration<IREarlyDepthStencilDecoration>()) - { - // https://www.khronos.org/opengl/wiki/Early_Fragment_Test - emit("layout(early_fragment_tests) in;\n"); - } - break; - } - // TODO: There are other stages that will need this kind of handling. - default: - break; - } - } - - void emitIREntryPointAttributes( - IRFunc* irFunc, - EmitContext* ctx, - EntryPointLayout* entryPointLayout) - { - switch(getTarget(ctx)) - { - case CodeGenTarget::HLSL: - emitIREntryPointAttributes_HLSL(irFunc, ctx, entryPointLayout); - break; - - case CodeGenTarget::GLSL: - emitIREntryPointAttributes_GLSL(irFunc, ctx, entryPointLayout); - break; - } - } - - void emitPhiVarDecls( - EmitContext* ctx, - 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(ctx, pp); - emitIRType(ctx, pp->getFullType(), getIRName(pp)); - emit(";\n"); - } - } - } - - /// Emit high-level statements for the body of a function. - void emitIRFunctionBody( - EmitContext* ctx, - IRGlobalValueWithCode* code) - { - // Compute a structured region tree that can represent - // the control flow of our function. - // - RefPtr<RegionTree> regionTree = generateRegionTreeForFunc( - code, - ctx->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(ctx, regionTree); - } - - void emitIRSimpleFunc( - EmitContext* ctx, - IRFunc* func) - { - auto resultType = func->getResultType(); - - // Deal with decorations that need - // to be emitted as attributes - auto entryPointLayout = asEntryPoint(func); - if (entryPointLayout) - { - emitIREntryPointAttributes(func, ctx, entryPointLayout); - } - - const CodeGenTarget target = ctx->shared->target; - - auto name = getIRFuncName(func); - - EmitType(resultType, name); - - emit("("); - auto firstParam = func->getFirstParam(); - for( auto pp = firstParam; pp; pp = pp->getNextParam()) - { - if(pp != firstParam) - emit(", "); - - auto paramName = getIRName(pp); - auto paramType = pp->getDataType(); - - if (target == CodeGenTarget::HLSL) - { - if (auto layoutDecor = pp->findDecoration<IRLayoutDecoration>()) - { - Layout* layout = layoutDecor->getLayout(); - VarLayout* varLayout = as<VarLayout>(layout); - - if (varLayout) - { - auto var = varLayout->getVariable(); - - if (auto primTypeModifier = var->FindModifier<HLSLGeometryShaderInputPrimitiveTypeModifier>()) - { - if (as<HLSLTriangleModifier>(primTypeModifier)) - emit("triangle "); - else if (as<HLSLPointModifier>(primTypeModifier)) - emit("point "); - else if (as<HLSLLineModifier>(primTypeModifier)) - emit("line "); - else if (as<HLSLLineAdjModifier>(primTypeModifier)) - emit("lineadj "); - else if (as<HLSLTriangleAdjModifier>(primTypeModifier)) - emit("triangleadj "); - } - } - } - } - - emitIRParamType(ctx, paramType, paramName); - - emitIRSemantics(ctx, pp); - } - emit(")"); - - - emitIRSemantics(ctx, func); - - // TODO: encode declaration vs. definition - if(isDefinition(func)) - { - emit("\n{\n"); - indent(); - - // HACK: forward-declare all the local variables needed for the - // parameters of non-entry blocks. - emitPhiVarDecls(ctx, func); - - // Need to emit the operations in the blocks of the function - emitIRFunctionBody(ctx, func); - - dedent(); - emit("}\n\n"); - } - else - { - emit(";\n\n"); - } - } - - void emitIRParamType( - EmitContext* ctx, - 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<IROutType>(type)) - { - emit("out "); - type = outType->getValueType(); - } - else if( auto inOutType = as<IRInOutType>(type)) - { - emit("inout "); - type = inOutType->getValueType(); - } - else if( auto refType = as<IRRefType>(type)) - { - // Note: There is no HLSL/GLSL equivalent for by-reference parameters, - // so we don't actually expect to encounter these in user code. - emit("inout "); - type = inOutType->getValueType(); - } - - emitIRType(ctx, type, name); - } - - IRInst* getSpecializedValue(IRSpecialize* specInst) - { - auto base = specInst->getBase(); - auto baseGeneric = as<IRGeneric>(base); - if (!baseGeneric) - return base; - - auto lastBlock = baseGeneric->getLastBlock(); - if (!lastBlock) - return base; - - auto returnInst = as<IRReturnVal>(lastBlock->getTerminator()); - if (!returnInst) - return base; - - return returnInst->getVal(); - } - - void emitIRFuncDecl( - EmitContext* ctx, - 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(ctx, 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(ctx, resultType, name); - - emit("("); - auto paramCount = funcType->getParamCount(); - for(UInt pp = 0; pp < paramCount; ++pp) - { - if(pp != 0) - emit(", "); - - String paramName; - paramName.append("_"); - paramName.append(Int32(pp)); - auto paramType = funcType->getParamType(pp); - - emitIRParamType(ctx, paramType, paramName); - } - emit(");\n\n"); - } - - EntryPointLayout* getEntryPointLayout( - EmitContext* /*context*/, - IRFunc* func) - { - if( auto layoutDecoration = func->findDecoration<IRLayoutDecoration>() ) - { - return as<EntryPointLayout>(layoutDecoration->getLayout()); - } - return nullptr; - } - - EntryPointLayout* asEntryPoint(IRFunc* func) - { - if (auto layoutDecoration = func->findDecoration<IRLayoutDecoration>()) - { - if (auto entryPointLayout = as<EntryPointLayout>(layoutDecoration->getLayout())) - { - return entryPointLayout; - } - } - - return nullptr; - } - - // Detect if the given IR function represents a - // declaration of an intrinsic/builtin for the - // current code-generation target. - bool isTargetIntrinsic( - EmitContext* /*ctxt*/, - 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); - } - - // Check whether a given value names a target intrinsic, - // and return the IR function representing the intrinsic - // if it does. - IRFunc* asTargetIntrinsic( - EmitContext* ctxt, - IRInst* value) - { - if(!value) - return nullptr; - - while (auto specInst = as<IRSpecialize>(value)) - { - value = getSpecializedValue(specInst); - } - - if(value->op != kIROp_Func) - return nullptr; - - IRFunc* func = (IRFunc*) value; - if(!isTargetIntrinsic(ctxt, func)) - return nullptr; - - return func; - } - - void emitIRFunc( - EmitContext* ctx, - 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(ctx, func)) - return; - - emitIRFuncDecl(ctx, func); - } - else - { - // The common case is that what we - // have is just an ordinary function, - // and we can emit it as such. - emitIRSimpleFunc(ctx, func); - } - } - - void emitIRStruct( - EmitContext* ctx, - 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(ctx, structType)) - { - return; - } - - emit("struct "); - emit(getIRName(structType)); - emit("\n{\n"); - 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<IRVoidType>(fieldType)) - continue; - - // Note: GLSL doesn't support interpolation modifiers on `struct` fields - if( ctx->shared->target != CodeGenTarget::GLSL ) - { - emitInterpolationModifiers(ctx, fieldKey, fieldType, nullptr); - } - - emitIRType(ctx, fieldType, getIRName(fieldKey)); - emitIRSemantics(ctx, fieldKey); - emit(";\n"); - } - - dedent(); - emit("};\n\n"); - } - - void emitIRMatrixLayoutModifiers( - EmitContext* ctx, - 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<ArrayTypeLayout>(typeLayout)) - typeLayout = arrayTypeLayout->elementTypeLayout; - - if (auto matrixTypeLayout = typeLayout.as<MatrixTypeLayout>()) - { - auto target = ctx->shared->target; - - switch (target) - { - case CodeGenTarget::HLSL: - switch (matrixTypeLayout->mode) - { - case kMatrixLayoutMode_ColumnMajor: - emit("column_major "); - break; - - case kMatrixLayoutMode_RowMajor: - 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: - emit("layout(row_major)\n"); - break; - - case kMatrixLayoutMode_RowMajor: - emit("layout(column_major)\n"); - break; - } - break; - - default: - break; - } - - } - - } - - // Emit the `flat` qualifier if the underlying type - // of the variable is an integer type. - void maybeEmitGLSLFlatModifier( - EmitContext*, - IRType* valueType) - { - auto tt = valueType; - if(auto vecType = as<IRVectorType>(tt)) - tt = vecType->getElementType(); - if(auto vecType = as<IRMatrixType>(tt)) - tt = vecType->getElementType(); - - switch(tt->op) - { - default: - break; - - case kIROp_IntType: - case kIROp_UIntType: - case kIROp_UInt64Type: - Emit("flat "); - break; - } - } - - void emitInterpolationModifiers( - EmitContext* ctx, - IRInst* varInst, - IRType* valueType, - VarLayout* layout) - { - bool isGLSL = (ctx->shared->target == 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; - Emit(isGLSL ? "flat " : "nointerpolation "); - break; - - case IRInterpolationMode::NoPerspective: - anyModifiers = true; - Emit("noperspective "); - break; - - case IRInterpolationMode::Linear: - anyModifiers = true; - Emit(isGLSL ? "smooth " : "linear "); - break; - - case IRInterpolationMode::Sample: - anyModifiers = true; - Emit("sample "); - break; - - case IRInterpolationMode::Centroid: - anyModifiers = true; - 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(ctx, valueType); - } - } - } - - UInt getRayPayloadLocation( - EmitContext* ctx, - IRInst* inst) - { - auto& map = ctx->shared->mapIRValueToRayPayloadLocation; - UInt value = 0; - if(map.TryGetValue(inst, value)) - return value; - - value = map.Count(); - map.Add(inst, value); - return value; - } - - UInt getCallablePayloadLocation( - EmitContext* ctx, - IRInst* inst) - { - auto& map = ctx->shared->mapIRValueToCallablePayloadLocation; - UInt value = 0; - if(map.TryGetValue(inst, value)) - return value; - - value = map.Count(); - map.Add(inst, value); - return value; - } - - void 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<IRFormatDecoration>()) - { - 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. - // - Emit("layout("); - Emit(getGLSLNameForImageFormat(format)); - 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->context->shared->compileRequest->useUnknownImageFormatAsDefault) - { - requireGLSLExtension("GL_EXT_shader_image_load_formatted"); - return; - } - - // At this point we have a resource type like `RWTexture2D<X>` - // 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<float4>` 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<X>` 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<IRVectorType>(elementType)) - { - if(auto intLitVal = as<IRIntLit>(elementVecType->getElementCount())) - { - vectorWidth = (Int) intLitVal->getValue(); - } - else - { - vectorWidth = 0; - } - elementType = elementVecType->getElementType(); - } - if(auto elementBasicType = as<IRBasicType>(elementType)) - { - Emit("layout("); - switch(vectorWidth) - { - default: 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). - - Emit("rgba"); - //Emit("rgb"); - break; - } - - case 2: Emit("rg"); break; - case 1: Emit("r"); break; - } - switch(elementBasicType->getBaseType()) - { - default: - case BaseType::Float: Emit("32f"); break; - case BaseType::Half: Emit("16f"); break; - case BaseType::UInt: Emit("32ui"); break; - case BaseType::Int: 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 - } - Emit(")\n"); - } - } - - /// Emit modifiers that should apply even for a declaration of an SSA temporary. - void emitIRTempModifiers( - EmitContext* ctx, - IRInst* temp) - { - SLANG_UNUSED(ctx); - - if(temp->findDecoration<IRPreciseDecoration>()) - { - emit("precise "); - } - } - - void emitIRVarModifiers( - EmitContext* ctx, - 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<IRVulkanRayPayloadDecoration>()) - { - emit("layout(location = "); - Emit(getRayPayloadLocation(ctx, varDecl)); - emit(")\n"); - emit("rayPayloadNV\n"); - } - if(varDecl->findDecoration<IRVulkanCallablePayloadDecoration>()) - { - emit("layout(location = "); - Emit(getCallablePayloadLocation(ctx, varDecl)); - emit(")\n"); - emit("callableDataNV\n"); - } - - if(varDecl->findDecoration<IRVulkanHitAttributesDecoration>()) - { - emit("hitAttributeNV\n"); - } - - if(varDecl->findDecoration<IRGloballyCoherentDecoration>()) - { - switch(getTarget(context)) - { - default: - break; - - case CodeGenTarget::HLSL: - emit("globallycoherent\n"); - break; - - case CodeGenTarget::GLSL: - emit("coherent\n"); - break; - } - } - - emitIRTempModifiers(ctx, varDecl); - - if (!layout) - return; - - emitIRMatrixLayoutModifiers(ctx, 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(context) == CodeGenTarget::GLSL) - { - if(auto resourceType = as<IRTextureType>(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(ctx, varDecl, varType, layout); - } - - if (ctx->shared->target == 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: - emit("uniform "); - break; - - case LayoutResourceKind::VaryingInput: - { - emit("in "); - } - break; - - case LayoutResourceKind::VaryingOutput: - { - emit("out "); - } - break; - - case LayoutResourceKind::RayPayload: - { - emit("rayPayloadInNV "); - } - break; - - case LayoutResourceKind::CallablePayload: - { - emit("callableDataInNV "); - } - break; - - case LayoutResourceKind::HitAttributes: - { - emit("hitAttributeNV "); - } - break; - - default: - continue; - } - - break; - } - } - } - - void emitHLSLParameterGroup( - EmitContext* ctx, - IRGlobalParam* varDecl, - IRUniformParameterGroupType* type) - { - if(as<IRTextureBufferType>(type)) - { - emit("tbuffer "); - } - else - { - emit("cbuffer "); - } - emit(getIRName(varDecl)); - - auto varLayout = getVarLayout(ctx, varDecl); - SLANG_RELEASE_ASSERT(varLayout); - - EmitVarChain blockChain(varLayout); - - EmitVarChain containerChain = blockChain; - EmitVarChain elementChain = blockChain; - - auto typeLayout = varLayout->typeLayout; - if( auto parameterGroupTypeLayout = as<ParameterGroupTypeLayout>(typeLayout) ) - { - containerChain = EmitVarChain(parameterGroupTypeLayout->containerVarLayout, &blockChain); - elementChain = EmitVarChain(parameterGroupTypeLayout->elementVarLayout, &blockChain); - - typeLayout = parameterGroupTypeLayout->elementVarLayout->typeLayout; - } - - emitHLSLRegisterSemantic(LayoutResourceKind::ConstantBuffer, &containerChain); - - emit("\n{\n"); - indent(); - - auto elementType = type->getElementType(); - - emitIRType(ctx, elementType, getIRName(varDecl)); - emit(";\n"); - - dedent(); - emit("}\n"); - } - - /// Emit the array brackets that go on the end of a declaration of the given type. - void emitArrayBrackets( - EmitContext* ctx, - IRType* inType) - { - SLANG_UNUSED(ctx); - - // 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<IRArrayType>(type)) - { - emit("["); - EmitVal(arrayType->getElementCount(), kEOp_General); - emit("]"); - - // Continue looping on the next layer in. - // - type = arrayType->getElementType(); - } - else if(auto unsizedArrayType = as<IRUnsizedArrayType>(type)) - { - emit("[]"); - - // Continue looping on the next layer in. - // - type = unsizedArrayType->getElementType(); - } - else - { - // This layer wasn't an array, so we are done. - // - return; - } - } - } - - - void emitGLSLParameterGroup( - EmitContext* ctx, - IRGlobalParam* varDecl, - IRUniformParameterGroupType* type) - { - auto varLayout = getVarLayout(ctx, varDecl); - SLANG_RELEASE_ASSERT(varLayout); - - EmitVarChain blockChain(varLayout); - - EmitVarChain containerChain = blockChain; - EmitVarChain elementChain = blockChain; - - auto typeLayout = varLayout->typeLayout->unwrapArray(); - if( auto parameterGroupTypeLayout = as<ParameterGroupTypeLayout>(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. - emit("buffer "); - } - else if(as<IRGLSLShaderStorageBufferType>(type)) - { - // Is writable - emit("layout(std430) buffer "); - } - // TODO: what to do with HLSL `tbuffer` style buffers? - else - { - // uniform is implicitly read only - emit("layout(std140) uniform "); - } - - // Generate a dummy name for the block - emit("_S"); - Emit(ctx->shared->uniqueIDCounter++); - - emit("\n{\n"); - indent(); - - auto elementType = type->getElementType(); - - emitIRType(ctx, elementType, "_data"); - emit(";\n"); - - dedent(); - emit("} "); - - 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(ctx, varDecl->getDataType()); - - emit(";\n"); - } - - void emitIRParameterGroup( - EmitContext* ctx, - IRGlobalParam* varDecl, - IRUniformParameterGroupType* type) - { - switch (ctx->shared->target) - { - case CodeGenTarget::HLSL: - emitHLSLParameterGroup(ctx, varDecl, type); - break; - - case CodeGenTarget::GLSL: - emitGLSLParameterGroup(ctx, varDecl, type); - break; - } - } - - void emitIRVar( - EmitContext* ctx, - 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(ctx, varDecl); - - emitIRVarModifiers(ctx, layout, varDecl, varType); - -#if 0 - switch (addressSpace) - { - default: - break; - - case kIRAddressSpace_GroupShared: - emit("groupshared "); - break; - } -#endif - emitIRRateQualifiers(ctx, varDecl); - - emitIRType(ctx, varType, getIRName(varDecl)); - - emitIRSemantics(ctx, varDecl); - - emitIRLayoutSemantics(ctx, varDecl); - - emit(";\n"); - } - - void emitIRStructuredBuffer_GLSL( - EmitContext* ctx, - IRGlobalParam* varDecl, - IRHLSLStructuredBufferTypeBase* structuredBufferType) - { - // Shader storage buffer is an OpenGL 430 feature - // - // TODO: we should require either the extension or the version... - requireGLSLVersion(430); - - Emit("layout(std430"); - - auto layout = getVarLayout(ctx, varDecl); - if (layout) - { - LayoutResourceKind kind = LayoutResourceKind::DescriptorTableSlot; - EmitVarChain chain(layout); - - const UInt index = getBindingOffset(&chain, kind); - const UInt space = getBindingSpace(&chain, kind); - - Emit(", binding = "); - Emit(index); - if (space) - { - Emit(", set = "); - Emit(space); - } - } - - 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<IRHLSLStructuredBufferType>(structuredBufferType)) - { - emit("readonly "); - } - - emit("buffer "); - - // Generate a dummy name for the block - emit("_S"); - Emit(ctx->shared->uniqueIDCounter++); - - emit(" {\n"); - indent(); - - - auto elementType = structuredBufferType->getElementType(); - emitIRType(ctx, elementType, "_data[]"); - emit(";\n"); - - dedent(); - emit("} "); - - emit(getIRName(varDecl)); - emitArrayBrackets(ctx, varDecl->getDataType()); - - emit(";\n"); - } - - void emitIRByteAddressBuffer_GLSL( - EmitContext* ctx, - 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); - - Emit("layout(std430"); - - auto layout = getVarLayout(ctx, varDecl); - if (layout) - { - LayoutResourceKind kind = LayoutResourceKind::DescriptorTableSlot; - EmitVarChain chain(layout); - - const UInt index = getBindingOffset(&chain, kind); - const UInt space = getBindingSpace(&chain, kind); - - Emit(", binding = "); - Emit(index); - if (space) - { - Emit(", set = "); - Emit(space); - } - } - - 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<IRHLSLByteAddressBufferType>(byteAddressBufferType)) - { - emit("readonly "); - } - - emit("buffer "); - - // Generate a dummy name for the block - emit("_S"); - Emit(ctx->shared->uniqueIDCounter++); - emit("\n{\n"); - indent(); - - emit("uint _data[];\n"); - - dedent(); - emit("} "); - - emit(getIRName(varDecl)); - emitArrayBrackets(ctx, varDecl->getDataType()); - - emit(";\n"); - } - - void emitIRGlobalVar( - EmitContext* ctx, - 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"); - - emit("\n"); - emitIRType(ctx, varType, initFuncName); - Emit("()\n{\n"); - indent(); - emitIRFunctionBody(ctx, varDecl); - dedent(); - Emit("}\n"); - } - - // An ordinary global variable won't have a layout - // associated with it, since it is not a shader - // parameter. - // - SLANG_ASSERT(!getVarLayout(ctx, 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(ctx)) - { - case CodeGenTarget::HLSL: - // HLSL requires the `static` modifier on any - // global variables; otherwise they are assumed - // to be uniforms. - Emit("static "); - break; - - default: - break; - } - - emitIRVarModifiers(ctx, layout, varDecl, varType); - - emitIRRateQualifiers(ctx, varDecl); - emitIRType(ctx, varType, getIRName(varDecl)); - - // TODO: These shouldn't be needed for ordinary - // global variables. - // - emitIRSemantics(ctx, varDecl); - emitIRLayoutSemantics(ctx, varDecl); - - if (varDecl->getFirstBlock()) - { - Emit(" = "); - emit(initFuncName); - Emit("()"); - } - - emit(";\n\n"); - } - - void emitIRGlobalParam( - EmitContext* ctx, - IRGlobalParam* varDecl) - { - auto rawType = varDecl->getDataType(); - - auto varType = rawType; - if( auto outType = as<IROutTypeBase>(varType) ) - { - varType = outType->getValueType(); - } - if (as<IRVoidType>(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<T>` type. - // - // Alternatively, we could make this choice based on profile, and - // prefer `ConstantBuffer<T>` on profiles that support it and/or when - // the input code used that syntax. - // - if (auto paramBlockType = as<IRUniformParameterGroupType>(varType)) - { - emitIRParameterGroup( - ctx, - varDecl, - paramBlockType); - return; - } - - if(getTarget(ctx) == 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<IRUniformParameterGroupType>(unwrapArray(varType))) - { - emitGLSLParameterGroup( - ctx, - varDecl, - paramBlockType); - return; - } - if( auto structuredBufferType = as<IRHLSLStructuredBufferTypeBase>(unwrapArray(varType)) ) - { - emitIRStructuredBuffer_GLSL( - ctx, - varDecl, - structuredBufferType); - return; - } - if( auto byteAddressBufferType = as<IRByteAddressBufferTypeBase>(unwrapArray(varType)) ) - { - emitIRByteAddressBuffer_GLSL( - ctx, - 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<IRLinkageDecoration>()) - { - 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<IRUnsizedArrayType>(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 associted with them. - // - auto layout = getVarLayout(ctx, varDecl); - SLANG_ASSERT(layout); - - emitIRVarModifiers(ctx, layout, varDecl, varType); - - emitIRRateQualifiers(ctx, varDecl); - emitIRType(ctx, varType, getIRName(varDecl)); - - emitIRSemantics(ctx, varDecl); - - emitIRLayoutSemantics(ctx, varDecl); - - // A shader parameter cannot have an initializer, - // so we do need to consider emitting one here. - - emit(";\n\n"); - } - - - void emitIRGlobalConstantInitializer( - EmitContext* ctx, - 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(ctx, returnInst->getVal(), IREmitMode::GlobalConstant, kEOp_General); - } - - void emitIRGlobalConstant( - EmitContext* ctx, - IRGlobalConstant* valDecl) - { - auto valType = valDecl->getDataType(); - - if( ctx->shared->target != CodeGenTarget::GLSL ) - { - emit("static "); - } - emit("const "); - emitIRRateQualifiers(ctx, valDecl); - emitIRType(ctx, valType, getIRName(valDecl)); - - if (valDecl->getFirstBlock()) - { - // There is an initializer (which we expect for - // any global constant...). - - emit(" = "); - - // We need to emit the entire initializer as - // a single expression. - emitIRGlobalConstantInitializer(ctx, valDecl); - } - - - emit(";\n"); - } - - void emitIRGlobalInst( - EmitContext* ctx, - IRInst* inst) - { - advanceToSourceLocation(inst->sourceLoc); - - switch(inst->op) - { - case kIROp_Func: - emitIRFunc(ctx, (IRFunc*) inst); - break; - - case kIROp_GlobalVar: - emitIRGlobalVar(ctx, (IRGlobalVar*) inst); - break; - - case kIROp_GlobalParam: - emitIRGlobalParam(ctx, (IRGlobalParam*) inst); - break; - - case kIROp_GlobalConstant: - emitIRGlobalConstant(ctx, (IRGlobalConstant*) inst); - break; - - case kIROp_Var: - emitIRVar(ctx, (IRVar*) inst); - break; - - case kIROp_StructType: - emitIRStruct(ctx, cast<IRStructType>(inst)); - break; - - default: - break; - } - } - - // An action to be performed during code emit. - struct EmitAction - { - enum Level - { - ForwardDeclaration, - Definition, - }; - Level level; - IRInst* inst; - }; - - struct ComputeEmitActionsContext - { - IRInst* moduleInst; - HashSet<IRInst*> openInsts; - Dictionary<IRInst*, EmitAction::Level> mapInstToLevel; - List<EmitAction>* actions; - }; - - void ensureInstOperand( - ComputeEmitActionsContext* ctx, - IRInst* inst, - EmitAction::Level requiredLevel = EmitAction::Level::Definition) - { - if(!inst) return; - - if(inst->getParent() == ctx->moduleInst) - { - ensureGlobalInst(ctx, inst, requiredLevel); - } - } - - void 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 ensureGlobalInst( - ComputeEmitActionsContext* ctx, - IRInst* inst, - EmitAction::Level requiredLevel) - { - // Skip certain instrutions, 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 computeIREmitActions( - IRModule* module, - List<EmitAction>& ioActions) - { - ComputeEmitActionsContext ctx; - ctx.moduleInst = module->getModuleInst(); - ctx.actions = &ioActions; - - for(auto inst : module->getGlobalInsts()) - { - if( as<IRType>(inst) ) - { - // Don't emit a type unless it is actually used. - continue; - } - - ensureGlobalInst(&ctx, inst, EmitAction::Level::Definition); - } - } - - void executeIREmitActions( - EmitContext* ctx, - List<EmitAction> const& actions) - { - for(auto action : actions) - { - switch(action.level) - { - case EmitAction::Level::ForwardDeclaration: - emitIRFuncDecl(ctx, cast<IRFunc>(action.inst)); - break; - - case EmitAction::Level::Definition: - emitIRGlobalInst(ctx, action.inst); - break; - } - } - } - - void emitIRModule( - EmitContext* ctx, - 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<EmitAction> actions; - - computeIREmitActions(module, actions); - executeIREmitActions(ctx, actions); - } -}; // @@ -7046,32 +160,44 @@ String emitEntryPoint( // auto translationUnit = entryPoint->getTranslationUnit(); - SharedEmitContext sharedContext; - sharedContext.compileRequest = compileRequest; - sharedContext.target = target; - sharedContext.finalTarget = targetRequest->getTarget(); - sharedContext.entryPoint = entryPoint; - sharedContext.effectiveProfile = getEffectiveProfile(entryPoint, targetRequest); + auto lineDirectiveMode = compileRequest->getLineDirectiveMode(); + // To try to make the default behavior reasonable, we will + // always use C-style line directives (to give the user + // good source locations on error messages from downstream + // compilers) *unless* they requested raw GLSL as the + // output (in which case we want to maximize compatibility + // with downstream tools). + if (lineDirectiveMode == LineDirectiveMode::Default && targetRequest->getTarget() == CodeGenTarget::GLSL) + { + lineDirectiveMode = LineDirectiveMode::GLSL; + } - if (entryPoint) + SourceStream sourceStream(compileRequest->getSourceManager(), lineDirectiveMode ); + + EmitContext emitContext; + emitContext.compileRequest = compileRequest; + emitContext.target = target; + emitContext.entryPoint = entryPoint; + emitContext.effectiveProfile = getEffectiveProfile(entryPoint, targetRequest); + emitContext.stream = &sourceStream; + + if (entryPoint && programLayout) { - sharedContext.entryPointLayout = findEntryPointLayout( + emitContext.entryPointLayout = findEntryPointLayout( programLayout, entryPoint); } - sharedContext.programLayout = programLayout; + emitContext.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 = getGlobalStructLayout(programLayout); - sharedContext.globalStructLayout = globalStructLayout; - - EmitContext context; - context.shared = &sharedContext; + + StructTypeLayout* globalStructLayout = programLayout ? getGlobalStructLayout(programLayout) : nullptr; + emitContext.globalStructLayout = globalStructLayout; - EmitVisitor visitor(&context); + CLikeSourceEmitter sourceEmitter(&emitContext); { auto session = targetRequest->getSession(); @@ -7297,7 +423,7 @@ String emitEntryPoint( irModule, irEntryPoint, compileRequest->getSink(), - &sharedContext.extensionUsageTracker); + &emitContext.extensionUsageTracker); #if 0 dumpIRIfEnabled(compileRequest, irModule, "GLSL LEGALIZED"); @@ -7332,7 +458,7 @@ String emitEntryPoint( // // TODO: do we want to emit directly from IR, or translate the // IR back into AST for emission? - visitor.emitIRModule(&context, irModule); + sourceEmitter.emitIRModule(irModule); } // Deal with cases where a particular stage requires certain GLSL versions @@ -7350,30 +476,29 @@ String emitEntryPoint( case Stage::RayGeneration: if( target == CodeGenTarget::GLSL ) { - requireGLSLExtension(&context.shared->extensionUsageTracker, "GL_NV_ray_tracing"); - requireGLSLVersionImpl(&context.shared->extensionUsageTracker, ProfileVersion::GLSL_460); + emitContext.extensionUsageTracker.requireGLSLExtension("GL_NV_ray_tracing"); + emitContext.extensionUsageTracker.requireGLSLVersion(ProfileVersion::GLSL_460); } break; } - String code = sharedContext.sb.ProduceString(); - sharedContext.sb.Clear(); + String code = sourceStream.getContent(); + sourceStream.clearContent(); // Now that we've emitted the code for all the declarations in the file, // it is time to stitch together the final output. // There may be global-scope modifiers that we should emit now - visitor.emitGLSLPreprocessorDirectives(); - - visitor.emitLayoutDirectives(targetRequest); - - String prefix = sharedContext.sb.ProduceString(); + sourceEmitter.emitGLSLPreprocessorDirectives(); + sourceEmitter.emitLayoutDirectives(targetRequest); + String prefix = sourceStream.getContent(); + StringBuilder finalResultBuilder; finalResultBuilder << prefix; - finalResultBuilder << sharedContext.extensionUsageTracker.glslExtensionRequireLines.ProduceString(); + finalResultBuilder << emitContext.extensionUsageTracker.getGLSLExtensionRequireLines(); finalResultBuilder << code; diff --git a/source/slang/emit.h b/source/slang/emit.h index 317afcf6b..dc9300025 100644 --- a/source/slang/emit.h +++ b/source/slang/emit.h @@ -12,11 +12,6 @@ namespace Slang class ProgramLayout; class TranslationUnitRequest; - struct ExtensionUsageTracker; - void requireGLSLExtension( - ExtensionUsageTracker* tracker, - String const& name); - // Emit code for a single entry point, based on // the input translation unit. String emitEntryPoint( diff --git a/source/slang/ir-glsl-legalize.cpp b/source/slang/ir-glsl-legalize.cpp index 6a50d32cd..b63225729 100644 --- a/source/slang/ir-glsl-legalize.cpp +++ b/source/slang/ir-glsl-legalize.cpp @@ -4,6 +4,8 @@ #include "ir.h" #include "ir-insts.h" +#include "slang-extension-usage-tracker.h" + namespace Slang { @@ -174,14 +176,6 @@ struct GLSLSystemValueInfo IRType* requiredType; }; -void requireGLSLVersionImpl( - ExtensionUsageTracker* tracker, - ProfileVersion version); - -void requireGLSLExtension( - ExtensionUsageTracker* tracker, - String const& name); - struct GLSLLegalizationContext { Session* session; @@ -191,12 +185,12 @@ struct GLSLLegalizationContext void requireGLSLExtension(String const& name) { - Slang::requireGLSLExtension(extensionUsageTracker, name); + extensionUsageTracker->requireGLSLExtension(name); } void requireGLSLVersion(ProfileVersion version) { - Slang::requireGLSLVersionImpl(extensionUsageTracker, version); + extensionUsageTracker->requireGLSLVersion(version); } Stage getStage() diff --git a/source/slang/ir-glsl-legalize.h b/source/slang/ir-glsl-legalize.h index 7fabac869..994a68247 100644 --- a/source/slang/ir-glsl-legalize.h +++ b/source/slang/ir-glsl-legalize.h @@ -7,7 +7,8 @@ namespace Slang class DiagnosticSink; class Session; -struct ExtensionUsageTracker; +class ExtensionUsageTracker; + struct IRFunc; struct IRModule; diff --git a/source/slang/ir-link.cpp b/source/slang/ir-link.cpp index d4f736a08..dc433663a 100644 --- a/source/slang/ir-link.cpp +++ b/source/slang/ir-link.cpp @@ -811,6 +811,12 @@ String getTargetName(IRSpecContext* context) case CodeGenTarget::GLSL: return "glsl"; + case CodeGenTarget::CSource: + return "c"; + + case CodeGenTarget::CPPSource: + return "cpp"; + default: SLANG_UNEXPECTED("unhandled case"); UNREACHABLE_RETURN("unknown"); diff --git a/source/slang/lower-to-ir.h b/source/slang/lower-to-ir.h index 79e1acc61..6ac2e182a 100644 --- a/source/slang/lower-to-ir.h +++ b/source/slang/lower-to-ir.h @@ -17,8 +17,6 @@ namespace Slang class ProgramLayout; class TranslationUnitRequest; - struct ExtensionUsageTracker; - IRModule* generateIRForTranslationUnit( TranslationUnitRequest* translationUnit); diff --git a/source/slang/options.cpp b/source/slang/options.cpp index a5cdfef44..46e0203cf 100644 --- a/source/slang/options.cpp +++ b/source/slang/options.cpp @@ -360,6 +360,9 @@ struct OptionsParser CASE(".spv", SPIRV); CASE(".spv.asm", SPIRV_ASM); + CASE(".c", C_SOURCE); + CASE(".cpp", CPP_SOURCE); + #undef CASE else if (path.endsWith(".slang-module")) @@ -499,6 +502,8 @@ struct OptionsParser CASE("dxil", DXIL) CASE("dxil-assembly", DXIL_ASM) CASE("dxil-asm", DXIL_ASM) + CASE("c", C_SOURCE) + CASE("cpp", CPP_SOURCE) #undef CASE /* else */ diff --git a/source/slang/slang-c-like-source-emitter.cpp b/source/slang/slang-c-like-source-emitter.cpp new file mode 100644 index 000000000..917779b6d --- /dev/null +++ b/source/slang/slang-c-like-source-emitter.cpp @@ -0,0 +1,5811 @@ +// slang-c-like-source-emitter.cpp +#include "slang-c-like-source-emitter.h" + +#include "../core/slang-writer.h" +#include "ir-bind-existentials.h" +#include "ir-dce.h" +#include "ir-entry-point-uniforms.h" +#include "ir-glsl-legalize.h" + +#include "ir-link.h" +#include "ir-restructure-scoping.h" +#include "ir-specialize.h" +#include "ir-specialize-resources.h" +#include "ir-ssa.h" +#include "ir-union.h" +#include "ir-validate.h" +#include "legalize-types.h" +#include "lower-to-ir.h" +#include "mangle.h" +#include "name.h" +#include "syntax.h" +#include "type-layout.h" +#include "visitor.h" + +#include "slang-source-stream.h" +#include "slang-emit-context.h" +#include "slang-mangled-lexer.h" + +#include <assert.h> + +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<IRInst*> openInsts; + Dictionary<IRInst*, EmitAction::Level> mapInstToLevel; + List<EmitAction>* 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<IRVectorType>(type)->getElementType(), promoteHalfToFloat); + break; + + case kIROp_MatrixType: + emitGLSLTypePrefix(cast<IRMatrixType>(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<const IRConstant*>(matType->getRowCount())->value.intVal; + const auto colCount = static_cast<const IRConstant*>(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<Foo>` as `Foo[]` and `RWStructuredBuffer<Foo>` + // 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<IRSamplerStateTypeBase>(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<IRTextureType>(type)) + { + emitTextureType(texType); + return; + } + else if (auto textureSamplerType = as<IRTextureSamplerType>(type)) + { + emitTextureSamplerType(textureSamplerType); + return; + } + else if (auto imageType = as<IRGLSLImageType>(type)) + { + emitImageType(imageType); + return; + } + else if (auto structuredBufferType = as<IRHLSLStructuredBufferTypeBase>(type)) + { + emitStructuredBufferType(structuredBufferType); + return; + } + else if(auto untypedBufferType = as<IRUntypedBufferResourceType>(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<IRRateQualifiedType>(type); + emitTypeImpl(rateQualifiedType->getValueType(), declarator); + } + break; + + case kIROp_ArrayType: + emitArrayTypeImpl(cast<IRArrayType>(type), declarator); + break; + + case kIROp_UnsizedArrayType: + emitUnsizedArrayTypeImpl(cast<IRUnsizedArrayType>(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<IRType>(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<VarLayout> 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<VarLayout> 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<IRNameHintDecoration>()) + { + // The name we output will basically be: + // + // <nameHint>_<uniqueID> + // + // 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<IRLinkageDecoration>()) + { + 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<IRArrayTypeBase>(type)) + { + type = arrayType->getElementType(); + } + + // Don't allow temporaries of pointer types to be created. + if(as<IRPtrTypeBase>(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<IRUniformParameterGroupType>(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<IRHLSLStreamOutputType>(type)) + { + return true; + } + else if (as<IRHLSLPatchType>(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<IRResourceTypeBase>(type)) + { + return true; + } + else if(as<IRHLSLStructuredBufferTypeBase>(type)) + { + return true; + } + else if(as<IRUntypedBufferResourceType>(type)) + { + return true; + } + else if(as<IRSamplerStateTypeBase>(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<IRModuleInst>(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<IRPreciseDecoration>()) + 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<IRConstExprRate>(rate)) + { + switch( getTarget() ) + { + case CodeGenTarget::GLSL: + m_stream->emit("const "); + break; + + default: + break; + } + } + + if (as<IRGroupSharedRate>(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<IRVoidType>(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<IRVoidType>(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<IRTextureType>(textureArg->getDataType())) + { + emitGLSLTextureOrTextureSamplerType(baseTextureType, "sampler"); + + if (auto samplerType = as<IRSamplerStateTypeBase>(samplerArg->getDataType())) + { + if (as<IRSamplerComparisonStateType>(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<IRTextureType>(textureArg->getDataType())) + { + auto elementType = baseTextureType->getElementType(); + IRBasicType* underlyingType = nullptr; + if (auto basicType = as<IRBasicType>(elementType)) + { + underlyingType = basicType; + } + else if (auto vectorType = as<IRVectorType>(elementType)) + { + underlyingType = as<IRBasicType>(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*<T>`, 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<IRTextureType>(textureArg->getDataType())) + { + auto elementType = baseTextureType->getElementType(); + if (auto basicType = as<IRBasicType>(elementType)) + { + // A scalar result is expected + m_stream->emit(".x"); + } + else if (auto vectorType = as<IRVectorType>(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<IRVectorType>(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<IRVectorType>(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<uint, N>` because that is how the + // HLSL image subscript operations are defined. + // In contrast, the GLSL `imageAtomic*` operations + // expect `vector<int, N>` 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<IRVectorType>(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<IRLoad>(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<IRLoad>(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<IRBlock>(valueForName->parent); + if(!parentBlock) + break; + + auto parentGeneric = as<IRGeneric>(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<IRLinkageDecoration>(); + 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<T>.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<IRVectorType>(inst->getDataType())) + { + m_stream->emit("ivec"); + m_stream->emit(as<IRConstant>(vectorType->getElementCount())->value.intVal); + m_stream->emit("("); + } + else if (auto scalarType = as<IRBasicType>(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<IRSpecialize>(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<IRVoidType>(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<IRVectorType>(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<IRVectorType>(left->getDataType()); + auto rightVectorType = as<IRVectorType>(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<IRUniformParameterGroupType>(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<IRUniformParameterGroupType>(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<IRMatrixType>(inst->getOperand(0)->getDataType()) + && as<IRMatrixType>(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<IRBoolType>(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<IRBoolType>(inst->getOperand(0)->getDataType()) + && as<IRBoolType>(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<IRBoolType>(inst->getOperand(0)->getDataType()) + && as<IRBoolType>(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<IRUniformParameterGroupType>(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<IRGLSLOuterArrayDecoration>()) + { + 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<IRBasicType>(type)) + { + return irBaseType->getBaseType(); + } + else if(auto vecType = as<IRVectorType>(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<IRPtrType>(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<IRSwizzledStore>(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<IRSemanticDecoration>()) + { + m_stream->emit(" : "); + m_stream->emit(semanticDecoration->getSemanticName()); + return; + } + + if(auto layoutDecoration = inst->findDecoration<IRLayoutDecoration>()) + { + auto layout = layoutDecoration->getLayout(); + if(auto varLayout = as<VarLayout>(layout)) + { + emitIRSemantics(varLayout); + } + else if (auto entryPointLayout = as<EntryPointLayout>(layout)) + { + if(auto resultLayout = entryPointLayout->resultLayout) + { + emitIRSemantics(resultLayout); + } + } + } +} + +VarLayout* CLikeSourceEmitter::getVarLayout(IRInst* var) +{ + auto decoration = var->findDecoration<IRLayoutDecoration>(); + 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<IRLoopControlDecoration>()) + { + 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<StringLiteralExpr>(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<IntegerLiteralExpr>(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<IRPatchConstantFuncDecoration>(); + 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<MaxVertexCountAttribute>()) + { + m_stream->emit("[maxvertexcount("); + m_stream->emit(attrib->value); + m_stream->emit(")]\n"); + } + if (auto attrib = entryPointLayout->entryPoint->FindModifier<InstanceAttribute>()) + { + 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<DomainAttribute>()) + { + 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<DomainAttribute>()) + { + emitAttributeSingleString("domain", entryPoint, attrib); + } + /* [domain("partitioning")] */ + if (auto attrib = entryPoint->FindModifier<PartitioningAttribute>()) + { + emitAttributeSingleString("partitioning", entryPoint, attrib); + } + /* [outputtopology("line")] */ + if (auto attrib = entryPoint->FindModifier<OutputTopologyAttribute>()) + { + emitAttributeSingleString("outputtopology", entryPoint, attrib); + } + /* [outputcontrolpoints(4)] */ + if (auto attrib = entryPoint->FindModifier<OutputControlPointsAttribute>()) + { + emitAttributeSingleInt("outputcontrolpoints", entryPoint, attrib); + } + /* [patchconstantfunc("HSConst")] */ + if (auto attrib = entryPoint->FindModifier<PatchConstantFuncAttribute>()) + { + emitFuncDeclPatchConstantFuncAttribute(irFunc, entryPoint, attrib); + } + + break; + } + case Stage::Pixel: + { + if (irFunc->findDecoration<IREarlyDepthStencilDecoration>()) + { + 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<MaxVertexCountAttribute>()) + { + m_stream->emit("layout(max_vertices = "); + m_stream->emit(attrib->value); + m_stream->emit(") out;\n"); + } + if (auto attrib = entryPointLayout->entryPoint->FindModifier<InstanceAttribute>()) + { + 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<HLSLGeometryShaderInputPrimitiveTypeModifier>()) + { + if(as<HLSLTriangleModifier>(inputPrimitiveTypeModifier)) + { + m_stream->emit("layout(triangles) in;\n"); + } + else if(as<HLSLLineModifier>(inputPrimitiveTypeModifier)) + { + m_stream->emit("layout(lines) in;\n"); + } + else if(as<HLSLLineAdjModifier>(inputPrimitiveTypeModifier)) + { + m_stream->emit("layout(lines_adjacency) in;\n"); + } + else if(as<HLSLPointModifier>(inputPrimitiveTypeModifier)) + { + m_stream->emit("layout(points) in;\n"); + } + else if(as<HLSLTriangleAdjModifier>(inputPrimitiveTypeModifier)) + { + m_stream->emit("layout(triangles_adjacency) in;\n"); + } + } + + if(auto outputStreamType = as<HLSLStreamOutputType>(pp->type)) + { + if(as<HLSLTriangleStreamType>(outputStreamType)) + { + m_stream->emit("layout(triangle_strip) out;\n"); + } + else if(as<HLSLLineStreamType>(outputStreamType)) + { + m_stream->emit("layout(line_strip) out;\n"); + } + else if(as<HLSLPointStreamType>(outputStreamType)) + { + m_stream->emit("layout(points) out;\n"); + } + } + } + + + } + break; + case Stage::Pixel: + { + if (irFunc->findDecoration<IREarlyDepthStencilDecoration>()) + { + // 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> 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<IRLayoutDecoration>()) + { + Layout* layout = layoutDecor->getLayout(); + VarLayout* varLayout = as<VarLayout>(layout); + + if (varLayout) + { + auto var = varLayout->getVariable(); + + if (auto primTypeModifier = var->FindModifier<HLSLGeometryShaderInputPrimitiveTypeModifier>()) + { + if (as<HLSLTriangleModifier>(primTypeModifier)) + m_stream->emit("triangle "); + else if (as<HLSLPointModifier>(primTypeModifier)) + m_stream->emit("point "); + else if (as<HLSLLineModifier>(primTypeModifier)) + m_stream->emit("line "); + else if (as<HLSLLineAdjModifier>(primTypeModifier)) + m_stream->emit("lineadj "); + else if (as<HLSLTriangleAdjModifier>(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<IROutType>(type)) + { + m_stream->emit("out "); + type = outType->getValueType(); + } + else if( auto inOutType = as<IRInOutType>(type)) + { + m_stream->emit("inout "); + type = inOutType->getValueType(); + } + else if( auto refType = as<IRRefType>(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<IRGeneric>(base); + if (!baseGeneric) + return base; + + auto lastBlock = baseGeneric->getLastBlock(); + if (!lastBlock) + return base; + + auto returnInst = as<IRReturnVal>(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<IRLayoutDecoration>() ) + { + return as<EntryPointLayout>(layoutDecoration->getLayout()); + } + return nullptr; +} + +EntryPointLayout* CLikeSourceEmitter::asEntryPoint(IRFunc* func) +{ + if (auto layoutDecoration = func->findDecoration<IRLayoutDecoration>()) + { + if (auto entryPointLayout = as<EntryPointLayout>(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<IRSpecialize>(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<IRVoidType>(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<ArrayTypeLayout>(typeLayout)) + typeLayout = arrayTypeLayout->elementTypeLayout; + + if (auto matrixTypeLayout = typeLayout.as<MatrixTypeLayout>()) + { + 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<IRVectorType>(tt)) + tt = vecType->getElementType(); + if(auto vecType = as<IRMatrixType>(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<IRFormatDecoration>()) + { + 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<X>` + // 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<float4>` 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<X>` 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<IRVectorType>(elementType)) + { + if(auto intLitVal = as<IRIntLit>(elementVecType->getElementCount())) + { + vectorWidth = (Int) intLitVal->getValue(); + } + else + { + vectorWidth = 0; + } + elementType = elementVecType->getElementType(); + } + if(auto elementBasicType = as<IRBasicType>(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<IRPreciseDecoration>()) + { + 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<IRVulkanRayPayloadDecoration>()) + { + m_stream->emit("layout(location = "); + m_stream->emit(getRayPayloadLocation(varDecl)); + m_stream->emit(")\n"); + m_stream->emit("rayPayloadNV\n"); + } + if(varDecl->findDecoration<IRVulkanCallablePayloadDecoration>()) + { + m_stream->emit("layout(location = "); + m_stream->emit(getCallablePayloadLocation(varDecl)); + m_stream->emit(")\n"); + m_stream->emit("callableDataNV\n"); + } + + if(varDecl->findDecoration<IRVulkanHitAttributesDecoration>()) + { + m_stream->emit("hitAttributeNV\n"); + } + + if(varDecl->findDecoration<IRGloballyCoherentDecoration>()) + { + 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<IRTextureType>(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<IRTextureBufferType>(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<ParameterGroupTypeLayout>(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<IRArrayType>(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<IRUnsizedArrayType>(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<ParameterGroupTypeLayout>(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<IRGLSLShaderStorageBufferType>(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<IRHLSLStructuredBufferType>(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<IRHLSLByteAddressBufferType>(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<IROutTypeBase>(varType) ) + { + varType = outType->getValueType(); + } + if (as<IRVoidType>(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<T>` type. + // + // Alternatively, we could make this choice based on profile, and + // prefer `ConstantBuffer<T>` on profiles that support it and/or when + // the input code used that syntax. + // + if (auto paramBlockType = as<IRUniformParameterGroupType>(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<IRUniformParameterGroupType>(unwrapArray(varType))) + { + emitGLSLParameterGroup(varDecl, paramBlockType); + return; + } + if( auto structuredBufferType = as<IRHLSLStructuredBufferTypeBase>(unwrapArray(varType)) ) + { + emitIRStructuredBuffer_GLSL(varDecl, structuredBufferType); + return; + } + if( auto byteAddressBufferType = as<IRByteAddressBufferTypeBase>(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<IRLinkageDecoration>()) + { + 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<IRUnsizedArrayType>(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<IRStructType>(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<EmitAction>& ioActions) +{ + ComputeEmitActionsContext ctx; + ctx.moduleInst = module->getModuleInst(); + ctx.actions = &ioActions; + + for(auto inst : module->getGlobalInsts()) + { + if( as<IRType>(inst) ) + { + // Don't emit a type unless it is actually used. + continue; + } + + ensureGlobalInst(&ctx, inst, EmitAction::Level::Definition); + } +} + +void CLikeSourceEmitter::executeIREmitActions(List<EmitAction> const& actions) +{ + for(auto action : actions) + { + switch(action.level) + { + case EmitAction::Level::ForwardDeclaration: + emitIRFuncDecl(cast<IRFunc>(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<EmitAction> 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 new file mode 100644 index 000000000..3c8f3dbef --- /dev/null +++ b/source/slang/slang-c-like-source-emitter.h @@ -0,0 +1,379 @@ +// slang-c-like-source-emitter.h +#ifndef SLANG_C_LIKE_SOURCE_EMITTER_H_INCLUDED +#define SLANG_C_LIKE_SOURCE_EMITTER_H_INCLUDED + +#include "../core/basic.h" + +#include "compiler.h" + +#include "slang-emit-context.h" +#include "slang-extension-usage-tracker.h" +#include "slang-emit-precedence.h" + +#include "ir.h" +#include "ir-insts.h" +#include "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<VarLayout> fieldLayout, EmitVarChain* inChain); + + bool emitGLSLLayoutQualifier(LayoutResourceKind kind, EmitVarChain* chain); + + void emitGLSLLayoutQualifiers(RefPtr<VarLayout> 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<EmitAction>& ioActions); + + void executeIREmitActions(List<EmitAction> 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-context.cpp b/source/slang/slang-emit-context.cpp new file mode 100644 index 000000000..b330b86f9 --- /dev/null +++ b/source/slang/slang-emit-context.cpp @@ -0,0 +1,7 @@ +// 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 new file mode 100644 index 000000000..75e65feee --- /dev/null +++ b/source/slang/slang-emit-context.h @@ -0,0 +1,79 @@ +// slang-emit-context.h +#ifndef SLANG_EMIT_CONTEXT_H_INCLUDED +#define SLANG_EMIT_CONTEXT_H_INCLUDED + +#include "../core/basic.h" + +#include "compiler.h" +#include "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<ModuleDecl*> 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<IRInst*, UInt> mapIRValueToID; + Dictionary<Decl*, UInt> mapDeclToID; + + HashSet<String> irDeclsVisited; + + HashSet<String> 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<String, UInt> uniqueNameCounters; + + // Map an IR instruction to the name that we've decided + // to use for it when emitting code. + Dictionary<IRInst*, String> mapInstToName; + + Dictionary<IRInst*, UInt> mapIRValueToRayPayloadLocation; + Dictionary<IRInst*, UInt> mapIRValueToCallablePayloadLocation; +}; + +} +#endif diff --git a/source/slang/slang-emit-precedence.cpp b/source/slang/slang-emit-precedence.cpp new file mode 100644 index 000000000..43a573f20 --- /dev/null +++ b/source/slang/slang-emit-precedence.cpp @@ -0,0 +1,13 @@ +// slang-emit-precedence.cpp +#include "slang-emit-precedence.h" + +namespace Slang { + +#define SLANG_OP_INFO_EXPAND(op, name, precedence) {name, kEPrecedence_##precedence##_Left, kEPrecedence_##precedence##_Right, }, + +/* static */const EmitOpInfo EmitOpInfo::s_infos[int(EmitOp::CountOf)] = +{ + SLANG_OP_INFO(SLANG_OP_INFO_EXPAND) +}; + +} // namespace Slang diff --git a/source/slang/slang-emit-precedence.h b/source/slang/slang-emit-precedence.h new file mode 100644 index 000000000..30783f685 --- /dev/null +++ b/source/slang/slang-emit-precedence.h @@ -0,0 +1,156 @@ +// slang-emit-precedence.h +#ifndef SLANG_EMIT_PRECEDENCE_H_INCLUDED +#define SLANG_EMIT_PRECEDENCE_H_INCLUDED + +#include "../core/basic.h" + +namespace Slang +{ + +// Macros for setting up precedence +#define SLANG_PRECEDENCE_LEFT(NAME) \ + kEPrecedence_##NAME##_Left, \ + kEPrecedence_##NAME##_Right, + +#define SLANG_PRECEDENCE_RIGHT(NAME) \ + kEPrecedence_##NAME##_Right, \ + kEPrecedence_##NAME##_Left, + +#define SLANG_PRECEDENCE_NON_ASSOC(NAME) \ + kEPrecedence_##NAME##_Left, \ + kEPrecedence_##NAME##_Right = kEPrecedence_##NAME##_Left, + +#define SLANG_PRECEDENCE_EXPAND(NAME, ASSOC) SLANG_PRECEDENCE_##ASSOC(NAME) + +// x macro of precedence of types in order. +// Used because in header, need to prefix macros to avoid clashes, and this style allows for prefixing without additional clutter +#define SLANG_PRECEDENCE(x) \ + x(None, NON_ASSOC) \ + x(Comma, LEFT) \ + \ + x(General, NON_ASSOC) \ + \ + x(Assign, RIGHT) \ + \ + x(Conditional, RIGHT) \ + \ + x(Or, LEFT) \ + x(And, LEFT) \ + x(BitOr, LEFT) \ + x(BitXor, LEFT) \ + x(BitAnd, LEFT) \ + \ + x(Equality, LEFT) \ + x(Relational, LEFT) \ + x(Shift, LEFT) \ + x(Additive, LEFT) \ + x(Multiplicative, LEFT) \ + x(Prefix, RIGHT) \ + x(Postfix, LEFT) \ + x(Atomic, NON_ASSOC) + +// Precedence enum produced from the SLANG_PRECEDENCE macro +enum EPrecedence +{ + SLANG_PRECEDENCE(SLANG_PRECEDENCE_EXPAND) +}; + +// Macro for define OpInfo and an associated enum type. Order or macro parameters is +// Op, OpName, Precedence +#define SLANG_OP_INFO(x) \ + x(None, "", None) \ + \ + x(Comma, ",", Comma) \ + \ + x(General, "", General) \ + \ + x(Assign, "=", Assign) \ + x(AddAssign, "+=", Assign) \ + x(SubAssign, "-=", Assign) \ + x(MulAssign, "*=", Assign) \ + x(DivAssign, "/=", Assign) \ + x(ModAssign, "%=", Assign) \ + x(LshAssign, "<<=", Assign) \ + x(RshAssign, ">>=", Assign) \ + x(OrAssign, "|=", Assign) \ + x(AndAssign, "&=", Assign) \ + x(XorAssign, "^=", Assign) \ + \ + x(Conditional, "?:", Conditional) \ + \ + x(Or, "||", Or) \ + x(And, "&&", And) \ + x(BitOr, "|", BitOr) \ + x(BitXor, "^", BitXor) \ + x(BitAnd, "&", BitAnd) \ + \ + x(Eql, "==", Equality) \ + x(Neq, "!=", Equality) \ + \ + x(Less, "<", Relational) \ + x(Greater, ">", Relational) \ + x(Leq, "<=", Relational) \ + x(Geq, ">=", Relational) \ + \ + x(Lsh, "<<", Shift) \ + x(Rsh, ">>", Shift) \ + \ + x(Add, "+", Additive) \ + x(Sub, "-", Additive) \ + \ + x(Mul, "*", Multiplicative) \ + x(Div, "/", Multiplicative) \ + x(Mod, "%", Multiplicative) \ + \ + x(Prefix, "", Prefix) \ + x(Postfix, "", Postfix) \ + x(Atomic, "", Atomic) + +#define SLANG_OP_INFO_ENUM(op, name, precedence) op, + +enum class EmitOp +{ + SLANG_OP_INFO(SLANG_OP_INFO_ENUM) + CountOf, +}; + +// Info on an op for emit purposes +struct EmitOpInfo +{ + SLANG_FORCE_INLINE static const EmitOpInfo& get(EmitOp inOp) { return s_infos[int(inOp)]; } + + char const* op; + EPrecedence leftPrecedence; + EPrecedence rightPrecedence; + + static const EmitOpInfo s_infos[int(EmitOp::CountOf)]; +}; + +SLANG_FORCE_INLINE const EmitOpInfo& getInfo(EmitOp op) { return EmitOpInfo::s_infos[int(op)]; } + +SLANG_INLINE EmitOpInfo leftSide(EmitOpInfo const& outerPrec, EmitOpInfo const& prec) +{ + EmitOpInfo result; + result.op = nullptr; + result.leftPrecedence = outerPrec.leftPrecedence; + result.rightPrecedence = prec.leftPrecedence; + return result; +} + +SLANG_INLINE EmitOpInfo rightSide(EmitOpInfo const& prec, EmitOpInfo const& outerPrec) +{ + EmitOpInfo result; + result.op = nullptr; + result.leftPrecedence = prec.rightPrecedence; + result.rightPrecedence = outerPrec.rightPrecedence; + return result; +} + +// Precedence macros no longer needed +#undef SLANG_PRECEDENCE_EXPAND +#undef SLANG_PRECEDENCE_NON_ASSOC +#undef SLANG_PRECEDENCE_RIGHT +#undef SLANG_PRECEDENCE_LEFT + +} +#endif diff --git a/source/slang/slang-extension-usage-tracker.cpp b/source/slang/slang-extension-usage-tracker.cpp new file mode 100644 index 000000000..f679e15db --- /dev/null +++ b/source/slang/slang-extension-usage-tracker.cpp @@ -0,0 +1,44 @@ +// 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 new file mode 100644 index 000000000..32002261d --- /dev/null +++ b/source/slang/slang-extension-usage-tracker.h @@ -0,0 +1,34 @@ +// slang-extension-usage-tracker.h +#ifndef SLANG_EXTENSION_USAGE_TRACKER_H_INCLUDED +#define SLANG_EXTENSION_USAGE_TRACKER_H_INCLUDED + +#include "../core/basic.h" + +#include "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<String> m_glslExtensionsRequired; + StringBuilder m_glslExtensionRequireLines; + + ProfileVersion m_glslProfileVersion = ProfileVersion::GLSL_110; + + bool m_hasGLSLHalfExtension = false; +}; + +} +#endif diff --git a/source/slang/slang-mangled-lexer.cpp b/source/slang/slang-mangled-lexer.cpp new file mode 100644 index 000000000..f1f5ec903 --- /dev/null +++ b/source/slang/slang-mangled-lexer.cpp @@ -0,0 +1,184 @@ +// slang-mangled-lexer.cpp +#include "slang-mangled-lexer.h" + +#include <assert.h> + +namespace Slang { + +UInt MangledLexer::readCount() +{ + int c = _peek(); + if (!_isDigit((char)c)) + { + SLANG_UNEXPECTED("bad name mangling"); + UNREACHABLE_RETURN(0); + } + _next(); + + if (c == '0') + return 0; + + UInt count = 0; + for (;;) + { + count = count * 10 + c - '0'; + c = _peek(); + if (!_isDigit((char)c)) + return count; + + _next(); + } +} + +void MangledLexer::readGenericParam() +{ + switch (_peek()) + { + case 'T': + case 'C': + _next(); + break; + + case 'v': + _next(); + readType(); + break; + + default: + SLANG_UNEXPECTED("bad name mangling"); + break; + } +} + +void MangledLexer::readGenericParams() +{ + _expect("g"); + UInt paramCount = readCount(); + for (UInt pp = 0; pp < paramCount; pp++) + { + readGenericParam(); + } +} + +void MangledLexer::readType() +{ + int c = _peek(); + switch (c) + { + case 'V': + case 'b': + case 'i': + case 'u': + case 'U': + case 'h': + case 'f': + case 'd': + _next(); + break; + + case 'v': + _next(); + readSimpleIntVal(); + readType(); + break; + + default: + readNamedType(); + break; + } +} + +void MangledLexer::readVal() +{ + switch (_peek()) + { + case 'k': + _next(); + readCount(); + break; + + case 'K': + _next(); + readRawStringSegment(); + break; + + default: + readType(); + break; + } + +} + +void MangledLexer::readGenericArgs() +{ + _expect("G"); + UInt argCount = readCount(); + for (UInt aa = 0; aa < argCount; aa++) + { + readGenericArg(); + } +} + +UnownedStringSlice MangledLexer::readSimpleName() +{ + UnownedStringSlice result; + for (;;) + { + int c = _peek(); + + if (c == 'g') + { + readGenericParams(); + continue; + } + else if (c == 'G') + { + readGenericArgs(); + continue; + } + else if (c == 'X') + { + readExtensionSpec(); + continue; + } + + if (!_isDigit((char)c)) + return result; + + // Read the length part + UInt count = readCount(); + if (count > UInt(m_end - m_cursor)) + { + SLANG_UNEXPECTED("bad name mangling"); + UNREACHABLE_RETURN(result); + } + + result = UnownedStringSlice(m_cursor, m_cursor + count); + m_cursor += count; + } +} + +UnownedStringSlice MangledLexer::readRawStringSegment() +{ + // Read the length part + UInt count = readCount(); + if (count > UInt(m_end - m_cursor)) + { + SLANG_UNEXPECTED("bad name mangling"); + UNREACHABLE_RETURN(UnownedStringSlice()); + } + + auto result = UnownedStringSlice(m_cursor, m_cursor + count); + m_cursor += count; + return result; +} + +UInt MangledLexer::readParamCount() +{ + _expect("p"); + UInt count = readCount(); + _expect("p"); + return count; +} + +} // namespace Slang diff --git a/source/slang/slang-mangled-lexer.h b/source/slang/slang-mangled-lexer.h new file mode 100644 index 000000000..4890ae80f --- /dev/null +++ b/source/slang/slang-mangled-lexer.h @@ -0,0 +1,125 @@ +// slang-mangled-lexer.h +#ifndef SLANG_MANGLED_LEXER_H_INCLUDED +#define SLANG_MANGLED_LEXER_H_INCLUDED + +#include "../core/basic.h" + +#include "compiler.h" + +namespace Slang +{ + +/* A lexer like utility class used for decoding mangled names. +Expects names to be correctly constructed - any errors will cause asserts/failures */ +class MangledLexer +{ +public: + /// Reads a count at current position + UInt readCount(); + + void readGenericParam(); + + void readGenericParams(); + + SLANG_INLINE void readSimpleIntVal(); + + UnownedStringSlice readRawStringSegment(); + + void readNamedType(); + + void readType(); + + void readVal(); + + void readGenericArg() { readVal(); } + + void readGenericArgs(); + + SLANG_INLINE void readExtensionSpec(); + + UnownedStringSlice readSimpleName(); + + UInt readParamCount(); + + /// Ctor + SLANG_FORCE_INLINE MangledLexer(String const& str); + +private: + + // Call at the beginning of a mangled name, + // to strip off the main prefix + void _start() { _expect("_S"); } + + static bool _isDigit(char c) { return (c >= '0') && (c <= '9'); } + + /// Returns the character at the current position + char _peek() { return *m_cursor; } + // Returns the current character and moves to next character. + char _next() { return *m_cursor++; } + + SLANG_INLINE void _expect(char c); + + void _expect(char const* str) + { + while (char c = *str++) + _expect(c); + } + + char const* m_cursor = nullptr; + char const* m_begin = nullptr; + char const* m_end = nullptr; +}; + +// -------------------------------------------------------------------------- - +SLANG_FORCE_INLINE MangledLexer::MangledLexer(String const& str) + : m_cursor(str.begin()) + , m_begin(str.begin()) + , m_end(str.end()) +{ + _start(); +} + +// --------------------------------------------------------------------------- +SLANG_INLINE void MangledLexer::readSimpleIntVal() +{ + int c = _peek(); + if (_isDigit((char)c)) + { + _next(); + } + else + { + readVal(); + } +} + +// --------------------------------------------------------------------------- +SLANG_INLINE void MangledLexer::readNamedType() +{ + // TODO: handle types with more complicated names + readRawStringSegment(); +} + +// --------------------------------------------------------------------------- +SLANG_INLINE void MangledLexer::readExtensionSpec() +{ + _expect("X"); + readType(); +} + +// --------------------------------------------------------------------------- +SLANG_INLINE void MangledLexer::_expect(char c) +{ + if (_peek() == c) + { + _next(); + } + else + { + // ERROR! + SLANG_UNEXPECTED("mangled name error"); + } +} + +} +#endif diff --git a/source/slang/slang-source-stream.cpp b/source/slang/slang-source-stream.cpp new file mode 100644 index 000000000..e561176ae --- /dev/null +++ b/source/slang/slang-source-stream.cpp @@ -0,0 +1,396 @@ +// 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 <sstream> + +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 new file mode 100644 index 000000000..8dcd29c8d --- /dev/null +++ b/source/slang/slang-source-stream.h @@ -0,0 +1,113 @@ +// slang-source-stream.h +#ifndef SLANG_SOURCE_STREAM_H_INCLUDED +#define SLANG_SOURCE_STREAM_H_INCLUDED + +#include "../core/basic.h" + +#include "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<String, int> m_mapGLSLSourcePathToID; + int m_glslSourceIDCount = 0; + + LineDirectiveMode m_lineDirectiveMode; +}; + +} +#endif diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj index 128b0b481..f85aad7e0 100644 --- a/source/slang/slang.vcxproj +++ b/source/slang/slang.vcxproj @@ -218,7 +218,13 @@ <ClInclude Include="profile-defs.h" /> <ClInclude Include="profile.h" /> <ClInclude Include="reflection.h" /> + <ClInclude Include="slang-c-like-source-emitter.h" /> + <ClInclude Include="slang-emit-context.h" /> + <ClInclude Include="slang-emit-precedence.h" /> + <ClInclude Include="slang-extension-usage-tracker.h" /> <ClInclude Include="slang-file-system.h" /> + <ClInclude Include="slang-mangled-lexer.h" /> + <ClInclude Include="slang-source-stream.h" /> <ClInclude Include="source-loc.h" /> <ClInclude Include="stmt-defs.h" /> <ClInclude Include="syntax-base-defs.h" /> @@ -271,7 +277,13 @@ <ClCompile Include="preprocessor.cpp" /> <ClCompile Include="profile.cpp" /> <ClCompile Include="reflection.cpp" /> + <ClCompile Include="slang-c-like-source-emitter.cpp" /> + <ClCompile Include="slang-emit-context.cpp" /> + <ClCompile Include="slang-emit-precedence.cpp" /> + <ClCompile Include="slang-extension-usage-tracker.cpp" /> <ClCompile Include="slang-file-system.cpp" /> + <ClCompile Include="slang-mangled-lexer.cpp" /> + <ClCompile Include="slang-source-stream.cpp" /> <ClCompile Include="slang-stdlib.cpp" /> <ClCompile Include="slang.cpp" /> <ClCompile Include="source-loc.cpp" /> diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters index b88fbcc25..f08395da7 100644 --- a/source/slang/slang.vcxproj.filters +++ b/source/slang/slang.vcxproj.filters @@ -153,9 +153,27 @@ <ClInclude Include="reflection.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="slang-c-like-source-emitter.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="slang-emit-context.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="slang-emit-precedence.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="slang-extension-usage-tracker.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="slang-file-system.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="slang-mangled-lexer.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="slang-source-stream.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="source-loc.h"> <Filter>Header Files</Filter> </ClInclude> @@ -308,9 +326,27 @@ <ClCompile Include="reflection.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="slang-c-like-source-emitter.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="slang-emit-context.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="slang-emit-precedence.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="slang-extension-usage-tracker.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="slang-file-system.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="slang-mangled-lexer.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="slang-source-stream.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="slang-stdlib.cpp"> <Filter>Source Files</Filter> </ClCompile> diff --git a/source/slang/type-layout.cpp b/source/slang/type-layout.cpp index ba6085e0a..92f7d6af3 100644 --- a/source/slang/type-layout.cpp +++ b/source/slang/type-layout.cpp @@ -814,6 +814,19 @@ LayoutRulesFamilyImpl* getDefaultLayoutRulesFamilyForTarget(TargetRequest* targe case CodeGenTarget::SPIRVAssembly: return &kGLSLLayoutRulesFamilyImpl; + + case CodeGenTarget::CPPSource: + case CodeGenTarget::CSource: + { + // We just need to decide here what style of layout is appropriate, in terms of memory + // and binding. That in terms of the actual binding that will be injected into functions + // in the form of a BindContext. For now we'll go with HLSL layout - + // that we may want to rethink that with the use of arrays and binding VK style binding might be + // more appropriate in some ways. + + return &kHLSLLayoutRulesFamilyImpl; + } + default: return nullptr; } diff --git a/tests/cross-compile/c-simple.slang b/tests/cross-compile/c-simple.slang new file mode 100644 index 000000000..e47cd15c3 --- /dev/null +++ b/tests/cross-compile/c-simple.slang @@ -0,0 +1,16 @@ +//DISABLE_TEST:SIMPLE: -profile ps_5_0 -entry main -target c + +//Texture2D t; +//SamplerState s; + +float4 main(float2 uv) : SV_Target +{ + float4 result = 1; + + result.x += uv.x; + result.y += uv.y; + result.z -= uv.x; + result.w -= uv.y; + + return result; +} diff --git a/tools/slang-test/slang-test-main.cpp b/tools/slang-test/slang-test-main.cpp index ff449f64e..b7de3082f 100644 --- a/tools/slang-test/slang-test-main.cpp +++ b/tools/slang-test/slang-test-main.cpp @@ -516,8 +516,11 @@ static BackendFlags _getBackendFlagsForTarget(SlangCompileTarget target) switch (target) { case SLANG_TARGET_UNKNOWN: + case SLANG_HLSL: case SLANG_GLSL: + case SLANG_C_SOURCE: + case SLANG_CPP_SOURCE: { return 0; } @@ -570,6 +573,8 @@ static SlangCompileTarget _getCompileTarget(const UnownedStringSlice& name) CASE("dxil", DXIL) CASE("dxil-assembly", DXIL_ASM) CASE("dxil-asm", DXIL_ASM) + CASE("c", C_SOURCE) + CASE("cpp", CPP_SOURCE) #undef CASE return SLANG_TARGET_UNKNOWN; |
