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 /source/slang/emit.cpp | |
| 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.
Diffstat (limited to 'source/slang/emit.cpp')
| -rw-r--r-- | source/slang/emit.cpp | 6965 |
1 files changed, 45 insertions, 6920 deletions
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; |
