summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
authorjsmall-nvidia <jsmall@nvidia.com>2019-05-31 13:17:34 -0400
committerTim Foley <tfoleyNV@users.noreply.github.com>2019-05-31 10:17:34 -0700
commitb81ff3ef968d1cc4e954b31a1812b3c391d17b02 (patch)
treed9669f736c3be30c569b1c0dbd0abfaca6e85a0c /source
parentd4924f5fc67f56b60d11381bf77d21bc01eb8763 (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')
-rw-r--r--source/slang/compiler.cpp17
-rw-r--r--source/slang/compiler.h2
-rw-r--r--source/slang/emit.cpp6965
-rw-r--r--source/slang/emit.h5
-rw-r--r--source/slang/ir-glsl-legalize.cpp14
-rw-r--r--source/slang/ir-glsl-legalize.h3
-rw-r--r--source/slang/ir-link.cpp6
-rw-r--r--source/slang/lower-to-ir.h2
-rw-r--r--source/slang/options.cpp5
-rw-r--r--source/slang/slang-c-like-source-emitter.cpp5811
-rw-r--r--source/slang/slang-c-like-source-emitter.h379
-rw-r--r--source/slang/slang-emit-context.cpp7
-rw-r--r--source/slang/slang-emit-context.h79
-rw-r--r--source/slang/slang-emit-precedence.cpp13
-rw-r--r--source/slang/slang-emit-precedence.h156
-rw-r--r--source/slang/slang-extension-usage-tracker.cpp44
-rw-r--r--source/slang/slang-extension-usage-tracker.h34
-rw-r--r--source/slang/slang-mangled-lexer.cpp184
-rw-r--r--source/slang/slang-mangled-lexer.h125
-rw-r--r--source/slang/slang-source-stream.cpp396
-rw-r--r--source/slang/slang-source-stream.h113
-rw-r--r--source/slang/slang.vcxproj12
-rw-r--r--source/slang/slang.vcxproj.filters36
-rw-r--r--source/slang/type-layout.cpp13
24 files changed, 7483 insertions, 6938 deletions
diff --git a/source/slang/compiler.cpp b/source/slang/compiler.cpp
index 83a579345..22c1d4cd8 100644
--- a/source/slang/compiler.cpp
+++ b/source/slang/compiler.cpp
@@ -365,6 +365,12 @@ namespace Slang
{
return PassThroughMode::dxc;
}
+ case CodeGenTarget::CPPSource:
+ case CodeGenTarget::CSource:
+ {
+ // Don't need an external compiler to output C and C++ code
+ return PassThroughMode::None;
+ }
default: break;
}
@@ -1051,6 +1057,17 @@ SlangResult dissassembleDXILUsingDXC(
}
break;
+ case CodeGenTarget::CPPSource:
+ case CodeGenTarget::CSource:
+ {
+ return emitEntryPoint(
+ compileRequest,
+ entryPoint,
+ target,
+ targetReq);
+ }
+ break;
+
#if SLANG_ENABLE_DXBC_SUPPORT
case CodeGenTarget::DXBytecode:
{
diff --git a/source/slang/compiler.h b/source/slang/compiler.h
index 9b7e06be0..5d9e47aee 100644
--- a/source/slang/compiler.h
+++ b/source/slang/compiler.h
@@ -55,6 +55,8 @@ namespace Slang
DXBytecodeAssembly = SLANG_DXBC_ASM,
DXIL = SLANG_DXIL,
DXILAssembly = SLANG_DXIL_ASM,
+ CSource = SLANG_C_SOURCE,
+ CPPSource = SLANG_CPP_SOURCE,
};
enum class ContainerFormat
diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp
index 0d5e51747..d90415d89 100644
--- a/source/slang/emit.cpp
+++ b/source/slang/emit.cpp
@@ -23,6907 +23,21 @@
#include "type-layout.h"
#include "visitor.h"
-#include <assert.h>
+#include "slang-source-stream.h"
+#include "slang-emit-context.h"
-// Note: using C++ stdio just to get a locale-independent
-// way to format floating-point values.
-//
-// TODO: Go ahead and implement teh Dragon4 algorithm so
-// that we can print floating-point values to arbitrary
-// precision as needed.
-#include <sstream>
+#include "slang-c-like-source-emitter.h"
-#ifdef _WIN32
-#include <d3dcompiler.h>
-#pragma warning(disable:4996)
-#endif
+#include <assert.h>
namespace Slang {
-struct ExtensionUsageTracker
-{
- // Record the GLSL extnsions we have already emitted a `#extension` for
- HashSet<String> glslExtensionsRequired;
- StringBuilder glslExtensionRequireLines;
-
- ProfileVersion profileVersion = ProfileVersion::GLSL_110;
-
- bool hasHalfExtension = false;
-};
-
-void requireGLSLExtension(
- ExtensionUsageTracker* tracker,
- String const& name)
-{
- if (tracker->glslExtensionsRequired.Contains(name))
- return;
-
- StringBuilder& sb = tracker->glslExtensionRequireLines;
-
- sb.append("#extension ");
- sb.append(name);
- sb.append(" : require\n");
-
- tracker->glslExtensionsRequired.Add(name);
-}
-
-void requireGLSLVersionImpl(
- ExtensionUsageTracker* tracker,
- ProfileVersion version)
-{
- // Check if this profile is newer
- if ((UInt)version > (UInt)tracker->profileVersion)
- {
- tracker->profileVersion = version;
- }
-}
-
-void requireGLSLHalfExtension(ExtensionUsageTracker* tracker)
-{
- if (!tracker->hasHalfExtension)
- {
- // https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_shader_16bit_storage.txt
- requireGLSLExtension(tracker, "GL_EXT_shader_16bit_storage");
-
- // https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_shader_explicit_arithmetic_types.txt
- requireGLSLExtension(tracker, "GL_EXT_shader_explicit_arithmetic_types");
-
- tracker->hasHalfExtension = true;
- }
-}
-
-
-// Shared state for an entire emit session
-struct SharedEmitContext
+enum class BuiltInCOp
{
- BackEndCompileRequest* compileRequest = nullptr;
-
- // The entry point we are being asked to compile
- EntryPoint* entryPoint;
-
- // The layout for the entry point
- EntryPointLayout* entryPointLayout;
-
- // The target language we want to generate code for
- CodeGenTarget target;
-
- // The final code generation target
- //
- // For example, `target` might be `GLSL`, while `finalTarget` might be `SPIRV`
- CodeGenTarget finalTarget;
-
- // The string of code we've built so far
- StringBuilder sb;
-
- // Current source position for tracking purposes...
- HumaneSourceLoc loc;
- HumaneSourceLoc nextSourceLocation;
- bool needToUpdateSourceLocation;
-
- // For GLSL output, we can't emit traditional `#line` directives
- // with a file path in them, so we maintain a map that associates
- // each path with a unique integer, and then we output those
- // instead.
- Dictionary<String, int> mapGLSLSourcePathToID;
- int glslSourceIDCount = 0;
-
- // We only want to emit each `import`ed module one time, so
- // we maintain a set of already-emitted modules.
- HashSet<ModuleDecl*> modulesAlreadyEmitted;
-
- // We track the original global-scope layout so that we can
- // find layout information for `import`ed parameters.
- //
- // TODO: This will probably change if we represent imports
- // explicitly in the layout data.
- StructTypeLayout* globalStructLayout;
-
- ProgramLayout* programLayout;
-
- ModuleDecl* program;
-
- ExtensionUsageTracker extensionUsageTracker;
-
- UInt uniqueIDCounter = 1;
- Dictionary<IRInst*, UInt> mapIRValueToID;
- Dictionary<Decl*, UInt> mapDeclToID;
-
- HashSet<String> irDeclsVisited;
-
- HashSet<String> irTupleTypes;
-
- // The "effective" profile that is being used to emit code,
- // combining information from the target and entry point.
- Profile effectiveProfile;
-
-
- // Are we at the start of a line, so that we should indent
- // before writing any other text?
- bool isAtStartOfLine = true;
-
- // How far are we indented?
- Int indentLevel = 0;
-
- // Map a string name to the number of times we have seen this
- // name used so far during code emission.
- Dictionary<String, UInt> uniqueNameCounters;
-
- // Map an IR instruction to the name that we've decided
- // to use for it when emitting code.
- Dictionary<IRInst*, String> mapInstToName;
-
- DiagnosticSink* getSink() { return compileRequest->getSink(); }
-
- Dictionary<IRInst*, UInt> mapIRValueToRayPayloadLocation;
- Dictionary<IRInst*, UInt> mapIRValueToCallablePayloadLocation;
+ Splat, //< Splat a single value to all values of a vector or matrix type
+ Init, //< Initialize with parameters (must match the type)
};
-struct EmitContext
-{
- // The shared context that is in effect
- SharedEmitContext* shared;
-
- DiagnosticSink* getSink() { return shared->getSink(); }
-
- LineDirectiveMode getLineDirectiveMode() { return shared->compileRequest->getLineDirectiveMode(); }
- SourceManager* getSourceManager() { return shared->compileRequest->getSourceManager(); }
- void noteInternalErrorLoc(SourceLoc loc) { return getSink()->noteInternalErrorLoc(loc); }
-};
-
-//
-
-enum EPrecedence
-{
-#define LEFT(NAME) \
- kEPrecedence_##NAME##_Left, \
- kEPrecedence_##NAME##_Right
-
-#define RIGHT(NAME) \
- kEPrecedence_##NAME##_Right, \
- kEPrecedence_##NAME##_Left
-
-#define NONASSOC(NAME) \
- kEPrecedence_##NAME##_Left, \
- kEPrecedence_##NAME##_Right = kEPrecedence_##NAME##_Left
-
- NONASSOC(None),
- LEFT(Comma),
-
- NONASSOC(General),
-
- RIGHT(Assign),
-
- RIGHT(Conditional),
-
- LEFT(Or),
- LEFT(And),
- LEFT(BitOr),
- LEFT(BitXor),
- LEFT(BitAnd),
-
- LEFT(Equality),
- LEFT(Relational),
- LEFT(Shift),
- LEFT(Additive),
- LEFT(Multiplicative),
- RIGHT(Prefix),
- LEFT(Postfix),
- NONASSOC(Atomic),
-};
-
-// Info on an op for emit purposes
-struct EOpInfo
-{
- char const* op;
- EPrecedence leftPrecedence;
- EPrecedence rightPrecedence;
-};
-
-#define OP(NAME, TEXT, PREC) \
-static const EOpInfo kEOp_##NAME = { TEXT, kEPrecedence_##PREC##_Left, kEPrecedence_##PREC##_Right, }
-
-OP(None, "", None);
-
-OP(Comma, ",", Comma);
-
-OP(General, "", General);
-
-OP(Assign, "=", Assign);
-OP(AddAssign, "+=", Assign);
-OP(SubAssign, "-=", Assign);
-OP(MulAssign, "*=", Assign);
-OP(DivAssign, "/=", Assign);
-OP(ModAssign, "%=", Assign);
-OP(LshAssign, "<<=", Assign);
-OP(RshAssign, ">>=", Assign);
-OP(OrAssign, "|=", Assign);
-OP(AndAssign, "&=", Assign);
-OP(XorAssign, "^=", Assign);
-
-OP(Conditional, "?:", Conditional);
-
-OP(Or, "||", Or);
-OP(And, "&&", And);
-OP(BitOr, "|", BitOr);
-OP(BitXor, "^", BitXor);
-OP(BitAnd, "&", BitAnd);
-
-OP(Eql, "==", Equality);
-OP(Neq, "!=", Equality);
-
-OP(Less, "<", Relational);
-OP(Greater, ">", Relational);
-OP(Leq, "<=", Relational);
-OP(Geq, ">=", Relational);
-
-OP(Lsh, "<<", Shift);
-OP(Rsh, ">>", Shift);
-
-OP(Add, "+", Additive);
-OP(Sub, "-", Additive);
-
-OP(Mul, "*", Multiplicative);
-OP(Div, "/", Multiplicative);
-OP(Mod, "%", Multiplicative);
-
-OP(Prefix, "", Prefix);
-OP(Postfix, "", Postfix);
-OP(Atomic, "", Atomic);
-
-#undef OP
-
-// Table to allow data-driven lookup of an op based on its
-// name (to assist when outputting unchecked operator calls)
-static EOpInfo const* const kInfixOpInfos[] =
-{
- &kEOp_Comma,
- &kEOp_Assign,
- &kEOp_AddAssign,
- &kEOp_SubAssign,
- &kEOp_MulAssign,
- &kEOp_DivAssign,
- &kEOp_ModAssign,
- &kEOp_LshAssign,
- &kEOp_RshAssign,
- &kEOp_OrAssign,
- &kEOp_AndAssign,
- &kEOp_XorAssign,
- &kEOp_Or,
- &kEOp_And,
- &kEOp_BitOr,
- &kEOp_BitXor,
- &kEOp_BitAnd,
- &kEOp_Eql,
- &kEOp_Neq,
- &kEOp_Less,
- &kEOp_Greater,
- &kEOp_Leq,
- &kEOp_Geq,
- &kEOp_Lsh,
- &kEOp_Rsh,
- &kEOp_Add,
- &kEOp_Sub,
- &kEOp_Mul,
- &kEOp_Div,
- &kEOp_Mod,
-};
-
-//
-
-// represents a declarator for use in emitting types
-struct EDeclarator
-{
- enum class Flavor
- {
- name,
- Array,
- UnsizedArray,
- };
- Flavor flavor;
- EDeclarator* next = nullptr;
-
- // Used for `Flavor::name`
- Name* name;
- SourceLoc loc;
-
- // Used for `Flavor::Array`
- IRInst* elementCount;
-};
-
-struct EmitVisitor
-{
- EmitContext* context;
- EmitVisitor(EmitContext* context)
- : context(context)
- {}
-
- // Low-level emit logic
-
- void emitRawTextSpan(char const* textBegin, char const* textEnd)
- {
- // TODO(tfoley): Need to make "corelib" not use `int` for pointer-sized things...
- auto len = textEnd - textBegin;
- context->shared->sb.Append(textBegin, len);
- }
-
- void emitRawText(char const* text)
- {
- emitRawTextSpan(text, text + strlen(text));
- }
-
- void emitTextSpan(char const* textBegin, char const* textEnd)
- {
- // Don't change anything given an empty string
- if(textBegin == textEnd)
- return;
-
- // If the source location has changed in a way that required update,
- // do it now!
- flushSourceLocationChange();
-
- // Note: we don't want to emit indentation on a line that is empty.
- // The logic in `Emit(textBegin, textEnd)` below will have broken
- // the text into lines, so we can simply check if a line consists
- // of just a newline.
- if(context->shared->isAtStartOfLine && *textBegin != '\n')
- {
- // We are about to emit text (other than a newline)
- // at the start of a line, so we will emit the proper
- // amount of indentation to keep things looking nice.
- context->shared->isAtStartOfLine = false;
- for(Int ii = 0; ii < context->shared->indentLevel; ++ii)
- {
- char const* indentString = " ";
- size_t indentStringSize = strlen(indentString);
- emitRawTextSpan(indentString, indentString + indentStringSize);
-
- // We will also update our tracking location, just in
- // case other logic needs it.
- //
- // TODO: We may need to have a switch that controls whether
- // we are in "pretty-printing" mode or "follow the locations
- // in the original code" mode.
- context->shared->loc.column += indentStringSize;
- }
- }
-
- // Emit the raw text
- emitRawTextSpan(textBegin, textEnd);
-
- // Update our logical position
- auto len = int(textEnd - textBegin);
- context->shared->loc.column += len;
- }
-
- void indent()
- {
- context->shared->indentLevel++;
- }
-
- void dedent()
- {
- context->shared->indentLevel--;
- }
-
- void Emit(char const* textBegin, char const* textEnd)
- {
- char const* spanBegin = textBegin;
- char const* spanEnd = spanBegin;
- for(;;)
- {
- if(spanEnd == textEnd)
- {
- // We have a whole range of text waiting to be flushed
- emitTextSpan(spanBegin, spanEnd);
- return;
- }
-
- auto c = *spanEnd++;
-
- if( c == '\n' )
- {
- // At the end of a line, we need to update our tracking
- // information on code positions
- emitTextSpan(spanBegin, spanEnd);
- context->shared->loc.line++;
- context->shared->loc.column = 1;
- context->shared->isAtStartOfLine = true;
-
- // Start a new span for emit purposes
- spanBegin = spanEnd;
- }
- }
- }
-
- void Emit(char const* text)
- {
- Emit(text, text + strlen(text));
- }
-
- void emit(String const& text)
- {
- Emit(text.begin(), text.end());
- }
-
- void emit(UnownedStringSlice const& text)
- {
- Emit(text.begin(), text.end());
- }
-
-
- void emit(Name* name)
- {
- emit(getText(name));
- }
-
- void emit(NameLoc const& nameAndLoc)
- {
- advanceToSourceLocation(nameAndLoc.loc);
- emit(getText(nameAndLoc.name));
- }
-
- void emitName(
- Name* name,
- SourceLoc const& loc)
- {
- advanceToSourceLocation(loc);
- emit(name);
- }
-
- void emitName(NameLoc const& nameAndLoc)
- {
- emitName(nameAndLoc.name, nameAndLoc.loc);
- }
-
- void emitName(Name* name)
- {
- emitName(name, SourceLoc());
- }
-
- void Emit(IntegerLiteralValue value)
- {
- char buffer[32];
- sprintf(buffer, "%lld", (long long int)value);
- Emit(buffer);
- }
-
-
- void Emit(UInt value)
- {
- char buffer[32];
- sprintf(buffer, "%llu", (unsigned long long)(value));
- Emit(buffer);
- }
-
- void Emit(int value)
- {
- char buffer[16];
- sprintf(buffer, "%d", value);
- Emit(buffer);
- }
-
- void Emit(double value)
- {
- // There are a few different requirements here that we need to deal with:
- //
- // 1) We need to print something that is valid syntax in the target language
- // (this means that hex floats are off the table for now)
- //
- // 2) We need our printing to be independent of the current global locale in C,
- // so that we don't depend on the application leaving it as the default,
- // and we also don't revert any changes they make.
- // (this means that `sprintf` and friends are off the table)
- //
- // 3) We need to be sure that floating-point literals specified by the user will
- // "round-trip" and turn into the same value when parsed back in. This means
- // that we need to print a reasonable number of digits of precision.
- //
- // For right now, the easiest option that can balance these is to use
- // the C++ standard library `iostream`s, because they support an explicit locale,
- // and can (hopefully) print floating-point numbers accurately.
- //
- // Eventually, the right move here would be to implement proper floating-point
- // number formatting ourselves, but that would require extensive testing to
- // make sure we get it right.
-
- std::ostringstream stream;
- stream.imbue(std::locale::classic());
- stream.setf(std::ios::fixed,std::ios::floatfield);
- stream.precision(20);
- stream << value;
-
- Emit(stream.str().c_str());
- }
-
-
- // Emit a `#line` directive to the output.
- // Doesn't update state of source-location tracking.
- void emitLineDirective(
- HumaneSourceLoc const& sourceLocation)
- {
- emitRawText("\n#line ");
-
- char buffer[16];
- sprintf(buffer, "%llu", (unsigned long long)sourceLocation.line);
- emitRawText(buffer);
-
- // Only emit the path part of a `#line` directive if needed
- if(sourceLocation.pathInfo.foundPath != context->shared->loc.pathInfo.foundPath)
- {
- emitRawText(" ");
-
- bool shouldUseGLSLStyleLineDirective = false;
-
- auto mode = context->getLineDirectiveMode();
- switch (mode)
- {
- case LineDirectiveMode::None:
- SLANG_UNEXPECTED("should not be trying to emit '#line' directive");
- return;
-
- case LineDirectiveMode::Default:
- default:
- // To try to make the default behavior reasonable, we will
- // always use C-style line directives (to give the user
- // good source locations on error messages from downstream
- // compilers) *unless* they requested raw GLSL as the
- // output (in which case we want to maximize compatibility
- // with downstream tools).
- if (context->shared->finalTarget == CodeGenTarget::GLSL)
- {
- shouldUseGLSLStyleLineDirective = true;
- }
- break;
-
- case LineDirectiveMode::Standard:
- break;
-
- case LineDirectiveMode::GLSL:
- shouldUseGLSLStyleLineDirective = true;
- break;
- }
-
- if(shouldUseGLSLStyleLineDirective)
- {
- auto path = sourceLocation.pathInfo.foundPath;
-
- // GLSL doesn't support the traditional form of a `#line` directive without
- // an extension. Rather than depend on that extension we will output
- // a directive in the traditional GLSL fashion.
- //
- // TODO: Add some kind of configuration where we require the appropriate
- // extension and then emit a traditional line directive.
-
- int id = 0;
- if(!context->shared->mapGLSLSourcePathToID.TryGetValue(path, id))
- {
- id = context->shared->glslSourceIDCount++;
- context->shared->mapGLSLSourcePathToID.Add(path, id);
- }
-
- sprintf(buffer, "%d", id);
- emitRawText(buffer);
- }
- else
- {
- // The simple case is to emit the path for the current source
- // location. We need to be a little bit careful with this,
- // because the path might include backslash characters if we
- // are on Windows, and we want to canonicalize those over
- // to forward slashes.
- //
- // TODO: Canonicalization like this should be done centrally
- // in a module that tracks source files.
-
- emitRawText("\"");
- const auto& path = sourceLocation.pathInfo.foundPath;
- for(auto c : path)
- {
- char charBuffer[] = { c, 0 };
- switch(c)
- {
- default:
- emitRawText(charBuffer);
- break;
-
- // The incoming file path might use `/` and/or `\\` as
- // a directory separator. We want to canonicalize this.
- //
- // TODO: should probably canonicalize paths to not use backslash somewhere else
- // in the compilation pipeline...
- case '\\':
- emitRawText("/");
- break;
- }
- }
- emitRawText("\"");
- }
- }
-
- emitRawText("\n");
- }
-
- // Emit a `#line` directive to the output, and also
- // ensure that source location tracking information
- // is correct based on the directive we just output.
- void emitLineDirectiveAndUpdateSourceLocation(
- HumaneSourceLoc const& sourceLocation)
- {
- emitLineDirective(sourceLocation);
-
- HumaneSourceLoc newLoc = sourceLocation;
- newLoc.column = 1;
-
- context->shared->loc = newLoc;
- }
-
- void emitLineDirectiveIfNeeded(
- HumaneSourceLoc const& sourceLocation)
- {
- // Don't do any of this work if the user has requested that we
- // not emit line directives.
- auto mode = context->getLineDirectiveMode();
- switch(mode)
- {
- case LineDirectiveMode::None:
- return;
-
- case LineDirectiveMode::Default:
- default:
- break;
- }
-
- // Ignore invalid source locations
- if(sourceLocation.line <= 0)
- return;
-
- // If we are currently emitting code at a source location with
- // a differnet file or line, *or* if the source location is
- // somehow later on the line than what we want to emit,
- // then we need to emit a new `#line` directive.
- if(sourceLocation.pathInfo.foundPath != context->shared->loc.pathInfo.foundPath
- || sourceLocation.line != context->shared->loc.line
- || sourceLocation.column < context->shared->loc.column)
- {
- // Special case: if we are in the same file, and within a small number
- // of lines of the target location, then go ahead and output newlines
- // to get us caught up.
- enum { kSmallLineCount = 3 };
- auto lineDiff = sourceLocation.line - context->shared->loc.line;
- if(sourceLocation.pathInfo.foundPath == context->shared->loc.pathInfo.foundPath
- && sourceLocation.line > context->shared->loc.line
- && lineDiff <= kSmallLineCount)
- {
- for(int ii = 0; ii < lineDiff; ++ii )
- {
- Emit("\n");
- }
- SLANG_RELEASE_ASSERT(sourceLocation.line == context->shared->loc.line);
- }
- else
- {
- // Go ahead and output a `#line` directive to get us caught up
- emitLineDirectiveAndUpdateSourceLocation(sourceLocation);
- }
- }
- }
-
- void advanceToSourceLocation(
- HumaneSourceLoc const& sourceLocation)
- {
- // Skip invalid locations
- if(sourceLocation.line <= 0)
- return;
-
- context->shared->needToUpdateSourceLocation = true;
- context->shared->nextSourceLocation = sourceLocation;
- }
-
- SourceManager* getSourceManager()
- {
- return context->getSourceManager();
- }
-
- void advanceToSourceLocation(
- SourceLoc const& sourceLocation)
- {
- advanceToSourceLocation(getSourceManager()->getHumaneLoc(sourceLocation));
- }
-
- void flushSourceLocationChange()
- {
- if(!context->shared->needToUpdateSourceLocation)
- return;
-
- // Note: the order matters here, because trying to update
- // the source location may involve outputting text that
- // advances the location, and outputting text is what
- // triggers this flush operation.
- context->shared->needToUpdateSourceLocation = false;
- emitLineDirectiveIfNeeded(context->shared->nextSourceLocation);
- }
-
- DiagnosticSink* getSink()
- {
- return context->getSink();
- }
-
- //
- // Types
- //
-
- void EmitDeclarator(EDeclarator* declarator)
- {
- if (!declarator) return;
-
- Emit(" ");
-
- switch (declarator->flavor)
- {
- case EDeclarator::Flavor::name:
- emitName(declarator->name, declarator->loc);
- break;
-
- case EDeclarator::Flavor::Array:
- EmitDeclarator(declarator->next);
- Emit("[");
- if(auto elementCount = declarator->elementCount)
- {
- EmitVal(elementCount, kEOp_General);
- }
- Emit("]");
- break;
-
- case EDeclarator::Flavor::UnsizedArray:
- EmitDeclarator(declarator->next);
- Emit("[]");
- break;
-
- default:
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unknown declarator flavor");
- break;
- }
- }
-
- void emitGLSLTypePrefix(
- IRType* type,
- bool promoteHalfToFloat = false)
- {
- switch (type->op)
- {
- case kIROp_FloatType:
- // no prefix
- break;
-
- case kIROp_Int8Type: Emit("i8"); break;
- case kIROp_Int16Type: Emit("i16"); break;
- case kIROp_IntType: Emit("i"); break;
- case kIROp_Int64Type: Emit("i64"); break;
-
- case kIROp_UInt8Type: Emit("u8"); break;
- case kIROp_UInt16Type: Emit("u16"); break;
- case kIROp_UIntType: Emit("u"); break;
- case kIROp_UInt64Type: Emit("u64"); break;
-
- case kIROp_BoolType: Emit("b"); break;
-
- case kIROp_HalfType:
- {
- _requireHalf();
- if (promoteHalfToFloat)
- {
- // no prefix
- }
- else
- {
- Emit("f16");
- }
- break;
- }
- case kIROp_DoubleType: Emit("d"); break;
-
- case kIROp_VectorType:
- emitGLSLTypePrefix(cast<IRVectorType>(type)->getElementType(), promoteHalfToFloat);
- break;
-
- case kIROp_MatrixType:
- emitGLSLTypePrefix(cast<IRMatrixType>(type)->getElementType(), promoteHalfToFloat);
- break;
-
- default:
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled GLSL type prefix");
- break;
- }
- }
-
- void emitHLSLTextureType(
- IRTextureTypeBase* texType)
- {
- switch(texType->getAccess())
- {
- case SLANG_RESOURCE_ACCESS_READ:
- break;
-
- case SLANG_RESOURCE_ACCESS_READ_WRITE:
- Emit("RW");
- break;
-
- case SLANG_RESOURCE_ACCESS_RASTER_ORDERED:
- Emit("RasterizerOrdered");
- break;
-
- case SLANG_RESOURCE_ACCESS_APPEND:
- Emit("Append");
- break;
-
- case SLANG_RESOURCE_ACCESS_CONSUME:
- Emit("Consume");
- break;
-
- default:
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled resource access mode");
- break;
- }
-
- switch (texType->GetBaseShape())
- {
- case TextureFlavor::Shape::Shape1D: Emit("Texture1D"); break;
- case TextureFlavor::Shape::Shape2D: Emit("Texture2D"); break;
- case TextureFlavor::Shape::Shape3D: Emit("Texture3D"); break;
- case TextureFlavor::Shape::ShapeCube: Emit("TextureCube"); break;
- case TextureFlavor::Shape::ShapeBuffer: Emit("Buffer"); break;
- default:
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled resource shape");
- break;
- }
-
- if (texType->isMultisample())
- {
- Emit("MS");
- }
- if (texType->isArray())
- {
- Emit("Array");
- }
- Emit("<");
- EmitType(texType->getElementType());
- Emit(" >");
- }
-
- void emitGLSLTextureOrTextureSamplerType(
- IRTextureTypeBase* type,
- char const* baseName)
- {
- if (type->getElementType()->op == kIROp_HalfType)
- {
- // Texture access is always as float types if half is specified
-
- }
- else
- {
- emitGLSLTypePrefix(type->getElementType(), true);
- }
-
- Emit(baseName);
- switch (type->GetBaseShape())
- {
- case TextureFlavor::Shape::Shape1D: Emit("1D"); break;
- case TextureFlavor::Shape::Shape2D: Emit("2D"); break;
- case TextureFlavor::Shape::Shape3D: Emit("3D"); break;
- case TextureFlavor::Shape::ShapeCube: Emit("Cube"); break;
- case TextureFlavor::Shape::ShapeBuffer: Emit("Buffer"); break;
- default:
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled resource shape");
- break;
- }
-
- if (type->isMultisample())
- {
- Emit("MS");
- }
- if (type->isArray())
- {
- Emit("Array");
- }
- }
-
- void emitGLSLTextureType(
- IRTextureType* texType)
- {
- switch(texType->getAccess())
- {
- case SLANG_RESOURCE_ACCESS_READ_WRITE:
- case SLANG_RESOURCE_ACCESS_RASTER_ORDERED:
- emitGLSLTextureOrTextureSamplerType(texType, "image");
- break;
-
- default:
- emitGLSLTextureOrTextureSamplerType(texType, "texture");
- break;
- }
- }
-
- void emitGLSLTextureSamplerType(
- IRTextureSamplerType* type)
- {
- emitGLSLTextureOrTextureSamplerType(type, "sampler");
- }
-
- void emitGLSLImageType(
- IRGLSLImageType* type)
- {
- emitGLSLTextureOrTextureSamplerType(type, "image");
- }
-
- void emitTextureType(
- IRTextureType* texType)
- {
- switch(context->shared->target)
- {
- case CodeGenTarget::HLSL:
- emitHLSLTextureType(texType);
- break;
-
- case CodeGenTarget::GLSL:
- emitGLSLTextureType(texType);
- break;
-
- default:
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target");
- break;
- }
- }
-
- void emitTextureSamplerType(
- IRTextureSamplerType* type)
- {
- switch(context->shared->target)
- {
- case CodeGenTarget::GLSL:
- emitGLSLTextureSamplerType(type);
- break;
-
- default:
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "this target should see combined texture-sampler types");
- break;
- }
- }
-
- void emitImageType(
- IRGLSLImageType* type)
- {
- switch(context->shared->target)
- {
- case CodeGenTarget::HLSL:
- emitHLSLTextureType(type);
- break;
-
- case CodeGenTarget::GLSL:
- emitGLSLImageType(type);
- break;
-
- default:
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "this target should see GLSL image types");
- break;
- }
- }
-
- void emitVectorTypeName(IRType* elementType, IRIntegerValue elementCount)
- {
- switch(context->shared->target)
- {
- case CodeGenTarget::GLSL:
- case CodeGenTarget::GLSL_Vulkan:
- case CodeGenTarget::GLSL_Vulkan_OneDesc:
- {
- if (elementCount > 1)
- {
- emitGLSLTypePrefix(elementType);
- Emit("vec");
- emit(elementCount);
- }
- else
- {
- emitSimpleTypeImpl(elementType);
- }
- }
- break;
-
- case CodeGenTarget::HLSL:
- // TODO(tfoley): should really emit these with sugar
- Emit("vector<");
- EmitType(elementType);
- Emit(",");
- emit(elementCount);
- Emit(">");
- break;
-
- default:
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target");
- break;
- }
- }
-
- void emitVectorTypeImpl(IRVectorType* vecType)
- {
- IRInst* elementCountInst = vecType->getElementCount();
- if (elementCountInst->op != kIROp_IntLit)
- {
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "Expecting an integral size for vector size");
- return;
- }
-
- const IRConstant* irConst = (const IRConstant*)elementCountInst;
- const IRIntegerValue elementCount = irConst->value.intVal;
- if (elementCount <= 0)
- {
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "Vector size must be greater than 0");
- return;
- }
-
- auto* elementType = vecType->getElementType();
-
- emitVectorTypeName(elementType, elementCount);
- }
-
- void emitMatrixTypeImpl(IRMatrixType* matType)
- {
- switch(context->shared->target)
- {
- case CodeGenTarget::GLSL:
- case CodeGenTarget::GLSL_Vulkan:
- case CodeGenTarget::GLSL_Vulkan_OneDesc:
- {
- emitGLSLTypePrefix(matType->getElementType());
- Emit("mat");
- EmitVal(matType->getRowCount(), kEOp_General);
- // TODO(tfoley): only emit the next bit
- // for non-square matrix
- Emit("x");
- EmitVal(matType->getColumnCount(), kEOp_General);
- }
- break;
-
- case CodeGenTarget::HLSL:
- // TODO(tfoley): should really emit these with sugar
- Emit("matrix<");
- EmitType(matType->getElementType());
- Emit(",");
- EmitVal(matType->getRowCount(), kEOp_General);
- Emit(",");
- EmitVal(matType->getColumnCount(), kEOp_General);
- Emit("> ");
- break;
-
- default:
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target");
- break;
- }
- }
-
- void emitSamplerStateType(IRSamplerStateTypeBase* samplerStateType)
- {
- switch(context->shared->target)
- {
- case CodeGenTarget::HLSL:
- default:
- switch (samplerStateType->op)
- {
- case kIROp_SamplerStateType: Emit("SamplerState"); break;
- case kIROp_SamplerComparisonStateType: Emit("SamplerComparisonState"); break;
- default:
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled sampler state flavor");
- break;
- }
- break;
-
- case CodeGenTarget::GLSL:
- switch (samplerStateType->op)
- {
- case kIROp_SamplerStateType: Emit("sampler"); break;
- case kIROp_SamplerComparisonStateType: Emit("samplerShadow"); break;
- default:
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled sampler state flavor");
- break;
- }
- break;
- break;
- }
- }
-
- void emitStructuredBufferType(IRHLSLStructuredBufferTypeBase* type)
- {
- switch(context->shared->target)
- {
- case CodeGenTarget::HLSL:
- default:
- {
- switch (type->op)
- {
- case kIROp_HLSLStructuredBufferType: Emit("StructuredBuffer"); break;
- case kIROp_HLSLRWStructuredBufferType: Emit("RWStructuredBuffer"); break;
- case kIROp_HLSLRasterizerOrderedStructuredBufferType: Emit("RasterizerOrderedStructuredBuffer"); break;
- case kIROp_HLSLAppendStructuredBufferType: Emit("AppendStructuredBuffer"); break;
- case kIROp_HLSLConsumeStructuredBufferType: Emit("ConsumeStructuredBuffer"); break;
-
- default:
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled structured buffer type");
- break;
- }
-
- Emit("<");
- EmitType(type->getElementType());
- Emit(" >");
- }
- break;
-
- case CodeGenTarget::GLSL:
- // TODO: We desugar global variables with structured-buffer type into GLSL
- // `buffer` declarations, but we don't currently handle structured-buffer types
- // in other contexts (e.g., as function parameters). The simplest thing to do
- // would be to emit a `StructuredBuffer<Foo>` as `Foo[]` and `RWStructuredBuffer<Foo>`
- // as `in out Foo[]`, but that is starting to get into the realm of transformations
- // that should really be handled during legalization, rather than during emission.
- //
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "structured buffer type used unexpectedly");
- break;
- }
- }
-
- void emitUntypedBufferType(IRUntypedBufferResourceType* type)
- {
- switch(context->shared->target)
- {
- case CodeGenTarget::HLSL:
- default:
- {
- switch (type->op)
- {
- case kIROp_HLSLByteAddressBufferType: Emit("ByteAddressBuffer"); break;
- case kIROp_HLSLRWByteAddressBufferType: Emit("RWByteAddressBuffer"); break;
- case kIROp_HLSLRasterizerOrderedByteAddressBufferType: Emit("RasterizerOrderedByteAddressBuffer"); break;
- case kIROp_RaytracingAccelerationStructureType: Emit("RaytracingAccelerationStructure"); break;
-
- default:
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled buffer type");
- break;
- }
- }
- break;
-
- case CodeGenTarget::GLSL:
- {
- switch (type->op)
- {
- case kIROp_RaytracingAccelerationStructureType:
- requireGLSLExtension("GL_NV_ray_tracing");
- Emit("accelerationStructureNV");
- break;
-
- // TODO: These "translations" are obviously wrong for GLSL.
- case kIROp_HLSLByteAddressBufferType: Emit("ByteAddressBuffer"); break;
- case kIROp_HLSLRWByteAddressBufferType: Emit("RWByteAddressBuffer"); break;
- case kIROp_HLSLRasterizerOrderedByteAddressBufferType: Emit("RasterizerOrderedByteAddressBuffer"); break;
-
- default:
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled buffer type");
- break;
- }
- }
- break;
- }
- }
-
- void _requireHalf()
- {
- if (getTarget(context) == CodeGenTarget::GLSL)
- {
- requireGLSLHalfExtension(&context->shared->extensionUsageTracker);
- }
- }
-
- void emitSimpleTypeImpl(IRType* type)
- {
- switch (type->op)
- {
- default:
- break;
-
- case kIROp_VoidType: Emit("void"); return;
- case kIROp_BoolType: Emit("bool"); return;
-
- case kIROp_Int8Type: Emit("int8_t"); return;
- case kIROp_Int16Type: Emit("int16_t"); return;
- case kIROp_IntType: Emit("int"); return;
- case kIROp_Int64Type: Emit("int64_t"); return;
-
- case kIROp_UInt8Type: Emit("uint8_t"); return;
- case kIROp_UInt16Type: Emit("uint16_t"); return;
- case kIROp_UIntType: Emit("uint"); return;
- case kIROp_UInt64Type: Emit("uint64_t"); return;
-
- case kIROp_HalfType:
- {
- _requireHalf();
- if (getTarget(context) == CodeGenTarget::GLSL)
- {
- Emit("float16_t");
- }
- else
- {
- Emit("half");
- }
- return;
- }
- case kIROp_FloatType: Emit("float"); return;
- case kIROp_DoubleType: Emit("double"); return;
-
- case kIROp_VectorType:
- emitVectorTypeImpl((IRVectorType*)type);
- return;
-
- case kIROp_MatrixType:
- emitMatrixTypeImpl((IRMatrixType*)type);
- return;
-
- case kIROp_SamplerStateType:
- case kIROp_SamplerComparisonStateType:
- emitSamplerStateType(cast<IRSamplerStateTypeBase>(type));
- return;
-
- case kIROp_StructType:
- emit(getIRName(type));
- return;
- }
-
- // TODO: Ideally the following should be data-driven,
- // based on meta-data attached to the definitions of
- // each of these IR opcodes.
-
- if (auto texType = as<IRTextureType>(type))
- {
- emitTextureType(texType);
- return;
- }
- else if (auto textureSamplerType = as<IRTextureSamplerType>(type))
- {
- emitTextureSamplerType(textureSamplerType);
- return;
- }
- else if (auto imageType = as<IRGLSLImageType>(type))
- {
- emitImageType(imageType);
- return;
- }
- else if (auto structuredBufferType = as<IRHLSLStructuredBufferTypeBase>(type))
- {
- emitStructuredBufferType(structuredBufferType);
- return;
- }
- else if(auto untypedBufferType = as<IRUntypedBufferResourceType>(type))
- {
- emitUntypedBufferType(untypedBufferType);
- return;
- }
-
- // HACK: As a fallback for HLSL targets, assume that the name of the
- // instruction being used is the same as the name of the HLSL type.
- if(context->shared->target == CodeGenTarget::HLSL)
- {
- auto opInfo = getIROpInfo(type->op);
- emit(opInfo.name);
- UInt operandCount = type->getOperandCount();
- if(operandCount)
- {
- emit("<");
- for(UInt ii = 0; ii < operandCount; ++ii)
- {
- if(ii != 0) emit(", ");
- EmitVal(type->getOperand(ii), kEOp_General);
- }
- emit(" >");
- }
-
- return;
- }
-
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled type");
- }
-
- void emitArrayTypeImpl(IRArrayType* arrayType, EDeclarator* declarator)
- {
- EDeclarator arrayDeclarator;
- arrayDeclarator.flavor = EDeclarator::Flavor::Array;
- arrayDeclarator.next = declarator;
- arrayDeclarator.elementCount = arrayType->getElementCount();
-
- emitTypeImpl(arrayType->getElementType(), &arrayDeclarator);
- }
-
- void emitUnsizedArrayTypeImpl(IRUnsizedArrayType* arrayType, EDeclarator* declarator)
- {
- EDeclarator arrayDeclarator;
- arrayDeclarator.flavor = EDeclarator::Flavor::UnsizedArray;
- arrayDeclarator.next = declarator;
-
- emitTypeImpl(arrayType->getElementType(), &arrayDeclarator);
- }
-
- void emitTypeImpl(IRType* type, EDeclarator* declarator)
- {
- switch (type->op)
- {
- default:
- emitSimpleTypeImpl(type);
- EmitDeclarator(declarator);
- break;
-
- case kIROp_RateQualifiedType:
- {
- auto rateQualifiedType = cast<IRRateQualifiedType>(type);
- emitTypeImpl(rateQualifiedType->getValueType(), declarator);
- }
- break;
-
- case kIROp_ArrayType:
- emitArrayTypeImpl(cast<IRArrayType>(type), declarator);
- break;
-
- case kIROp_UnsizedArrayType:
- emitUnsizedArrayTypeImpl(cast<IRUnsizedArrayType>(type), declarator);
- break;
- }
-
- }
-
- void EmitType(
- IRType* type,
- SourceLoc const& typeLoc,
- Name* name,
- SourceLoc const& nameLoc)
- {
- advanceToSourceLocation(typeLoc);
-
- EDeclarator nameDeclarator;
- nameDeclarator.flavor = EDeclarator::Flavor::name;
- nameDeclarator.name = name;
- nameDeclarator.loc = nameLoc;
- emitTypeImpl(type, &nameDeclarator);
- }
-
- void EmitType(IRType* type, Name* name)
- {
- EmitType(type, SourceLoc(), name, SourceLoc());
- }
-
- void EmitType(IRType* type, String const& name)
- {
- // HACK: the rest of the code wants a `Name`,
- // so we'll create one for a bit...
- Name tempName;
- tempName.text = name;
-
- EmitType(type, SourceLoc(), &tempName, SourceLoc());
- }
-
-
- void EmitType(IRType* type)
- {
- emitTypeImpl(type, nullptr);
- }
-
- //
- // Expressions
- //
-
- bool maybeEmitParens(EOpInfo& outerPrec, EOpInfo prec)
- {
- bool needParens = (prec.leftPrecedence <= outerPrec.leftPrecedence)
- || (prec.rightPrecedence <= outerPrec.rightPrecedence);
-
- if (needParens)
- {
- Emit("(");
-
- outerPrec = kEOp_None;
- }
- return needParens;
- }
-
- void maybeCloseParens(bool needClose)
- {
- if(needClose) Emit(")");
- }
-
- bool isTargetIntrinsicModifierApplicable(
- String const& targetName)
- {
- switch(context->shared->target)
- {
- default:
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target");
- return false;
-
- case CodeGenTarget::GLSL: return targetName == "glsl";
- case CodeGenTarget::HLSL: return targetName == "hlsl";
- }
- }
-
- void EmitType(IRType* type, Name* name, SourceLoc const& nameLoc)
- {
- EmitType(
- type,
- SourceLoc(),
- name,
- nameLoc);
- }
-
- void EmitType(IRType* type, NameLoc const& nameAndLoc)
- {
- EmitType(type, nameAndLoc.name, nameAndLoc.loc);
- }
-
- bool isTargetIntrinsicModifierApplicable(
- IRTargetIntrinsicDecoration* decoration)
- {
- auto targetName = String(decoration->getTargetName());
-
- // If no target name was specified, then the modifier implicitly
- // applies to all targets.
- if(targetName.getLength() == 0)
- return true;
-
- return isTargetIntrinsicModifierApplicable(targetName);
- }
-
- void emitStringLiteral(
- String const& value)
- {
- emit("\"");
- for (auto c : value)
- {
- // TODO: This needs a more complete implementation,
- // especially if we want to support Unicode.
-
- char buffer[] = { c, 0 };
- switch (c)
- {
- default:
- emit(buffer);
- break;
-
- case '\"': emit("\\\"");
- case '\'': emit("\\\'");
- case '\\': emit("\\\\");
- case '\n': emit("\\n");
- case '\r': emit("\\r");
- case '\t': emit("\\t");
- }
- }
- emit("\"");
- }
-
- EOpInfo leftSide(EOpInfo const& outerPrec, EOpInfo const& prec)
- {
- EOpInfo result;
- result.op = nullptr;
- result.leftPrecedence = outerPrec.leftPrecedence;
- result.rightPrecedence = prec.leftPrecedence;
- return result;
- }
-
- EOpInfo rightSide(EOpInfo const& prec, EOpInfo const& outerPrec)
- {
- EOpInfo result;
- result.op = nullptr;
- result.leftPrecedence = prec.rightPrecedence;
- result.rightPrecedence = outerPrec.rightPrecedence;
- return result;
- }
-
- void requireGLSLExtension(String const& name)
- {
- Slang::requireGLSLExtension(&context->shared->extensionUsageTracker, name);
- }
-
- void requireGLSLVersion(ProfileVersion version)
- {
- if (context->shared->target != CodeGenTarget::GLSL)
- return;
-
- Slang::requireGLSLVersionImpl(&context->shared->extensionUsageTracker, version);
- }
-
- void requireGLSLVersion(int version)
- {
- switch (version)
- {
- #define CASE(NUMBER) \
- case NUMBER: requireGLSLVersion(ProfileVersion::GLSL_##NUMBER); break
-
- CASE(110);
- CASE(120);
- CASE(130);
- CASE(140);
- CASE(150);
- CASE(330);
- CASE(400);
- CASE(410);
- CASE(420);
- CASE(430);
- CASE(440);
- CASE(450);
-
- #undef CASE
- }
- }
-
- void setSampleRateFlag()
- {
- context->shared->entryPointLayout->flags |= EntryPointLayout::Flag::usesAnySampleRateInput;
- }
-
- void doSampleRateInputCheck(Name* name)
- {
- auto text = getText(name);
- if (text == "gl_SampleID")
- {
- setSampleRateFlag();
- }
- }
-
- void EmitVal(
- IRInst* val,
- EOpInfo const& outerPrec)
- {
- if(auto type = as<IRType>(val))
- {
- EmitType(type);
- }
- else
- {
- emitIRInstExpr(context, val, IREmitMode::Default, outerPrec);
- }
- }
-
- typedef unsigned int ESemanticMask;
- enum
- {
- kESemanticMask_None = 0,
-
- kESemanticMask_NoPackOffset = 1 << 0,
-
- kESemanticMask_Default = kESemanticMask_NoPackOffset,
- };
-
- // A chain of variables to use for emitting semantic/layout info
- struct EmitVarChain
- {
- VarLayout* varLayout;
- EmitVarChain* next;
-
- EmitVarChain()
- : varLayout(0)
- , next(0)
- {}
-
- EmitVarChain(VarLayout* varLayout)
- : varLayout(varLayout)
- , next(0)
- {}
-
- EmitVarChain(VarLayout* varLayout, EmitVarChain* next)
- : varLayout(varLayout)
- , next(next)
- {}
- };
-
- UInt getBindingOffset(EmitVarChain* chain, LayoutResourceKind kind)
- {
- UInt offset = 0;
- for(auto cc = chain; cc; cc = cc->next)
- {
- if(auto resInfo = cc->varLayout->FindResourceInfo(kind))
- {
- offset += resInfo->index;
- }
- }
- return offset;
- }
-
- UInt getBindingSpace(EmitVarChain* chain, LayoutResourceKind kind)
- {
- UInt space = 0;
- for(auto cc = chain; cc; cc = cc->next)
- {
- auto varLayout = cc->varLayout;
- if(auto resInfo = varLayout->FindResourceInfo(kind))
- {
- space += resInfo->space;
- }
- if(auto resInfo = varLayout->FindResourceInfo(LayoutResourceKind::RegisterSpace))
- {
- space += resInfo->index;
- }
- }
- return space;
- }
-
- // Emit a single `register` semantic, as appropriate for a given resource-type-specific layout info
- void emitHLSLRegisterSemantic(
- LayoutResourceKind kind,
- EmitVarChain* chain,
- // Keyword to use in the uniform case (`register` for globals, `packoffset` inside a `cbuffer`)
- char const* uniformSemanticSpelling = "register")
- {
- if(!chain)
- return;
- if(!chain->varLayout->FindResourceInfo(kind))
- return;
-
- UInt index = getBindingOffset(chain, kind);
- UInt space = getBindingSpace(chain, kind);
-
- switch(kind)
- {
- case LayoutResourceKind::Uniform:
- {
- UInt offset = index;
-
- // The HLSL `c` register space is logically grouped in 16-byte registers,
- // while we try to traffic in byte offsets. That means we need to pick
- // a register number, based on the starting offset in 16-byte register
- // units, and then a "component" within that register, based on 4-byte
- // offsets from there. We cannot support more fine-grained offsets than that.
-
- Emit(" : ");
- Emit(uniformSemanticSpelling);
- Emit("(c");
-
- // Size of a logical `c` register in bytes
- auto registerSize = 16;
-
- // Size of each component of a logical `c` register, in bytes
- auto componentSize = 4;
-
- size_t startRegister = offset / registerSize;
- Emit(int(startRegister));
-
- size_t byteOffsetInRegister = offset % registerSize;
-
- // If this field doesn't start on an even register boundary,
- // then we need to emit additional information to pick the
- // right component to start from
- if (byteOffsetInRegister != 0)
- {
- // The value had better occupy a whole number of components.
- SLANG_RELEASE_ASSERT(byteOffsetInRegister % componentSize == 0);
-
- size_t startComponent = byteOffsetInRegister / componentSize;
-
- static const char* kComponentNames[] = {"x", "y", "z", "w"};
- Emit(".");
- Emit(kComponentNames[startComponent]);
- }
- Emit(")");
- }
- break;
-
- case LayoutResourceKind::RegisterSpace:
- case LayoutResourceKind::GenericResource:
- case LayoutResourceKind::ExistentialTypeParam:
- case LayoutResourceKind::ExistentialObjectParam:
- // ignore
- break;
- default:
- {
- Emit(" : register(");
- switch( kind )
- {
- case LayoutResourceKind::ConstantBuffer:
- Emit("b");
- break;
- case LayoutResourceKind::ShaderResource:
- Emit("t");
- break;
- case LayoutResourceKind::UnorderedAccess:
- Emit("u");
- break;
- case LayoutResourceKind::SamplerState:
- Emit("s");
- break;
- default:
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled HLSL register type");
- break;
- }
- Emit(index);
- if(space)
- {
- Emit(", space");
- Emit(space);
- }
- Emit(")");
- }
- }
- }
-
- // Emit all the `register` semantics that are appropriate for a particular variable layout
- void emitHLSLRegisterSemantics(
- EmitVarChain* chain,
- char const* uniformSemanticSpelling = "register")
- {
- if (!chain) return;
-
- auto layout = chain->varLayout;
-
- switch( context->shared->target )
- {
- default:
- return;
-
- case CodeGenTarget::HLSL:
- break;
- }
-
- for( auto rr : layout->resourceInfos )
- {
- emitHLSLRegisterSemantic(rr.kind, chain, uniformSemanticSpelling);
- }
- }
-
- void emitHLSLRegisterSemantics(
- VarLayout* varLayout,
- char const* uniformSemanticSpelling = "register")
- {
- if(!varLayout)
- return;
-
- EmitVarChain chain(varLayout);
- emitHLSLRegisterSemantics(&chain, uniformSemanticSpelling);
- }
-
- void emitHLSLParameterGroupFieldLayoutSemantics(
- EmitVarChain* chain)
- {
- if(!chain)
- return;
-
- auto layout = chain->varLayout;
- for( auto rr : layout->resourceInfos )
- {
- emitHLSLRegisterSemantic(rr.kind, chain, "packoffset");
- }
- }
-
-
- void emitHLSLParameterGroupFieldLayoutSemantics(
- RefPtr<VarLayout> fieldLayout,
- EmitVarChain* inChain)
- {
- EmitVarChain chain(fieldLayout, inChain);
- emitHLSLParameterGroupFieldLayoutSemantics(&chain);
- }
-
- bool emitGLSLLayoutQualifier(
- LayoutResourceKind kind,
- EmitVarChain* chain)
- {
- if(!chain)
- return false;
- if(!chain->varLayout->FindResourceInfo(kind))
- return false;
-
- UInt index = getBindingOffset(chain, kind);
- UInt space = getBindingSpace(chain, kind);
- switch(kind)
- {
- case LayoutResourceKind::Uniform:
- {
- // Explicit offsets require a GLSL extension (which
- // is not universally supported, it seems) or a new
- // enough GLSL version (which we don't want to
- // universally require), so for right now we
- // won't actually output explicit offsets for uniform
- // shader parameters.
- //
- // TODO: We should fix this so that we skip any
- // extra work for parameters that are laid out as
- // expected by the default rules, but do *something*
- // for parameters that need non-default layout.
- //
- // Using the `GL_ARB_enhanced_layouts` feature is one
- // option, but we should also be able to do some
- // things by introducing padding into the declaration
- // (padding insertion would probably be best done at
- // the IR level).
- bool useExplicitOffsets = false;
- if (useExplicitOffsets)
- {
- requireGLSLExtension("GL_ARB_enhanced_layouts");
-
- Emit("layout(offset = ");
- Emit(index);
- Emit(")\n");
- }
- }
- break;
-
- case LayoutResourceKind::VertexInput:
- case LayoutResourceKind::FragmentOutput:
- Emit("layout(location = ");
- Emit(index);
- Emit(")\n");
- break;
-
- case LayoutResourceKind::SpecializationConstant:
- Emit("layout(constant_id = ");
- Emit(index);
- Emit(")\n");
- break;
-
- case LayoutResourceKind::ConstantBuffer:
- case LayoutResourceKind::ShaderResource:
- case LayoutResourceKind::UnorderedAccess:
- case LayoutResourceKind::SamplerState:
- case LayoutResourceKind::DescriptorTableSlot:
- Emit("layout(binding = ");
- Emit(index);
- if(space)
- {
- Emit(", set = ");
- Emit(space);
- }
- Emit(")\n");
- break;
-
- case LayoutResourceKind::PushConstantBuffer:
- Emit("layout(push_constant)\n");
- break;
- case LayoutResourceKind::ShaderRecord:
- Emit("layout(shaderRecordNV)\n");
- break;
-
- }
- return true;
- }
-
- void emitGLSLLayoutQualifiers(
- RefPtr<VarLayout> layout,
- EmitVarChain* inChain,
- LayoutResourceKind filter = LayoutResourceKind::None)
- {
- if(!layout) return;
-
- switch( context->shared->target )
- {
- default:
- return;
-
- case CodeGenTarget::GLSL:
- break;
- }
-
- EmitVarChain chain(layout, inChain);
-
- for( auto info : layout->resourceInfos )
- {
- // Skip info that doesn't match our filter
- if (filter != LayoutResourceKind::None
- && filter != info.kind)
- {
- continue;
- }
-
- emitGLSLLayoutQualifier(info.kind, &chain);
- }
- }
-
- void emitGLSLVersionDirective()
- {
- auto effectiveProfile = context->shared->effectiveProfile;
- if(effectiveProfile.getFamily() == ProfileFamily::GLSL)
- {
- requireGLSLVersion(effectiveProfile.GetVersion());
- }
-
- // HACK: We aren't picking GLSL versions carefully right now,
- // and so we might end up only requiring the initial 1.10 version,
- // even though even basic functionality needs a higher version.
- //
- // For now, we'll work around this by just setting the minimum required
- // version to a high one:
- //
- // TODO: Either correctly compute a minimum required version, or require
- // the user to specify a version as part of the target.
- requireGLSLVersionImpl(&context->shared->extensionUsageTracker, ProfileVersion::GLSL_450);
-
- auto requiredProfileVersion = context->shared->extensionUsageTracker.profileVersion;
- switch (requiredProfileVersion)
- {
-#define CASE(TAG, VALUE) \
- case ProfileVersion::TAG: Emit("#version " #VALUE "\n"); return
-
- CASE(GLSL_110, 110);
- CASE(GLSL_120, 120);
- CASE(GLSL_130, 130);
- CASE(GLSL_140, 140);
- CASE(GLSL_150, 150);
- CASE(GLSL_330, 330);
- CASE(GLSL_400, 400);
- CASE(GLSL_410, 410);
- CASE(GLSL_420, 420);
- CASE(GLSL_430, 430);
- CASE(GLSL_440, 440);
- CASE(GLSL_450, 450);
- CASE(GLSL_460, 460);
-#undef CASE
-
- default:
- break;
- }
-
- // No information is available for us to guess a profile,
- // so it seems like we need to pick one out of thin air.
- //
- // Ideally we should infer a minimum required version based
- // on the constructs we have seen used in the user's code
- //
- // For now we just fall back to a reasonably recent version.
-
- Emit("#version 420\n");
- }
-
- void emitGLSLPreprocessorDirectives()
- {
- switch(context->shared->target)
- {
- // Don't emit this stuff unless we are targetting GLSL
- default:
- return;
-
- case CodeGenTarget::GLSL:
- break;
- }
-
- emitGLSLVersionDirective();
- }
-
- /// Emit directives to control overall layout computation for the emitted code.
- void emitLayoutDirectives(TargetRequest* targetReq)
- {
- // We are going to emit the target-language-specific directives
- // needed to get the default matrix layout to match what was requested
- // for the given target.
- //
- // Note: we do not rely on the defaults for the target language,
- // because a user could take the HLSL/GLSL generated by Slang and pass
- // it to another compiler with non-default options specified on
- // the command line, leading to all kinds of trouble.
- //
- // TODO: We need an approach to "global" layout directives that will work
- // in the presence of multiple modules. If modules A and B were each
- // compiled with different assumptions about how layout is performed,
- // then types/variables defined in those modules should be emitted in
- // a way that is consistent with that layout...
-
- auto matrixLayoutMode = targetReq->getDefaultMatrixLayoutMode();
-
- switch(context->shared->target)
- {
- default:
- return;
-
- case CodeGenTarget::GLSL:
- // Reminder: the meaning of row/column major layout
- // in our semantics is the *opposite* of what GLSL
- // calls them, because what they call "columns"
- // are what we call "rows."
- //
- switch(matrixLayoutMode)
- {
- case kMatrixLayoutMode_RowMajor:
- default:
- Emit("layout(column_major) uniform;\n");
- Emit("layout(column_major) buffer;\n");
- break;
-
- case kMatrixLayoutMode_ColumnMajor:
- Emit("layout(row_major) uniform;\n");
- Emit("layout(row_major) buffer;\n");
- break;
- }
- break;
-
- case CodeGenTarget::HLSL:
- switch(matrixLayoutMode)
- {
- case kMatrixLayoutMode_RowMajor:
- default:
- Emit("#pragma pack_matrix(row_major)\n");
- break;
-
- case kMatrixLayoutMode_ColumnMajor:
- Emit("#pragma pack_matrix(column_major)\n");
- break;
- }
- break;
- }
- }
-
- // Utility code for generating unique IDs as needed
- // during the emit process (e.g., for declarations
- // that didn't origianlly have names, but now need to).
-
- UInt allocateUniqueID()
- {
- return context->shared->uniqueIDCounter++;
- }
-
- // IR-level emit logc
-
- UInt getID(IRInst* value)
- {
- auto& mapIRValueToID = context->shared->mapIRValueToID;
-
- UInt id = 0;
- if (mapIRValueToID.TryGetValue(value, id))
- return id;
-
- id = allocateUniqueID();
- mapIRValueToID.Add(value, id);
- return id;
- }
-
- /// "Scrub" a name so that it complies with restrictions of the target language.
- String scrubName(
- String const& name)
- {
- // We will use a plain `U` as a dummy character to insert
- // whenever we need to insert things to make a string into
- // valid name.
- //
- char const* dummyChar = "U";
-
- // Special case a name that is the empty string, just in case.
- if(name.getLength() == 0)
- return dummyChar;
-
- // Otherwise, we are going to walk over the name byte by byte
- // and write some legal characters to the output as we go.
- StringBuilder sb;
-
- if(getTarget(context) == CodeGenTarget::GLSL)
- {
- // GLSL reserverse all names that start with `gl_`,
- // so if we are in danger of collision, then make
- // our name start with a dummy character instead.
- if(name.startsWith("gl_"))
- {
- sb.append(dummyChar);
- }
- }
-
- // We will also detect user-defined names that
- // might overlap with our convention for mangled names,
- // to avoid an possible collision.
- if(name.startsWith("_S"))
- {
- sb.Append(dummyChar);
- }
-
- // TODO: This is where we might want to consult
- // a dictionary of reserved words for the chosen target
- //
- // if(isReservedWord(name)) { sb.Append(dummyChar); }
- //
-
- // We need to track the previous byte in
- // order to detect consecutive underscores for GLSL.
- int prevChar = -1;
-
- for(auto c : name)
- {
- // We will treat a dot character just like an underscore
- // for the purposes of producing a scrubbed name, so
- // that we translate `SomeType.someMethod` into
- // `SomeType_someMethod`.
- //
- // By handling this case at the top of this loop, we
- // ensure that a `.`-turned-`_` is handled just like
- // a `_` in the original name, and will be properly
- // scrubbed for GLSL output.
- //
- if(c == '.')
- {
- c = '_';
- }
-
- if(((c >= 'a') && (c <= 'z'))
- || ((c >= 'A') && (c <= 'Z')))
- {
- // Ordinary ASCII alphabetic characters are assumed
- // to always be okay.
- }
- else if((c >= '0') && (c <= '9'))
- {
- // We don't want to allow a digit as the first
- // byte in a name, since the result wouldn't
- // be a valid identifier in many target languages.
- if(prevChar == -1)
- {
- sb.append(dummyChar);
- }
- }
- else if(c == '_')
- {
- // We will collapse any consecutive sequence of `_`
- // characters into a single one (this means that
- // some names that were unique in the original
- // code might not resolve to unique names after
- // scrubbing, but that was true in general).
-
- if(prevChar == '_')
- {
- // Skip this underscore, so we don't output
- // more than one in a row.
- continue;
- }
- }
- else
- {
- // If we run into a character that wouldn't normally
- // be allowed in an identifier, we need to translate
- // it into something that *is* valid.
- //
- // Our solution for now will be very clumsy: we will
- // emit `x` and then the hexadecimal version of
- // the byte we were given.
- sb.append("x");
- sb.append(uint32_t((unsigned char) c), 16);
-
- // We don't want to apply the default handling below,
- // so skip to the top of the loop now.
- prevChar = c;
- continue;
- }
-
- sb.append(c);
- prevChar = c;
- }
-
- return sb.ProduceString();
- }
-
- String generateIRName(
- IRInst* inst)
- {
- // If the instruction names something
- // that should be emitted as a target intrinsic,
- // then use that name instead.
- if(auto intrinsicDecoration = findTargetIntrinsicDecoration(context, inst))
- {
- return String(intrinsicDecoration->getDefinition());
- }
-
- // If we have a name hint on the instruction, then we will try to use that
- // to provide the actual name in the output code.
- //
- // We need to be careful that the name follows the rules of the target language,
- // so there is a "scrubbing" step that needs to be applied here.
- //
- // We also need to make sure that the name won't collide with other declarations
- // that might have the same name hint applied, so we will still unique
- // them by appending the numeric ID of the instruction.
- //
- // TODO: Find cases where we can drop the suffix safely.
- //
- // TODO: When we start having to handle symbols with external linkage for
- // things like DXIL libraries, we will need to *not* use the friendly
- // names for stuff that should be link-able.
- //
- if(auto nameHintDecoration = inst->findDecoration<IRNameHintDecoration>())
- {
- // The name we output will basically be:
- //
- // <nameHint>_<uniqueID>
- //
- // Except that we will "scrub" the name hint first,
- // and we will omit the underscore if the (scrubbed)
- // name hint already ends with one.
- //
-
- String nameHint = nameHintDecoration->getName();
- nameHint = scrubName(nameHint);
-
- StringBuilder sb;
- sb.append(nameHint);
-
- // Avoid introducing a double underscore
- if(!nameHint.endsWith("_"))
- {
- sb.append("_");
- }
-
- String key = sb.ProduceString();
- UInt count = 0;
- context->shared->uniqueNameCounters.TryGetValue(key, count);
-
- context->shared->uniqueNameCounters[key] = count+1;
-
- sb.append(Int32(count));
- return sb.ProduceString();
- }
-
-
-
-
- // If the instruction has a mangled name, then emit using that.
- if(auto linkageDecoration = inst->findDecoration<IRLinkageDecoration>())
- {
- return linkageDecoration->getMangledName();
- }
-
- // Otherwise fall back to a construct temporary name
- // for the instruction.
- StringBuilder sb;
- sb << "_S";
- sb << Int32(getID(inst));
-
- return sb.ProduceString();
- }
-
- String getIRName(
- IRInst* inst)
- {
- String name;
- if(!context->shared->mapInstToName.TryGetValue(inst, name))
- {
- name = generateIRName(inst);
- context->shared->mapInstToName.Add(inst, name);
- }
- return name;
- }
-
- struct IRDeclaratorInfo
- {
- enum class Flavor
- {
- Simple,
- Ptr,
- Array,
- };
-
- Flavor flavor;
- IRDeclaratorInfo* next;
- union
- {
- String const* name;
- IRInst* elementCount;
- };
- };
-
- void emitDeclarator(
- EmitContext* ctx,
- IRDeclaratorInfo* declarator)
- {
- if(!declarator)
- return;
-
- switch( declarator->flavor )
- {
- case IRDeclaratorInfo::Flavor::Simple:
- emit(" ");
- emit(*declarator->name);
- break;
-
- case IRDeclaratorInfo::Flavor::Ptr:
- emit("*");
- emitDeclarator(ctx, declarator->next);
- break;
-
- case IRDeclaratorInfo::Flavor::Array:
- emitDeclarator(ctx, declarator->next);
- emit("[");
- emitIROperand(ctx, declarator->elementCount, IREmitMode::Default, kEOp_General);
- emit("]");
- break;
- }
- }
-
- void emitIRSimpleValue(
- EmitContext* /*context*/,
- IRInst* inst)
- {
- switch(inst->op)
- {
- case kIROp_IntLit:
- emit(((IRConstant*) inst)->value.intVal);
- break;
-
- case kIROp_FloatLit:
- Emit(((IRConstant*) inst)->value.floatVal);
- break;
-
- case kIROp_BoolLit:
- {
- bool val = ((IRConstant*)inst)->value.intVal != 0;
- emit(val ? "true" : "false");
- }
- break;
-
- default:
- SLANG_UNIMPLEMENTED_X("val case for emit");
- break;
- }
-
- }
-
- CodeGenTarget getTarget(EmitContext* ctx)
- {
- return ctx->shared->target;
- }
-
- // Hack to allow IR emit for global constant to override behavior
- enum class IREmitMode
- {
- Default,
- GlobalConstant,
- };
-
- bool shouldFoldIRInstIntoUseSites(
- EmitContext* ctx,
- IRInst* inst,
- IREmitMode mode)
- {
- // Certain opcodes should never/always be folded in
- switch( inst->op )
- {
- default:
- break;
-
- // Never fold these in, because they represent declarations
- //
- case kIROp_Var:
- case kIROp_GlobalVar:
- case kIROp_GlobalConstant:
- case kIROp_GlobalParam:
- case kIROp_Param:
- case kIROp_Func:
- return false;
-
- // Always fold these in, because they are trivial
- //
- case kIROp_IntLit:
- case kIROp_FloatLit:
- case kIROp_BoolLit:
- return true;
-
- // Always fold these in, because their results
- // cannot be represented in the type system of
- // our current targets.
- //
- // TODO: when we add C/C++ as an optional target,
- // we could consider lowering insts that result
- // in pointers directly.
- //
- case kIROp_FieldAddress:
- case kIROp_getElementPtr:
- case kIROp_Specialize:
- return true;
- }
-
- // Always fold when we are inside a global constant initializer
- if (mode == IREmitMode::GlobalConstant)
- return true;
-
- switch( inst->op )
- {
- default:
- break;
-
- // HACK: don't fold these in because we currently lower
- // them to initializer lists, which aren't allowed in
- // general expression contexts.
- //
- // Note: we are doing this check *after* the check for `GlobalConstant`
- // mode, because otherwise we'd fail to emit initializer lists in
- // the main place where we want/need them.
- //
- case kIROp_makeStruct:
- case kIROp_makeArray:
- return false;
-
- }
-
- // Instructions with specific result *types* will usually
- // want to be folded in, because they aren't allowed as types
- // for temporary variables.
- auto type = inst->getDataType();
-
- // Unwrap any layers of array-ness from the type, so that
- // we can look at the underlying data type, in case we
- // should *never* expose a value of that type
- while (auto arrayType = as<IRArrayTypeBase>(type))
- {
- type = arrayType->getElementType();
- }
-
- // Don't allow temporaries of pointer types to be created.
- if(as<IRPtrTypeBase>(type))
- {
- return true;
- }
-
- // First we check for uniform parameter groups,
- // because a `cbuffer` or GLSL `uniform` block
- // does not have a first-class type that we can
- // pass around.
- //
- // TODO: We need to ensure that type legalization
- // cleans up cases where we use a parameter group
- // or parameter block type as a function parameter...
- //
- if(as<IRUniformParameterGroupType>(type))
- {
- // TODO: we need to be careful here, because
- // HLSL shader model 6 allows these as explicit
- // types.
- return true;
- }
- //
- // The stream-output and patch types need to be handled
- // too, because they are not really first class (especially
- // not in GLSL, but they also seem to confuse the HLSL
- // compiler when they get used as temporaries).
- //
- else if (as<IRHLSLStreamOutputType>(type))
- {
- return true;
- }
- else if (as<IRHLSLPatchType>(type))
- {
- return true;
- }
-
-
- // GLSL doesn't allow texture/resource types to
- // be used as first-class values, so we need
- // to fold them into their use sites in all cases
- if (getTarget(ctx) == CodeGenTarget::GLSL)
- {
- if(as<IRResourceTypeBase>(type))
- {
- return true;
- }
- else if(as<IRHLSLStructuredBufferTypeBase>(type))
- {
- return true;
- }
- else if(as<IRUntypedBufferResourceType>(type))
- {
- return true;
- }
- else if(as<IRSamplerStateTypeBase>(type))
- {
- return true;
- }
- }
-
- // If the instruction is at global scope, then it might represent
- // a constant (e.g., the value of an enum case).
- //
- if(as<IRModuleInst>(inst->getParent()))
- {
- if(!inst->mightHaveSideEffects())
- return true;
- }
-
- // Having dealt with all of the cases where we *must* fold things
- // above, we can now deal with the more general cases where we
- // *should not* fold things.
-
- // Don't fold somethin with no users:
- if(!inst->hasUses())
- return false;
-
- // Don't fold something that has multiple users:
- if(inst->hasMoreThanOneUse())
- return false;
-
- // Don't fold something that might have side effects:
- if(inst->mightHaveSideEffects())
- return false;
-
- // Don't fold instructions that are marked `[precise]`.
- // This could in principle be extended to any other
- // decorations that affect the semantics of an instruction
- // in ways that require a temporary to be introduced.
- //
- if(inst->findDecoration<IRPreciseDecoration>())
- return false;
-
- // Okay, at this point we know our instruction must have a single use.
- auto use = inst->firstUse;
- SLANG_ASSERT(use);
- SLANG_ASSERT(!use->nextUse);
-
- auto user = use->getUser();
-
- // We'd like to figure out if it is safe to fold our instruction into `user`
-
- // First, let's make sure they are in the same block/parent:
- if(inst->getParent() != user->getParent())
- return false;
-
- // Now let's look at all the instructions between this instruction
- // and the user. If any of them might have side effects, then lets
- // bail out now.
- for(auto ii = inst->getNextInst(); ii != user; ii = ii->getNextInst())
- {
- if(!ii)
- {
- // We somehow reached the end of the block without finding
- // the user, which doesn't make sense if uses dominate
- // defs. Let's just play it safe and bail out.
- return false;
- }
-
- if(ii->mightHaveSideEffects())
- return false;
- }
-
- // Okay, if we reach this point then the user comes later in
- // the same block, and there are no instructions with side
- // effects in between, so it seems safe to fold things in.
- return true;
- }
-
- void emitIROperand(
- EmitContext* ctx,
- IRInst* inst,
- IREmitMode mode,
- EOpInfo const& outerPrec)
- {
- if( shouldFoldIRInstIntoUseSites(ctx, inst, mode) )
- {
- emitIRInstExpr(ctx, inst, mode, outerPrec);
- return;
- }
-
- switch(inst->op)
- {
- case 0: // nothing yet
- default:
- emit(getIRName(inst));
- break;
- }
- }
-
- void emitIRArgs(
- EmitContext* ctx,
- IRInst* inst,
- IREmitMode mode)
- {
- UInt argCount = inst->getOperandCount();
- IRUse* args = inst->getOperands();
-
- emit("(");
- for(UInt aa = 0; aa < argCount; ++aa)
- {
- if(aa != 0) emit(", ");
- emitIROperand(ctx, args[aa].get(), mode, kEOp_General);
- }
- emit(")");
- }
-
- void emitIRType(
- EmitContext* /*context*/,
- IRType* type,
- String const& name)
- {
- EmitType(type, name);
- }
-
- void emitIRType(
- EmitContext* /*context*/,
- IRType* type,
- Name* name)
- {
- EmitType(type, name);
- }
-
- void emitIRType(
- EmitContext* /*context*/,
- IRType* type)
- {
- EmitType(type);
- }
-
- void emitIRRateQualifiers(
- EmitContext* ctx,
- IRRate* rate)
- {
- if(!rate) return;
-
- if(as<IRConstExprRate>(rate))
- {
- switch( getTarget(ctx) )
- {
- case CodeGenTarget::GLSL:
- emit("const ");
- break;
-
- default:
- break;
- }
- }
-
- if (as<IRGroupSharedRate>(rate))
- {
- switch( getTarget(ctx) )
- {
- case CodeGenTarget::HLSL:
- Emit("groupshared ");
- break;
-
- case CodeGenTarget::GLSL:
- Emit("shared ");
- break;
-
- default:
- break;
- }
- }
- }
-
- void emitIRRateQualifiers(
- EmitContext* ctx,
- IRInst* value)
- {
- emitIRRateQualifiers(ctx, value->getRate());
- }
-
-
- void emitIRInstResultDecl(
- EmitContext* ctx,
- IRInst* inst)
- {
- auto type = inst->getDataType();
- if(!type)
- return;
-
- if (as<IRVoidType>(type))
- return;
-
- emitIRTempModifiers(ctx, inst);
-
- emitIRRateQualifiers(ctx, inst);
-
- emitIRType(ctx, type, getIRName(inst));
- emit(" = ");
- }
-
- class UnmangleContext
- {
- private:
- char const* cursor_ = nullptr;
- char const* begin_ = nullptr;
- char const* end_ = nullptr;
-
- bool isDigit(char c)
- {
- return (c >= '0') && (c <= '9');
- }
-
- char peek()
- {
- return *cursor_;
- }
-
- char get()
- {
- return *cursor_++;
- }
-
- void expect(char c)
- {
- if(peek() == c)
- {
- get();
- }
- else
- {
- // ERROR!
- SLANG_UNEXPECTED("mangled name error");
- }
- }
-
- void expect(char const* str)
- {
- while(char c = *str++)
- expect(c);
- }
-
- public:
- UnmangleContext()
- {}
-
- UnmangleContext(String const& str)
- : cursor_(str.begin())
- , begin_(str.begin())
- , end_(str.end())
- {}
-
- // Call at the beginning of a mangled name,
- // to strip off the main prefix
- void startUnmangling()
- {
- expect("_S");
- }
-
- UInt readCount()
- {
- int c = peek();
- if(!isDigit((char)c))
- {
- SLANG_UNEXPECTED("bad name mangling");
- UNREACHABLE_RETURN(0);
- }
- get();
-
- if(c == '0')
- return 0;
-
- UInt count = 0;
- for(;;)
- {
- count = count*10 + c - '0';
- c = peek();
- if(!isDigit((char)c))
- return count;
-
- get();
- }
- }
-
- void readGenericParam()
- {
- switch(peek())
- {
- case 'T':
- case 'C':
- get();
- break;
-
- case 'v':
- get();
- readType();
- break;
-
- default:
- SLANG_UNEXPECTED("bad name mangling");
- break;
- }
- }
-
- void readGenericParams()
- {
- expect("g");
- UInt paramCount = readCount();
- for(UInt pp = 0; pp < paramCount; pp++)
- {
- readGenericParam();
- }
- }
-
- void readSimpleIntVal()
- {
- int c = peek();
- if(isDigit((char)c))
- {
- get();
- }
- else
- {
- readVal();
- }
- }
-
-
- UnownedStringSlice readRawStringSegment()
- {
- // Read the length part
- UInt count = readCount();
- if(count > UInt(end_ - cursor_))
- {
- SLANG_UNEXPECTED("bad name mangling");
- UNREACHABLE_RETURN(UnownedStringSlice());
- }
-
- auto result = UnownedStringSlice(cursor_, cursor_ + count);
- cursor_ += count;
- return result;
- }
-
- void readNamedType()
- {
- // TODO: handle types with more complicated names
- readRawStringSegment();
- }
-
- void readType()
- {
- int c = peek();
- switch(c)
- {
- case 'V':
- case 'b':
- case 'i':
- case 'u':
- case 'U':
- case 'h':
- case 'f':
- case 'd':
- get();
- break;
-
- case 'v':
- get();
- readSimpleIntVal();
- readType();
- break;
-
- default:
- readNamedType();
- break;
- }
- }
-
- void readVal()
- {
- switch(peek())
- {
- case 'k':
- get();
- readCount();
- break;
-
- case 'K':
- get();
- readRawStringSegment();
- break;
-
- default:
- readType();
- break;
- }
-
- }
-
- void readGenericArg()
- {
- readVal();
- }
-
- void readGenericArgs()
- {
- expect("G");
- UInt argCount = readCount();
- for(UInt aa = 0; aa < argCount; aa++)
- {
- readGenericArg();
- }
- }
-
- void readExtensionSpec()
- {
- expect("X");
- readType();
- }
-
- UnownedStringSlice readSimpleName()
- {
- UnownedStringSlice result;
- for(;;)
- {
- int c = peek();
-
- if(c == 'g')
- {
- readGenericParams();
- continue;
- }
- else if(c == 'G')
- {
- readGenericArgs();
- continue;
- }
- else if(c == 'X')
- {
- readExtensionSpec();
- continue;
- }
-
- if(!isDigit((char)c))
- return result;
-
- // Read the length part
- UInt count = readCount();
- if(count > UInt(end_ - cursor_))
- {
- SLANG_UNEXPECTED("bad name mangling");
- UNREACHABLE_RETURN(result);
- }
-
- result = UnownedStringSlice(cursor_, cursor_ + count);
- cursor_ += count;
- }
- }
-
- UInt readParamCount()
- {
- expect("p");
- UInt count = readCount();
- expect("p");
- return count;
- }
- };
-
- IRTargetIntrinsicDecoration* findTargetIntrinsicDecoration(
- EmitContext* /* ctx */,
- IRInst* inst)
- {
- for(auto dd : inst->getDecorations())
- {
- if (dd->op != kIROp_TargetIntrinsicDecoration)
- continue;
-
- auto targetIntrinsic = (IRTargetIntrinsicDecoration*)dd;
- if (isTargetIntrinsicModifierApplicable(targetIntrinsic))
- return targetIntrinsic;
- }
-
- return nullptr;
- }
-
- // Check if the string being used to define a target intrinsic
- // is an "ordinary" name, such that we can simply emit a call
- // to the new name with the arguments of the old operation.
- bool isOrdinaryName(String const& name)
- {
- char const* cursor = name.begin();
- char const* end = name.end();
-
- while(cursor != end)
- {
- int c = *cursor++;
- if( (c >= 'a') && (c <= 'z') ) continue;
- if( (c >= 'A') && (c <= 'Z') ) continue;
- if( c == '_' ) continue;
-
- return false;
- }
- return true;
- }
-
- void emitTargetIntrinsicCallExpr(
- EmitContext* ctx,
- IRCall* inst,
- IRFunc* /* func */,
- IRTargetIntrinsicDecoration* targetIntrinsic,
- IREmitMode mode,
- EOpInfo const& inOuterPrec)
- {
- auto outerPrec = inOuterPrec;
-
- IRUse* args = inst->getOperands();
- Index argCount = inst->getOperandCount();
-
- // First operand was the function to be called
- args++;
- argCount--;
-
- auto name = String(targetIntrinsic->getDefinition());
-
- if(isOrdinaryName(name))
- {
- // Simple case: it is just an ordinary name, so we call it like a builtin.
- auto prec = kEOp_Postfix;
- bool needClose = maybeEmitParens(outerPrec, prec);
-
- emit(name);
- Emit("(");
- for (Index aa = 0; aa < argCount; ++aa)
- {
- if (aa != 0) Emit(", ");
- emitIROperand(ctx, args[aa].get(), mode, kEOp_General);
- }
- Emit(")");
-
- maybeCloseParens(needClose);
- return;
- }
- else
- {
- int openParenCount = 0;
-
- const auto returnType = inst->getDataType();
-
- // If it returns void -> then we don't need parenthesis
- if (as<IRVoidType>(returnType) == nullptr)
- {
- Emit("(");
- openParenCount++;
- }
-
- // General case: we are going to emit some more complex text.
-
- char const* cursor = name.begin();
- char const* end = name.end();
- while(cursor != end)
- {
- char c = *cursor++;
- if( c != '$' )
- {
- // Not an escape sequence
- emitRawTextSpan(&c, &c+1);
- continue;
- }
-
- SLANG_RELEASE_ASSERT(cursor != end);
-
- char d = *cursor++;
-
- switch (d)
- {
- case '0': case '1': case '2': case '3': case '4':
- case '5': case '6': case '7': case '8': case '9':
- {
- // Simple case: emit one of the direct arguments to the call
- Index argIndex = d - '0';
- SLANG_RELEASE_ASSERT((0 <= argIndex) && (argIndex < argCount));
- Emit("(");
- emitIROperand(ctx, args[argIndex].get(), mode, kEOp_General);
- Emit(")");
- }
- break;
-
- case 'p':
- {
- // If we are calling a D3D texturing operation in the form t.Foo(s, ...),
- // then this form will pair up the t and s arguments as needed for a GLSL
- // texturing operation.
- SLANG_RELEASE_ASSERT(argCount >= 2);
-
- auto textureArg = args[0].get();
- auto samplerArg = args[1].get();
-
- if (auto baseTextureType = as<IRTextureType>(textureArg->getDataType()))
- {
- emitGLSLTextureOrTextureSamplerType(baseTextureType, "sampler");
-
- if (auto samplerType = as<IRSamplerStateTypeBase>(samplerArg->getDataType()))
- {
- if (as<IRSamplerComparisonStateType>(samplerType))
- {
- Emit("Shadow");
- }
- }
-
- Emit("(");
- emitIROperand(ctx, textureArg, mode, kEOp_General);
- Emit(",");
- emitIROperand(ctx, samplerArg, mode, kEOp_General);
- Emit(")");
- }
- else
- {
- SLANG_UNEXPECTED("bad format in intrinsic definition");
- }
- }
- break;
-
- case 'c':
- {
- // When doing texture access in glsl the result may need to be cast.
- // In particular if the underlying texture is 'half' based, glsl only accesses (read/write)
- // as float. So we need to cast to a half type on output.
- // When storing into a texture it is still the case the value written must be half - but
- // we don't need to do any casting there as half is coerced to float without a problem.
- SLANG_RELEASE_ASSERT(argCount >= 1);
-
- auto textureArg = args[0].get();
- if (auto baseTextureType = as<IRTextureType>(textureArg->getDataType()))
- {
- auto elementType = baseTextureType->getElementType();
- IRBasicType* underlyingType = nullptr;
- if (auto basicType = as<IRBasicType>(elementType))
- {
- underlyingType = basicType;
- }
- else if (auto vectorType = as<IRVectorType>(elementType))
- {
- underlyingType = as<IRBasicType>(vectorType->getElementType());
- }
-
- // We only need to output a cast if the underlying type is half.
- if (underlyingType && underlyingType->op == kIROp_HalfType)
- {
- emitSimpleTypeImpl(elementType);
- emit("(");
- openParenCount++;
- }
- }
- }
- break;
-
- case 'z':
- {
- // If we are calling a D3D texturing operation in the form t.Foo(s, ...),
- // where `t` is a `Texture*<T>`, then this is the step where we try to
- // properly swizzle the output of the equivalent GLSL call into the right
- // shape.
- SLANG_RELEASE_ASSERT(argCount >= 1);
-
- auto textureArg = args[0].get();
- if (auto baseTextureType = as<IRTextureType>(textureArg->getDataType()))
- {
- auto elementType = baseTextureType->getElementType();
- if (auto basicType = as<IRBasicType>(elementType))
- {
- // A scalar result is expected
- Emit(".x");
- }
- else if (auto vectorType = as<IRVectorType>(elementType))
- {
- // A vector result is expected
- auto elementCount = GetIntVal(vectorType->getElementCount());
-
- if (elementCount < 4)
- {
- char const* swiz[] = { "", ".x", ".xy", ".xyz", "" };
- Emit(swiz[elementCount]);
- }
- }
- else
- {
- // What other cases are possible?
- }
- }
- else
- {
- SLANG_UNEXPECTED("bad format in intrinsic definition");
- }
- }
- break;
-
- case 'N':
- {
- // Extract the element count from a vector argument so that
- // we can use it in the constructed expression.
-
- SLANG_RELEASE_ASSERT(*cursor >= '0' && *cursor <= '9');
- Index argIndex = (*cursor++) - '0';
- SLANG_RELEASE_ASSERT(argCount > argIndex);
-
- auto vectorArg = args[argIndex].get();
- if (auto vectorType = as<IRVectorType>(vectorArg->getDataType()))
- {
- auto elementCount = GetIntVal(vectorType->getElementCount());
- Emit(elementCount);
- }
- else
- {
- SLANG_UNEXPECTED("bad format in intrinsic definition");
- }
- }
- break;
-
- case 'V':
- {
- // Take an argument of some scalar/vector type and pad
- // it out to a 4-vector with the same element type
- // (this is the inverse of `$z`).
- //
- SLANG_RELEASE_ASSERT(*cursor >= '0' && *cursor <= '9');
- Index argIndex = (*cursor++) - '0';
- SLANG_RELEASE_ASSERT(argCount > argIndex);
-
- auto arg = args[argIndex].get();
- IRIntegerValue elementCount = 1;
- IRType* elementType = arg->getDataType();
- if (auto vectorType = as<IRVectorType>(elementType))
- {
- elementCount = GetIntVal(vectorType->getElementCount());
- elementType = vectorType->getElementType();
- }
-
- if(elementCount == 4)
- {
- // In the simple case, the operand is already a 4-vector,
- // so we can just emit it as-is.
- emitIROperand(ctx, arg, mode, kEOp_General);
- }
- else
- {
- // Otherwise, we need to construct a 4-vector from the
- // value we have, padding it out with zero elements as
- // needed.
- //
- emitVectorTypeName(elementType, 4);
- Emit("(");
- emitIROperand(ctx, arg, mode, kEOp_General);
- for(IRIntegerValue ii = elementCount; ii < 4; ++ii)
- {
- Emit(", ");
- if(getTarget(ctx) == CodeGenTarget::GLSL)
- {
- emitSimpleTypeImpl(elementType);
- Emit("(0)");
- }
- else
- {
- Emit("0");
- }
- }
- Emit(")");
- }
- }
- break;
-
- case 'a':
- {
- // We have an operation that needs to lower to either
- // `atomic*` or `imageAtomic*` for GLSL, depending on
- // whether its first operand is a subscript into an
- // array. This `$a` is the first `a` in `atomic`,
- // so we will replace it accordingly.
- //
- // TODO: This distinction should be made earlier,
- // with the front-end picking the right overload
- // based on the "address space" of the argument.
-
- Index argIndex = 0;
- SLANG_RELEASE_ASSERT(argCount > argIndex);
-
- auto arg = args[argIndex].get();
- if(arg->op == kIROp_ImageSubscript)
- {
- Emit("imageA");
- }
- else
- {
- Emit("a");
- }
- }
- break;
-
- case 'A':
- {
- // We have an operand that represents the destination
- // of an atomic operation in GLSL, and it should
- // be lowered based on whether it is an ordinary l-value,
- // or an image subscript. In the image subscript case
- // this operand will turn into multiple arguments
- // to the `imageAtomic*` function.
- //
-
- Index argIndex = 0;
- SLANG_RELEASE_ASSERT(argCount > argIndex);
-
- auto arg = args[argIndex].get();
- if(arg->op == kIROp_ImageSubscript)
- {
- if(getTarget(ctx) == CodeGenTarget::GLSL)
- {
- // TODO: we don't handle the multisample
- // case correctly here, where the last
- // component of the image coordinate needs
- // to be broken out into its own argument.
- //
- Emit("(");
- emitIROperand(ctx, arg->getOperand(0), mode, kEOp_General);
- Emit("), ");
-
- // The coordinate argument will have been computed
- // as a `vector<uint, N>` because that is how the
- // HLSL image subscript operations are defined.
- // In contrast, the GLSL `imageAtomic*` operations
- // expect `vector<int, N>` coordinates, so we
- // hill hackily insert the conversion here as
- // part of the intrinsic op.
- //
- auto coords = arg->getOperand(1);
- auto coordsType = coords->getDataType();
-
- auto coordsVecType = as<IRVectorType>(coordsType);
- IRIntegerValue elementCount = 1;
- if(coordsVecType)
- {
- coordsType = coordsVecType->getElementType();
- elementCount = GetIntVal(coordsVecType->getElementCount());
- }
-
- SLANG_ASSERT(coordsType->op == kIROp_UIntType);
-
- if (elementCount > 1)
- {
- Emit("ivec");
- emit(elementCount);
- }
- else
- {
- Emit("int");
- }
-
- Emit("(");
- emitIROperand(ctx, arg->getOperand(1), mode, kEOp_General);
- Emit(")");
- }
- else
- {
- Emit("(");
- emitIROperand(ctx, arg, mode, kEOp_General);
- Emit(")");
- }
- }
- else
- {
- Emit("(");
- emitIROperand(ctx, arg, mode, kEOp_General);
- Emit(")");
- }
- }
- break;
-
- // We will use the `$X` case as a prefix for
- // special logic needed when cross-compiling ray-tracing
- // shaders.
- case 'X':
- {
- SLANG_RELEASE_ASSERT(*cursor);
- switch(*cursor++)
- {
- case 'P':
- {
- // The `$XP` case handles looking up
- // the associated `location` for a variable
- // used as the argument ray payload at a
- // trace call site.
-
- Index argIndex = 0;
- SLANG_RELEASE_ASSERT(argCount > argIndex);
- auto arg = args[argIndex].get();
- auto argLoad = as<IRLoad>(arg);
- SLANG_RELEASE_ASSERT(argLoad);
- auto argVar = argLoad->getOperand(0);
- Emit(getRayPayloadLocation(ctx, argVar));
- }
- break;
-
- case 'C':
- {
- // The `$XC` case handles looking up
- // the associated `location` for a variable
- // used as the argument callable payload at a
- // call site.
-
- Index argIndex = 0;
- SLANG_RELEASE_ASSERT(argCount > argIndex);
- auto arg = args[argIndex].get();
- auto argLoad = as<IRLoad>(arg);
- SLANG_RELEASE_ASSERT(argLoad);
- auto argVar = argLoad->getOperand(0);
- Emit(getCallablePayloadLocation(ctx, argVar));
- }
- break;
-
- case 'T':
- {
- // The `$XT` case handles selecting between
- // the `gl_HitTNV` and `gl_RayTmaxNV` builtins,
- // based on what stage we are using:
- switch( ctx->shared->entryPoint->getStage() )
- {
- default:
- Emit("gl_RayTmaxNV");
- break;
-
- case Stage::AnyHit:
- case Stage::ClosestHit:
- Emit("gl_HitTNV");
- break;
- }
- }
- break;
-
- default:
- SLANG_RELEASE_ASSERT(false);
- break;
- }
- }
- break;
-
- default:
- SLANG_UNEXPECTED("bad format in intrinsic definition");
- break;
- }
- }
-
- // Close any remaining open parens
- for (; openParenCount > 0; --openParenCount)
- {
- Emit(")");
- }
- }
- }
-
- void emitIntrinsicCallExpr(
- EmitContext* ctx,
- IRCall* inst,
- IRFunc* func,
- IREmitMode mode,
- EOpInfo const& inOuterPrec)
- {
- auto outerPrec = inOuterPrec;
- bool needClose = false;
-
- // For a call with N arguments, the instruction will
- // have N+1 operands. We will start consuming operands
- // starting at the index 1.
- UInt operandCount = inst->getOperandCount();
- UInt argCount = operandCount - 1;
- UInt operandIndex = 1;
-
-
- //
- if (auto targetIntrinsicDecoration = findTargetIntrinsicDecoration(ctx, func))
- {
- emitTargetIntrinsicCallExpr(
- ctx,
- inst,
- func,
- targetIntrinsicDecoration,
- mode,
- outerPrec);
- return;
- }
-
- // Our current strategy for dealing with intrinsic
- // calls is to "un-mangle" the mangled name, in
- // order to figure out what the user was originally
- // calling. This is a bit messy, and there might
- // be better strategies (including just stuffing
- // a pointer to the original decl onto the callee).
-
- // If the intrinsic the user is calling is a generic,
- // then the mangled name will have been set on the
- // outer-most generic, and not on the leaf value
- // (which is `func` above), so we need to walk
- // upwards to find it.
- //
- IRInst* valueForName = func;
- for(;;)
- {
- auto parentBlock = as<IRBlock>(valueForName->parent);
- if(!parentBlock)
- break;
-
- auto parentGeneric = as<IRGeneric>(parentBlock->parent);
- if(!parentGeneric)
- break;
-
- valueForName = parentGeneric;
- }
-
- // If we reach this point, we are assuming that the value
- // has some kind of linkage, and thus a mangled name.
- //
- auto linkageDecoration = valueForName->findDecoration<IRLinkageDecoration>();
- SLANG_ASSERT(linkageDecoration);
- auto mangledName = String(linkageDecoration->getMangledName());
-
-
- // We will use the `UnmangleContext` utility to
- // help us split the original name into its pieces.
- UnmangleContext um(mangledName);
- um.startUnmangling();
-
- // We'll read through the qualified name of the
- // symbol (e.g., `Texture2D<T>.Sample`) and then
- // only keep the last segment of the name (e.g.,
- // the `Sample` part).
- auto name = um.readSimpleName();
-
- // We will special-case some names here, that
- // represent callable declarations that aren't
- // ordinary functions, and thus may use different
- // syntax.
- if(name == "operator[]")
- {
- // The user is invoking a built-in subscript operator
-
- auto prec = kEOp_Postfix;
- needClose = maybeEmitParens(outerPrec, prec);
-
- emitIROperand(ctx, inst->getOperand(operandIndex++), mode, leftSide(outerPrec, prec));
- emit("[");
- emitIROperand(ctx, inst->getOperand(operandIndex++), mode, kEOp_General);
- emit("]");
-
- if(operandIndex < operandCount)
- {
- emit(" = ");
- emitIROperand(ctx, inst->getOperand(operandIndex++), mode, kEOp_General);
- }
-
- maybeCloseParens(needClose);
- return;
- }
-
- auto prec = kEOp_Postfix;
- needClose = maybeEmitParens(outerPrec, prec);
-
- // The mangled function name currently records
- // the number of explicit parameters, and thus
- // doesn't include the implicit `this` parameter.
- // We can compare the argument and parameter counts
- // to figure out whether we have a member function call.
- UInt paramCount = um.readParamCount();
-
- if(argCount != paramCount)
- {
- // Looks like a member function call
- emitIROperand(ctx, inst->getOperand(operandIndex), mode, leftSide(outerPrec, prec));
- emit(".");
- operandIndex++;
- }
- // fixing issue #602 for GLSL sign function: https://github.com/shader-slang/slang/issues/602
- bool glslSignFix = ctx->shared->target == CodeGenTarget::GLSL && name == "sign";
- if (glslSignFix)
- {
- if (auto vectorType = as<IRVectorType>(inst->getDataType()))
- {
- emit("ivec");
- emit(as<IRConstant>(vectorType->getElementCount())->value.intVal);
- emit("(");
- }
- else if (auto scalarType = as<IRBasicType>(inst->getDataType()))
- {
- emit("int(");
- }
- else
- glslSignFix = false;
- }
- emit(name);
- emit("(");
- bool first = true;
- for(; operandIndex < operandCount; ++operandIndex )
- {
- if(!first) emit(", ");
- emitIROperand(ctx, inst->getOperand(operandIndex), mode, kEOp_General);
- first = false;
- }
- emit(")");
- if (glslSignFix)
- emit(")");
- maybeCloseParens(needClose);
- }
-
- void emitIRCallExpr(
- EmitContext* ctx,
- IRCall* inst,
- IREmitMode mode,
- EOpInfo outerPrec)
- {
- auto funcValue = inst->getOperand(0);
-
- // Does this function declare any requirements on GLSL version or
- // extensions, which should affect our output?
- if(getTarget(ctx) == CodeGenTarget::GLSL)
- {
- auto decoratedValue = funcValue;
- while (auto specInst = as<IRSpecialize>(decoratedValue))
- {
- decoratedValue = getSpecializedValue(specInst);
- }
-
- for( auto decoration : decoratedValue->getDecorations() )
- {
- switch(decoration->op)
- {
- default:
- break;
-
- case kIROp_RequireGLSLExtensionDecoration:
- requireGLSLExtension(String(((IRRequireGLSLExtensionDecoration*)decoration)->getExtensionName()));
- break;
-
- case kIROp_RequireGLSLVersionDecoration:
- requireGLSLVersion(int(((IRRequireGLSLVersionDecoration*)decoration)->getLanguageVersion()));
- break;
- }
- }
- }
-
- // We want to detect any call to an intrinsic operation,
- // that we can emit it directly without mangling, etc.
- if(auto irFunc = asTargetIntrinsic(ctx, funcValue))
- {
- emitIntrinsicCallExpr(ctx, inst, irFunc, mode, outerPrec);
- }
- else
- {
- auto prec = kEOp_Postfix;
- bool needClose = maybeEmitParens(outerPrec, prec);
-
- emitIROperand(ctx, funcValue, mode, leftSide(outerPrec, prec));
- emit("(");
- UInt argCount = inst->getOperandCount();
- for( UInt aa = 1; aa < argCount; ++aa )
- {
- auto operand = inst->getOperand(aa);
- if (as<IRVoidType>(operand->getDataType()))
- continue;
- if(aa != 1) emit(", ");
- emitIROperand(ctx, inst->getOperand(aa), mode, kEOp_General);
- }
- emit(")");
-
- maybeCloseParens(needClose);
- }
- }
-
- static const char* getGLSLVectorCompareFunctionName(IROp op)
- {
- // Glsl vector comparisons use functions...
- // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/equal.xhtml
-
- switch (op)
- {
- case kIROp_Eql: return "equal";
- case kIROp_Neq: return "notEqual";
- case kIROp_Greater: return "greaterThan";
- case kIROp_Less: return "lessThan";
- case kIROp_Geq: return "greaterThanEqual";
- case kIROp_Leq: return "lessThanEqual";
- default: return nullptr;
- }
- }
-
- void _maybeEmitGLSLCast(EmitContext* ctx, IRType* castType, IRInst* inst, IREmitMode mode)
- {
- // Wrap in cast if a cast type is specified
- if (castType)
- {
- emitIRType(ctx, castType);
- emit("(");
-
- // Emit the operand
- emitIROperand(ctx, inst, mode, kEOp_General);
-
- emit(")");
- }
- else
- {
- // Emit the operand
- emitIROperand(ctx, inst, mode, kEOp_General);
- }
- }
-
- void emitNot(EmitContext* ctx, IRInst* inst, IREmitMode mode, EOpInfo& ioOuterPrec, bool* outNeedClose)
- {
- IRInst* operand = inst->getOperand(0);
-
- if (getTarget(ctx) == CodeGenTarget::GLSL)
- {
- if (auto vectorType = as<IRVectorType>(operand->getDataType()))
- {
- // Handle as a function call
- auto prec = kEOp_Postfix;
- *outNeedClose = maybeEmitParens(ioOuterPrec, prec);
-
- emit("not(");
- emitIROperand(ctx, operand, mode, kEOp_General);
- emit(")");
- return;
- }
- }
-
- auto prec = kEOp_Prefix;
- *outNeedClose = maybeEmitParens(ioOuterPrec, prec);
-
- emit("!");
- emitIROperand(ctx, operand, mode, rightSide(prec, ioOuterPrec));
- }
-
-
- void emitComparison(EmitContext* ctx, IRInst* inst, IREmitMode mode, EOpInfo& ioOuterPrec, const EOpInfo& opPrec, bool* needCloseOut)
- {
- if (getTarget(ctx) == CodeGenTarget::GLSL)
- {
- IRInst* left = inst->getOperand(0);
- IRInst* right = inst->getOperand(1);
-
- auto leftVectorType = as<IRVectorType>(left->getDataType());
- auto rightVectorType = as<IRVectorType>(right->getDataType());
-
- // If either side is a vector handle as a vector
- if (leftVectorType || rightVectorType)
- {
- const char* funcName = getGLSLVectorCompareFunctionName(inst->op);
- SLANG_ASSERT(funcName);
-
- // Determine the vector type
- const auto vecType = leftVectorType ? leftVectorType : rightVectorType;
-
- // Handle as a function call
- auto prec = kEOp_Postfix;
- *needCloseOut = maybeEmitParens(ioOuterPrec, prec);
-
- emit(funcName);
- emit("(");
- _maybeEmitGLSLCast(ctx, (leftVectorType ? nullptr : vecType), left, mode);
- emit(",");
- _maybeEmitGLSLCast(ctx, (rightVectorType ? nullptr : vecType), right, mode);
- emit(")");
-
- return;
- }
- }
-
- *needCloseOut = maybeEmitParens(ioOuterPrec, opPrec);
-
- emitIROperand(ctx, inst->getOperand(0), mode, leftSide(ioOuterPrec, opPrec));
- emit(" ");
- emit(opPrec.op);
- emit(" ");
- emitIROperand(ctx, inst->getOperand(1), mode, rightSide(ioOuterPrec, opPrec));
- }
-
- void emitIRInstExpr(
- EmitContext* ctx,
- IRInst* inst,
- IREmitMode mode,
- EOpInfo const& inOuterPrec)
- {
- EOpInfo outerPrec = inOuterPrec;
- bool needClose = false;
- switch(inst->op)
- {
- case kIROp_IntLit:
- case kIROp_FloatLit:
- case kIROp_BoolLit:
- emitIRSimpleValue(ctx, inst);
- break;
-
- case kIROp_Construct:
- case kIROp_makeVector:
- case kIROp_MakeMatrix:
- // Simple constructor call
- if( inst->getOperandCount() == 1 && getTarget(ctx) == CodeGenTarget::HLSL)
- {
- auto prec = kEOp_Prefix;
- needClose = maybeEmitParens(outerPrec, prec);
-
- // Need to emit as cast for HLSL
- emit("(");
- emitIRType(ctx, inst->getDataType());
- emit(") ");
- emitIROperand(ctx, inst->getOperand(0), mode, rightSide(outerPrec,prec));
- }
- else
- {
- emitIRType(ctx, inst->getDataType());
- emitIRArgs(ctx, inst, mode);
- }
- break;
-
- case kIROp_constructVectorFromScalar:
-
- // Simple constructor call
- if( getTarget(ctx) == CodeGenTarget::HLSL )
- {
- auto prec = kEOp_Prefix;
- needClose = maybeEmitParens(outerPrec, prec);
-
- emit("(");
- emitIRType(ctx, inst->getDataType());
- emit(")");
-
- emitIROperand(ctx, inst->getOperand(0), mode, rightSide(outerPrec,prec));
- }
- else
- {
- auto prec = kEOp_Postfix;
- needClose = maybeEmitParens(outerPrec, prec);
-
- emitIRType(ctx, inst->getDataType());
- emit("(");
- emitIROperand(ctx, inst->getOperand(0), mode, kEOp_General);
- emit(")");
- }
- break;
-
- case kIROp_FieldExtract:
- {
- // Extract field from aggregate
-
- IRFieldExtract* fieldExtract = (IRFieldExtract*) inst;
-
- auto prec = kEOp_Postfix;
- needClose = maybeEmitParens(outerPrec, prec);
-
- auto base = fieldExtract->getBase();
- emitIROperand(ctx, base, mode, leftSide(outerPrec, prec));
- emit(".");
- if(getTarget(ctx) == CodeGenTarget::GLSL
- && as<IRUniformParameterGroupType>(base->getDataType()))
- {
- emit("_data.");
- }
- emit(getIRName(fieldExtract->getField()));
- }
- break;
-
- case kIROp_FieldAddress:
- {
- // Extract field "address" from aggregate
-
- IRFieldAddress* ii = (IRFieldAddress*) inst;
-
- auto prec = kEOp_Postfix;
- needClose = maybeEmitParens(outerPrec, prec);
-
- auto base = ii->getBase();
- emitIROperand(ctx, base, mode, leftSide(outerPrec, prec));
- emit(".");
- if(getTarget(ctx) == CodeGenTarget::GLSL
- && as<IRUniformParameterGroupType>(base->getDataType()))
- {
- emit("_data.");
- }
- emit(getIRName(ii->getField()));
- }
- break;
-
-
-#define CASE_COMPARE(OPCODE, PREC, OP) \
- case OPCODE: \
- emitComparison(ctx, inst, mode, outerPrec, kEOp_##PREC, &needClose); \
- break
-
-#define CASE(OPCODE, PREC, OP) \
- case OPCODE: \
- needClose = maybeEmitParens(outerPrec, kEOp_##PREC); \
- emitIROperand(ctx, inst->getOperand(0), mode, leftSide(outerPrec, kEOp_##PREC)); \
- emit(" " #OP " "); \
- emitIROperand(ctx, inst->getOperand(1), mode, rightSide(outerPrec, kEOp_##PREC)); \
- break
-
- CASE(kIROp_Add, Add, +);
- CASE(kIROp_Sub, Sub, -);
- CASE(kIROp_Div, Div, /);
- CASE(kIROp_Mod, Mod, %);
-
- CASE(kIROp_Lsh, Lsh, <<);
- CASE(kIROp_Rsh, Rsh, >>);
-
- // TODO: Need to pull out component-wise
- // comparison cases for matrices/vectors
- CASE_COMPARE(kIROp_Eql, Eql, ==);
- CASE_COMPARE(kIROp_Neq, Neq, !=);
- CASE_COMPARE(kIROp_Greater, Greater, >);
- CASE_COMPARE(kIROp_Less, Less, <);
- CASE_COMPARE(kIROp_Geq, Geq, >=);
- CASE_COMPARE(kIROp_Leq, Leq, <=);
-
- CASE(kIROp_BitXor, BitXor, ^);
-
- CASE(kIROp_And, And, &&);
- CASE(kIROp_Or, Or, ||);
-
-#undef CASE
-
- // Component-wise ultiplication needs to be special cased,
- // because GLSL uses infix `*` to express inner product
- // when working with matrices.
- case kIROp_Mul:
- // Are we targetting GLSL, and are both operands matrices?
- if(getTarget(ctx) == CodeGenTarget::GLSL
- && as<IRMatrixType>(inst->getOperand(0)->getDataType())
- && as<IRMatrixType>(inst->getOperand(1)->getDataType()))
- {
- emit("matrixCompMult(");
- emitIROperand(ctx, inst->getOperand(0), mode, kEOp_General);
- emit(", ");
- emitIROperand(ctx, inst->getOperand(1), mode, kEOp_General);
- emit(")");
- }
- else
- {
- // Default handling is to just rely on infix
- // `operator*`.
- auto prec = kEOp_Mul;
- needClose = maybeEmitParens(outerPrec, prec);
- emitIROperand(ctx, inst->getOperand(0), mode, leftSide(outerPrec, prec));
- emit(" * ");
- emitIROperand(ctx, inst->getOperand(1), mode, rightSide(prec, outerPrec));
- }
- break;
-
- case kIROp_Not:
- {
- emitNot(ctx, inst, mode, outerPrec, &needClose);
- }
- break;
-
- case kIROp_Neg:
- {
- auto prec = kEOp_Prefix;
- needClose = maybeEmitParens(outerPrec, prec);
-
- emit("-");
- emitIROperand(ctx, inst->getOperand(0), mode, rightSide(prec, outerPrec));
- }
- break;
-
- case kIROp_BitNot:
- {
- auto prec = kEOp_Prefix;
- needClose = maybeEmitParens(outerPrec, prec);
-
- if (as<IRBoolType>(inst->getDataType()))
- {
- emit("!");
- }
- else
- {
- emit("~");
- }
- emitIROperand(ctx, inst->getOperand(0), mode, rightSide(prec, outerPrec));
- }
- break;
-
- case kIROp_BitAnd:
- {
- auto prec = kEOp_BitAnd;
- needClose = maybeEmitParens(outerPrec, prec);
-
- // TODO: handle a bitwise And of a vector of bools by casting to
- // a uvec and performing the bitwise operation
-
- emitIROperand(ctx, inst->getOperand(0), mode, leftSide(outerPrec, prec));
-
- // Are we targetting GLSL, and are both operands scalar bools?
- // In that case convert the operation to a logical And
- if (getTarget(ctx) == CodeGenTarget::GLSL
- && as<IRBoolType>(inst->getOperand(0)->getDataType())
- && as<IRBoolType>(inst->getOperand(1)->getDataType()))
- {
- emit("&&");
- }
- else
- {
- emit("&");
- }
-
- emitIROperand(ctx, inst->getOperand(1), mode, rightSide(outerPrec, prec));
- }
- break;
-
- case kIROp_BitOr:
- {
- auto prec = kEOp_BitOr;
- needClose = maybeEmitParens(outerPrec, prec);
-
- // TODO: handle a bitwise Or of a vector of bools by casting to
- // a uvec and performing the bitwise operation
-
- emitIROperand(ctx, inst->getOperand(0), mode, leftSide(outerPrec, prec));
-
- // Are we targetting GLSL, and are both operands scalar bools?
- // In that case convert the operation to a logical Or
- if (getTarget(ctx) == CodeGenTarget::GLSL
- && as<IRBoolType>(inst->getOperand(0)->getDataType())
- && as<IRBoolType>(inst->getOperand(1)->getDataType()))
- {
- emit("||");
- }
- else
- {
- emit("|");
- }
-
- emitIROperand(ctx, inst->getOperand(1), mode, rightSide(outerPrec, prec));
- }
- break;
-
- case kIROp_Load:
- {
- auto base = inst->getOperand(0);
- emitIROperand(ctx, base, mode, outerPrec);
- if(getTarget(ctx) == CodeGenTarget::GLSL
- && as<IRUniformParameterGroupType>(base->getDataType()))
- {
- emit("._data");
- }
- }
- break;
-
- case kIROp_Store:
- {
- auto prec = kEOp_Assign;
- needClose = maybeEmitParens(outerPrec, prec);
-
- emitIROperand(ctx, inst->getOperand(0), mode, leftSide(outerPrec, prec));
- emit(" = ");
- emitIROperand(ctx, inst->getOperand(1), mode, rightSide(prec, outerPrec));
- }
- break;
-
- case kIROp_Call:
- {
- emitIRCallExpr(ctx, (IRCall*)inst, mode, outerPrec);
- }
- break;
-
- case kIROp_GroupMemoryBarrierWithGroupSync:
- emit("GroupMemoryBarrierWithGroupSync()");
- break;
-
- case kIROp_getElement:
- case kIROp_getElementPtr:
- case kIROp_ImageSubscript:
- // HACK: deal with translation of GLSL geometry shader input arrays.
- if(auto decoration = inst->getOperand(0)->findDecoration<IRGLSLOuterArrayDecoration>())
- {
- auto prec = kEOp_Postfix;
- needClose = maybeEmitParens(outerPrec, prec);
-
- emit(decoration->getOuterArrayName());
- emit("[");
- emitIROperand(ctx, inst->getOperand(1), mode, kEOp_General);
- emit("].");
- emitIROperand(ctx, inst->getOperand(0), mode, rightSide(prec, outerPrec));
- break;
- }
- else
- {
- auto prec = kEOp_Postfix;
- needClose = maybeEmitParens(outerPrec, prec);
-
- emitIROperand(ctx, inst->getOperand(0), mode, leftSide(outerPrec, prec));
- emit("[");
- emitIROperand(ctx, inst->getOperand(1), mode, kEOp_General);
- emit("]");
- }
- break;
-
- case kIROp_Mul_Vector_Matrix:
- case kIROp_Mul_Matrix_Vector:
- case kIROp_Mul_Matrix_Matrix:
- if(getTarget(ctx) == CodeGenTarget::GLSL)
- {
- // GLSL expresses inner-product multiplications
- // with the ordinary infix `*` operator.
- //
- // Note that the order of the operands is reversed
- // compared to HLSL (and Slang's internal representation)
- // because the notion of what is a "row" vs. a "column"
- // is reversed between HLSL/Slang and GLSL.
- //
- auto prec = kEOp_Mul;
- needClose = maybeEmitParens(outerPrec, prec);
-
- emitIROperand(ctx, inst->getOperand(1), mode, leftSide(outerPrec, prec));
- emit(" * ");
- emitIROperand(ctx, inst->getOperand(0), mode, rightSide(prec, outerPrec));
- }
- else
- {
- emit("mul(");
- emitIROperand(ctx, inst->getOperand(0), mode, kEOp_General);
- emit(", ");
- emitIROperand(ctx, inst->getOperand(1), mode, kEOp_General);
- emit(")");
- }
- break;
-
- case kIROp_swizzle:
- {
- auto prec = kEOp_Postfix;
- needClose = maybeEmitParens(outerPrec, prec);
-
- auto ii = (IRSwizzle*)inst;
- emitIROperand(ctx, ii->getBase(), mode, leftSide(outerPrec, prec));
- emit(".");
- const Index elementCount = Index(ii->getElementCount());
- for (Index ee = 0; ee < elementCount; ++ee)
- {
- IRInst* irElementIndex = ii->getElementIndex(ee);
- SLANG_RELEASE_ASSERT(irElementIndex->op == kIROp_IntLit);
- IRConstant* irConst = (IRConstant*)irElementIndex;
-
- UInt elementIndex = (UInt)irConst->value.intVal;
- SLANG_RELEASE_ASSERT(elementIndex < 4);
-
- char const* kComponents[] = { "x", "y", "z", "w" };
- emit(kComponents[elementIndex]);
- }
- }
- break;
-
- case kIROp_Specialize:
- {
- emitIROperand(ctx, inst->getOperand(0), mode, outerPrec);
- }
- break;
-
- case kIROp_Select:
- {
- if (getTarget(ctx) == CodeGenTarget::GLSL &&
- inst->getOperand(0)->getDataType()->op != kIROp_BoolType)
- {
- // For GLSL, emit a call to `mix` if condition is a vector
- emit("mix(");
- emitIROperand(ctx, inst->getOperand(2), mode, leftSide(kEOp_General, kEOp_General));
- emit(", ");
- emitIROperand(ctx, inst->getOperand(1), mode, leftSide(kEOp_General, kEOp_General));
- emit(", ");
- emitIROperand(ctx, inst->getOperand(0), mode, leftSide(kEOp_General, kEOp_General));
- emit(")");
- }
- else
- {
- auto prec = kEOp_Conditional;
- needClose = maybeEmitParens(outerPrec, prec);
-
- emitIROperand(ctx, inst->getOperand(0), mode, leftSide(outerPrec, prec));
- emit(" ? ");
- emitIROperand(ctx, inst->getOperand(1), mode, prec);
- emit(" : ");
- emitIROperand(ctx, inst->getOperand(2), mode, rightSide(prec, outerPrec));
- }
- }
- break;
-
- case kIROp_Param:
- emit(getIRName(inst));
- break;
-
- case kIROp_makeArray:
- case kIROp_makeStruct:
- {
- // TODO: initializer-list syntax may not always
- // be appropriate, depending on the context
- // of the expression.
-
- emit("{ ");
- UInt argCount = inst->getOperandCount();
- for (UInt aa = 0; aa < argCount; ++aa)
- {
- if (aa != 0) emit(", ");
- emitIROperand(ctx, inst->getOperand(aa), mode, kEOp_General);
- }
- emit(" }");
- }
- break;
-
- case kIROp_BitCast:
- {
- // TODO: we can simplify the logic for arbitrary bitcasts
- // by always bitcasting the source to a `uint*` type (if it
- // isn't already) and then bitcasting that to the destination
- // type (if it isn't already `uint*`.
- //
- // For now we are assuming the source type is *already*
- // a `uint*` type of the appropriate size.
- //
-// auto fromType = extractBaseType(inst->getOperand(0)->getDataType());
- auto toType = extractBaseType(inst->getDataType());
- switch(getTarget(ctx))
- {
- case CodeGenTarget::GLSL:
- switch(toType)
- {
- default:
- emit("/* unhandled */");
- break;
-
- case BaseType::UInt:
- break;
-
- case BaseType::Int:
- emitIRType(ctx, inst->getDataType());
- break;
-
- case BaseType::Float:
- emit("uintBitsToFloat(");
- break;
- }
- break;
-
- case CodeGenTarget::HLSL:
- switch(toType)
- {
- default:
- emit("/* unhandled */");
- break;
-
- case BaseType::UInt:
- break;
- case BaseType::Int:
- emit("(");
- emitIRType(ctx, inst->getDataType());
- emit(")");
- break;
- case BaseType::Float:
- emit("asfloat");
- break;
- }
- break;
-
-
- default:
- SLANG_UNEXPECTED("unhandled codegen target");
- break;
- }
-
- emit("(");
- emitIROperand(ctx, inst->getOperand(0), mode, kEOp_General);
- emit(")");
- }
- break;
-
- default:
- emit("/* unhandled */");
- break;
- }
- maybeCloseParens(needClose);
- }
-
- BaseType extractBaseType(IRType* inType)
- {
- auto type = inType;
- for(;;)
- {
- if(auto irBaseType = as<IRBasicType>(type))
- {
- return irBaseType->getBaseType();
- }
- else if(auto vecType = as<IRVectorType>(type))
- {
- type = vecType->getElementType();
- continue;
- }
- else
- {
- return BaseType::Void;
- }
- }
- }
-
- void emitIRInst(
- EmitContext* ctx,
- IRInst* inst,
- IREmitMode mode)
- {
- try
- {
- emitIRInstImpl(ctx, inst, mode);
- }
- // Don't emit any context message for an explicit `AbortCompilationException`
- // because it should only happen when an error is already emitted.
- catch(AbortCompilationException&) { throw; }
- catch(...)
- {
- ctx->noteInternalErrorLoc(inst->sourceLoc);
- throw;
- }
- }
-
- void emitIRInstImpl(
- EmitContext* ctx,
- IRInst* inst,
- IREmitMode mode)
- {
- if (shouldFoldIRInstIntoUseSites(ctx, inst, mode))
- {
- return;
- }
-
- advanceToSourceLocation(inst->sourceLoc);
-
- switch(inst->op)
- {
- default:
- emitIRInstResultDecl(ctx, inst);
- emitIRInstExpr(ctx, inst, mode, kEOp_General);
- emit(";\n");
- break;
-
- case kIROp_undefined:
- {
- auto type = inst->getDataType();
- emitIRType(ctx, type, getIRName(inst));
- emit(";\n");
- }
- break;
-
- case kIROp_Var:
- {
- auto ptrType = cast<IRPtrType>(inst->getDataType());
- auto valType = ptrType->getValueType();
-
- auto name = getIRName(inst);
- emitIRRateQualifiers(ctx, inst);
- emitIRType(ctx, valType, name);
- emit(";\n");
- }
- break;
-
- case kIROp_Param:
- // Don't emit parameters, since they are declared as part of the function.
- break;
-
- case kIROp_FieldAddress:
- // skip during code emit, since it should be
- // folded into use site(s)
- break;
-
- case kIROp_ReturnVoid:
- emit("return;\n");
- break;
-
- case kIROp_ReturnVal:
- emit("return ");
- emitIROperand(ctx, ((IRReturnVal*) inst)->getVal(), mode, kEOp_General);
- emit(";\n");
- break;
-
- case kIROp_discard:
- emit("discard;\n");
- break;
-
- case kIROp_swizzleSet:
- {
- auto ii = (IRSwizzleSet*)inst;
- emitIRInstResultDecl(ctx, inst);
- emitIROperand(ctx, inst->getOperand(0), mode, kEOp_General);
- emit(";\n");
-
- auto subscriptOuter = kEOp_General;
- auto subscriptPrec = kEOp_Postfix;
- bool needCloseSubscript = maybeEmitParens(subscriptOuter, subscriptPrec);
-
- emitIROperand(ctx, inst, mode, leftSide(subscriptOuter, subscriptPrec));
- emit(".");
- UInt elementCount = ii->getElementCount();
- for (UInt ee = 0; ee < elementCount; ++ee)
- {
- IRInst* irElementIndex = ii->getElementIndex(ee);
- SLANG_RELEASE_ASSERT(irElementIndex->op == kIROp_IntLit);
- IRConstant* irConst = (IRConstant*)irElementIndex;
-
- UInt elementIndex = (UInt)irConst->value.intVal;
- SLANG_RELEASE_ASSERT(elementIndex < 4);
-
- char const* kComponents[] = { "x", "y", "z", "w" };
- emit(kComponents[elementIndex]);
- }
- maybeCloseParens(needCloseSubscript);
-
- emit(" = ");
- emitIROperand(ctx, inst->getOperand(1), mode, kEOp_General);
- emit(";\n");
- }
- break;
-
- case kIROp_SwizzledStore:
- {
- auto subscriptOuter = kEOp_General;
- auto subscriptPrec = kEOp_Postfix;
- bool needCloseSubscript = maybeEmitParens(subscriptOuter, subscriptPrec);
-
-
- auto ii = cast<IRSwizzledStore>(inst);
- emitIROperand(ctx, ii->getDest(), mode, leftSide(subscriptOuter, subscriptPrec));
- emit(".");
- UInt elementCount = ii->getElementCount();
- for (UInt ee = 0; ee < elementCount; ++ee)
- {
- IRInst* irElementIndex = ii->getElementIndex(ee);
- SLANG_RELEASE_ASSERT(irElementIndex->op == kIROp_IntLit);
- IRConstant* irConst = (IRConstant*)irElementIndex;
-
- UInt elementIndex = (UInt)irConst->value.intVal;
- SLANG_RELEASE_ASSERT(elementIndex < 4);
-
- char const* kComponents[] = { "x", "y", "z", "w" };
- emit(kComponents[elementIndex]);
- }
- maybeCloseParens(needCloseSubscript);
-
- emit(" = ");
- emitIROperand(ctx, ii->getSource(), mode, kEOp_General);
- emit(";\n");
- }
- break;
- }
- }
-
- void emitIRSemantics(
- EmitContext*,
- VarLayout* varLayout)
- {
- if(varLayout->flags & VarLayoutFlag::HasSemantic)
- {
- Emit(" : ");
- emit(varLayout->semanticName);
- if(varLayout->semanticIndex)
- {
- Emit(varLayout->semanticIndex);
- }
- }
- }
-
- void emitIRSemantics(
- EmitContext* ctx,
- IRInst* inst)
- {
- // Don't emit semantics if we aren't translating down to HLSL
- switch (ctx->shared->target)
- {
- case CodeGenTarget::HLSL:
- break;
-
- default:
- return;
- }
-
- if (auto semanticDecoration = inst->findDecoration<IRSemanticDecoration>())
- {
- Emit(" : ");
- emit(semanticDecoration->getSemanticName());
- return;
- }
-
- if(auto layoutDecoration = inst->findDecoration<IRLayoutDecoration>())
- {
- auto layout = layoutDecoration->getLayout();
- if(auto varLayout = as<VarLayout>(layout))
- {
- emitIRSemantics(ctx, varLayout);
- }
- else if (auto entryPointLayout = as<EntryPointLayout>(layout))
- {
- if(auto resultLayout = entryPointLayout->resultLayout)
- {
- emitIRSemantics(ctx, resultLayout);
- }
- }
- }
- }
-
- VarLayout* getVarLayout(
- EmitContext* /*context*/,
- IRInst* var)
- {
- auto decoration = var->findDecoration<IRLayoutDecoration>();
- if (!decoration)
- return nullptr;
-
- return (VarLayout*) decoration->getLayout();
- }
-
- void emitIRLayoutSemantics(
- EmitContext* ctx,
- IRInst* inst,
- char const* uniformSemanticSpelling = "register")
- {
- auto layout = getVarLayout(ctx, inst);
- if (layout)
- {
- emitHLSLRegisterSemantics(layout, uniformSemanticSpelling);
- }
- }
-
- // When we are about to traverse an edge from one block to another,
- // we need to emit the assignments that conceptually occur "along"
- // the edge. In traditional SSA these are the phi nodes in the
- // target block, while in our representation these use the arguments
- // to the branch instruction to fill in the parameters of the target.
- void emitPhiVarAssignments(
- EmitContext* ctx,
- UInt argCount,
- IRUse* args,
- IRBlock* targetBlock)
- {
- UInt argCounter = 0;
- for (auto pp = targetBlock->getFirstParam(); pp; pp = pp->getNextParam())
- {
- UInt argIndex = argCounter++;
-
- if (argIndex >= argCount)
- {
- SLANG_UNEXPECTED("not enough arguments for branch");
- break;
- }
-
- IRInst* arg = args[argIndex].get();
-
- auto outerPrec = kEOp_General;
- auto prec = kEOp_Assign;
-
- emitIROperand(ctx, pp, IREmitMode::Default, leftSide(outerPrec, prec));
- emit(" = ");
- emitIROperand(ctx, arg, IREmitMode::Default, rightSide(prec, outerPrec));
- emit(";\n");
- }
- }
-
- /// Emit high-level language statements from a structrured region.
- void emitRegion(
- EmitContext* ctx,
- Region* inRegion)
- {
- // We will use a loop so that we can process sequential (simple)
- // regions iteratively rather than recursively.
- // This is effectively an emulation of tail recursion.
- Region* region = inRegion;
- while(region)
- {
- // What flavor of region are we trying to emit?
- switch(region->getFlavor())
- {
- case Region::Flavor::Simple:
- {
- // A simple region consists of a basic block followed
- // by another region.
- //
- auto simpleRegion = (SimpleRegion*) region;
-
- // We start by outputting all of the non-terminator
- // instructions in the block.
- //
- auto block = simpleRegion->block;
- auto terminator = block->getTerminator();
- for (auto inst = block->getFirstInst(); inst != terminator; inst = inst->getNextInst())
- {
- emitIRInst(ctx, inst, IREmitMode::Default);
- }
-
- // Next we have to deal with the terminator instruction
- // itself. In many cases, the terminator will have been
- // turned into a block of its own, but certain cases
- // of terminators are simple enough that we just fold
- // them into the current block.
- //
- advanceToSourceLocation(terminator->sourceLoc);
- switch(terminator->op)
- {
- default:
- // Don't do anything with the terminator, and assume
- // its behavior has been folded into the next region.
- break;
-
- case kIROp_ReturnVal:
- case kIROp_ReturnVoid:
- case kIROp_discard:
- // For extremely simple terminators, we just handle
- // them here, so that we don't have to allocate
- // separate `Region`s for them.
- emitIRInst(ctx, terminator, IREmitMode::Default);
- break;
-
- // We will also handle any unconditional branches
- // here, since they may have arguments to pass
- // to the target block (our encoding of SSA
- // "phi" operations).
- //
- // TODO: A better approach would be to move out of SSA
- // as an IR pass, and introduce explicit variables to
- // replace any "phi nodes." This would avoid possible
- // complications if we ever end up in the bad case where
- // one of the block arguments on a branch is also
- // a paremter of the target block, so that the order
- // of operations is important.
- //
- case kIROp_unconditionalBranch:
- {
- auto t = (IRUnconditionalBranch*)terminator;
- UInt argCount = t->getOperandCount();
- static const UInt kFixedArgCount = 1;
- emitPhiVarAssignments(
- ctx,
- argCount - kFixedArgCount,
- t->getOperands() + kFixedArgCount,
- t->getTargetBlock());
- }
- break;
- case kIROp_loop:
- {
- auto t = (IRLoop*) terminator;
- UInt argCount = t->getOperandCount();
- static const UInt kFixedArgCount = 3;
- emitPhiVarAssignments(
- ctx,
- argCount - kFixedArgCount,
- t->getOperands() + kFixedArgCount,
- t->getTargetBlock());
-
- }
- break;
- }
-
- // If the terminator required a full region to represent
- // its behavior in a structured form, then we will move
- // along to that region now.
- //
- // We do this iteratively rather than recursively, by
- // jumping back to the top of our loop with a new
- // value for `region`.
- //
- region = simpleRegion->nextRegion;
- continue;
- }
-
- // Break and continue regions are trivial to handle, as long as we
- // don't need to consider multi-level break/continue (which we
- // don't for now).
- case Region::Flavor::Break:
- emit("break;\n");
- break;
- case Region::Flavor::Continue:
- emit("continue;\n");
- break;
-
- case Region::Flavor::If:
- {
- auto ifRegion = (IfRegion*) region;
-
- // TODO: consider simplifying the code in
- // the case where `ifRegion == null`
- // so that we output `if(!condition) { elseRegion }`
- // instead of the current `if(condition) {} else { elseRegion }`
-
- emit("if(");
- emitIROperand(ctx, ifRegion->condition, IREmitMode::Default, kEOp_General);
- emit(")\n{\n");
- indent();
- emitRegion(ctx, ifRegion->thenRegion);
- dedent();
- emit("}\n");
-
- // Don't emit the `else` region if it would be empty
- //
- if(auto elseRegion = ifRegion->elseRegion)
- {
- emit("else\n{\n");
- indent();
- emitRegion(ctx, elseRegion);
- dedent();
- emit("}\n");
- }
-
- // Continue with the region after the `if`.
- //
- // TODO: consider just constructing a `SimpleRegion`
- // around an `IfRegion` to handle this sequencing,
- // rather than making `IfRegion` serve as both a
- // conditional and a sequence.
- //
- region = ifRegion->nextRegion;
- continue;
- }
- break;
-
- case Region::Flavor::Loop:
- {
- auto loopRegion = (LoopRegion*) region;
- auto loopInst = loopRegion->loopInst;
-
- // If the user applied an explicit decoration to the loop,
- // to control its unrolling behavior, then pass that
- // along in the output code (if the target language
- // supports the semantics of the decoration).
- //
- if (auto loopControlDecoration = loopInst->findDecoration<IRLoopControlDecoration>())
- {
- switch (loopControlDecoration->getMode())
- {
- case kIRLoopControl_Unroll:
- // Note: loop unrolling control is only available in HLSL, not GLSL
- if(getTarget(ctx) == CodeGenTarget::HLSL)
- {
- emit("[unroll]\n");
- }
- break;
-
- default:
- break;
- }
- }
-
- emit("for(;;)\n{\n");
- indent();
- emitRegion(ctx, loopRegion->body);
- dedent();
- emit("}\n");
-
- // Continue with the region after the loop
- region = loopRegion->nextRegion;
- continue;
- }
-
- case Region::Flavor::Switch:
- {
- auto switchRegion = (SwitchRegion*) region;
-
- // Emit the start of our statement.
- emit("switch(");
- emitIROperand(ctx, switchRegion->condition, IREmitMode::Default, kEOp_General);
- emit(")\n{\n");
-
- auto defaultCase = switchRegion->defaultCase;
- for(auto currentCase : switchRegion->cases)
- {
- for(auto caseVal : currentCase->values)
- {
- emit("case ");
- emitIROperand(ctx, caseVal, IREmitMode::Default, kEOp_General);
- emit(":\n");
- }
- if(currentCase.Ptr() == defaultCase)
- {
- emit("default:\n");
- }
-
- indent();
- emit("{\n");
- indent();
- emitRegion(ctx, currentCase->body);
- dedent();
- emit("}\n");
- dedent();
- }
-
- emit("}\n");
-
- // Continue with the region after the `switch`
- region = switchRegion->nextRegion;
- continue;
- }
- break;
- }
- break;
- }
- }
-
- /// Emit high-level language statements from a structured region tree.
- void emitRegionTree(
- EmitContext* ctx,
- RegionTree* regionTree)
- {
- emitRegion(ctx, regionTree->rootRegion);
- }
-
- // Is an IR function a definition? (otherwise it is a declaration)
- bool isDefinition(IRFunc* func)
- {
- // For now, we use a simple approach: a function is
- // a definition if it has any blocks, and a declaration otherwise.
- return func->getFirstBlock() != nullptr;
- }
-
- String getIRFuncName(
- IRFunc* func)
- {
- if (auto entryPointLayout = asEntryPoint(func))
- {
- // GLSL will always need to use `main` as the
- // name for an entry-point function, but other
- // targets should try to use the original name.
- //
- // TODO: always use `main`, and have any code
- // that wraps this know to use `main` instead
- // of the original entry-point name...
- //
- if (getTarget(context) != CodeGenTarget::GLSL)
- {
- return getText(entryPointLayout->entryPoint->getName());
- }
-
- //
-
- return "main";
- }
- else
- {
- return getIRName(func);
- }
- }
-
- void emitAttributeSingleString(const char* name, FuncDecl* entryPoint, Attribute* attrib)
- {
- assert(attrib);
-
- attrib->args.getCount();
- if (attrib->args.getCount() != 1)
- {
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute expects single parameter");
- return;
- }
-
- Expr* expr = attrib->args[0];
-
- auto stringLitExpr = as<StringLiteralExpr>(expr);
- if (!stringLitExpr)
- {
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute parameter expecting to be a string ");
- return;
- }
-
- emit("[");
- emit(name);
- emit("(\"");
- emit(stringLitExpr->value);
- emit("\")]\n");
- }
-
- void emitAttributeSingleInt(const char* name, FuncDecl* entryPoint, Attribute* attrib)
- {
- assert(attrib);
-
- attrib->args.getCount();
- if (attrib->args.getCount() != 1)
- {
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute expects single parameter");
- return;
- }
-
- Expr* expr = attrib->args[0];
-
- auto intLitExpr = as<IntegerLiteralExpr>(expr);
- if (!intLitExpr)
- {
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute expects an int");
- return;
- }
-
- emit("[");
- emit(name);
- emit("(");
- emit(intLitExpr->value);
- emit(")]\n");
- }
-
- void emitFuncDeclPatchConstantFuncAttribute(IRFunc* irFunc, FuncDecl* entryPoint, PatchConstantFuncAttribute* attrib)
- {
- SLANG_UNUSED(attrib);
-
- auto irPatchFunc = irFunc->findDecoration<IRPatchConstantFuncDecoration>();
- assert(irPatchFunc);
- if (!irPatchFunc)
- {
- SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Unable to find [patchConstantFunc(...)] decoration");
- return;
- }
-
- const String irName = getIRName(irPatchFunc->getFunc());
-
- emit("[patchconstantfunc(\"");
- emit(irName);
- emit("\")]\n");
- }
-
- void emitIREntryPointAttributes_HLSL(
- IRFunc* irFunc,
- EmitContext* ctx,
- EntryPointLayout* entryPointLayout)
- {
- auto profile = ctx->shared->effectiveProfile;
- auto stage = entryPointLayout->profile.GetStage();
-
- if(profile.getFamily() == ProfileFamily::DX)
- {
- if(profile.GetVersion() >= ProfileVersion::DX_6_1 )
- {
- char const* stageName = getStageName(stage);
- if(stageName)
- {
- emit("[shader(\"");
- emit(stageName);
- emit("\")]");
- }
- }
- }
-
- switch (stage)
- {
- case Stage::Compute:
- {
- static const UInt kAxisCount = 3;
- UInt sizeAlongAxis[kAxisCount];
-
- // TODO: this is kind of gross because we are using a public
- // reflection API function, rather than some kind of internal
- // utility it forwards to...
- spReflectionEntryPoint_getComputeThreadGroupSize(
- (SlangReflectionEntryPoint*)entryPointLayout,
- kAxisCount,
- &sizeAlongAxis[0]);
-
- emit("[numthreads(");
- for (int ii = 0; ii < 3; ++ii)
- {
- if (ii != 0) emit(", ");
- Emit(sizeAlongAxis[ii]);
- }
- emit(")]\n");
- }
- break;
- case Stage::Geometry:
- {
- if (auto attrib = entryPointLayout->entryPoint->FindModifier<MaxVertexCountAttribute>())
- {
- emit("[maxvertexcount(");
- Emit(attrib->value);
- emit(")]\n");
- }
- if (auto attrib = entryPointLayout->entryPoint->FindModifier<InstanceAttribute>())
- {
- emit("[instance(");
- Emit(attrib->value);
- emit(")]\n");
- }
- break;
- }
- case Stage::Domain:
- {
- FuncDecl* entryPoint = entryPointLayout->entryPoint;
- /* [domain("isoline")] */
- if (auto attrib = entryPoint->FindModifier<DomainAttribute>())
- {
- emitAttributeSingleString("domain", entryPoint, attrib);
- }
-
- break;
- }
- case Stage::Hull:
- {
- // Lists these are only attributes for hull shader
- // https://docs.microsoft.com/en-us/windows/desktop/direct3d11/direct3d-11-advanced-stages-hull-shader-design
-
- FuncDecl* entryPoint = entryPointLayout->entryPoint;
-
- /* [domain("isoline")] */
- if (auto attrib = entryPoint->FindModifier<DomainAttribute>())
- {
- emitAttributeSingleString("domain", entryPoint, attrib);
- }
- /* [domain("partitioning")] */
- if (auto attrib = entryPoint->FindModifier<PartitioningAttribute>())
- {
- emitAttributeSingleString("partitioning", entryPoint, attrib);
- }
- /* [outputtopology("line")] */
- if (auto attrib = entryPoint->FindModifier<OutputTopologyAttribute>())
- {
- emitAttributeSingleString("outputtopology", entryPoint, attrib);
- }
- /* [outputcontrolpoints(4)] */
- if (auto attrib = entryPoint->FindModifier<OutputControlPointsAttribute>())
- {
- emitAttributeSingleInt("outputcontrolpoints", entryPoint, attrib);
- }
- /* [patchconstantfunc("HSConst")] */
- if (auto attrib = entryPoint->FindModifier<PatchConstantFuncAttribute>())
- {
- emitFuncDeclPatchConstantFuncAttribute(irFunc, entryPoint, attrib);
- }
-
- break;
- }
- case Stage::Pixel:
- {
- if (irFunc->findDecoration<IREarlyDepthStencilDecoration>())
- {
- emit("[earlydepthstencil]\n");
- }
- break;
- }
- // TODO: There are other stages that will need this kind of handling.
- default:
- break;
- }
- }
-
- void emitIREntryPointAttributes_GLSL(
- IRFunc* irFunc,
- EmitContext* /*ctx*/,
- EntryPointLayout* entryPointLayout)
- {
- auto profile = entryPointLayout->profile;
- auto stage = profile.GetStage();
-
- switch (stage)
- {
- case Stage::Compute:
- {
- static const UInt kAxisCount = 3;
- UInt sizeAlongAxis[kAxisCount];
-
- // TODO: this is kind of gross because we are using a public
- // reflection API function, rather than some kind of internal
- // utility it forwards to...
- spReflectionEntryPoint_getComputeThreadGroupSize(
- (SlangReflectionEntryPoint*)entryPointLayout,
- kAxisCount,
- &sizeAlongAxis[0]);
-
- emit("layout(");
- char const* axes[] = { "x", "y", "z" };
- for (int ii = 0; ii < 3; ++ii)
- {
- if (ii != 0) emit(", ");
- emit("local_size_");
- emit(axes[ii]);
- emit(" = ");
- Emit(sizeAlongAxis[ii]);
- }
- emit(") in;");
- }
- break;
- case Stage::Geometry:
- {
- if (auto attrib = entryPointLayout->entryPoint->FindModifier<MaxVertexCountAttribute>())
- {
- emit("layout(max_vertices = ");
- Emit(attrib->value);
- emit(") out;\n");
- }
- if (auto attrib = entryPointLayout->entryPoint->FindModifier<InstanceAttribute>())
- {
- emit("layout(invocations = ");
- Emit(attrib->value);
- emit(") in;\n");
- }
-
- for(auto pp : entryPointLayout->entryPoint->GetParameters())
- {
- if(auto inputPrimitiveTypeModifier = pp->FindModifier<HLSLGeometryShaderInputPrimitiveTypeModifier>())
- {
- if(as<HLSLTriangleModifier>(inputPrimitiveTypeModifier))
- {
- emit("layout(triangles) in;\n");
- }
- else if(as<HLSLLineModifier>(inputPrimitiveTypeModifier))
- {
- emit("layout(lines) in;\n");
- }
- else if(as<HLSLLineAdjModifier>(inputPrimitiveTypeModifier))
- {
- emit("layout(lines_adjacency) in;\n");
- }
- else if(as<HLSLPointModifier>(inputPrimitiveTypeModifier))
- {
- emit("layout(points) in;\n");
- }
- else if(as<HLSLTriangleAdjModifier>(inputPrimitiveTypeModifier))
- {
- emit("layout(triangles_adjacency) in;\n");
- }
- }
-
- if(auto outputStreamType = as<HLSLStreamOutputType>(pp->type))
- {
- if(as<HLSLTriangleStreamType>(outputStreamType))
- {
- emit("layout(triangle_strip) out;\n");
- }
- else if(as<HLSLLineStreamType>(outputStreamType))
- {
- emit("layout(line_strip) out;\n");
- }
- else if(as<HLSLPointStreamType>(outputStreamType))
- {
- emit("layout(points) out;\n");
- }
- }
- }
-
-
- }
- break;
- case Stage::Pixel:
- {
- if (irFunc->findDecoration<IREarlyDepthStencilDecoration>())
- {
- // https://www.khronos.org/opengl/wiki/Early_Fragment_Test
- emit("layout(early_fragment_tests) in;\n");
- }
- break;
- }
- // TODO: There are other stages that will need this kind of handling.
- default:
- break;
- }
- }
-
- void emitIREntryPointAttributes(
- IRFunc* irFunc,
- EmitContext* ctx,
- EntryPointLayout* entryPointLayout)
- {
- switch(getTarget(ctx))
- {
- case CodeGenTarget::HLSL:
- emitIREntryPointAttributes_HLSL(irFunc, ctx, entryPointLayout);
- break;
-
- case CodeGenTarget::GLSL:
- emitIREntryPointAttributes_GLSL(irFunc, ctx, entryPointLayout);
- break;
- }
- }
-
- void emitPhiVarDecls(
- EmitContext* ctx,
- IRFunc* func)
- {
- // We will skip the first block, since its parameters are
- // the parameters of the whole function.
- auto bb = func->getFirstBlock();
- if (!bb)
- return;
- bb = bb->getNextBlock();
-
- for (; bb; bb = bb->getNextBlock())
- {
- for (auto pp = bb->getFirstParam(); pp; pp = pp->getNextParam())
- {
- emitIRTempModifiers(ctx, pp);
- emitIRType(ctx, pp->getFullType(), getIRName(pp));
- emit(";\n");
- }
- }
- }
-
- /// Emit high-level statements for the body of a function.
- void emitIRFunctionBody(
- EmitContext* ctx,
- IRGlobalValueWithCode* code)
- {
- // Compute a structured region tree that can represent
- // the control flow of our function.
- //
- RefPtr<RegionTree> regionTree = generateRegionTreeForFunc(
- code,
- ctx->getSink());
-
- // Now that we've computed the region tree, we have
- // an opportunity to perform some last-minute transformations
- // on the code to make sure it follows our rules.
- //
- // TODO: it would be better to do these transformations earlier,
- // so that we can, e.g., dump the final IR code *before* emission
- // starts, but that gets a bit complicated because we also want
- // to have the region tree available without having to recompute it.
- //
- // For now we are just going to do things the expedient way, but
- // eventually we should allow an IR module to have side-band
- // storage for derived structures like the region tree (and logic
- // for invalidating them when a transformation would break them).
- //
- fixValueScoping(regionTree);
-
- // Now emit high-level code from that structured region tree.
- //
- emitRegionTree(ctx, regionTree);
- }
-
- void emitIRSimpleFunc(
- EmitContext* ctx,
- IRFunc* func)
- {
- auto resultType = func->getResultType();
-
- // Deal with decorations that need
- // to be emitted as attributes
- auto entryPointLayout = asEntryPoint(func);
- if (entryPointLayout)
- {
- emitIREntryPointAttributes(func, ctx, entryPointLayout);
- }
-
- const CodeGenTarget target = ctx->shared->target;
-
- auto name = getIRFuncName(func);
-
- EmitType(resultType, name);
-
- emit("(");
- auto firstParam = func->getFirstParam();
- for( auto pp = firstParam; pp; pp = pp->getNextParam())
- {
- if(pp != firstParam)
- emit(", ");
-
- auto paramName = getIRName(pp);
- auto paramType = pp->getDataType();
-
- if (target == CodeGenTarget::HLSL)
- {
- if (auto layoutDecor = pp->findDecoration<IRLayoutDecoration>())
- {
- Layout* layout = layoutDecor->getLayout();
- VarLayout* varLayout = as<VarLayout>(layout);
-
- if (varLayout)
- {
- auto var = varLayout->getVariable();
-
- if (auto primTypeModifier = var->FindModifier<HLSLGeometryShaderInputPrimitiveTypeModifier>())
- {
- if (as<HLSLTriangleModifier>(primTypeModifier))
- emit("triangle ");
- else if (as<HLSLPointModifier>(primTypeModifier))
- emit("point ");
- else if (as<HLSLLineModifier>(primTypeModifier))
- emit("line ");
- else if (as<HLSLLineAdjModifier>(primTypeModifier))
- emit("lineadj ");
- else if (as<HLSLTriangleAdjModifier>(primTypeModifier))
- emit("triangleadj ");
- }
- }
- }
- }
-
- emitIRParamType(ctx, paramType, paramName);
-
- emitIRSemantics(ctx, pp);
- }
- emit(")");
-
-
- emitIRSemantics(ctx, func);
-
- // TODO: encode declaration vs. definition
- if(isDefinition(func))
- {
- emit("\n{\n");
- indent();
-
- // HACK: forward-declare all the local variables needed for the
- // parameters of non-entry blocks.
- emitPhiVarDecls(ctx, func);
-
- // Need to emit the operations in the blocks of the function
- emitIRFunctionBody(ctx, func);
-
- dedent();
- emit("}\n\n");
- }
- else
- {
- emit(";\n\n");
- }
- }
-
- void emitIRParamType(
- EmitContext* ctx,
- IRType* type,
- String const& name)
- {
- // An `out` or `inout` parameter will have been
- // encoded as a parameter of pointer type, so
- // we need to decode that here.
- //
- if( auto outType = as<IROutType>(type))
- {
- emit("out ");
- type = outType->getValueType();
- }
- else if( auto inOutType = as<IRInOutType>(type))
- {
- emit("inout ");
- type = inOutType->getValueType();
- }
- else if( auto refType = as<IRRefType>(type))
- {
- // Note: There is no HLSL/GLSL equivalent for by-reference parameters,
- // so we don't actually expect to encounter these in user code.
- emit("inout ");
- type = inOutType->getValueType();
- }
-
- emitIRType(ctx, type, name);
- }
-
- IRInst* getSpecializedValue(IRSpecialize* specInst)
- {
- auto base = specInst->getBase();
- auto baseGeneric = as<IRGeneric>(base);
- if (!baseGeneric)
- return base;
-
- auto lastBlock = baseGeneric->getLastBlock();
- if (!lastBlock)
- return base;
-
- auto returnInst = as<IRReturnVal>(lastBlock->getTerminator());
- if (!returnInst)
- return base;
-
- return returnInst->getVal();
- }
-
- void emitIRFuncDecl(
- EmitContext* ctx,
- IRFunc* func)
- {
- // We don't want to emit declarations for operations
- // that only appear in the IR as stand-ins for built-in
- // operations on that target.
- if (isTargetIntrinsic(ctx, func))
- return;
-
- // Finally, don't emit a declaration for an entry point,
- // because it might need meta-data attributes attached
- // to it, and the HLSL compiler will get upset if the
- // forward declaration doesn't *also* have those
- // attributes.
- if(asEntryPoint(func))
- return;
-
-
- // A function declaration doesn't have any IR basic blocks,
- // and as a result it *also* doesn't have the IR `param` instructions,
- // so we need to emit a declaration entirely from the type.
-
- auto funcType = func->getDataType();
- auto resultType = func->getResultType();
-
- auto name = getIRFuncName(func);
-
- emitIRType(ctx, resultType, name);
-
- emit("(");
- auto paramCount = funcType->getParamCount();
- for(UInt pp = 0; pp < paramCount; ++pp)
- {
- if(pp != 0)
- emit(", ");
-
- String paramName;
- paramName.append("_");
- paramName.append(Int32(pp));
- auto paramType = funcType->getParamType(pp);
-
- emitIRParamType(ctx, paramType, paramName);
- }
- emit(");\n\n");
- }
-
- EntryPointLayout* getEntryPointLayout(
- EmitContext* /*context*/,
- IRFunc* func)
- {
- if( auto layoutDecoration = func->findDecoration<IRLayoutDecoration>() )
- {
- return as<EntryPointLayout>(layoutDecoration->getLayout());
- }
- return nullptr;
- }
-
- EntryPointLayout* asEntryPoint(IRFunc* func)
- {
- if (auto layoutDecoration = func->findDecoration<IRLayoutDecoration>())
- {
- if (auto entryPointLayout = as<EntryPointLayout>(layoutDecoration->getLayout()))
- {
- return entryPointLayout;
- }
- }
-
- return nullptr;
- }
-
- // Detect if the given IR function represents a
- // declaration of an intrinsic/builtin for the
- // current code-generation target.
- bool isTargetIntrinsic(
- EmitContext* /*ctxt*/,
- IRFunc* func)
- {
- // For now we do this in an overly simplistic
- // fashion: we say that *any* function declaration
- // (rather then definition) must be an intrinsic:
- return !isDefinition(func);
- }
-
- // Check whether a given value names a target intrinsic,
- // and return the IR function representing the intrinsic
- // if it does.
- IRFunc* asTargetIntrinsic(
- EmitContext* ctxt,
- IRInst* value)
- {
- if(!value)
- return nullptr;
-
- while (auto specInst = as<IRSpecialize>(value))
- {
- value = getSpecializedValue(specInst);
- }
-
- if(value->op != kIROp_Func)
- return nullptr;
-
- IRFunc* func = (IRFunc*) value;
- if(!isTargetIntrinsic(ctxt, func))
- return nullptr;
-
- return func;
- }
-
- void emitIRFunc(
- EmitContext* ctx,
- IRFunc* func)
- {
- if(!isDefinition(func))
- {
- // This is just a function declaration,
- // and so we want to emit it as such.
- // (Or maybe not emit it at all).
-
- // We do not emit the declaration for
- // functions that appear to be intrinsics/builtins
- // in the target language.
- if (isTargetIntrinsic(ctx, func))
- return;
-
- emitIRFuncDecl(ctx, func);
- }
- else
- {
- // The common case is that what we
- // have is just an ordinary function,
- // and we can emit it as such.
- emitIRSimpleFunc(ctx, func);
- }
- }
-
- void emitIRStruct(
- EmitContext* ctx,
- IRStructType* structType)
- {
- // If the selected `struct` type is actually an intrinsic
- // on our target, then we don't want to emit anything at all.
- if(auto intrinsicDecoration = findTargetIntrinsicDecoration(ctx, structType))
- {
- return;
- }
-
- emit("struct ");
- emit(getIRName(structType));
- emit("\n{\n");
- indent();
-
- for(auto ff : structType->getFields())
- {
- auto fieldKey = ff->getKey();
- auto fieldType = ff->getFieldType();
-
- // Filter out fields with `void` type that might
- // have been introduced by legalization.
- if(as<IRVoidType>(fieldType))
- continue;
-
- // Note: GLSL doesn't support interpolation modifiers on `struct` fields
- if( ctx->shared->target != CodeGenTarget::GLSL )
- {
- emitInterpolationModifiers(ctx, fieldKey, fieldType, nullptr);
- }
-
- emitIRType(ctx, fieldType, getIRName(fieldKey));
- emitIRSemantics(ctx, fieldKey);
- emit(";\n");
- }
-
- dedent();
- emit("};\n\n");
- }
-
- void emitIRMatrixLayoutModifiers(
- EmitContext* ctx,
- VarLayout* layout)
- {
- // When a variable has a matrix type, we want to emit an explicit
- // layout qualifier based on what the layout has been computed to be.
- //
-
- auto typeLayout = layout->typeLayout;
- while(auto arrayTypeLayout = as<ArrayTypeLayout>(typeLayout))
- typeLayout = arrayTypeLayout->elementTypeLayout;
-
- if (auto matrixTypeLayout = typeLayout.as<MatrixTypeLayout>())
- {
- auto target = ctx->shared->target;
-
- switch (target)
- {
- case CodeGenTarget::HLSL:
- switch (matrixTypeLayout->mode)
- {
- case kMatrixLayoutMode_ColumnMajor:
- emit("column_major ");
- break;
-
- case kMatrixLayoutMode_RowMajor:
- emit("row_major ");
- break;
- }
- break;
-
- case CodeGenTarget::GLSL:
- // Reminder: the meaning of row/column major layout
- // in our semantics is the *opposite* of what GLSL
- // calls them, because what they call "columns"
- // are what we call "rows."
- //
- switch (matrixTypeLayout->mode)
- {
- case kMatrixLayoutMode_ColumnMajor:
- emit("layout(row_major)\n");
- break;
-
- case kMatrixLayoutMode_RowMajor:
- emit("layout(column_major)\n");
- break;
- }
- break;
-
- default:
- break;
- }
-
- }
-
- }
-
- // Emit the `flat` qualifier if the underlying type
- // of the variable is an integer type.
- void maybeEmitGLSLFlatModifier(
- EmitContext*,
- IRType* valueType)
- {
- auto tt = valueType;
- if(auto vecType = as<IRVectorType>(tt))
- tt = vecType->getElementType();
- if(auto vecType = as<IRMatrixType>(tt))
- tt = vecType->getElementType();
-
- switch(tt->op)
- {
- default:
- break;
-
- case kIROp_IntType:
- case kIROp_UIntType:
- case kIROp_UInt64Type:
- Emit("flat ");
- break;
- }
- }
-
- void emitInterpolationModifiers(
- EmitContext* ctx,
- IRInst* varInst,
- IRType* valueType,
- VarLayout* layout)
- {
- bool isGLSL = (ctx->shared->target == CodeGenTarget::GLSL);
- bool anyModifiers = false;
-
- for(auto dd : varInst->getDecorations())
- {
- if(dd->op != kIROp_InterpolationModeDecoration)
- continue;
-
- auto decoration = (IRInterpolationModeDecoration*)dd;
- auto mode = decoration->getMode();
-
- switch(mode)
- {
- case IRInterpolationMode::NoInterpolation:
- anyModifiers = true;
- Emit(isGLSL ? "flat " : "nointerpolation ");
- break;
-
- case IRInterpolationMode::NoPerspective:
- anyModifiers = true;
- Emit("noperspective ");
- break;
-
- case IRInterpolationMode::Linear:
- anyModifiers = true;
- Emit(isGLSL ? "smooth " : "linear ");
- break;
-
- case IRInterpolationMode::Sample:
- anyModifiers = true;
- Emit("sample ");
- break;
-
- case IRInterpolationMode::Centroid:
- anyModifiers = true;
- Emit("centroid ");
- break;
-
- default:
- break;
- }
- }
-
- // If the user didn't explicitly qualify a varying
- // with integer type, then we need to explicitly
- // add the `flat` modifier for GLSL.
- if(!anyModifiers && isGLSL)
- {
- // Only emit a default `flat` for fragment
- // stage varying inputs.
- //
- // TODO: double-check that this works for
- // signature matching even if the producing
- // stage didn't use `flat`.
- //
- // If this ends up being a problem we can instead
- // output everything with `flat` except for
- // fragment *outputs* (and maybe vertex inputs).
- //
- if(layout && layout->stage == Stage::Fragment
- && layout->FindResourceInfo(LayoutResourceKind::VaryingInput))
- {
- maybeEmitGLSLFlatModifier(ctx, valueType);
- }
- }
- }
-
- UInt getRayPayloadLocation(
- EmitContext* ctx,
- IRInst* inst)
- {
- auto& map = ctx->shared->mapIRValueToRayPayloadLocation;
- UInt value = 0;
- if(map.TryGetValue(inst, value))
- return value;
-
- value = map.Count();
- map.Add(inst, value);
- return value;
- }
-
- UInt getCallablePayloadLocation(
- EmitContext* ctx,
- IRInst* inst)
- {
- auto& map = ctx->shared->mapIRValueToCallablePayloadLocation;
- UInt value = 0;
- if(map.TryGetValue(inst, value))
- return value;
-
- value = map.Count();
- map.Add(inst, value);
- return value;
- }
-
- void emitGLSLImageFormatModifier(
- IRInst* var,
- IRTextureType* resourceType)
- {
- // If the user specified a format manually, using `[format(...)]`,
- // then we will respect that format and emit a matching `layout` modifier.
- //
- if(auto formatDecoration = var->findDecoration<IRFormatDecoration>())
- {
- auto format = formatDecoration->getFormat();
- if(format == ImageFormat::unknown)
- {
- // If the user explicitly opts out of having a format, then
- // the output shader will require the extension to support
- // load/store from format-less images.
- //
- // TODO: We should have a validation somewhere in the compiler
- // that atomic operations are only allowed on images with
- // explicit formats (and then only on specific formats).
- // This is really an argument that format should be part of
- // the image *type* (with a "base type" for images with
- // unknown format).
- //
- requireGLSLExtension("GL_EXT_shader_image_load_formatted");
- }
- else
- {
- // If there is an explicit format specified, then we
- // should emit a `layout` modifier using the GLSL name
- // for the format.
- //
- Emit("layout(");
- Emit(getGLSLNameForImageFormat(format));
- Emit(")\n");
- }
-
- // No matter what, if an explicit `[format(...)]` was given,
- // then we don't need to emit anything else.
- //
- return;
- }
-
-
- // When no explicit format is specified, we need to either
- // emit the image as having an unknown format, or else infer
- // a format from the type.
- //
- // For now our default behavior is to infer (so that unmodified
- // HLSL input is more likely to generate valid SPIR-V that
- // runs anywhere), but we provide a flag to opt into
- // treating images without explicit formats as having
- // unknown format.
- //
- if(this->context->shared->compileRequest->useUnknownImageFormatAsDefault)
- {
- requireGLSLExtension("GL_EXT_shader_image_load_formatted");
- return;
- }
-
- // At this point we have a resource type like `RWTexture2D<X>`
- // and we want to infer a reasonable format from the element
- // type `X` that was specified.
- //
- // E.g., if `X` is `float` then we can infer a format like `r32f`,
- // and so forth. The catch of course is that it is possible to
- // specify a shader parameter with a type like `RWTexture2D<float4>` but
- // provide an image at runtime with a format like `rgba8`, so
- // this inference is never guaranteed to give perfect results.
- //
- // If users don't like our inferred result, they need to use a
- // `[format(...)]` attribute to manually specify what they want.
- //
- // TODO: We should consider whether we can expand the space of
- // allowed types for `X` in `RWTexture2D<X>` to include special
- // pseudo-types that act just like, e.g., `float4`, but come
- // with attached/implied format information.
- //
- auto elementType = resourceType->getElementType();
- Int vectorWidth = 1;
- if(auto elementVecType = as<IRVectorType>(elementType))
- {
- if(auto intLitVal = as<IRIntLit>(elementVecType->getElementCount()))
- {
- vectorWidth = (Int) intLitVal->getValue();
- }
- else
- {
- vectorWidth = 0;
- }
- elementType = elementVecType->getElementType();
- }
- if(auto elementBasicType = as<IRBasicType>(elementType))
- {
- Emit("layout(");
- switch(vectorWidth)
- {
- default: Emit("rgba"); break;
-
- case 3:
- {
- // TODO: GLSL doesn't support 3-component formats so for now we are going to
- // default to rgba
- //
- // The SPIR-V spec (https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.pdf)
- // section 3.11 on Image Formats it does not list rgbf32.
- //
- // It seems SPIR-V can support having an image with an unknown-at-compile-time
- // format, so long as the underlying API supports it. Ideally this would mean that we can
- // just drop all these qualifiers when emitting GLSL for Vulkan targets.
- //
- // This raises the question of what to do more long term. For Vulkan hopefully we can just
- // drop the layout. For OpenGL targets it would seem reasonable to have well-defined rules
- // for inferring the format (and just document that 3-component formats map to 4-component formats,
- // but that shouldn't matter because the API wouldn't let the user allocate those 3-component formats anyway),
- // and add an attribute for specifying the format manually if you really want to override our
- // inference (e.g., to specify r11fg11fb10f).
-
- Emit("rgba");
- //Emit("rgb");
- break;
- }
-
- case 2: Emit("rg"); break;
- case 1: Emit("r"); break;
- }
- switch(elementBasicType->getBaseType())
- {
- default:
- case BaseType::Float: Emit("32f"); break;
- case BaseType::Half: Emit("16f"); break;
- case BaseType::UInt: Emit("32ui"); break;
- case BaseType::Int: Emit("32i"); break;
-
- // TODO: Here are formats that are available in GLSL,
- // but that are not handled by the above cases.
- //
- // r11f_g11f_b10f
- //
- // rgba16
- // rgb10_a2
- // rgba8
- // rg16
- // rg8
- // r16
- // r8
- //
- // rgba16_snorm
- // rgba8_snorm
- // rg16_snorm
- // rg8_snorm
- // r16_snorm
- // r8_snorm
- //
- // rgba16i
- // rgba8i
- // rg16i
- // rg8i
- // r16i
- // r8i
- //
- // rgba16ui
- // rgb10_a2ui
- // rgba8ui
- // rg16ui
- // rg8ui
- // r16ui
- // r8ui
- }
- Emit(")\n");
- }
- }
-
- /// Emit modifiers that should apply even for a declaration of an SSA temporary.
- void emitIRTempModifiers(
- EmitContext* ctx,
- IRInst* temp)
- {
- SLANG_UNUSED(ctx);
-
- if(temp->findDecoration<IRPreciseDecoration>())
- {
- emit("precise ");
- }
- }
-
- void emitIRVarModifiers(
- EmitContext* ctx,
- VarLayout* layout,
- IRInst* varDecl,
- IRType* varType)
- {
- // Deal with Vulkan raytracing layout stuff *before* we
- // do the check for whether `layout` is null, because
- // the payload won't automatically get a layout applied
- // (it isn't part of the user-visible interface...)
- //
- if(varDecl->findDecoration<IRVulkanRayPayloadDecoration>())
- {
- emit("layout(location = ");
- Emit(getRayPayloadLocation(ctx, varDecl));
- emit(")\n");
- emit("rayPayloadNV\n");
- }
- if(varDecl->findDecoration<IRVulkanCallablePayloadDecoration>())
- {
- emit("layout(location = ");
- Emit(getCallablePayloadLocation(ctx, varDecl));
- emit(")\n");
- emit("callableDataNV\n");
- }
-
- if(varDecl->findDecoration<IRVulkanHitAttributesDecoration>())
- {
- emit("hitAttributeNV\n");
- }
-
- if(varDecl->findDecoration<IRGloballyCoherentDecoration>())
- {
- switch(getTarget(context))
- {
- default:
- break;
-
- case CodeGenTarget::HLSL:
- emit("globallycoherent\n");
- break;
-
- case CodeGenTarget::GLSL:
- emit("coherent\n");
- break;
- }
- }
-
- emitIRTempModifiers(ctx, varDecl);
-
- if (!layout)
- return;
-
- emitIRMatrixLayoutModifiers(ctx, layout);
-
- // As a special case, if we are emitting a GLSL declaration
- // for an HLSL `RWTexture*` then we need to emit a `format` layout qualifier.
- if(getTarget(context) == CodeGenTarget::GLSL)
- {
- if(auto resourceType = as<IRTextureType>(unwrapArray(varType)))
- {
- switch(resourceType->getAccess())
- {
- case SLANG_RESOURCE_ACCESS_READ_WRITE:
- case SLANG_RESOURCE_ACCESS_RASTER_ORDERED:
- {
- emitGLSLImageFormatModifier(varDecl, resourceType);
- }
- break;
-
- default:
- break;
- }
- }
- }
-
- if(layout->FindResourceInfo(LayoutResourceKind::VaryingInput)
- || layout->FindResourceInfo(LayoutResourceKind::VaryingOutput))
- {
- emitInterpolationModifiers(ctx, varDecl, varType, layout);
- }
-
- if (ctx->shared->target == CodeGenTarget::GLSL)
- {
- // Layout-related modifiers need to come before the declaration,
- // so deal with them here.
- emitGLSLLayoutQualifiers(layout, nullptr);
-
- // try to emit an appropriate leading qualifier
- for (auto rr : layout->resourceInfos)
- {
- switch (rr.kind)
- {
- case LayoutResourceKind::Uniform:
- case LayoutResourceKind::ShaderResource:
- case LayoutResourceKind::DescriptorTableSlot:
- emit("uniform ");
- break;
-
- case LayoutResourceKind::VaryingInput:
- {
- emit("in ");
- }
- break;
-
- case LayoutResourceKind::VaryingOutput:
- {
- emit("out ");
- }
- break;
-
- case LayoutResourceKind::RayPayload:
- {
- emit("rayPayloadInNV ");
- }
- break;
-
- case LayoutResourceKind::CallablePayload:
- {
- emit("callableDataInNV ");
- }
- break;
-
- case LayoutResourceKind::HitAttributes:
- {
- emit("hitAttributeNV ");
- }
- break;
-
- default:
- continue;
- }
-
- break;
- }
- }
- }
-
- void emitHLSLParameterGroup(
- EmitContext* ctx,
- IRGlobalParam* varDecl,
- IRUniformParameterGroupType* type)
- {
- if(as<IRTextureBufferType>(type))
- {
- emit("tbuffer ");
- }
- else
- {
- emit("cbuffer ");
- }
- emit(getIRName(varDecl));
-
- auto varLayout = getVarLayout(ctx, varDecl);
- SLANG_RELEASE_ASSERT(varLayout);
-
- EmitVarChain blockChain(varLayout);
-
- EmitVarChain containerChain = blockChain;
- EmitVarChain elementChain = blockChain;
-
- auto typeLayout = varLayout->typeLayout;
- if( auto parameterGroupTypeLayout = as<ParameterGroupTypeLayout>(typeLayout) )
- {
- containerChain = EmitVarChain(parameterGroupTypeLayout->containerVarLayout, &blockChain);
- elementChain = EmitVarChain(parameterGroupTypeLayout->elementVarLayout, &blockChain);
-
- typeLayout = parameterGroupTypeLayout->elementVarLayout->typeLayout;
- }
-
- emitHLSLRegisterSemantic(LayoutResourceKind::ConstantBuffer, &containerChain);
-
- emit("\n{\n");
- indent();
-
- auto elementType = type->getElementType();
-
- emitIRType(ctx, elementType, getIRName(varDecl));
- emit(";\n");
-
- dedent();
- emit("}\n");
- }
-
- /// Emit the array brackets that go on the end of a declaration of the given type.
- void emitArrayBrackets(
- EmitContext* ctx,
- IRType* inType)
- {
- SLANG_UNUSED(ctx);
-
- // A declaration may require zero, one, or
- // more array brackets. When writing out array
- // brackets from left to right, they represent
- // the structure of the type from the "outside"
- // in (that is, if we have a 5-element array of
- // 3-element arrays we should output `[5][3]`),
- // because of C-style declarator rules.
- //
- // This conveniently means that we can print
- // out all the array brackets with a looping
- // rather than a recursive structure.
- //
- // We will peel the input type like an onion,
- // looking at one layer at a time until we
- // reach a non-array type in the middle.
- //
- IRType* type = inType;
- for(;;)
- {
- if(auto arrayType = as<IRArrayType>(type))
- {
- emit("[");
- EmitVal(arrayType->getElementCount(), kEOp_General);
- emit("]");
-
- // Continue looping on the next layer in.
- //
- type = arrayType->getElementType();
- }
- else if(auto unsizedArrayType = as<IRUnsizedArrayType>(type))
- {
- emit("[]");
-
- // Continue looping on the next layer in.
- //
- type = unsizedArrayType->getElementType();
- }
- else
- {
- // This layer wasn't an array, so we are done.
- //
- return;
- }
- }
- }
-
-
- void emitGLSLParameterGroup(
- EmitContext* ctx,
- IRGlobalParam* varDecl,
- IRUniformParameterGroupType* type)
- {
- auto varLayout = getVarLayout(ctx, varDecl);
- SLANG_RELEASE_ASSERT(varLayout);
-
- EmitVarChain blockChain(varLayout);
-
- EmitVarChain containerChain = blockChain;
- EmitVarChain elementChain = blockChain;
-
- auto typeLayout = varLayout->typeLayout->unwrapArray();
- if( auto parameterGroupTypeLayout = as<ParameterGroupTypeLayout>(typeLayout) )
- {
- containerChain = EmitVarChain(parameterGroupTypeLayout->containerVarLayout, &blockChain);
- elementChain = EmitVarChain(parameterGroupTypeLayout->elementVarLayout, &blockChain);
-
- typeLayout = parameterGroupTypeLayout->elementVarLayout->typeLayout;
- }
-
- /*
- With resources backed by 'buffer' on glsl, we want to output 'readonly' if that is a good match
- for the underlying type. If uniform it's implicit it's readonly
-
- Here this only happens with isShaderRecord which is a 'constant buffer' (ie implicitly readonly)
- or IRGLSLShaderStorageBufferType which is read write.
- */
-
- emitGLSLLayoutQualifier(LayoutResourceKind::DescriptorTableSlot, &containerChain);
- emitGLSLLayoutQualifier(LayoutResourceKind::PushConstantBuffer, &containerChain);
- bool isShaderRecord = emitGLSLLayoutQualifier(LayoutResourceKind::ShaderRecord, &containerChain);
-
- if( isShaderRecord )
- {
- // TODO: A shader record in vk can be potentially read-write. Currently slang doesn't support write access
- // and readonly buffer generates SPIRV validation error.
- emit("buffer ");
- }
- else if(as<IRGLSLShaderStorageBufferType>(type))
- {
- // Is writable
- emit("layout(std430) buffer ");
- }
- // TODO: what to do with HLSL `tbuffer` style buffers?
- else
- {
- // uniform is implicitly read only
- emit("layout(std140) uniform ");
- }
-
- // Generate a dummy name for the block
- emit("_S");
- Emit(ctx->shared->uniqueIDCounter++);
-
- emit("\n{\n");
- indent();
-
- auto elementType = type->getElementType();
-
- emitIRType(ctx, elementType, "_data");
- emit(";\n");
-
- dedent();
- emit("} ");
-
- emit(getIRName(varDecl));
-
- // If the underlying variable was an array (or array of arrays, etc.)
- // we need to emit all those array brackets here.
- emitArrayBrackets(ctx, varDecl->getDataType());
-
- emit(";\n");
- }
-
- void emitIRParameterGroup(
- EmitContext* ctx,
- IRGlobalParam* varDecl,
- IRUniformParameterGroupType* type)
- {
- switch (ctx->shared->target)
- {
- case CodeGenTarget::HLSL:
- emitHLSLParameterGroup(ctx, varDecl, type);
- break;
-
- case CodeGenTarget::GLSL:
- emitGLSLParameterGroup(ctx, varDecl, type);
- break;
- }
- }
-
- void emitIRVar(
- EmitContext* ctx,
- IRVar* varDecl)
- {
- auto allocatedType = varDecl->getDataType();
- auto varType = allocatedType->getValueType();
-// auto addressSpace = allocatedType->getAddressSpace();
-
-#if 0
- switch( varType->op )
- {
- case kIROp_ConstantBufferType:
- case kIROp_TextureBufferType:
- emitIRParameterGroup(ctx, varDecl, (IRUniformBufferType*) varType);
- return;
-
- default:
- break;
- }
-#endif
-
- // Need to emit appropriate modifiers here.
-
- auto layout = getVarLayout(ctx, varDecl);
-
- emitIRVarModifiers(ctx, layout, varDecl, varType);
-
-#if 0
- switch (addressSpace)
- {
- default:
- break;
-
- case kIRAddressSpace_GroupShared:
- emit("groupshared ");
- break;
- }
-#endif
- emitIRRateQualifiers(ctx, varDecl);
-
- emitIRType(ctx, varType, getIRName(varDecl));
-
- emitIRSemantics(ctx, varDecl);
-
- emitIRLayoutSemantics(ctx, varDecl);
-
- emit(";\n");
- }
-
- void emitIRStructuredBuffer_GLSL(
- EmitContext* ctx,
- IRGlobalParam* varDecl,
- IRHLSLStructuredBufferTypeBase* structuredBufferType)
- {
- // Shader storage buffer is an OpenGL 430 feature
- //
- // TODO: we should require either the extension or the version...
- requireGLSLVersion(430);
-
- Emit("layout(std430");
-
- auto layout = getVarLayout(ctx, varDecl);
- if (layout)
- {
- LayoutResourceKind kind = LayoutResourceKind::DescriptorTableSlot;
- EmitVarChain chain(layout);
-
- const UInt index = getBindingOffset(&chain, kind);
- const UInt space = getBindingSpace(&chain, kind);
-
- Emit(", binding = ");
- Emit(index);
- if (space)
- {
- Emit(", set = ");
- Emit(space);
- }
- }
-
- emit(") ");
-
- /*
- If the output type is a buffer, and we can determine it is only readonly we can prefix before
- buffer with 'readonly'
-
- The actual structuredBufferType could be
-
- HLSLStructuredBufferType - This is unambiguously read only
- HLSLRWStructuredBufferType - Read write
- HLSLRasterizerOrderedStructuredBufferType - Allows read/write access
- HLSLAppendStructuredBufferType - Write
- HLSLConsumeStructuredBufferType - TODO (JS): Its possible that this can be readonly, but we currently don't support on GLSL
- */
-
- if (as<IRHLSLStructuredBufferType>(structuredBufferType))
- {
- emit("readonly ");
- }
-
- emit("buffer ");
-
- // Generate a dummy name for the block
- emit("_S");
- Emit(ctx->shared->uniqueIDCounter++);
-
- emit(" {\n");
- indent();
-
-
- auto elementType = structuredBufferType->getElementType();
- emitIRType(ctx, elementType, "_data[]");
- emit(";\n");
-
- dedent();
- emit("} ");
-
- emit(getIRName(varDecl));
- emitArrayBrackets(ctx, varDecl->getDataType());
-
- emit(";\n");
- }
-
- void emitIRByteAddressBuffer_GLSL(
- EmitContext* ctx,
- IRGlobalParam* varDecl,
- IRByteAddressBufferTypeBase* byteAddressBufferType)
- {
- // TODO: A lot of this logic is copy-pasted from `emitIRStructuredBuffer_GLSL`.
- // It might be worthwhile to share the common code to avoid regressions sneaking
- // in when one or the other, but not both, gets updated.
-
- // Shader storage buffer is an OpenGL 430 feature
- //
- // TODO: we should require either the extension or the version...
- requireGLSLVersion(430);
-
- Emit("layout(std430");
-
- auto layout = getVarLayout(ctx, varDecl);
- if (layout)
- {
- LayoutResourceKind kind = LayoutResourceKind::DescriptorTableSlot;
- EmitVarChain chain(layout);
-
- const UInt index = getBindingOffset(&chain, kind);
- const UInt space = getBindingSpace(&chain, kind);
-
- Emit(", binding = ");
- Emit(index);
- if (space)
- {
- Emit(", set = ");
- Emit(space);
- }
- }
-
- emit(") ");
-
- /*
- If the output type is a buffer, and we can determine it is only readonly we can prefix before
- buffer with 'readonly'
-
- HLSLByteAddressBufferType - This is unambiguously read only
- HLSLRWByteAddressBufferType - Read write
- HLSLRasterizerOrderedByteAddressBufferType - Allows read/write access
- */
-
- if (as<IRHLSLByteAddressBufferType>(byteAddressBufferType))
- {
- emit("readonly ");
- }
-
- emit("buffer ");
-
- // Generate a dummy name for the block
- emit("_S");
- Emit(ctx->shared->uniqueIDCounter++);
- emit("\n{\n");
- indent();
-
- emit("uint _data[];\n");
-
- dedent();
- emit("} ");
-
- emit(getIRName(varDecl));
- emitArrayBrackets(ctx, varDecl->getDataType());
-
- emit(";\n");
- }
-
- void emitIRGlobalVar(
- EmitContext* ctx,
- IRGlobalVar* varDecl)
- {
- auto allocatedType = varDecl->getDataType();
- auto varType = allocatedType->getValueType();
-
- String initFuncName;
- if (varDecl->getFirstBlock())
- {
- // A global variable with code means it has an initializer
- // associated with it. Eventually we'd like to emit that
- // initializer directly as an expression here, but for
- // now we'll emit it as a separate function.
-
- initFuncName = getIRName(varDecl);
- initFuncName.append("_init");
-
- emit("\n");
- emitIRType(ctx, varType, initFuncName);
- Emit("()\n{\n");
- indent();
- emitIRFunctionBody(ctx, varDecl);
- dedent();
- Emit("}\n");
- }
-
- // An ordinary global variable won't have a layout
- // associated with it, since it is not a shader
- // parameter.
- //
- SLANG_ASSERT(!getVarLayout(ctx, varDecl));
- VarLayout* layout = nullptr;
-
- // An ordinary global variable (which is not a
- // shader parameter) may need special
- // modifiers to indicate it as such.
- //
- switch (getTarget(ctx))
- {
- case CodeGenTarget::HLSL:
- // HLSL requires the `static` modifier on any
- // global variables; otherwise they are assumed
- // to be uniforms.
- Emit("static ");
- break;
-
- default:
- break;
- }
-
- emitIRVarModifiers(ctx, layout, varDecl, varType);
-
- emitIRRateQualifiers(ctx, varDecl);
- emitIRType(ctx, varType, getIRName(varDecl));
-
- // TODO: These shouldn't be needed for ordinary
- // global variables.
- //
- emitIRSemantics(ctx, varDecl);
- emitIRLayoutSemantics(ctx, varDecl);
-
- if (varDecl->getFirstBlock())
- {
- Emit(" = ");
- emit(initFuncName);
- Emit("()");
- }
-
- emit(";\n\n");
- }
-
- void emitIRGlobalParam(
- EmitContext* ctx,
- IRGlobalParam* varDecl)
- {
- auto rawType = varDecl->getDataType();
-
- auto varType = rawType;
- if( auto outType = as<IROutTypeBase>(varType) )
- {
- varType = outType->getValueType();
- }
- if (as<IRVoidType>(varType))
- return;
-
- // When a global shader parameter represents a "parameter group"
- // (either a constant buffer or a parameter block with non-resource
- // data in it), we will prefer to emit it as an ordinary `cbuffer`
- // declaration or `uniform` block, even when emitting HLSL for
- // D3D profiles that support the explicit `ConstantBuffer<T>` type.
- //
- // Alternatively, we could make this choice based on profile, and
- // prefer `ConstantBuffer<T>` on profiles that support it and/or when
- // the input code used that syntax.
- //
- if (auto paramBlockType = as<IRUniformParameterGroupType>(varType))
- {
- emitIRParameterGroup(
- ctx,
- varDecl,
- paramBlockType);
- return;
- }
-
- if(getTarget(ctx) == CodeGenTarget::GLSL)
- {
- // There are a number of types that are (or can be)
- // "first-class" in D3D HLSL, but are second-class in GLSL in
- // that they require explicit global declarations for each value/object,
- // and don't support declaration as ordinary variables.
- //
- // This includes constant buffers (`uniform` blocks) and well as
- // structured and byte-address buffers (both mapping to `buffer` blocks).
- //
- // We intercept these types, and arrays thereof, to produce the required
- // global declarations. This assumes that earlier "legalization" passes
- // already performed the work of pulling fields with these types out of
- // aggregates.
- //
- // Note: this also assumes that these types are not used as function
- // parameters/results, local variables, etc. Additional legalization
- // steps are required to guarantee these conditions.
- //
- if (auto paramBlockType = as<IRUniformParameterGroupType>(unwrapArray(varType)))
- {
- emitGLSLParameterGroup(
- ctx,
- varDecl,
- paramBlockType);
- return;
- }
- if( auto structuredBufferType = as<IRHLSLStructuredBufferTypeBase>(unwrapArray(varType)) )
- {
- emitIRStructuredBuffer_GLSL(
- ctx,
- varDecl,
- structuredBufferType);
- return;
- }
- if( auto byteAddressBufferType = as<IRByteAddressBufferTypeBase>(unwrapArray(varType)) )
- {
- emitIRByteAddressBuffer_GLSL(
- ctx,
- varDecl,
- byteAddressBufferType);
- return;
- }
-
- // We want to skip the declaration of any system-value variables
- // when outputting GLSL (well, except in the case where they
- // actually *require* redeclaration...).
- //
- // Note: these won't be variables the user declare explicitly
- // in their code, but rather variables that we generated as
- // part of legalizing the varying input/output signature of
- // an entry point for GL/Vulkan.
- //
- // TODO: This could be handled more robustly by attaching an
- // appropriate decoration to these variables to indicate their
- // purpose.
- //
- if(auto linkageDecoration = varDecl->findDecoration<IRLinkageDecoration>())
- {
- if(linkageDecoration->getMangledName().startsWith("gl_"))
- {
- // The variable represents an OpenGL system value,
- // so we will assume that it doesn't need to be declared.
- //
- // TODO: handle case where we *should* declare the variable.
- return;
- }
- }
-
- // When emitting unbounded-size resource arrays with GLSL we need
- // to use the `GL_EXT_nonuniform_qualifier` extension to ensure
- // that they are not treated as "implicitly-sized arrays" which
- // are arrays that have a fixed size that just isn't specified
- // at the declaration site (instead being inferred from use sites).
- //
- // While the extension primarily introduces the `nonuniformEXT`
- // qualifier that we use to implement `NonUniformResourceIndex`,
- // it also changes the GLSL language semantics around (resource) array
- // declarations that don't specify a size.
- //
- if( as<IRUnsizedArrayType>(varType) )
- {
- if(isResourceType(unwrapArray(varType)))
- {
- requireGLSLExtension("GL_EXT_nonuniform_qualifier");
- }
- }
- }
-
- // Need to emit appropriate modifiers here.
-
- // We expect/require all shader parameters to
- // have some kind of layout information associted with them.
- //
- auto layout = getVarLayout(ctx, varDecl);
- SLANG_ASSERT(layout);
-
- emitIRVarModifiers(ctx, layout, varDecl, varType);
-
- emitIRRateQualifiers(ctx, varDecl);
- emitIRType(ctx, varType, getIRName(varDecl));
-
- emitIRSemantics(ctx, varDecl);
-
- emitIRLayoutSemantics(ctx, varDecl);
-
- // A shader parameter cannot have an initializer,
- // so we do need to consider emitting one here.
-
- emit(";\n\n");
- }
-
-
- void emitIRGlobalConstantInitializer(
- EmitContext* ctx,
- IRGlobalConstant* valDecl)
- {
- // We expect to see only a single block
- auto block = valDecl->getFirstBlock();
- SLANG_RELEASE_ASSERT(block);
- SLANG_RELEASE_ASSERT(!block->getNextBlock());
-
- // We expect the terminator to be a `return`
- // instruction with a value.
- auto returnInst = (IRReturnVal*) block->getLastDecorationOrChild();
- SLANG_RELEASE_ASSERT(returnInst->op == kIROp_ReturnVal);
-
- // We will emit the value in the `GlobalConstant` mode, which
- // more or less says to fold all instructions into their use
- // sites, so that we end up with a single expression tree even
- // in cases that would otherwise trip up our analysis.
- //
- // Note: We are emitting the value as an *operand* here instead
- // of directly calling `emitIRInstExpr` because we need to handle
- // cases where the value might *need* to emit as a named referenced
- // (e.g., when it names another constant directly).
- //
- emitIROperand(ctx, returnInst->getVal(), IREmitMode::GlobalConstant, kEOp_General);
- }
-
- void emitIRGlobalConstant(
- EmitContext* ctx,
- IRGlobalConstant* valDecl)
- {
- auto valType = valDecl->getDataType();
-
- if( ctx->shared->target != CodeGenTarget::GLSL )
- {
- emit("static ");
- }
- emit("const ");
- emitIRRateQualifiers(ctx, valDecl);
- emitIRType(ctx, valType, getIRName(valDecl));
-
- if (valDecl->getFirstBlock())
- {
- // There is an initializer (which we expect for
- // any global constant...).
-
- emit(" = ");
-
- // We need to emit the entire initializer as
- // a single expression.
- emitIRGlobalConstantInitializer(ctx, valDecl);
- }
-
-
- emit(";\n");
- }
-
- void emitIRGlobalInst(
- EmitContext* ctx,
- IRInst* inst)
- {
- advanceToSourceLocation(inst->sourceLoc);
-
- switch(inst->op)
- {
- case kIROp_Func:
- emitIRFunc(ctx, (IRFunc*) inst);
- break;
-
- case kIROp_GlobalVar:
- emitIRGlobalVar(ctx, (IRGlobalVar*) inst);
- break;
-
- case kIROp_GlobalParam:
- emitIRGlobalParam(ctx, (IRGlobalParam*) inst);
- break;
-
- case kIROp_GlobalConstant:
- emitIRGlobalConstant(ctx, (IRGlobalConstant*) inst);
- break;
-
- case kIROp_Var:
- emitIRVar(ctx, (IRVar*) inst);
- break;
-
- case kIROp_StructType:
- emitIRStruct(ctx, cast<IRStructType>(inst));
- break;
-
- default:
- break;
- }
- }
-
- // An action to be performed during code emit.
- struct EmitAction
- {
- enum Level
- {
- ForwardDeclaration,
- Definition,
- };
- Level level;
- IRInst* inst;
- };
-
- struct ComputeEmitActionsContext
- {
- IRInst* moduleInst;
- HashSet<IRInst*> openInsts;
- Dictionary<IRInst*, EmitAction::Level> mapInstToLevel;
- List<EmitAction>* actions;
- };
-
- void ensureInstOperand(
- ComputeEmitActionsContext* ctx,
- IRInst* inst,
- EmitAction::Level requiredLevel = EmitAction::Level::Definition)
- {
- if(!inst) return;
-
- if(inst->getParent() == ctx->moduleInst)
- {
- ensureGlobalInst(ctx, inst, requiredLevel);
- }
- }
-
- void ensureInstOperandsRec(
- ComputeEmitActionsContext* ctx,
- IRInst* inst)
- {
- ensureInstOperand(ctx, inst->getFullType());
-
- UInt operandCount = inst->operandCount;
- for(UInt ii = 0; ii < operandCount; ++ii)
- {
- // TODO: there are some special cases we can add here,
- // to avoid outputting full definitions in cases that
- // can get by with forward declarations.
- //
- // For example, true pointer types should (in principle)
- // only need the type they point to to be forward-declared.
- // Similarly, a `call` instruction only needs the callee
- // to be forward-declared, etc.
-
- ensureInstOperand(ctx, inst->getOperand(ii));
- }
-
- for(auto child : inst->getDecorationsAndChildren())
- {
- ensureInstOperandsRec(ctx, child);
- }
- }
-
- void ensureGlobalInst(
- ComputeEmitActionsContext* ctx,
- IRInst* inst,
- EmitAction::Level requiredLevel)
- {
- // Skip certain instrutions, since they
- // don't affect output.
- switch(inst->op)
- {
- case kIROp_WitnessTable:
- case kIROp_Generic:
- return;
-
- default:
- break;
- }
-
- // Have we already processed this instruction?
- EmitAction::Level existingLevel;
- if(ctx->mapInstToLevel.TryGetValue(inst, existingLevel))
- {
- // If we've already emitted it suitably,
- // then don't worry about it.
- if(existingLevel >= requiredLevel)
- return;
- }
-
- EmitAction action;
- action.level = requiredLevel;
- action.inst = inst;
-
- if(requiredLevel == EmitAction::Level::Definition)
- {
- if(ctx->openInsts.Contains(inst))
- {
- SLANG_UNEXPECTED("circularity during codegen");
- return;
- }
-
- ctx->openInsts.Add(inst);
-
- ensureInstOperandsRec(ctx, inst);
-
- ctx->openInsts.Remove(inst);
- }
-
- ctx->mapInstToLevel[inst] = requiredLevel;
- ctx->actions->add(action);
- }
-
- void computeIREmitActions(
- IRModule* module,
- List<EmitAction>& ioActions)
- {
- ComputeEmitActionsContext ctx;
- ctx.moduleInst = module->getModuleInst();
- ctx.actions = &ioActions;
-
- for(auto inst : module->getGlobalInsts())
- {
- if( as<IRType>(inst) )
- {
- // Don't emit a type unless it is actually used.
- continue;
- }
-
- ensureGlobalInst(&ctx, inst, EmitAction::Level::Definition);
- }
- }
-
- void executeIREmitActions(
- EmitContext* ctx,
- List<EmitAction> const& actions)
- {
- for(auto action : actions)
- {
- switch(action.level)
- {
- case EmitAction::Level::ForwardDeclaration:
- emitIRFuncDecl(ctx, cast<IRFunc>(action.inst));
- break;
-
- case EmitAction::Level::Definition:
- emitIRGlobalInst(ctx, action.inst);
- break;
- }
- }
- }
-
- void emitIRModule(
- EmitContext* ctx,
- IRModule* module)
- {
- // The IR will usually come in an order that respects
- // dependencies between global declarations, but this
- // isn't guaranteed, so we need to be careful about
- // the order in which we emit things.
-
- List<EmitAction> actions;
-
- computeIREmitActions(module, actions);
- executeIREmitActions(ctx, actions);
- }
-};
//
@@ -7046,32 +160,44 @@ String emitEntryPoint(
// auto translationUnit = entryPoint->getTranslationUnit();
- SharedEmitContext sharedContext;
- sharedContext.compileRequest = compileRequest;
- sharedContext.target = target;
- sharedContext.finalTarget = targetRequest->getTarget();
- sharedContext.entryPoint = entryPoint;
- sharedContext.effectiveProfile = getEffectiveProfile(entryPoint, targetRequest);
+ auto lineDirectiveMode = compileRequest->getLineDirectiveMode();
+ // To try to make the default behavior reasonable, we will
+ // always use C-style line directives (to give the user
+ // good source locations on error messages from downstream
+ // compilers) *unless* they requested raw GLSL as the
+ // output (in which case we want to maximize compatibility
+ // with downstream tools).
+ if (lineDirectiveMode == LineDirectiveMode::Default && targetRequest->getTarget() == CodeGenTarget::GLSL)
+ {
+ lineDirectiveMode = LineDirectiveMode::GLSL;
+ }
- if (entryPoint)
+ SourceStream sourceStream(compileRequest->getSourceManager(), lineDirectiveMode );
+
+ EmitContext emitContext;
+ emitContext.compileRequest = compileRequest;
+ emitContext.target = target;
+ emitContext.entryPoint = entryPoint;
+ emitContext.effectiveProfile = getEffectiveProfile(entryPoint, targetRequest);
+ emitContext.stream = &sourceStream;
+
+ if (entryPoint && programLayout)
{
- sharedContext.entryPointLayout = findEntryPointLayout(
+ emitContext.entryPointLayout = findEntryPointLayout(
programLayout,
entryPoint);
}
- sharedContext.programLayout = programLayout;
+ emitContext.programLayout = programLayout;
// Layout information for the global scope is either an ordinary
// `struct` in the common case, or a constant buffer in the case
// where there were global-scope uniforms.
- StructTypeLayout* globalStructLayout = getGlobalStructLayout(programLayout);
- sharedContext.globalStructLayout = globalStructLayout;
-
- EmitContext context;
- context.shared = &sharedContext;
+
+ StructTypeLayout* globalStructLayout = programLayout ? getGlobalStructLayout(programLayout) : nullptr;
+ emitContext.globalStructLayout = globalStructLayout;
- EmitVisitor visitor(&context);
+ CLikeSourceEmitter sourceEmitter(&emitContext);
{
auto session = targetRequest->getSession();
@@ -7297,7 +423,7 @@ String emitEntryPoint(
irModule,
irEntryPoint,
compileRequest->getSink(),
- &sharedContext.extensionUsageTracker);
+ &emitContext.extensionUsageTracker);
#if 0
dumpIRIfEnabled(compileRequest, irModule, "GLSL LEGALIZED");
@@ -7332,7 +458,7 @@ String emitEntryPoint(
//
// TODO: do we want to emit directly from IR, or translate the
// IR back into AST for emission?
- visitor.emitIRModule(&context, irModule);
+ sourceEmitter.emitIRModule(irModule);
}
// Deal with cases where a particular stage requires certain GLSL versions
@@ -7350,30 +476,29 @@ String emitEntryPoint(
case Stage::RayGeneration:
if( target == CodeGenTarget::GLSL )
{
- requireGLSLExtension(&context.shared->extensionUsageTracker, "GL_NV_ray_tracing");
- requireGLSLVersionImpl(&context.shared->extensionUsageTracker, ProfileVersion::GLSL_460);
+ emitContext.extensionUsageTracker.requireGLSLExtension("GL_NV_ray_tracing");
+ emitContext.extensionUsageTracker.requireGLSLVersion(ProfileVersion::GLSL_460);
}
break;
}
- String code = sharedContext.sb.ProduceString();
- sharedContext.sb.Clear();
+ String code = sourceStream.getContent();
+ sourceStream.clearContent();
// Now that we've emitted the code for all the declarations in the file,
// it is time to stitch together the final output.
// There may be global-scope modifiers that we should emit now
- visitor.emitGLSLPreprocessorDirectives();
-
- visitor.emitLayoutDirectives(targetRequest);
-
- String prefix = sharedContext.sb.ProduceString();
+ sourceEmitter.emitGLSLPreprocessorDirectives();
+ sourceEmitter.emitLayoutDirectives(targetRequest);
+ String prefix = sourceStream.getContent();
+
StringBuilder finalResultBuilder;
finalResultBuilder << prefix;
- finalResultBuilder << sharedContext.extensionUsageTracker.glslExtensionRequireLines.ProduceString();
+ finalResultBuilder << emitContext.extensionUsageTracker.getGLSLExtensionRequireLines();
finalResultBuilder << code;
diff --git a/source/slang/emit.h b/source/slang/emit.h
index 317afcf6b..dc9300025 100644
--- a/source/slang/emit.h
+++ b/source/slang/emit.h
@@ -12,11 +12,6 @@ namespace Slang
class ProgramLayout;
class TranslationUnitRequest;
- struct ExtensionUsageTracker;
- void requireGLSLExtension(
- ExtensionUsageTracker* tracker,
- String const& name);
-
// Emit code for a single entry point, based on
// the input translation unit.
String emitEntryPoint(
diff --git a/source/slang/ir-glsl-legalize.cpp b/source/slang/ir-glsl-legalize.cpp
index 6a50d32cd..b63225729 100644
--- a/source/slang/ir-glsl-legalize.cpp
+++ b/source/slang/ir-glsl-legalize.cpp
@@ -4,6 +4,8 @@
#include "ir.h"
#include "ir-insts.h"
+#include "slang-extension-usage-tracker.h"
+
namespace Slang
{
@@ -174,14 +176,6 @@ struct GLSLSystemValueInfo
IRType* requiredType;
};
-void requireGLSLVersionImpl(
- ExtensionUsageTracker* tracker,
- ProfileVersion version);
-
-void requireGLSLExtension(
- ExtensionUsageTracker* tracker,
- String const& name);
-
struct GLSLLegalizationContext
{
Session* session;
@@ -191,12 +185,12 @@ struct GLSLLegalizationContext
void requireGLSLExtension(String const& name)
{
- Slang::requireGLSLExtension(extensionUsageTracker, name);
+ extensionUsageTracker->requireGLSLExtension(name);
}
void requireGLSLVersion(ProfileVersion version)
{
- Slang::requireGLSLVersionImpl(extensionUsageTracker, version);
+ extensionUsageTracker->requireGLSLVersion(version);
}
Stage getStage()
diff --git a/source/slang/ir-glsl-legalize.h b/source/slang/ir-glsl-legalize.h
index 7fabac869..994a68247 100644
--- a/source/slang/ir-glsl-legalize.h
+++ b/source/slang/ir-glsl-legalize.h
@@ -7,7 +7,8 @@ namespace Slang
class DiagnosticSink;
class Session;
-struct ExtensionUsageTracker;
+class ExtensionUsageTracker;
+
struct IRFunc;
struct IRModule;
diff --git a/source/slang/ir-link.cpp b/source/slang/ir-link.cpp
index d4f736a08..dc433663a 100644
--- a/source/slang/ir-link.cpp
+++ b/source/slang/ir-link.cpp
@@ -811,6 +811,12 @@ String getTargetName(IRSpecContext* context)
case CodeGenTarget::GLSL:
return "glsl";
+ case CodeGenTarget::CSource:
+ return "c";
+
+ case CodeGenTarget::CPPSource:
+ return "cpp";
+
default:
SLANG_UNEXPECTED("unhandled case");
UNREACHABLE_RETURN("unknown");
diff --git a/source/slang/lower-to-ir.h b/source/slang/lower-to-ir.h
index 79e1acc61..6ac2e182a 100644
--- a/source/slang/lower-to-ir.h
+++ b/source/slang/lower-to-ir.h
@@ -17,8 +17,6 @@ namespace Slang
class ProgramLayout;
class TranslationUnitRequest;
- struct ExtensionUsageTracker;
-
IRModule* generateIRForTranslationUnit(
TranslationUnitRequest* translationUnit);
diff --git a/source/slang/options.cpp b/source/slang/options.cpp
index a5cdfef44..46e0203cf 100644
--- a/source/slang/options.cpp
+++ b/source/slang/options.cpp
@@ -360,6 +360,9 @@ struct OptionsParser
CASE(".spv", SPIRV);
CASE(".spv.asm", SPIRV_ASM);
+ CASE(".c", C_SOURCE);
+ CASE(".cpp", CPP_SOURCE);
+
#undef CASE
else if (path.endsWith(".slang-module"))
@@ -499,6 +502,8 @@ struct OptionsParser
CASE("dxil", DXIL)
CASE("dxil-assembly", DXIL_ASM)
CASE("dxil-asm", DXIL_ASM)
+ CASE("c", C_SOURCE)
+ CASE("cpp", CPP_SOURCE)
#undef CASE
/* else */
diff --git a/source/slang/slang-c-like-source-emitter.cpp b/source/slang/slang-c-like-source-emitter.cpp
new file mode 100644
index 000000000..917779b6d
--- /dev/null
+++ b/source/slang/slang-c-like-source-emitter.cpp
@@ -0,0 +1,5811 @@
+// slang-c-like-source-emitter.cpp
+#include "slang-c-like-source-emitter.h"
+
+#include "../core/slang-writer.h"
+#include "ir-bind-existentials.h"
+#include "ir-dce.h"
+#include "ir-entry-point-uniforms.h"
+#include "ir-glsl-legalize.h"
+
+#include "ir-link.h"
+#include "ir-restructure-scoping.h"
+#include "ir-specialize.h"
+#include "ir-specialize-resources.h"
+#include "ir-ssa.h"
+#include "ir-union.h"
+#include "ir-validate.h"
+#include "legalize-types.h"
+#include "lower-to-ir.h"
+#include "mangle.h"
+#include "name.h"
+#include "syntax.h"
+#include "type-layout.h"
+#include "visitor.h"
+
+#include "slang-source-stream.h"
+#include "slang-emit-context.h"
+#include "slang-mangled-lexer.h"
+
+#include <assert.h>
+
+namespace Slang {
+
+// represents a declarator for use in emitting types
+struct CLikeSourceEmitter::EDeclarator
+{
+ enum class Flavor
+ {
+ name,
+ Array,
+ UnsizedArray,
+ };
+ Flavor flavor;
+ EDeclarator* next = nullptr;
+
+ // Used for `Flavor::name`
+ Name* name;
+ SourceLoc loc;
+
+ // Used for `Flavor::Array`
+ IRInst* elementCount;
+};
+
+struct CLikeSourceEmitter::IRDeclaratorInfo
+{
+ enum class Flavor
+ {
+ Simple,
+ Ptr,
+ Array,
+ };
+
+ Flavor flavor;
+ IRDeclaratorInfo* next;
+ union
+ {
+ String const* name;
+ IRInst* elementCount;
+ };
+};
+
+// A chain of variables to use for emitting semantic/layout info
+struct CLikeSourceEmitter::EmitVarChain
+{
+ VarLayout* varLayout;
+ EmitVarChain* next;
+
+ EmitVarChain()
+ : varLayout(0)
+ , next(0)
+ {}
+
+ EmitVarChain(VarLayout* varLayout)
+ : varLayout(varLayout)
+ , next(0)
+ {}
+
+ EmitVarChain(VarLayout* varLayout, EmitVarChain* next)
+ : varLayout(varLayout)
+ , next(next)
+ {}
+};
+
+struct CLikeSourceEmitter::ComputeEmitActionsContext
+{
+ IRInst* moduleInst;
+ HashSet<IRInst*> openInsts;
+ Dictionary<IRInst*, EmitAction::Level> mapInstToLevel;
+ List<EmitAction>* actions;
+};
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!!! CLikeSourceEmitter !!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+CLikeSourceEmitter::CLikeSourceEmitter(EmitContext* context)
+ : m_context(context),
+ m_stream(context->stream)
+{}
+
+//
+// Types
+//
+
+void CLikeSourceEmitter::emitDeclarator(EDeclarator* declarator)
+{
+ if (!declarator) return;
+
+ m_stream->emit(" ");
+
+ switch (declarator->flavor)
+ {
+ case EDeclarator::Flavor::name:
+ m_stream->emitName(declarator->name, declarator->loc);
+ break;
+
+ case EDeclarator::Flavor::Array:
+ emitDeclarator(declarator->next);
+ m_stream->emit("[");
+ if(auto elementCount = declarator->elementCount)
+ {
+ emitVal(elementCount, getInfo(EmitOp::General));
+ }
+ m_stream->emit("]");
+ break;
+
+ case EDeclarator::Flavor::UnsizedArray:
+ emitDeclarator(declarator->next);
+ m_stream->emit("[]");
+ break;
+
+ default:
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unknown declarator flavor");
+ break;
+ }
+}
+
+void CLikeSourceEmitter::emitGLSLTypePrefix(IRType* type, bool promoteHalfToFloat)
+{
+ switch (type->op)
+ {
+ case kIROp_FloatType:
+ // no prefix
+ break;
+
+ case kIROp_Int8Type: m_stream->emit("i8"); break;
+ case kIROp_Int16Type: m_stream->emit("i16"); break;
+ case kIROp_IntType: m_stream->emit("i"); break;
+ case kIROp_Int64Type: m_stream->emit("i64"); break;
+
+ case kIROp_UInt8Type: m_stream->emit("u8"); break;
+ case kIROp_UInt16Type: m_stream->emit("u16"); break;
+ case kIROp_UIntType: m_stream->emit("u"); break;
+ case kIROp_UInt64Type: m_stream->emit("u64"); break;
+
+ case kIROp_BoolType: m_stream->emit("b"); break;
+
+ case kIROp_HalfType:
+ {
+ _requireHalf();
+ if (promoteHalfToFloat)
+ {
+ // no prefix
+ }
+ else
+ {
+ m_stream->emit("f16");
+ }
+ break;
+ }
+ case kIROp_DoubleType: m_stream->emit("d"); break;
+
+ case kIROp_VectorType:
+ emitGLSLTypePrefix(cast<IRVectorType>(type)->getElementType(), promoteHalfToFloat);
+ break;
+
+ case kIROp_MatrixType:
+ emitGLSLTypePrefix(cast<IRMatrixType>(type)->getElementType(), promoteHalfToFloat);
+ break;
+
+ default:
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled GLSL type prefix");
+ break;
+ }
+}
+
+void CLikeSourceEmitter::emitHLSLTextureType(IRTextureTypeBase* texType)
+{
+ switch(texType->getAccess())
+ {
+ case SLANG_RESOURCE_ACCESS_READ:
+ break;
+
+ case SLANG_RESOURCE_ACCESS_READ_WRITE:
+ m_stream->emit("RW");
+ break;
+
+ case SLANG_RESOURCE_ACCESS_RASTER_ORDERED:
+ m_stream->emit("RasterizerOrdered");
+ break;
+
+ case SLANG_RESOURCE_ACCESS_APPEND:
+ m_stream->emit("Append");
+ break;
+
+ case SLANG_RESOURCE_ACCESS_CONSUME:
+ m_stream->emit("Consume");
+ break;
+
+ default:
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled resource access mode");
+ break;
+ }
+
+ switch (texType->GetBaseShape())
+ {
+ case TextureFlavor::Shape::Shape1D: m_stream->emit("Texture1D"); break;
+ case TextureFlavor::Shape::Shape2D: m_stream->emit("Texture2D"); break;
+ case TextureFlavor::Shape::Shape3D: m_stream->emit("Texture3D"); break;
+ case TextureFlavor::Shape::ShapeCube: m_stream->emit("TextureCube"); break;
+ case TextureFlavor::Shape::ShapeBuffer: m_stream->emit("Buffer"); break;
+ default:
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled resource shape");
+ break;
+ }
+
+ if (texType->isMultisample())
+ {
+ m_stream->emit("MS");
+ }
+ if (texType->isArray())
+ {
+ m_stream->emit("Array");
+ }
+ m_stream->emit("<");
+ emitType(texType->getElementType());
+ m_stream->emit(" >");
+}
+
+void CLikeSourceEmitter::emitGLSLTextureOrTextureSamplerType(IRTextureTypeBase* type, char const* baseName)
+{
+ if (type->getElementType()->op == kIROp_HalfType)
+ {
+ // Texture access is always as float types if half is specified
+
+ }
+ else
+ {
+ emitGLSLTypePrefix(type->getElementType(), true);
+ }
+
+ m_stream->emit(baseName);
+ switch (type->GetBaseShape())
+ {
+ case TextureFlavor::Shape::Shape1D: m_stream->emit("1D"); break;
+ case TextureFlavor::Shape::Shape2D: m_stream->emit("2D"); break;
+ case TextureFlavor::Shape::Shape3D: m_stream->emit("3D"); break;
+ case TextureFlavor::Shape::ShapeCube: m_stream->emit("Cube"); break;
+ case TextureFlavor::Shape::ShapeBuffer: m_stream->emit("Buffer"); break;
+ default:
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled resource shape");
+ break;
+ }
+
+ if (type->isMultisample())
+ {
+ m_stream->emit("MS");
+ }
+ if (type->isArray())
+ {
+ m_stream->emit("Array");
+ }
+}
+
+void CLikeSourceEmitter::emitGLSLTextureType(
+ IRTextureType* texType)
+{
+ switch(texType->getAccess())
+ {
+ case SLANG_RESOURCE_ACCESS_READ_WRITE:
+ case SLANG_RESOURCE_ACCESS_RASTER_ORDERED:
+ emitGLSLTextureOrTextureSamplerType(texType, "image");
+ break;
+
+ default:
+ emitGLSLTextureOrTextureSamplerType(texType, "texture");
+ break;
+ }
+}
+
+void CLikeSourceEmitter::emitGLSLTextureSamplerType(IRTextureSamplerType* type)
+{
+ emitGLSLTextureOrTextureSamplerType(type, "sampler");
+}
+
+void CLikeSourceEmitter::emitGLSLImageType(IRGLSLImageType* type)
+{
+ emitGLSLTextureOrTextureSamplerType(type, "image");
+}
+
+void CLikeSourceEmitter::emitTextureType(IRTextureType* texType)
+{
+ switch(m_context->target)
+ {
+ case CodeGenTarget::HLSL:
+ emitHLSLTextureType(texType);
+ break;
+
+ case CodeGenTarget::GLSL:
+ emitGLSLTextureType(texType);
+ break;
+
+ default:
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target");
+ break;
+ }
+}
+
+void CLikeSourceEmitter::emitTextureSamplerType(IRTextureSamplerType* type)
+{
+ switch(m_context->target)
+ {
+ case CodeGenTarget::GLSL:
+ emitGLSLTextureSamplerType(type);
+ break;
+
+ default:
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "this target should see combined texture-sampler types");
+ break;
+ }
+}
+
+void CLikeSourceEmitter::emitImageType(IRGLSLImageType* type)
+{
+ switch(m_context->target)
+ {
+ case CodeGenTarget::HLSL:
+ emitHLSLTextureType(type);
+ break;
+
+ case CodeGenTarget::GLSL:
+ emitGLSLImageType(type);
+ break;
+
+ default:
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "this target should see GLSL image types");
+ break;
+ }
+}
+
+static IROp _getCType(IROp op)
+{
+ switch (op)
+ {
+ case kIROp_VoidType:
+ case kIROp_BoolType:
+ {
+ return op;
+ }
+ case kIROp_Int8Type:
+ case kIROp_Int16Type:
+ case kIROp_IntType:
+ case kIROp_UInt8Type:
+ case kIROp_UInt16Type:
+ case kIROp_UIntType:
+ {
+ // Promote all these to Int
+ return kIROp_IntType;
+ }
+ case kIROp_Int64Type:
+ case kIROp_UInt64Type:
+ {
+ // Promote all these to Int16, we can just vary the call to make these work
+ return kIROp_Int64Type;
+ }
+ case kIROp_DoubleType:
+ {
+ return kIROp_DoubleType;
+ }
+ case kIROp_HalfType:
+ case kIROp_FloatType:
+ {
+ // Promote both to float
+ return kIROp_FloatType;
+ }
+ default:
+ {
+ SLANG_ASSERT(!"Unhandled type");
+ return kIROp_undefined;
+ }
+ }
+}
+
+static UnownedStringSlice _getCTypeVecPostFix(IROp op)
+{
+ switch (op)
+ {
+ case kIROp_BoolType: return UnownedStringSlice::fromLiteral("B");
+ case kIROp_IntType: return UnownedStringSlice::fromLiteral("I");
+ case kIROp_FloatType: return UnownedStringSlice::fromLiteral("F");
+ case kIROp_Int64Type: return UnownedStringSlice::fromLiteral("I64");
+ case kIROp_DoubleType: return UnownedStringSlice::fromLiteral("F64");
+ default: return UnownedStringSlice::fromLiteral("?");
+ }
+}
+
+#if 0
+static UnownedStringSlice _getCTypeName(IROp op)
+{
+ switch (op)
+ {
+ case kIROp_BoolType: return UnownedStringSlice::fromLiteral("Bool");
+ case kIROp_IntType: return UnownedStringSlice::fromLiteral("I32");
+ case kIROp_FloatType: return UnownedStringSlice::fromLiteral("F32");
+ case kIROp_Int64Type: return UnownedStringSlice::fromLiteral("I64");
+ case kIROp_DoubleType: return UnownedStringSlice::fromLiteral("F64");
+ default: return UnownedStringSlice::fromLiteral("?");
+ }
+}
+#endif
+
+void CLikeSourceEmitter::_emitCVecType(IROp op, Int size)
+{
+ m_stream->emit("Vec");
+ const UnownedStringSlice postFix = _getCTypeVecPostFix(_getCType(op));
+ m_stream->emit(postFix);
+ if (postFix.size() > 1)
+ {
+ m_stream->emit("_");
+ }
+ m_stream->emit(size);
+}
+
+void CLikeSourceEmitter::_emitCMatType(IROp op, IRIntegerValue rowCount, IRIntegerValue colCount)
+{
+ m_stream->emit("Mat");
+ const UnownedStringSlice postFix = _getCTypeVecPostFix(_getCType(op));
+ m_stream->emit(postFix);
+ if (postFix.size() > 1)
+ {
+ m_stream->emit("_");
+ }
+ m_stream->emit(rowCount);
+ m_stream->emit(colCount);
+}
+
+void CLikeSourceEmitter::_emitCFunc(BuiltInCOp cop, IRType* type)
+{
+ emitSimpleTypeImpl(type);
+ m_stream->emit("_");
+
+ switch (cop)
+ {
+ case BuiltInCOp::Init: m_stream->emit("init");
+ case BuiltInCOp::Splat: m_stream->emit("splat"); break;
+ }
+}
+
+void CLikeSourceEmitter::emitVectorTypeName(IRType* elementType, IRIntegerValue elementCount)
+{
+ switch(m_context->target)
+ {
+ case CodeGenTarget::GLSL:
+ case CodeGenTarget::GLSL_Vulkan:
+ case CodeGenTarget::GLSL_Vulkan_OneDesc:
+ {
+ if (elementCount > 1)
+ {
+ emitGLSLTypePrefix(elementType);
+ m_stream->emit("vec");
+ m_stream->emit(elementCount);
+ }
+ else
+ {
+ emitSimpleTypeImpl(elementType);
+ }
+ }
+ break;
+
+ case CodeGenTarget::HLSL:
+ // TODO(tfoley): should really emit these with sugar
+ m_stream->emit("vector<");
+ emitType(elementType);
+ m_stream->emit(",");
+ m_stream->emit(elementCount);
+ m_stream->emit(">");
+ break;
+
+ case CodeGenTarget::CSource:
+ case CodeGenTarget::CPPSource:
+ _emitCVecType(elementType->op, Int(elementCount));
+ break;
+
+ default:
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target");
+ break;
+ }
+}
+
+void CLikeSourceEmitter::emitVectorTypeImpl(IRVectorType* vecType)
+{
+ IRInst* elementCountInst = vecType->getElementCount();
+ if (elementCountInst->op != kIROp_IntLit)
+ {
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "Expecting an integral size for vector size");
+ return;
+ }
+
+ const IRConstant* irConst = (const IRConstant*)elementCountInst;
+ const IRIntegerValue elementCount = irConst->value.intVal;
+ if (elementCount <= 0)
+ {
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "Vector size must be greater than 0");
+ return;
+ }
+
+ auto* elementType = vecType->getElementType();
+
+ emitVectorTypeName(elementType, elementCount);
+}
+
+void CLikeSourceEmitter::emitMatrixTypeImpl(IRMatrixType* matType)
+{
+ switch(m_context->target)
+ {
+ case CodeGenTarget::GLSL:
+ case CodeGenTarget::GLSL_Vulkan:
+ case CodeGenTarget::GLSL_Vulkan_OneDesc:
+ {
+ emitGLSLTypePrefix(matType->getElementType());
+ m_stream->emit("mat");
+ emitVal(matType->getRowCount(), getInfo(EmitOp::General));
+ // TODO(tfoley): only emit the next bit
+ // for non-square matrix
+ m_stream->emit("x");
+ emitVal(matType->getColumnCount(), getInfo(EmitOp::General));
+ }
+ break;
+
+ case CodeGenTarget::HLSL:
+ // TODO(tfoley): should really emit these with sugar
+ m_stream->emit("matrix<");
+ emitType(matType->getElementType());
+ m_stream->emit(",");
+ emitVal(matType->getRowCount(), getInfo(EmitOp::General));
+ m_stream->emit(",");
+ emitVal(matType->getColumnCount(), getInfo(EmitOp::General));
+ m_stream->emit("> ");
+ break;
+
+ case CodeGenTarget::CPPSource:
+ case CodeGenTarget::CSource:
+ {
+ const auto rowCount = static_cast<const IRConstant*>(matType->getRowCount())->value.intVal;
+ const auto colCount = static_cast<const IRConstant*>(matType->getColumnCount())->value.intVal;
+
+ _emitCMatType(matType->getElementType()->op, rowCount, colCount);
+ break;
+ }
+
+ default:
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target");
+ break;
+ }
+}
+
+void CLikeSourceEmitter::emitSamplerStateType(IRSamplerStateTypeBase* samplerStateType)
+{
+ switch(m_context->target)
+ {
+ case CodeGenTarget::HLSL:
+ default:
+ switch (samplerStateType->op)
+ {
+ case kIROp_SamplerStateType: m_stream->emit("SamplerState"); break;
+ case kIROp_SamplerComparisonStateType: m_stream->emit("SamplerComparisonState"); break;
+ default:
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled sampler state flavor");
+ break;
+ }
+ break;
+
+ case CodeGenTarget::GLSL:
+ switch (samplerStateType->op)
+ {
+ case kIROp_SamplerStateType: m_stream->emit("sampler"); break;
+ case kIROp_SamplerComparisonStateType: m_stream->emit("samplerShadow"); break;
+ default:
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled sampler state flavor");
+ break;
+ }
+ break;
+ break;
+ }
+}
+
+void CLikeSourceEmitter::emitStructuredBufferType(IRHLSLStructuredBufferTypeBase* type)
+{
+ switch(m_context->target)
+ {
+ case CodeGenTarget::HLSL:
+ default:
+ {
+ switch (type->op)
+ {
+ case kIROp_HLSLStructuredBufferType: m_stream->emit("StructuredBuffer"); break;
+ case kIROp_HLSLRWStructuredBufferType: m_stream->emit("RWStructuredBuffer"); break;
+ case kIROp_HLSLRasterizerOrderedStructuredBufferType: m_stream->emit("RasterizerOrderedStructuredBuffer"); break;
+ case kIROp_HLSLAppendStructuredBufferType: m_stream->emit("AppendStructuredBuffer"); break;
+ case kIROp_HLSLConsumeStructuredBufferType: m_stream->emit("ConsumeStructuredBuffer"); break;
+
+ default:
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled structured buffer type");
+ break;
+ }
+
+ m_stream->emit("<");
+ emitType(type->getElementType());
+ m_stream->emit(" >");
+ }
+ break;
+
+ case CodeGenTarget::GLSL:
+ // TODO: We desugar global variables with structured-buffer type into GLSL
+ // `buffer` declarations, but we don't currently handle structured-buffer types
+ // in other contexts (e.g., as function parameters). The simplest thing to do
+ // would be to emit a `StructuredBuffer<Foo>` as `Foo[]` and `RWStructuredBuffer<Foo>`
+ // as `in out Foo[]`, but that is starting to get into the realm of transformations
+ // that should really be handled during legalization, rather than during emission.
+ //
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "structured buffer type used unexpectedly");
+ break;
+ }
+}
+
+void CLikeSourceEmitter::emitUntypedBufferType(IRUntypedBufferResourceType* type)
+{
+ switch(m_context->target)
+ {
+ case CodeGenTarget::HLSL:
+ default:
+ {
+ switch (type->op)
+ {
+ case kIROp_HLSLByteAddressBufferType: m_stream->emit("ByteAddressBuffer"); break;
+ case kIROp_HLSLRWByteAddressBufferType: m_stream->emit("RWByteAddressBuffer"); break;
+ case kIROp_HLSLRasterizerOrderedByteAddressBufferType: m_stream->emit("RasterizerOrderedByteAddressBuffer"); break;
+ case kIROp_RaytracingAccelerationStructureType: m_stream->emit("RaytracingAccelerationStructure"); break;
+
+ default:
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled buffer type");
+ break;
+ }
+ }
+ break;
+
+ case CodeGenTarget::GLSL:
+ {
+ switch (type->op)
+ {
+ case kIROp_RaytracingAccelerationStructureType:
+ requireGLSLExtension("GL_NV_ray_tracing");
+ m_stream->emit("accelerationStructureNV");
+ break;
+
+ // TODO: These "translations" are obviously wrong for GLSL.
+ case kIROp_HLSLByteAddressBufferType: m_stream->emit("ByteAddressBuffer"); break;
+ case kIROp_HLSLRWByteAddressBufferType: m_stream->emit("RWByteAddressBuffer"); break;
+ case kIROp_HLSLRasterizerOrderedByteAddressBufferType: m_stream->emit("RasterizerOrderedByteAddressBuffer"); break;
+
+ default:
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled buffer type");
+ break;
+ }
+ }
+ break;
+ }
+}
+
+void CLikeSourceEmitter::_requireHalf()
+{
+ if (getTarget() == CodeGenTarget::GLSL)
+ {
+ m_context->extensionUsageTracker.requireGLSLHalfExtension();
+ }
+}
+
+void CLikeSourceEmitter::emitSimpleTypeImpl(IRType* type)
+{
+ switch (type->op)
+ {
+ default:
+ break;
+
+ case kIROp_VoidType: m_stream->emit("void"); return;
+ case kIROp_BoolType: m_stream->emit("bool"); return;
+
+ case kIROp_Int8Type: m_stream->emit("int8_t"); return;
+ case kIROp_Int16Type: m_stream->emit("int16_t"); return;
+ case kIROp_IntType: m_stream->emit("int"); return;
+ case kIROp_Int64Type: m_stream->emit("int64_t"); return;
+
+ case kIROp_UInt8Type: m_stream->emit("uint8_t"); return;
+ case kIROp_UInt16Type: m_stream->emit("uint16_t"); return;
+ case kIROp_UIntType: m_stream->emit("uint"); return;
+ case kIROp_UInt64Type: m_stream->emit("uint64_t"); return;
+
+ case kIROp_HalfType:
+ {
+ _requireHalf();
+ if (getTarget() == CodeGenTarget::GLSL)
+ {
+ m_stream->emit("float16_t");
+ }
+ else
+ {
+ m_stream->emit("half");
+ }
+ return;
+ }
+ case kIROp_FloatType: m_stream->emit("float"); return;
+ case kIROp_DoubleType: m_stream->emit("double"); return;
+
+ case kIROp_VectorType:
+ emitVectorTypeImpl((IRVectorType*)type);
+ return;
+
+ case kIROp_MatrixType:
+ emitMatrixTypeImpl((IRMatrixType*)type);
+ return;
+
+ case kIROp_SamplerStateType:
+ case kIROp_SamplerComparisonStateType:
+ emitSamplerStateType(cast<IRSamplerStateTypeBase>(type));
+ return;
+
+ case kIROp_StructType:
+ m_stream->emit(getIRName(type));
+ return;
+ }
+
+ // TODO: Ideally the following should be data-driven,
+ // based on meta-data attached to the definitions of
+ // each of these IR opcodes.
+
+ if (auto texType = as<IRTextureType>(type))
+ {
+ emitTextureType(texType);
+ return;
+ }
+ else if (auto textureSamplerType = as<IRTextureSamplerType>(type))
+ {
+ emitTextureSamplerType(textureSamplerType);
+ return;
+ }
+ else if (auto imageType = as<IRGLSLImageType>(type))
+ {
+ emitImageType(imageType);
+ return;
+ }
+ else if (auto structuredBufferType = as<IRHLSLStructuredBufferTypeBase>(type))
+ {
+ emitStructuredBufferType(structuredBufferType);
+ return;
+ }
+ else if(auto untypedBufferType = as<IRUntypedBufferResourceType>(type))
+ {
+ emitUntypedBufferType(untypedBufferType);
+ return;
+ }
+
+ // HACK: As a fallback for HLSL targets, assume that the name of the
+ // instruction being used is the same as the name of the HLSL type.
+ if(m_context->target == CodeGenTarget::HLSL)
+ {
+ auto opInfo = getIROpInfo(type->op);
+ m_stream->emit(opInfo.name);
+ UInt operandCount = type->getOperandCount();
+ if(operandCount)
+ {
+ m_stream->emit("<");
+ for(UInt ii = 0; ii < operandCount; ++ii)
+ {
+ if(ii != 0) m_stream->emit(", ");
+ emitVal(type->getOperand(ii), getInfo(EmitOp::General));
+ }
+ m_stream->emit(" >");
+ }
+
+ return;
+ }
+
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled type");
+}
+
+void CLikeSourceEmitter::emitArrayTypeImpl(IRArrayType* arrayType, EDeclarator* declarator)
+{
+ EDeclarator arrayDeclarator;
+ arrayDeclarator.flavor = EDeclarator::Flavor::Array;
+ arrayDeclarator.next = declarator;
+ arrayDeclarator.elementCount = arrayType->getElementCount();
+
+ emitTypeImpl(arrayType->getElementType(), &arrayDeclarator);
+}
+
+void CLikeSourceEmitter::emitUnsizedArrayTypeImpl(IRUnsizedArrayType* arrayType, EDeclarator* declarator)
+{
+ EDeclarator arrayDeclarator;
+ arrayDeclarator.flavor = EDeclarator::Flavor::UnsizedArray;
+ arrayDeclarator.next = declarator;
+
+ emitTypeImpl(arrayType->getElementType(), &arrayDeclarator);
+}
+
+void CLikeSourceEmitter::emitTypeImpl(IRType* type, EDeclarator* declarator)
+{
+ switch (type->op)
+ {
+ default:
+ emitSimpleTypeImpl(type);
+ emitDeclarator(declarator);
+ break;
+
+ case kIROp_RateQualifiedType:
+ {
+ auto rateQualifiedType = cast<IRRateQualifiedType>(type);
+ emitTypeImpl(rateQualifiedType->getValueType(), declarator);
+ }
+ break;
+
+ case kIROp_ArrayType:
+ emitArrayTypeImpl(cast<IRArrayType>(type), declarator);
+ break;
+
+ case kIROp_UnsizedArrayType:
+ emitUnsizedArrayTypeImpl(cast<IRUnsizedArrayType>(type), declarator);
+ break;
+ }
+
+}
+
+void CLikeSourceEmitter::emitType(
+ IRType* type,
+ SourceLoc const& typeLoc,
+ Name* name,
+ SourceLoc const& nameLoc)
+{
+ m_stream->advanceToSourceLocation(typeLoc);
+
+ EDeclarator nameDeclarator;
+ nameDeclarator.flavor = EDeclarator::Flavor::name;
+ nameDeclarator.name = name;
+ nameDeclarator.loc = nameLoc;
+ emitTypeImpl(type, &nameDeclarator);
+}
+
+void CLikeSourceEmitter::emitType(IRType* type, Name* name)
+{
+ emitType(type, SourceLoc(), name, SourceLoc());
+}
+
+void CLikeSourceEmitter::emitType(IRType* type, const String& name)
+{
+ // HACK: the rest of the code wants a `Name`,
+ // so we'll create one for a bit...
+ Name tempName;
+ tempName.text = name;
+
+ emitType(type, SourceLoc(), &tempName, SourceLoc());
+}
+
+
+void CLikeSourceEmitter::emitType(IRType* type)
+{
+ emitTypeImpl(type, nullptr);
+}
+
+//
+// Expressions
+//
+
+bool CLikeSourceEmitter::maybeEmitParens(EmitOpInfo& outerPrec, EmitOpInfo prec)
+{
+ bool needParens = (prec.leftPrecedence <= outerPrec.leftPrecedence)
+ || (prec.rightPrecedence <= outerPrec.rightPrecedence);
+
+ if (needParens)
+ {
+ m_stream->emit("(");
+
+ outerPrec = getInfo(EmitOp::None);
+ }
+ return needParens;
+}
+
+void CLikeSourceEmitter::maybeCloseParens(bool needClose)
+{
+ if(needClose) m_stream->emit(")");
+}
+
+bool CLikeSourceEmitter::isTargetIntrinsicModifierApplicable(const String& targetName)
+{
+ switch(m_context->target)
+ {
+ default:
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target");
+ return false;
+
+ case CodeGenTarget::CSource: return targetName == "c";
+ case CodeGenTarget::CPPSource: return targetName == "cpp";
+ case CodeGenTarget::GLSL: return targetName == "glsl";
+ case CodeGenTarget::HLSL: return targetName == "hlsl";
+ }
+}
+
+void CLikeSourceEmitter::emitType(IRType* type, Name* name, SourceLoc const& nameLoc)
+{
+ emitType(
+ type,
+ SourceLoc(),
+ name,
+ nameLoc);
+}
+
+void CLikeSourceEmitter::emitType(IRType* type, NameLoc const& nameAndLoc)
+{
+ emitType(type, nameAndLoc.name, nameAndLoc.loc);
+}
+
+bool CLikeSourceEmitter::isTargetIntrinsicModifierApplicable(
+ IRTargetIntrinsicDecoration* decoration)
+{
+ auto targetName = String(decoration->getTargetName());
+
+ // If no target name was specified, then the modifier implicitly
+ // applies to all targets.
+ if(targetName.getLength() == 0)
+ return true;
+
+ return isTargetIntrinsicModifierApplicable(targetName);
+}
+
+void CLikeSourceEmitter::emitStringLiteral(
+ String const& value)
+{
+ m_stream->emit("\"");
+ for (auto c : value)
+ {
+ // TODO: This needs a more complete implementation,
+ // especially if we want to support Unicode.
+
+ char buffer[] = { c, 0 };
+ switch (c)
+ {
+ default:
+ m_stream->emit(buffer);
+ break;
+
+ case '\"': m_stream->emit("\\\"");
+ case '\'': m_stream->emit("\\\'");
+ case '\\': m_stream->emit("\\\\");
+ case '\n': m_stream->emit("\\n");
+ case '\r': m_stream->emit("\\r");
+ case '\t': m_stream->emit("\\t");
+ }
+ }
+ m_stream->emit("\"");
+}
+
+void CLikeSourceEmitter::requireGLSLExtension(String const& name)
+{
+ m_context->extensionUsageTracker.requireGLSLExtension(name);
+}
+
+void CLikeSourceEmitter::requireGLSLVersion(ProfileVersion version)
+{
+ if (m_context->target != CodeGenTarget::GLSL)
+ return;
+
+ m_context->extensionUsageTracker.requireGLSLVersion(version);
+}
+
+void CLikeSourceEmitter::requireGLSLVersion(int version)
+{
+ switch (version)
+ {
+#define CASE(NUMBER) \
+ case NUMBER: requireGLSLVersion(ProfileVersion::GLSL_##NUMBER); break
+
+ CASE(110);
+ CASE(120);
+ CASE(130);
+ CASE(140);
+ CASE(150);
+ CASE(330);
+ CASE(400);
+ CASE(410);
+ CASE(420);
+ CASE(430);
+ CASE(440);
+ CASE(450);
+
+#undef CASE
+ }
+}
+
+void CLikeSourceEmitter::setSampleRateFlag()
+{
+ m_context->entryPointLayout->flags |= EntryPointLayout::Flag::usesAnySampleRateInput;
+}
+
+void CLikeSourceEmitter::doSampleRateInputCheck(Name* name)
+{
+ auto text = getText(name);
+ if (text == "gl_SampleID")
+ {
+ setSampleRateFlag();
+ }
+}
+
+void CLikeSourceEmitter::emitVal(IRInst* val, EmitOpInfo const& outerPrec)
+{
+ if(auto type = as<IRType>(val))
+ {
+ emitType(type);
+ }
+ else
+ {
+ emitIRInstExpr(val, IREmitMode::Default, outerPrec);
+ }
+}
+
+UInt CLikeSourceEmitter::getBindingOffset(EmitVarChain* chain, LayoutResourceKind kind)
+{
+ UInt offset = 0;
+ for(auto cc = chain; cc; cc = cc->next)
+ {
+ if(auto resInfo = cc->varLayout->FindResourceInfo(kind))
+ {
+ offset += resInfo->index;
+ }
+ }
+ return offset;
+}
+
+UInt CLikeSourceEmitter::getBindingSpace(EmitVarChain* chain, LayoutResourceKind kind)
+{
+ UInt space = 0;
+ for(auto cc = chain; cc; cc = cc->next)
+ {
+ auto varLayout = cc->varLayout;
+ if(auto resInfo = varLayout->FindResourceInfo(kind))
+ {
+ space += resInfo->space;
+ }
+ if(auto resInfo = varLayout->FindResourceInfo(LayoutResourceKind::RegisterSpace))
+ {
+ space += resInfo->index;
+ }
+ }
+ return space;
+}
+
+void CLikeSourceEmitter::emitHLSLRegisterSemantic(LayoutResourceKind kind, EmitVarChain* chain, char const* uniformSemanticSpelling)
+{
+ if(!chain)
+ return;
+ if(!chain->varLayout->FindResourceInfo(kind))
+ return;
+
+ UInt index = getBindingOffset(chain, kind);
+ UInt space = getBindingSpace(chain, kind);
+
+ switch(kind)
+ {
+ case LayoutResourceKind::Uniform:
+ {
+ UInt offset = index;
+
+ // The HLSL `c` register space is logically grouped in 16-byte registers,
+ // while we try to traffic in byte offsets. That means we need to pick
+ // a register number, based on the starting offset in 16-byte register
+ // units, and then a "component" within that register, based on 4-byte
+ // offsets from there. We cannot support more fine-grained offsets than that.
+
+ m_stream->emit(" : ");
+ m_stream->emit(uniformSemanticSpelling);
+ m_stream->emit("(c");
+
+ // Size of a logical `c` register in bytes
+ auto registerSize = 16;
+
+ // Size of each component of a logical `c` register, in bytes
+ auto componentSize = 4;
+
+ size_t startRegister = offset / registerSize;
+ m_stream->emit(int(startRegister));
+
+ size_t byteOffsetInRegister = offset % registerSize;
+
+ // If this field doesn't start on an even register boundary,
+ // then we need to emit additional information to pick the
+ // right component to start from
+ if (byteOffsetInRegister != 0)
+ {
+ // The value had better occupy a whole number of components.
+ SLANG_RELEASE_ASSERT(byteOffsetInRegister % componentSize == 0);
+
+ size_t startComponent = byteOffsetInRegister / componentSize;
+
+ static const char* kComponentNames[] = {"x", "y", "z", "w"};
+ m_stream->emit(".");
+ m_stream->emit(kComponentNames[startComponent]);
+ }
+ m_stream->emit(")");
+ }
+ break;
+
+ case LayoutResourceKind::RegisterSpace:
+ case LayoutResourceKind::GenericResource:
+ case LayoutResourceKind::ExistentialTypeParam:
+ case LayoutResourceKind::ExistentialObjectParam:
+ // ignore
+ break;
+ default:
+ {
+ m_stream->emit(" : register(");
+ switch( kind )
+ {
+ case LayoutResourceKind::ConstantBuffer:
+ m_stream->emit("b");
+ break;
+ case LayoutResourceKind::ShaderResource:
+ m_stream->emit("t");
+ break;
+ case LayoutResourceKind::UnorderedAccess:
+ m_stream->emit("u");
+ break;
+ case LayoutResourceKind::SamplerState:
+ m_stream->emit("s");
+ break;
+ default:
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled HLSL register type");
+ break;
+ }
+ m_stream->emit(index);
+ if(space)
+ {
+ m_stream->emit(", space");
+ m_stream->emit(space);
+ }
+ m_stream->emit(")");
+ }
+ }
+}
+
+void CLikeSourceEmitter::emitHLSLRegisterSemantics(EmitVarChain* chain, char const* uniformSemanticSpelling)
+{
+ if (!chain) return;
+
+ auto layout = chain->varLayout;
+
+ switch( m_context->target )
+ {
+ default:
+ return;
+
+ case CodeGenTarget::HLSL:
+ break;
+ }
+
+ for( auto rr : layout->resourceInfos )
+ {
+ emitHLSLRegisterSemantic(rr.kind, chain, uniformSemanticSpelling);
+ }
+}
+
+void CLikeSourceEmitter::emitHLSLRegisterSemantics(VarLayout* varLayout, char const* uniformSemanticSpelling)
+{
+ if(!varLayout)
+ return;
+
+ EmitVarChain chain(varLayout);
+ emitHLSLRegisterSemantics(&chain, uniformSemanticSpelling);
+}
+
+void CLikeSourceEmitter::emitHLSLParameterGroupFieldLayoutSemantics(EmitVarChain* chain)
+{
+ if(!chain)
+ return;
+
+ auto layout = chain->varLayout;
+ for( auto rr : layout->resourceInfos )
+ {
+ emitHLSLRegisterSemantic(rr.kind, chain, "packoffset");
+ }
+}
+
+
+void CLikeSourceEmitter::emitHLSLParameterGroupFieldLayoutSemantics(RefPtr<VarLayout> fieldLayout, EmitVarChain* inChain)
+{
+ EmitVarChain chain(fieldLayout, inChain);
+ emitHLSLParameterGroupFieldLayoutSemantics(&chain);
+}
+
+bool CLikeSourceEmitter::emitGLSLLayoutQualifier(LayoutResourceKind kind, EmitVarChain* chain)
+{
+ if(!chain)
+ return false;
+ if(!chain->varLayout->FindResourceInfo(kind))
+ return false;
+
+ UInt index = getBindingOffset(chain, kind);
+ UInt space = getBindingSpace(chain, kind);
+ switch(kind)
+ {
+ case LayoutResourceKind::Uniform:
+ {
+ // Explicit offsets require a GLSL extension (which
+ // is not universally supported, it seems) or a new
+ // enough GLSL version (which we don't want to
+ // universally require), so for right now we
+ // won't actually output explicit offsets for uniform
+ // shader parameters.
+ //
+ // TODO: We should fix this so that we skip any
+ // extra work for parameters that are laid out as
+ // expected by the default rules, but do *something*
+ // for parameters that need non-default layout.
+ //
+ // Using the `GL_ARB_enhanced_layouts` feature is one
+ // option, but we should also be able to do some
+ // things by introducing padding into the declaration
+ // (padding insertion would probably be best done at
+ // the IR level).
+ bool useExplicitOffsets = false;
+ if (useExplicitOffsets)
+ {
+ requireGLSLExtension("GL_ARB_enhanced_layouts");
+
+ m_stream->emit("layout(offset = ");
+ m_stream->emit(index);
+ m_stream->emit(")\n");
+ }
+ }
+ break;
+
+ case LayoutResourceKind::VertexInput:
+ case LayoutResourceKind::FragmentOutput:
+ m_stream->emit("layout(location = ");
+ m_stream->emit(index);
+ m_stream->emit(")\n");
+ break;
+
+ case LayoutResourceKind::SpecializationConstant:
+ m_stream->emit("layout(constant_id = ");
+ m_stream->emit(index);
+ m_stream->emit(")\n");
+ break;
+
+ case LayoutResourceKind::ConstantBuffer:
+ case LayoutResourceKind::ShaderResource:
+ case LayoutResourceKind::UnorderedAccess:
+ case LayoutResourceKind::SamplerState:
+ case LayoutResourceKind::DescriptorTableSlot:
+ m_stream->emit("layout(binding = ");
+ m_stream->emit(index);
+ if(space)
+ {
+ m_stream->emit(", set = ");
+ m_stream->emit(space);
+ }
+ m_stream->emit(")\n");
+ break;
+
+ case LayoutResourceKind::PushConstantBuffer:
+ m_stream->emit("layout(push_constant)\n");
+ break;
+ case LayoutResourceKind::ShaderRecord:
+ m_stream->emit("layout(shaderRecordNV)\n");
+ break;
+
+ }
+ return true;
+}
+
+void CLikeSourceEmitter::emitGLSLLayoutQualifiers(RefPtr<VarLayout> layout, EmitVarChain* inChain, LayoutResourceKind filter)
+{
+ if(!layout) return;
+
+ switch( getTarget())
+ {
+ default:
+ return;
+
+ case CodeGenTarget::GLSL:
+ break;
+ }
+
+ EmitVarChain chain(layout, inChain);
+
+ for( auto info : layout->resourceInfos )
+ {
+ // Skip info that doesn't match our filter
+ if (filter != LayoutResourceKind::None
+ && filter != info.kind)
+ {
+ continue;
+ }
+
+ emitGLSLLayoutQualifier(info.kind, &chain);
+ }
+}
+
+void CLikeSourceEmitter::emitGLSLVersionDirective()
+{
+ auto effectiveProfile = m_context->effectiveProfile;
+ if(effectiveProfile.getFamily() == ProfileFamily::GLSL)
+ {
+ requireGLSLVersion(effectiveProfile.GetVersion());
+ }
+
+ // HACK: We aren't picking GLSL versions carefully right now,
+ // and so we might end up only requiring the initial 1.10 version,
+ // even though even basic functionality needs a higher version.
+ //
+ // For now, we'll work around this by just setting the minimum required
+ // version to a high one:
+ //
+ // TODO: Either correctly compute a minimum required version, or require
+ // the user to specify a version as part of the target.
+ m_context->extensionUsageTracker.requireGLSLVersion(ProfileVersion::GLSL_450);
+
+ auto requiredProfileVersion = m_context->extensionUsageTracker.getRequiredGLSLProfileVersion();
+ switch (requiredProfileVersion)
+ {
+#define CASE(TAG, VALUE) \
+ case ProfileVersion::TAG: m_stream->emit("#version " #VALUE "\n"); return
+
+ CASE(GLSL_110, 110);
+ CASE(GLSL_120, 120);
+ CASE(GLSL_130, 130);
+ CASE(GLSL_140, 140);
+ CASE(GLSL_150, 150);
+ CASE(GLSL_330, 330);
+ CASE(GLSL_400, 400);
+ CASE(GLSL_410, 410);
+ CASE(GLSL_420, 420);
+ CASE(GLSL_430, 430);
+ CASE(GLSL_440, 440);
+ CASE(GLSL_450, 450);
+ CASE(GLSL_460, 460);
+#undef CASE
+
+ default:
+ break;
+ }
+
+ // No information is available for us to guess a profile,
+ // so it seems like we need to pick one out of thin air.
+ //
+ // Ideally we should infer a minimum required version based
+ // on the constructs we have seen used in the user's code
+ //
+ // For now we just fall back to a reasonably recent version.
+
+ m_stream->emit("#version 420\n");
+}
+
+void CLikeSourceEmitter::emitGLSLPreprocessorDirectives()
+{
+ switch(getTarget())
+ {
+ // Don't emit this stuff unless we are targetting GLSL
+ default:
+ return;
+
+ case CodeGenTarget::GLSL:
+ break;
+ }
+
+ emitGLSLVersionDirective();
+}
+
+void CLikeSourceEmitter::emitLayoutDirectives(TargetRequest* targetReq)
+{
+ // We are going to emit the target-language-specific directives
+ // needed to get the default matrix layout to match what was requested
+ // for the given target.
+ //
+ // Note: we do not rely on the defaults for the target language,
+ // because a user could take the HLSL/GLSL generated by Slang and pass
+ // it to another compiler with non-default options specified on
+ // the command line, leading to all kinds of trouble.
+ //
+ // TODO: We need an approach to "global" layout directives that will work
+ // in the presence of multiple modules. If modules A and B were each
+ // compiled with different assumptions about how layout is performed,
+ // then types/variables defined in those modules should be emitted in
+ // a way that is consistent with that layout...
+
+ auto matrixLayoutMode = targetReq->getDefaultMatrixLayoutMode();
+
+ switch(m_context->target)
+ {
+ default:
+ return;
+
+ case CodeGenTarget::GLSL:
+ // Reminder: the meaning of row/column major layout
+ // in our semantics is the *opposite* of what GLSL
+ // calls them, because what they call "columns"
+ // are what we call "rows."
+ //
+ switch(matrixLayoutMode)
+ {
+ case kMatrixLayoutMode_RowMajor:
+ default:
+ m_stream->emit("layout(column_major) uniform;\n");
+ m_stream->emit("layout(column_major) buffer;\n");
+ break;
+
+ case kMatrixLayoutMode_ColumnMajor:
+ m_stream->emit("layout(row_major) uniform;\n");
+ m_stream->emit("layout(row_major) buffer;\n");
+ break;
+ }
+ break;
+
+ case CodeGenTarget::HLSL:
+ switch(matrixLayoutMode)
+ {
+ case kMatrixLayoutMode_RowMajor:
+ default:
+ m_stream->emit("#pragma pack_matrix(row_major)\n");
+ break;
+
+ case kMatrixLayoutMode_ColumnMajor:
+ m_stream->emit("#pragma pack_matrix(column_major)\n");
+ break;
+ }
+ break;
+ }
+}
+
+UInt CLikeSourceEmitter::allocateUniqueID()
+{
+ return m_context->uniqueIDCounter++;
+}
+
+// IR-level emit logic
+
+UInt CLikeSourceEmitter::getID(IRInst* value)
+{
+ auto& mapIRValueToID = m_context->mapIRValueToID;
+
+ UInt id = 0;
+ if (mapIRValueToID.TryGetValue(value, id))
+ return id;
+
+ id = allocateUniqueID();
+ mapIRValueToID.Add(value, id);
+ return id;
+}
+
+/// "Scrub" a name so that it complies with restrictions of the target language.
+String CLikeSourceEmitter::scrubName(const String& name)
+{
+ // We will use a plain `U` as a dummy character to insert
+ // whenever we need to insert things to make a string into
+ // valid name.
+ //
+ char const* dummyChar = "U";
+
+ // Special case a name that is the empty string, just in case.
+ if(name.getLength() == 0)
+ return dummyChar;
+
+ // Otherwise, we are going to walk over the name byte by byte
+ // and write some legal characters to the output as we go.
+ StringBuilder sb;
+
+ if(getTarget() == CodeGenTarget::GLSL)
+ {
+ // GLSL reserverse all names that start with `gl_`,
+ // so if we are in danger of collision, then make
+ // our name start with a dummy character instead.
+ if(name.startsWith("gl_"))
+ {
+ sb.append(dummyChar);
+ }
+ }
+
+ // We will also detect user-defined names that
+ // might overlap with our convention for mangled names,
+ // to avoid an possible collision.
+ if(name.startsWith("_S"))
+ {
+ sb.Append(dummyChar);
+ }
+
+ // TODO: This is where we might want to consult
+ // a dictionary of reserved words for the chosen target
+ //
+ // if(isReservedWord(name)) { sb.Append(dummyChar); }
+ //
+
+ // We need to track the previous byte in
+ // order to detect consecutive underscores for GLSL.
+ int prevChar = -1;
+
+ for(auto c : name)
+ {
+ // We will treat a dot character just like an underscore
+ // for the purposes of producing a scrubbed name, so
+ // that we translate `SomeType.someMethod` into
+ // `SomeType_someMethod`.
+ //
+ // By handling this case at the top of this loop, we
+ // ensure that a `.`-turned-`_` is handled just like
+ // a `_` in the original name, and will be properly
+ // scrubbed for GLSL output.
+ //
+ if(c == '.')
+ {
+ c = '_';
+ }
+
+ if(((c >= 'a') && (c <= 'z'))
+ || ((c >= 'A') && (c <= 'Z')))
+ {
+ // Ordinary ASCII alphabetic characters are assumed
+ // to always be okay.
+ }
+ else if((c >= '0') && (c <= '9'))
+ {
+ // We don't want to allow a digit as the first
+ // byte in a name, since the result wouldn't
+ // be a valid identifier in many target languages.
+ if(prevChar == -1)
+ {
+ sb.append(dummyChar);
+ }
+ }
+ else if(c == '_')
+ {
+ // We will collapse any consecutive sequence of `_`
+ // characters into a single one (this means that
+ // some names that were unique in the original
+ // code might not resolve to unique names after
+ // scrubbing, but that was true in general).
+
+ if(prevChar == '_')
+ {
+ // Skip this underscore, so we don't output
+ // more than one in a row.
+ continue;
+ }
+ }
+ else
+ {
+ // If we run into a character that wouldn't normally
+ // be allowed in an identifier, we need to translate
+ // it into something that *is* valid.
+ //
+ // Our solution for now will be very clumsy: we will
+ // emit `x` and then the hexadecimal version of
+ // the byte we were given.
+ sb.append("x");
+ sb.append(uint32_t((unsigned char) c), 16);
+
+ // We don't want to apply the default handling below,
+ // so skip to the top of the loop now.
+ prevChar = c;
+ continue;
+ }
+
+ sb.append(c);
+ prevChar = c;
+ }
+
+ return sb.ProduceString();
+}
+
+String CLikeSourceEmitter::generateIRName(IRInst* inst)
+{
+ // If the instruction names something
+ // that should be emitted as a target intrinsic,
+ // then use that name instead.
+ if(auto intrinsicDecoration = findTargetIntrinsicDecoration(inst))
+ {
+ return String(intrinsicDecoration->getDefinition());
+ }
+
+ // If we have a name hint on the instruction, then we will try to use that
+ // to provide the actual name in the output code.
+ //
+ // We need to be careful that the name follows the rules of the target language,
+ // so there is a "scrubbing" step that needs to be applied here.
+ //
+ // We also need to make sure that the name won't collide with other declarations
+ // that might have the same name hint applied, so we will still unique
+ // them by appending the numeric ID of the instruction.
+ //
+ // TODO: Find cases where we can drop the suffix safely.
+ //
+ // TODO: When we start having to handle symbols with external linkage for
+ // things like DXIL libraries, we will need to *not* use the friendly
+ // names for stuff that should be link-able.
+ //
+ if(auto nameHintDecoration = inst->findDecoration<IRNameHintDecoration>())
+ {
+ // The name we output will basically be:
+ //
+ // <nameHint>_<uniqueID>
+ //
+ // Except that we will "scrub" the name hint first,
+ // and we will omit the underscore if the (scrubbed)
+ // name hint already ends with one.
+ //
+
+ String nameHint = nameHintDecoration->getName();
+ nameHint = scrubName(nameHint);
+
+ StringBuilder sb;
+ sb.append(nameHint);
+
+ // Avoid introducing a double underscore
+ if(!nameHint.endsWith("_"))
+ {
+ sb.append("_");
+ }
+
+ String key = sb.ProduceString();
+ UInt count = 0;
+ m_context->uniqueNameCounters.TryGetValue(key, count);
+
+ m_context->uniqueNameCounters[key] = count+1;
+
+ sb.append(Int32(count));
+ return sb.ProduceString();
+ }
+
+ // If the instruction has a mangled name, then emit using that.
+ if(auto linkageDecoration = inst->findDecoration<IRLinkageDecoration>())
+ {
+ return linkageDecoration->getMangledName();
+ }
+
+ // Otherwise fall back to a construct temporary name
+ // for the instruction.
+ StringBuilder sb;
+ sb << "_S";
+ sb << Int32(getID(inst));
+
+ return sb.ProduceString();
+}
+
+String CLikeSourceEmitter::getIRName(IRInst* inst)
+{
+ String name;
+ if(!m_context->mapInstToName.TryGetValue(inst, name))
+ {
+ name = generateIRName(inst);
+ m_context->mapInstToName.Add(inst, name);
+ }
+ return name;
+}
+void CLikeSourceEmitter::emitDeclarator(IRDeclaratorInfo* declarator)
+{
+ if(!declarator)
+ return;
+
+ switch( declarator->flavor )
+ {
+ case IRDeclaratorInfo::Flavor::Simple:
+ m_stream->emit(" ");
+ m_stream->emit(*declarator->name);
+ break;
+
+ case IRDeclaratorInfo::Flavor::Ptr:
+ m_stream->emit("*");
+ emitDeclarator(declarator->next);
+ break;
+
+ case IRDeclaratorInfo::Flavor::Array:
+ emitDeclarator(declarator->next);
+ m_stream->emit("[");
+ emitIROperand(declarator->elementCount, IREmitMode::Default, getInfo(EmitOp::General));
+ m_stream->emit("]");
+ break;
+ }
+}
+
+void CLikeSourceEmitter::emitIRSimpleValue(IRInst* inst)
+{
+ switch(inst->op)
+ {
+ case kIROp_IntLit:
+ m_stream->emit(((IRConstant*) inst)->value.intVal);
+ break;
+
+ case kIROp_FloatLit:
+ m_stream->emit(((IRConstant*) inst)->value.floatVal);
+ break;
+
+ case kIROp_BoolLit:
+ {
+ bool val = ((IRConstant*)inst)->value.intVal != 0;
+ m_stream->emit(val ? "true" : "false");
+ }
+ break;
+
+ default:
+ SLANG_UNIMPLEMENTED_X("val case for emit");
+ break;
+ }
+
+}
+
+CodeGenTarget CLikeSourceEmitter::getTarget()
+{
+ return m_context->target;
+}
+
+bool CLikeSourceEmitter::shouldFoldIRInstIntoUseSites(IRInst* inst, IREmitMode mode)
+{
+ // Certain opcodes should never/always be folded in
+ switch( inst->op )
+ {
+ default:
+ break;
+
+ // Never fold these in, because they represent declarations
+ //
+ case kIROp_Var:
+ case kIROp_GlobalVar:
+ case kIROp_GlobalConstant:
+ case kIROp_GlobalParam:
+ case kIROp_Param:
+ case kIROp_Func:
+ return false;
+
+ // Always fold these in, because they are trivial
+ //
+ case kIROp_IntLit:
+ case kIROp_FloatLit:
+ case kIROp_BoolLit:
+ return true;
+
+ // Always fold these in, because their results
+ // cannot be represented in the type system of
+ // our current targets.
+ //
+ // TODO: when we add C/C++ as an optional target,
+ // we could consider lowering insts that result
+ // in pointers directly.
+ //
+ case kIROp_FieldAddress:
+ case kIROp_getElementPtr:
+ case kIROp_Specialize:
+ return true;
+ }
+
+ // Always fold when we are inside a global constant initializer
+ if (mode == IREmitMode::GlobalConstant)
+ return true;
+
+ switch( inst->op )
+ {
+ default:
+ break;
+
+ // HACK: don't fold these in because we currently lower
+ // them to initializer lists, which aren't allowed in
+ // general expression contexts.
+ //
+ // Note: we are doing this check *after* the check for `GlobalConstant`
+ // mode, because otherwise we'd fail to emit initializer lists in
+ // the main place where we want/need them.
+ //
+ case kIROp_makeStruct:
+ case kIROp_makeArray:
+ return false;
+
+ }
+
+ // Instructions with specific result *types* will usually
+ // want to be folded in, because they aren't allowed as types
+ // for temporary variables.
+ auto type = inst->getDataType();
+
+ // Unwrap any layers of array-ness from the type, so that
+ // we can look at the underlying data type, in case we
+ // should *never* expose a value of that type
+ while (auto arrayType = as<IRArrayTypeBase>(type))
+ {
+ type = arrayType->getElementType();
+ }
+
+ // Don't allow temporaries of pointer types to be created.
+ if(as<IRPtrTypeBase>(type))
+ {
+ return true;
+ }
+
+ // First we check for uniform parameter groups,
+ // because a `cbuffer` or GLSL `uniform` block
+ // does not have a first-class type that we can
+ // pass around.
+ //
+ // TODO: We need to ensure that type legalization
+ // cleans up cases where we use a parameter group
+ // or parameter block type as a function parameter...
+ //
+ if(as<IRUniformParameterGroupType>(type))
+ {
+ // TODO: we need to be careful here, because
+ // HLSL shader model 6 allows these as explicit
+ // types.
+ return true;
+ }
+ //
+ // The stream-output and patch types need to be handled
+ // too, because they are not really first class (especially
+ // not in GLSL, but they also seem to confuse the HLSL
+ // compiler when they get used as temporaries).
+ //
+ else if (as<IRHLSLStreamOutputType>(type))
+ {
+ return true;
+ }
+ else if (as<IRHLSLPatchType>(type))
+ {
+ return true;
+ }
+
+
+ // GLSL doesn't allow texture/resource types to
+ // be used as first-class values, so we need
+ // to fold them into their use sites in all cases
+ if (getTarget() == CodeGenTarget::GLSL)
+ {
+ if(as<IRResourceTypeBase>(type))
+ {
+ return true;
+ }
+ else if(as<IRHLSLStructuredBufferTypeBase>(type))
+ {
+ return true;
+ }
+ else if(as<IRUntypedBufferResourceType>(type))
+ {
+ return true;
+ }
+ else if(as<IRSamplerStateTypeBase>(type))
+ {
+ return true;
+ }
+ }
+
+ // If the instruction is at global scope, then it might represent
+ // a constant (e.g., the value of an enum case).
+ //
+ if(as<IRModuleInst>(inst->getParent()))
+ {
+ if(!inst->mightHaveSideEffects())
+ return true;
+ }
+
+ // Having dealt with all of the cases where we *must* fold things
+ // above, we can now deal with the more general cases where we
+ // *should not* fold things.
+
+ // Don't fold something with no users:
+ if(!inst->hasUses())
+ return false;
+
+ // Don't fold something that has multiple users:
+ if(inst->hasMoreThanOneUse())
+ return false;
+
+ // Don't fold something that might have side effects:
+ if(inst->mightHaveSideEffects())
+ return false;
+
+ // Don't fold instructions that are marked `[precise]`.
+ // This could in principle be extended to any other
+ // decorations that affect the semantics of an instruction
+ // in ways that require a temporary to be introduced.
+ //
+ if(inst->findDecoration<IRPreciseDecoration>())
+ return false;
+
+ // Okay, at this point we know our instruction must have a single use.
+ auto use = inst->firstUse;
+ SLANG_ASSERT(use);
+ SLANG_ASSERT(!use->nextUse);
+
+ auto user = use->getUser();
+
+ // We'd like to figure out if it is safe to fold our instruction into `user`
+
+ // First, let's make sure they are in the same block/parent:
+ if(inst->getParent() != user->getParent())
+ return false;
+
+ // Now let's look at all the instructions between this instruction
+ // and the user. If any of them might have side effects, then lets
+ // bail out now.
+ for(auto ii = inst->getNextInst(); ii != user; ii = ii->getNextInst())
+ {
+ if(!ii)
+ {
+ // We somehow reached the end of the block without finding
+ // the user, which doesn't make sense if uses dominate
+ // defs. Let's just play it safe and bail out.
+ return false;
+ }
+
+ if(ii->mightHaveSideEffects())
+ return false;
+ }
+
+ // Okay, if we reach this point then the user comes later in
+ // the same block, and there are no instructions with side
+ // effects in between, so it seems safe to fold things in.
+ return true;
+}
+
+void CLikeSourceEmitter::emitIROperand(IRInst* inst, IREmitMode mode, EmitOpInfo const& outerPrec)
+{
+ if( shouldFoldIRInstIntoUseSites(inst, mode) )
+ {
+ emitIRInstExpr(inst, mode, outerPrec);
+ return;
+ }
+
+ switch(inst->op)
+ {
+ case 0: // nothing yet
+ default:
+ m_stream->emit(getIRName(inst));
+ break;
+ }
+}
+
+void CLikeSourceEmitter::emitIRArgs(IRInst* inst, IREmitMode mode)
+{
+ UInt argCount = inst->getOperandCount();
+ IRUse* args = inst->getOperands();
+
+ m_stream->emit("(");
+ for(UInt aa = 0; aa < argCount; ++aa)
+ {
+ if(aa != 0) m_stream->emit(", ");
+ emitIROperand(args[aa].get(), mode, getInfo(EmitOp::General));
+ }
+ m_stream->emit(")");
+}
+
+void CLikeSourceEmitter::emitIRType(IRType* type, String const& name)
+{
+ emitType(type, name);
+}
+
+void CLikeSourceEmitter::emitIRType(IRType* type, Name* name)
+{
+ emitType(type, name);
+}
+
+void CLikeSourceEmitter::emitIRType(IRType* type)
+{
+ emitType(type);
+}
+
+void CLikeSourceEmitter::emitIRRateQualifiers(IRRate* rate)
+{
+ if(!rate) return;
+
+ if(as<IRConstExprRate>(rate))
+ {
+ switch( getTarget() )
+ {
+ case CodeGenTarget::GLSL:
+ m_stream->emit("const ");
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (as<IRGroupSharedRate>(rate))
+ {
+ switch( getTarget() )
+ {
+ case CodeGenTarget::HLSL:
+ m_stream->emit("groupshared ");
+ break;
+
+ case CodeGenTarget::GLSL:
+ m_stream->emit("shared ");
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+void CLikeSourceEmitter::emitIRRateQualifiers(IRInst* value)
+{
+ emitIRRateQualifiers(value->getRate());
+}
+
+void CLikeSourceEmitter::emitIRInstResultDecl(IRInst* inst)
+{
+ auto type = inst->getDataType();
+ if(!type)
+ return;
+
+ if (as<IRVoidType>(type))
+ return;
+
+ emitIRTempModifiers(inst);
+
+ emitIRRateQualifiers(inst);
+
+ emitIRType(type, getIRName(inst));
+ m_stream->emit(" = ");
+}
+
+IRTargetIntrinsicDecoration* CLikeSourceEmitter::findTargetIntrinsicDecoration(IRInst* inst)
+{
+ for(auto dd : inst->getDecorations())
+ {
+ if (dd->op != kIROp_TargetIntrinsicDecoration)
+ continue;
+
+ auto targetIntrinsic = (IRTargetIntrinsicDecoration*)dd;
+ if (isTargetIntrinsicModifierApplicable(targetIntrinsic))
+ return targetIntrinsic;
+ }
+
+ return nullptr;
+}
+
+/* static */bool CLikeSourceEmitter::isOrdinaryName(String const& name)
+{
+ char const* cursor = name.begin();
+ char const* end = name.end();
+
+ while(cursor != end)
+ {
+ int c = *cursor++;
+ if( (c >= 'a') && (c <= 'z') ) continue;
+ if( (c >= 'A') && (c <= 'Z') ) continue;
+ if( c == '_' ) continue;
+
+ return false;
+ }
+ return true;
+}
+
+void CLikeSourceEmitter::emitTargetIntrinsicCallExpr(
+ IRCall* inst,
+ IRFunc* /* func */,
+ IRTargetIntrinsicDecoration* targetIntrinsic,
+ IREmitMode mode,
+ EmitOpInfo const& inOuterPrec)
+{
+ auto outerPrec = inOuterPrec;
+
+ IRUse* args = inst->getOperands();
+ Index argCount = inst->getOperandCount();
+
+ // First operand was the function to be called
+ args++;
+ argCount--;
+
+ auto name = String(targetIntrinsic->getDefinition());
+
+ if(isOrdinaryName(name))
+ {
+ // Simple case: it is just an ordinary name, so we call it like a builtin.
+ auto prec = getInfo(EmitOp::Postfix);
+ bool needClose = maybeEmitParens(outerPrec, prec);
+
+ m_stream->emit(name);
+ m_stream->emit("(");
+ for (Index aa = 0; aa < argCount; ++aa)
+ {
+ if (aa != 0) m_stream->emit(", ");
+ emitIROperand(args[aa].get(), mode, getInfo(EmitOp::General));
+ }
+ m_stream->emit(")");
+
+ maybeCloseParens(needClose);
+ return;
+ }
+ else
+ {
+ int openParenCount = 0;
+
+ const auto returnType = inst->getDataType();
+
+ // If it returns void -> then we don't need parenthesis
+ if (as<IRVoidType>(returnType) == nullptr)
+ {
+ m_stream->emit("(");
+ openParenCount++;
+ }
+
+ // General case: we are going to emit some more complex text.
+
+ char const* cursor = name.begin();
+ char const* end = name.end();
+ while(cursor != end)
+ {
+ char c = *cursor++;
+ if( c != '$' )
+ {
+ // Not an escape sequence
+ m_stream->emitRawTextSpan(&c, &c+1);
+ continue;
+ }
+
+ SLANG_RELEASE_ASSERT(cursor != end);
+
+ char d = *cursor++;
+
+ switch (d)
+ {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ {
+ // Simple case: emit one of the direct arguments to the call
+ Index argIndex = d - '0';
+ SLANG_RELEASE_ASSERT((0 <= argIndex) && (argIndex < argCount));
+ m_stream->emit("(");
+ emitIROperand(args[argIndex].get(), mode, getInfo(EmitOp::General));
+ m_stream->emit(")");
+ }
+ break;
+
+ case 'p':
+ {
+ // If we are calling a D3D texturing operation in the form t.Foo(s, ...),
+ // then this form will pair up the t and s arguments as needed for a GLSL
+ // texturing operation.
+ SLANG_RELEASE_ASSERT(argCount >= 2);
+
+ auto textureArg = args[0].get();
+ auto samplerArg = args[1].get();
+
+ if (auto baseTextureType = as<IRTextureType>(textureArg->getDataType()))
+ {
+ emitGLSLTextureOrTextureSamplerType(baseTextureType, "sampler");
+
+ if (auto samplerType = as<IRSamplerStateTypeBase>(samplerArg->getDataType()))
+ {
+ if (as<IRSamplerComparisonStateType>(samplerType))
+ {
+ m_stream->emit("Shadow");
+ }
+ }
+
+ m_stream->emit("(");
+ emitIROperand(textureArg, mode, getInfo(EmitOp::General));
+ m_stream->emit(",");
+ emitIROperand(samplerArg, mode, getInfo(EmitOp::General));
+ m_stream->emit(")");
+ }
+ else
+ {
+ SLANG_UNEXPECTED("bad format in intrinsic definition");
+ }
+ }
+ break;
+
+ case 'c':
+ {
+ // When doing texture access in glsl the result may need to be cast.
+ // In particular if the underlying texture is 'half' based, glsl only accesses (read/write)
+ // as float. So we need to cast to a half type on output.
+ // When storing into a texture it is still the case the value written must be half - but
+ // we don't need to do any casting there as half is coerced to float without a problem.
+ SLANG_RELEASE_ASSERT(argCount >= 1);
+
+ auto textureArg = args[0].get();
+ if (auto baseTextureType = as<IRTextureType>(textureArg->getDataType()))
+ {
+ auto elementType = baseTextureType->getElementType();
+ IRBasicType* underlyingType = nullptr;
+ if (auto basicType = as<IRBasicType>(elementType))
+ {
+ underlyingType = basicType;
+ }
+ else if (auto vectorType = as<IRVectorType>(elementType))
+ {
+ underlyingType = as<IRBasicType>(vectorType->getElementType());
+ }
+
+ // We only need to output a cast if the underlying type is half.
+ if (underlyingType && underlyingType->op == kIROp_HalfType)
+ {
+ emitSimpleTypeImpl(elementType);
+ m_stream->emit("(");
+ openParenCount++;
+ }
+ }
+ }
+ break;
+
+ case 'z':
+ {
+ // If we are calling a D3D texturing operation in the form t.Foo(s, ...),
+ // where `t` is a `Texture*<T>`, then this is the step where we try to
+ // properly swizzle the output of the equivalent GLSL call into the right
+ // shape.
+ SLANG_RELEASE_ASSERT(argCount >= 1);
+
+ auto textureArg = args[0].get();
+ if (auto baseTextureType = as<IRTextureType>(textureArg->getDataType()))
+ {
+ auto elementType = baseTextureType->getElementType();
+ if (auto basicType = as<IRBasicType>(elementType))
+ {
+ // A scalar result is expected
+ m_stream->emit(".x");
+ }
+ else if (auto vectorType = as<IRVectorType>(elementType))
+ {
+ // A vector result is expected
+ auto elementCount = GetIntVal(vectorType->getElementCount());
+
+ if (elementCount < 4)
+ {
+ char const* swiz[] = { "", ".x", ".xy", ".xyz", "" };
+ m_stream->emit(swiz[elementCount]);
+ }
+ }
+ else
+ {
+ // What other cases are possible?
+ }
+ }
+ else
+ {
+ SLANG_UNEXPECTED("bad format in intrinsic definition");
+ }
+ }
+ break;
+
+ case 'N':
+ {
+ // Extract the element count from a vector argument so that
+ // we can use it in the constructed expression.
+
+ SLANG_RELEASE_ASSERT(*cursor >= '0' && *cursor <= '9');
+ Index argIndex = (*cursor++) - '0';
+ SLANG_RELEASE_ASSERT(argCount > argIndex);
+
+ auto vectorArg = args[argIndex].get();
+ if (auto vectorType = as<IRVectorType>(vectorArg->getDataType()))
+ {
+ auto elementCount = GetIntVal(vectorType->getElementCount());
+ m_stream->emit(elementCount);
+ }
+ else
+ {
+ SLANG_UNEXPECTED("bad format in intrinsic definition");
+ }
+ }
+ break;
+
+ case 'V':
+ {
+ // Take an argument of some scalar/vector type and pad
+ // it out to a 4-vector with the same element type
+ // (this is the inverse of `$z`).
+ //
+ SLANG_RELEASE_ASSERT(*cursor >= '0' && *cursor <= '9');
+ Index argIndex = (*cursor++) - '0';
+ SLANG_RELEASE_ASSERT(argCount > argIndex);
+
+ auto arg = args[argIndex].get();
+ IRIntegerValue elementCount = 1;
+ IRType* elementType = arg->getDataType();
+ if (auto vectorType = as<IRVectorType>(elementType))
+ {
+ elementCount = GetIntVal(vectorType->getElementCount());
+ elementType = vectorType->getElementType();
+ }
+
+ if(elementCount == 4)
+ {
+ // In the simple case, the operand is already a 4-vector,
+ // so we can just emit it as-is.
+ emitIROperand(arg, mode, getInfo(EmitOp::General));
+ }
+ else
+ {
+ // Otherwise, we need to construct a 4-vector from the
+ // value we have, padding it out with zero elements as
+ // needed.
+ //
+ emitVectorTypeName(elementType, 4);
+ m_stream->emit("(");
+ emitIROperand(arg, mode, getInfo(EmitOp::General));
+ for(IRIntegerValue ii = elementCount; ii < 4; ++ii)
+ {
+ m_stream->emit(", ");
+ if(getTarget() == CodeGenTarget::GLSL)
+ {
+ emitSimpleTypeImpl(elementType);
+ m_stream->emit("(0)");
+ }
+ else
+ {
+ m_stream->emit("0");
+ }
+ }
+ m_stream->emit(")");
+ }
+ }
+ break;
+
+ case 'a':
+ {
+ // We have an operation that needs to lower to either
+ // `atomic*` or `imageAtomic*` for GLSL, depending on
+ // whether its first operand is a subscript into an
+ // array. This `$a` is the first `a` in `atomic`,
+ // so we will replace it accordingly.
+ //
+ // TODO: This distinction should be made earlier,
+ // with the front-end picking the right overload
+ // based on the "address space" of the argument.
+
+ Index argIndex = 0;
+ SLANG_RELEASE_ASSERT(argCount > argIndex);
+
+ auto arg = args[argIndex].get();
+ if(arg->op == kIROp_ImageSubscript)
+ {
+ m_stream->emit("imageA");
+ }
+ else
+ {
+ m_stream->emit("a");
+ }
+ }
+ break;
+
+ case 'A':
+ {
+ // We have an operand that represents the destination
+ // of an atomic operation in GLSL, and it should
+ // be lowered based on whether it is an ordinary l-value,
+ // or an image subscript. In the image subscript case
+ // this operand will turn into multiple arguments
+ // to the `imageAtomic*` function.
+ //
+
+ Index argIndex = 0;
+ SLANG_RELEASE_ASSERT(argCount > argIndex);
+
+ auto arg = args[argIndex].get();
+ if(arg->op == kIROp_ImageSubscript)
+ {
+ if(getTarget() == CodeGenTarget::GLSL)
+ {
+ // TODO: we don't handle the multisample
+ // case correctly here, where the last
+ // component of the image coordinate needs
+ // to be broken out into its own argument.
+ //
+ m_stream->emit("(");
+ emitIROperand(arg->getOperand(0), mode, getInfo(EmitOp::General));
+ m_stream->emit("), ");
+
+ // The coordinate argument will have been computed
+ // as a `vector<uint, N>` because that is how the
+ // HLSL image subscript operations are defined.
+ // In contrast, the GLSL `imageAtomic*` operations
+ // expect `vector<int, N>` coordinates, so we
+ // hill hackily insert the conversion here as
+ // part of the intrinsic op.
+ //
+ auto coords = arg->getOperand(1);
+ auto coordsType = coords->getDataType();
+
+ auto coordsVecType = as<IRVectorType>(coordsType);
+ IRIntegerValue elementCount = 1;
+ if(coordsVecType)
+ {
+ coordsType = coordsVecType->getElementType();
+ elementCount = GetIntVal(coordsVecType->getElementCount());
+ }
+
+ SLANG_ASSERT(coordsType->op == kIROp_UIntType);
+
+ if (elementCount > 1)
+ {
+ m_stream->emit("ivec");
+ m_stream->emit(elementCount);
+ }
+ else
+ {
+ m_stream->emit("int");
+ }
+
+ m_stream->emit("(");
+ emitIROperand(arg->getOperand(1), mode, getInfo(EmitOp::General));
+ m_stream->emit(")");
+ }
+ else
+ {
+ m_stream->emit("(");
+ emitIROperand(arg, mode, getInfo(EmitOp::General));
+ m_stream->emit(")");
+ }
+ }
+ else
+ {
+ m_stream->emit("(");
+ emitIROperand(arg, mode, getInfo(EmitOp::General));
+ m_stream->emit(")");
+ }
+ }
+ break;
+
+ // We will use the `$X` case as a prefix for
+ // special logic needed when cross-compiling ray-tracing
+ // shaders.
+ case 'X':
+ {
+ SLANG_RELEASE_ASSERT(*cursor);
+ switch(*cursor++)
+ {
+ case 'P':
+ {
+ // The `$XP` case handles looking up
+ // the associated `location` for a variable
+ // used as the argument ray payload at a
+ // trace call site.
+
+ Index argIndex = 0;
+ SLANG_RELEASE_ASSERT(argCount > argIndex);
+ auto arg = args[argIndex].get();
+ auto argLoad = as<IRLoad>(arg);
+ SLANG_RELEASE_ASSERT(argLoad);
+ auto argVar = argLoad->getOperand(0);
+ m_stream->emit(getRayPayloadLocation(argVar));
+ }
+ break;
+
+ case 'C':
+ {
+ // The `$XC` case handles looking up
+ // the associated `location` for a variable
+ // used as the argument callable payload at a
+ // call site.
+
+ Index argIndex = 0;
+ SLANG_RELEASE_ASSERT(argCount > argIndex);
+ auto arg = args[argIndex].get();
+ auto argLoad = as<IRLoad>(arg);
+ SLANG_RELEASE_ASSERT(argLoad);
+ auto argVar = argLoad->getOperand(0);
+ m_stream->emit(getCallablePayloadLocation(argVar));
+ }
+ break;
+
+ case 'T':
+ {
+ // The `$XT` case handles selecting between
+ // the `gl_HitTNV` and `gl_RayTmaxNV` builtins,
+ // based on what stage we are using:
+ switch( m_context->entryPoint->getStage() )
+ {
+ default:
+ m_stream->emit("gl_RayTmaxNV");
+ break;
+
+ case Stage::AnyHit:
+ case Stage::ClosestHit:
+ m_stream->emit("gl_HitTNV");
+ break;
+ }
+ }
+ break;
+
+ default:
+ SLANG_RELEASE_ASSERT(false);
+ break;
+ }
+ }
+ break;
+
+ default:
+ SLANG_UNEXPECTED("bad format in intrinsic definition");
+ break;
+ }
+ }
+
+ // Close any remaining open parens
+ for (; openParenCount > 0; --openParenCount)
+ {
+ m_stream->emit(")");
+ }
+ }
+}
+
+void CLikeSourceEmitter::emitIntrinsicCallExpr(
+ IRCall* inst,
+ IRFunc* func,
+ IREmitMode mode,
+ EmitOpInfo const& inOuterPrec)
+{
+ auto outerPrec = inOuterPrec;
+ bool needClose = false;
+
+ // For a call with N arguments, the instruction will
+ // have N+1 operands. We will start consuming operands
+ // starting at the index 1.
+ UInt operandCount = inst->getOperandCount();
+ UInt argCount = operandCount - 1;
+ UInt operandIndex = 1;
+
+
+ //
+ if (auto targetIntrinsicDecoration = findTargetIntrinsicDecoration(func))
+ {
+ emitTargetIntrinsicCallExpr(
+ inst,
+ func,
+ targetIntrinsicDecoration,
+ mode,
+ outerPrec);
+ return;
+ }
+
+ // Our current strategy for dealing with intrinsic
+ // calls is to "un-mangle" the mangled name, in
+ // order to figure out what the user was originally
+ // calling. This is a bit messy, and there might
+ // be better strategies (including just stuffing
+ // a pointer to the original decl onto the callee).
+
+ // If the intrinsic the user is calling is a generic,
+ // then the mangled name will have been set on the
+ // outer-most generic, and not on the leaf value
+ // (which is `func` above), so we need to walk
+ // upwards to find it.
+ //
+ IRInst* valueForName = func;
+ for(;;)
+ {
+ auto parentBlock = as<IRBlock>(valueForName->parent);
+ if(!parentBlock)
+ break;
+
+ auto parentGeneric = as<IRGeneric>(parentBlock->parent);
+ if(!parentGeneric)
+ break;
+
+ valueForName = parentGeneric;
+ }
+
+ // If we reach this point, we are assuming that the value
+ // has some kind of linkage, and thus a mangled name.
+ //
+ auto linkageDecoration = valueForName->findDecoration<IRLinkageDecoration>();
+ SLANG_ASSERT(linkageDecoration);
+ auto mangledName = String(linkageDecoration->getMangledName());
+
+
+ // We will use the `MangledLexer` to
+ // help us split the original name into its pieces.
+ MangledLexer lexer(mangledName);
+
+ // We'll read through the qualified name of the
+ // symbol (e.g., `Texture2D<T>.Sample`) and then
+ // only keep the last segment of the name (e.g.,
+ // the `Sample` part).
+ auto name = lexer.readSimpleName();
+
+ // We will special-case some names here, that
+ // represent callable declarations that aren't
+ // ordinary functions, and thus may use different
+ // syntax.
+ if(name == "operator[]")
+ {
+ // The user is invoking a built-in subscript operator
+
+ auto prec = getInfo(EmitOp::Postfix);
+ needClose = maybeEmitParens(outerPrec, prec);
+
+ emitIROperand(inst->getOperand(operandIndex++), mode, leftSide(outerPrec, prec));
+ m_stream->emit("[");
+ emitIROperand(inst->getOperand(operandIndex++), mode, getInfo(EmitOp::General));
+ m_stream->emit("]");
+
+ if(operandIndex < operandCount)
+ {
+ m_stream->emit(" = ");
+ emitIROperand(inst->getOperand(operandIndex++), mode, getInfo(EmitOp::General));
+ }
+
+ maybeCloseParens(needClose);
+ return;
+ }
+
+ auto prec = getInfo(EmitOp::Postfix);
+ needClose = maybeEmitParens(outerPrec, prec);
+
+ // The mangled function name currently records
+ // the number of explicit parameters, and thus
+ // doesn't include the implicit `this` parameter.
+ // We can compare the argument and parameter counts
+ // to figure out whether we have a member function call.
+ UInt paramCount = lexer.readParamCount();
+
+ if(argCount != paramCount)
+ {
+ // Looks like a member function call
+ emitIROperand(inst->getOperand(operandIndex), mode, leftSide(outerPrec, prec));
+ m_stream->emit(".");
+ operandIndex++;
+ }
+ // fixing issue #602 for GLSL sign function: https://github.com/shader-slang/slang/issues/602
+ bool glslSignFix = getTarget() == CodeGenTarget::GLSL && name == "sign";
+ if (glslSignFix)
+ {
+ if (auto vectorType = as<IRVectorType>(inst->getDataType()))
+ {
+ m_stream->emit("ivec");
+ m_stream->emit(as<IRConstant>(vectorType->getElementCount())->value.intVal);
+ m_stream->emit("(");
+ }
+ else if (auto scalarType = as<IRBasicType>(inst->getDataType()))
+ {
+ m_stream->emit("int(");
+ }
+ else
+ glslSignFix = false;
+ }
+ m_stream->emit(name);
+ m_stream->emit("(");
+ bool first = true;
+ for(; operandIndex < operandCount; ++operandIndex )
+ {
+ if(!first) m_stream->emit(", ");
+ emitIROperand(inst->getOperand(operandIndex), mode, getInfo(EmitOp::General));
+ first = false;
+ }
+ m_stream->emit(")");
+ if (glslSignFix)
+ m_stream->emit(")");
+ maybeCloseParens(needClose);
+}
+
+void CLikeSourceEmitter::emitIRCallExpr(IRCall* inst, IREmitMode mode, EmitOpInfo outerPrec)
+{
+ auto funcValue = inst->getOperand(0);
+
+ // Does this function declare any requirements on GLSL version or
+ // extensions, which should affect our output?
+ if(getTarget() == CodeGenTarget::GLSL)
+ {
+ auto decoratedValue = funcValue;
+ while (auto specInst = as<IRSpecialize>(decoratedValue))
+ {
+ decoratedValue = getSpecializedValue(specInst);
+ }
+
+ for( auto decoration : decoratedValue->getDecorations() )
+ {
+ switch(decoration->op)
+ {
+ default:
+ break;
+
+ case kIROp_RequireGLSLExtensionDecoration:
+ requireGLSLExtension(String(((IRRequireGLSLExtensionDecoration*)decoration)->getExtensionName()));
+ break;
+
+ case kIROp_RequireGLSLVersionDecoration:
+ requireGLSLVersion(int(((IRRequireGLSLVersionDecoration*)decoration)->getLanguageVersion()));
+ break;
+ }
+ }
+ }
+
+ // We want to detect any call to an intrinsic operation,
+ // that we can emit it directly without mangling, etc.
+ if(auto irFunc = asTargetIntrinsic(funcValue))
+ {
+ emitIntrinsicCallExpr(inst, irFunc, mode, outerPrec);
+ }
+ else
+ {
+ auto prec = getInfo(EmitOp::Postfix);
+ bool needClose = maybeEmitParens(outerPrec, prec);
+
+ emitIROperand(funcValue, mode, leftSide(outerPrec, prec));
+ m_stream->emit("(");
+ UInt argCount = inst->getOperandCount();
+ for( UInt aa = 1; aa < argCount; ++aa )
+ {
+ auto operand = inst->getOperand(aa);
+ if (as<IRVoidType>(operand->getDataType()))
+ continue;
+ if(aa != 1) m_stream->emit(", ");
+ emitIROperand(inst->getOperand(aa), mode, getInfo(EmitOp::General));
+ }
+ m_stream->emit(")");
+
+ maybeCloseParens(needClose);
+ }
+}
+
+static const char* _getGLSLVectorCompareFunctionName(IROp op)
+{
+ // Glsl vector comparisons use functions...
+ // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/equal.xhtml
+
+ switch (op)
+ {
+ case kIROp_Eql: return "equal";
+ case kIROp_Neq: return "notEqual";
+ case kIROp_Greater: return "greaterThan";
+ case kIROp_Less: return "lessThan";
+ case kIROp_Geq: return "greaterThanEqual";
+ case kIROp_Leq: return "lessThanEqual";
+ default: return nullptr;
+ }
+}
+
+void CLikeSourceEmitter::_maybeEmitGLSLCast(IRType* castType, IRInst* inst, IREmitMode mode)
+{
+ // Wrap in cast if a cast type is specified
+ if (castType)
+ {
+ emitIRType(castType);
+ m_stream->emit("(");
+
+ // Emit the operand
+ emitIROperand(inst, mode, getInfo(EmitOp::General));
+
+ m_stream->emit(")");
+ }
+ else
+ {
+ // Emit the operand
+ emitIROperand(inst, mode, getInfo(EmitOp::General));
+ }
+}
+
+void CLikeSourceEmitter::emitNot(IRInst* inst, IREmitMode mode, EmitOpInfo& ioOuterPrec, bool* outNeedClose)
+{
+ IRInst* operand = inst->getOperand(0);
+
+ if (getTarget() == CodeGenTarget::GLSL)
+ {
+ if (auto vectorType = as<IRVectorType>(operand->getDataType()))
+ {
+ // Handle as a function call
+ auto prec = getInfo(EmitOp::Postfix);
+ *outNeedClose = maybeEmitParens(ioOuterPrec, prec);
+
+ m_stream->emit("not(");
+ emitIROperand(operand, mode, getInfo(EmitOp::General));
+ m_stream->emit(")");
+ return;
+ }
+ }
+
+ auto prec = getInfo(EmitOp::Prefix);
+ *outNeedClose = maybeEmitParens(ioOuterPrec, prec);
+
+ m_stream->emit("!");
+ emitIROperand(operand, mode, rightSide(prec, ioOuterPrec));
+}
+
+
+void CLikeSourceEmitter::emitComparison(IRInst* inst, IREmitMode mode, EmitOpInfo& ioOuterPrec, const EmitOpInfo& opPrec, bool* needCloseOut)
+{
+ if (getTarget() == CodeGenTarget::GLSL)
+ {
+ IRInst* left = inst->getOperand(0);
+ IRInst* right = inst->getOperand(1);
+
+ auto leftVectorType = as<IRVectorType>(left->getDataType());
+ auto rightVectorType = as<IRVectorType>(right->getDataType());
+
+ // If either side is a vector handle as a vector
+ if (leftVectorType || rightVectorType)
+ {
+ const char* funcName = _getGLSLVectorCompareFunctionName(inst->op);
+ SLANG_ASSERT(funcName);
+
+ // Determine the vector type
+ const auto vecType = leftVectorType ? leftVectorType : rightVectorType;
+
+ // Handle as a function call
+ auto prec = getInfo(EmitOp::Postfix);
+ *needCloseOut = maybeEmitParens(ioOuterPrec, prec);
+
+ m_stream->emit(funcName);
+ m_stream->emit("(");
+ _maybeEmitGLSLCast((leftVectorType ? nullptr : vecType), left, mode);
+ m_stream->emit(",");
+ _maybeEmitGLSLCast((rightVectorType ? nullptr : vecType), right, mode);
+ m_stream->emit(")");
+
+ return;
+ }
+ }
+
+ *needCloseOut = maybeEmitParens(ioOuterPrec, opPrec);
+
+ emitIROperand(inst->getOperand(0), mode, leftSide(ioOuterPrec, opPrec));
+ m_stream->emit(" ");
+ m_stream->emit(opPrec.op);
+ m_stream->emit(" ");
+ emitIROperand(inst->getOperand(1), mode, rightSide(ioOuterPrec, opPrec));
+}
+
+
+void CLikeSourceEmitter::emitIRInstExpr(IRInst* inst, IREmitMode mode, const EmitOpInfo& inOuterPrec)
+{
+ EmitOpInfo outerPrec = inOuterPrec;
+ bool needClose = false;
+ switch(inst->op)
+ {
+ case kIROp_IntLit:
+ case kIROp_FloatLit:
+ case kIROp_BoolLit:
+ emitIRSimpleValue(inst);
+ break;
+
+ case kIROp_Construct:
+ case kIROp_makeVector:
+ case kIROp_MakeMatrix:
+ // Simple constructor call
+
+ switch (getTarget())
+ {
+ case CodeGenTarget::HLSL:
+ {
+ if (inst->getOperandCount() == 1)
+ {
+ auto prec = getInfo(EmitOp::Prefix);
+ needClose = maybeEmitParens(outerPrec, prec);
+
+ // Need to emit as cast for HLSL
+ m_stream->emit("(");
+ emitIRType(inst->getDataType());
+ m_stream->emit(") ");
+ emitIROperand(inst->getOperand(0), mode, rightSide(outerPrec, prec));
+ break;
+ }
+ /* fallthru*/
+ }
+ case CodeGenTarget::GLSL:
+ {
+ emitIRType(inst->getDataType());
+ emitIRArgs(inst, mode);
+ break;
+ }
+ case CodeGenTarget::CPPSource:
+ case CodeGenTarget::CSource:
+ {
+ if (inst->getOperandCount() == 1)
+ {
+ _emitCFunc(BuiltInCOp::Splat, inst->getDataType());
+ emitIRArgs(inst, mode);
+ }
+ else
+ {
+ _emitCFunc(BuiltInCOp::Init, inst->getDataType());
+ emitIRArgs(inst, mode);
+ }
+ break;
+ }
+ }
+ break;
+ case kIROp_constructVectorFromScalar:
+
+ // Simple constructor call
+ if( getTarget() == CodeGenTarget::HLSL )
+ {
+ auto prec = getInfo(EmitOp::Prefix);
+ needClose = maybeEmitParens(outerPrec, prec);
+
+ m_stream->emit("(");
+ emitIRType(inst->getDataType());
+ m_stream->emit(")");
+
+ emitIROperand(inst->getOperand(0), mode, rightSide(outerPrec,prec));
+ }
+ else
+ {
+ auto prec = getInfo(EmitOp::Postfix);
+ needClose = maybeEmitParens(outerPrec, prec);
+
+ emitIRType(inst->getDataType());
+ m_stream->emit("(");
+ emitIROperand(inst->getOperand(0), mode, getInfo(EmitOp::General));
+ m_stream->emit(")");
+ }
+ break;
+
+ case kIROp_FieldExtract:
+ {
+ // Extract field from aggregate
+
+ IRFieldExtract* fieldExtract = (IRFieldExtract*) inst;
+
+ auto prec = getInfo(EmitOp::Postfix);
+ needClose = maybeEmitParens(outerPrec, prec);
+
+ auto base = fieldExtract->getBase();
+ emitIROperand(base, mode, leftSide(outerPrec, prec));
+ m_stream->emit(".");
+ if(getTarget() == CodeGenTarget::GLSL
+ && as<IRUniformParameterGroupType>(base->getDataType()))
+ {
+ m_stream->emit("_data.");
+ }
+ m_stream->emit(getIRName(fieldExtract->getField()));
+ }
+ break;
+
+ case kIROp_FieldAddress:
+ {
+ // Extract field "address" from aggregate
+
+ IRFieldAddress* ii = (IRFieldAddress*) inst;
+
+ auto prec = getInfo(EmitOp::Postfix);
+ needClose = maybeEmitParens(outerPrec, prec);
+
+ auto base = ii->getBase();
+ emitIROperand(base, mode, leftSide(outerPrec, prec));
+ m_stream->emit(".");
+ if(getTarget() == CodeGenTarget::GLSL
+ && as<IRUniformParameterGroupType>(base->getDataType()))
+ {
+ m_stream->emit("_data.");
+ }
+ m_stream->emit(getIRName(ii->getField()));
+ }
+ break;
+
+
+#define CASE_COMPARE(OPCODE, PREC, OP) \
+ case OPCODE: \
+ emitComparison(inst, mode, outerPrec, getInfo(EmitOp::PREC), &needClose); \
+ break
+
+#define CASE(OPCODE, PREC, OP) \
+ case OPCODE: \
+ needClose = maybeEmitParens(outerPrec, getInfo(EmitOp::PREC)); \
+ emitIROperand(inst->getOperand(0), mode, leftSide(outerPrec, getInfo(EmitOp::PREC))); \
+ m_stream->emit(" " #OP " "); \
+ emitIROperand(inst->getOperand(1), mode, rightSide(outerPrec, getInfo(EmitOp::PREC))); \
+ break
+
+ CASE(kIROp_Add, Add, +);
+ CASE(kIROp_Sub, Sub, -);
+ CASE(kIROp_Div, Div, /);
+ CASE(kIROp_Mod, Mod, %);
+
+ CASE(kIROp_Lsh, Lsh, <<);
+ CASE(kIROp_Rsh, Rsh, >>);
+
+ // TODO: Need to pull out component-wise
+ // comparison cases for matrices/vectors
+ CASE_COMPARE(kIROp_Eql, Eql, ==);
+ CASE_COMPARE(kIROp_Neq, Neq, !=);
+ CASE_COMPARE(kIROp_Greater, Greater, >);
+ CASE_COMPARE(kIROp_Less, Less, <);
+ CASE_COMPARE(kIROp_Geq, Geq, >=);
+ CASE_COMPARE(kIROp_Leq, Leq, <=);
+
+ CASE(kIROp_BitXor, BitXor, ^);
+
+ CASE(kIROp_And, And, &&);
+ CASE(kIROp_Or, Or, ||);
+
+#undef CASE
+
+ // Component-wise multiplication needs to be special cased,
+ // because GLSL uses infix `*` to express inner product
+ // when working with matrices.
+ case kIROp_Mul:
+ // Are we targetting GLSL, and are both operands matrices?
+ if(getTarget() == CodeGenTarget::GLSL
+ && as<IRMatrixType>(inst->getOperand(0)->getDataType())
+ && as<IRMatrixType>(inst->getOperand(1)->getDataType()))
+ {
+ m_stream->emit("matrixCompMult(");
+ emitIROperand(inst->getOperand(0), mode, getInfo(EmitOp::General));
+ m_stream->emit(", ");
+ emitIROperand(inst->getOperand(1), mode, getInfo(EmitOp::General));
+ m_stream->emit(")");
+ }
+ else
+ {
+ // Default handling is to just rely on infix
+ // `operator*`.
+ auto prec = getInfo(EmitOp::Mul);
+ needClose = maybeEmitParens(outerPrec, prec);
+ emitIROperand(inst->getOperand(0), mode, leftSide(outerPrec, prec));
+ m_stream->emit(" * ");
+ emitIROperand(inst->getOperand(1), mode, rightSide(prec, outerPrec));
+ }
+ break;
+
+ case kIROp_Not:
+ {
+ emitNot(inst, mode, outerPrec, &needClose);
+ }
+ break;
+
+ case kIROp_Neg:
+ {
+ auto prec = getInfo(EmitOp::Prefix);
+ needClose = maybeEmitParens(outerPrec, prec);
+
+ m_stream->emit("-");
+ emitIROperand(inst->getOperand(0), mode, rightSide(prec, outerPrec));
+ }
+ break;
+
+ case kIROp_BitNot:
+ {
+ auto prec = getInfo(EmitOp::Prefix);
+ needClose = maybeEmitParens(outerPrec, prec);
+
+ if (as<IRBoolType>(inst->getDataType()))
+ {
+ m_stream->emit("!");
+ }
+ else
+ {
+ m_stream->emit("~");
+ }
+ emitIROperand(inst->getOperand(0), mode, rightSide(prec, outerPrec));
+ }
+ break;
+
+ case kIROp_BitAnd:
+ {
+ auto prec = getInfo(EmitOp::BitAnd);
+ needClose = maybeEmitParens(outerPrec, prec);
+
+ // TODO: handle a bitwise And of a vector of bools by casting to
+ // a uvec and performing the bitwise operation
+
+ emitIROperand(inst->getOperand(0), mode, leftSide(outerPrec, prec));
+
+ // Are we targetting GLSL, and are both operands scalar bools?
+ // In that case convert the operation to a logical And
+ if (getTarget() == CodeGenTarget::GLSL
+ && as<IRBoolType>(inst->getOperand(0)->getDataType())
+ && as<IRBoolType>(inst->getOperand(1)->getDataType()))
+ {
+ m_stream->emit("&&");
+ }
+ else
+ {
+ m_stream->emit("&");
+ }
+
+ emitIROperand(inst->getOperand(1), mode, rightSide(outerPrec, prec));
+ }
+ break;
+
+ case kIROp_BitOr:
+ {
+ auto prec = getInfo(EmitOp::BitOr);
+ needClose = maybeEmitParens(outerPrec, prec);
+
+ // TODO: handle a bitwise Or of a vector of bools by casting to
+ // a uvec and performing the bitwise operation
+
+ emitIROperand(inst->getOperand(0), mode, leftSide(outerPrec, prec));
+
+ // Are we targetting GLSL, and are both operands scalar bools?
+ // In that case convert the operation to a logical Or
+ if (getTarget() == CodeGenTarget::GLSL
+ && as<IRBoolType>(inst->getOperand(0)->getDataType())
+ && as<IRBoolType>(inst->getOperand(1)->getDataType()))
+ {
+ m_stream->emit("||");
+ }
+ else
+ {
+ m_stream->emit("|");
+ }
+
+ emitIROperand(inst->getOperand(1), mode, rightSide(outerPrec, prec));
+ }
+ break;
+
+ case kIROp_Load:
+ {
+ auto base = inst->getOperand(0);
+ emitIROperand(base, mode, outerPrec);
+ if(getTarget() == CodeGenTarget::GLSL
+ && as<IRUniformParameterGroupType>(base->getDataType()))
+ {
+ m_stream->emit("._data");
+ }
+ }
+ break;
+
+ case kIROp_Store:
+ {
+ auto prec = getInfo(EmitOp::Assign);
+ needClose = maybeEmitParens(outerPrec, prec);
+
+ emitIROperand(inst->getOperand(0), mode, leftSide(outerPrec, prec));
+ m_stream->emit(" = ");
+ emitIROperand(inst->getOperand(1), mode, rightSide(prec, outerPrec));
+ }
+ break;
+
+ case kIROp_Call:
+ {
+ emitIRCallExpr((IRCall*)inst, mode, outerPrec);
+ }
+ break;
+
+ case kIROp_GroupMemoryBarrierWithGroupSync:
+ m_stream->emit("GroupMemoryBarrierWithGroupSync()");
+ break;
+
+ case kIROp_getElement:
+ case kIROp_getElementPtr:
+ case kIROp_ImageSubscript:
+ // HACK: deal with translation of GLSL geometry shader input arrays.
+ if(auto decoration = inst->getOperand(0)->findDecoration<IRGLSLOuterArrayDecoration>())
+ {
+ auto prec = getInfo(EmitOp::Postfix);
+ needClose = maybeEmitParens(outerPrec, prec);
+
+ m_stream->emit(decoration->getOuterArrayName());
+ m_stream->emit("[");
+ emitIROperand(inst->getOperand(1), mode, getInfo(EmitOp::General));
+ m_stream->emit("].");
+ emitIROperand(inst->getOperand(0), mode, rightSide(prec, outerPrec));
+ break;
+ }
+ else
+ {
+ auto prec = getInfo(EmitOp::Postfix);
+ needClose = maybeEmitParens(outerPrec, prec);
+
+ emitIROperand( inst->getOperand(0), mode, leftSide(outerPrec, prec));
+ m_stream->emit("[");
+ emitIROperand(inst->getOperand(1), mode, getInfo(EmitOp::General));
+ m_stream->emit("]");
+ }
+ break;
+
+ case kIROp_Mul_Vector_Matrix:
+ case kIROp_Mul_Matrix_Vector:
+ case kIROp_Mul_Matrix_Matrix:
+ if(getTarget() == CodeGenTarget::GLSL)
+ {
+ // GLSL expresses inner-product multiplications
+ // with the ordinary infix `*` operator.
+ //
+ // Note that the order of the operands is reversed
+ // compared to HLSL (and Slang's internal representation)
+ // because the notion of what is a "row" vs. a "column"
+ // is reversed between HLSL/Slang and GLSL.
+ //
+ auto prec = getInfo(EmitOp::Mul);
+ needClose = maybeEmitParens(outerPrec, prec);
+
+ emitIROperand(inst->getOperand(1), mode, leftSide(outerPrec, prec));
+ m_stream->emit(" * ");
+ emitIROperand(inst->getOperand(0), mode, rightSide(prec, outerPrec));
+ }
+ else
+ {
+ m_stream->emit("mul(");
+ emitIROperand(inst->getOperand(0), mode, getInfo(EmitOp::General));
+ m_stream->emit(", ");
+ emitIROperand(inst->getOperand(1), mode, getInfo(EmitOp::General));
+ m_stream->emit(")");
+ }
+ break;
+
+ case kIROp_swizzle:
+ {
+ auto prec = getInfo(EmitOp::Postfix);
+ needClose = maybeEmitParens(outerPrec, prec);
+
+ auto ii = (IRSwizzle*)inst;
+ emitIROperand(ii->getBase(), mode, leftSide(outerPrec, prec));
+ m_stream->emit(".");
+ const Index elementCount = Index(ii->getElementCount());
+ for (Index ee = 0; ee < elementCount; ++ee)
+ {
+ IRInst* irElementIndex = ii->getElementIndex(ee);
+ SLANG_RELEASE_ASSERT(irElementIndex->op == kIROp_IntLit);
+ IRConstant* irConst = (IRConstant*)irElementIndex;
+
+ UInt elementIndex = (UInt)irConst->value.intVal;
+ SLANG_RELEASE_ASSERT(elementIndex < 4);
+
+ char const* kComponents[] = { "x", "y", "z", "w" };
+ m_stream->emit(kComponents[elementIndex]);
+ }
+ }
+ break;
+
+ case kIROp_Specialize:
+ {
+ emitIROperand(inst->getOperand(0), mode, outerPrec);
+ }
+ break;
+
+ case kIROp_Select:
+ {
+ if (getTarget() == CodeGenTarget::GLSL &&
+ inst->getOperand(0)->getDataType()->op != kIROp_BoolType)
+ {
+ // For GLSL, emit a call to `mix` if condition is a vector
+ m_stream->emit("mix(");
+ emitIROperand(inst->getOperand(2), mode, leftSide(getInfo(EmitOp::General), getInfo(EmitOp::General)));
+ m_stream->emit(", ");
+ emitIROperand(inst->getOperand(1), mode, leftSide(getInfo(EmitOp::General), getInfo(EmitOp::General)));
+ m_stream->emit(", ");
+ emitIROperand(inst->getOperand(0), mode, leftSide(getInfo(EmitOp::General), getInfo(EmitOp::General)));
+ m_stream->emit(")");
+ }
+ else
+ {
+ auto prec = getInfo(EmitOp::Conditional);
+ needClose = maybeEmitParens(outerPrec, prec);
+
+ emitIROperand(inst->getOperand(0), mode, leftSide(outerPrec, prec));
+ m_stream->emit(" ? ");
+ emitIROperand(inst->getOperand(1), mode, prec);
+ m_stream->emit(" : ");
+ emitIROperand(inst->getOperand(2), mode, rightSide(prec, outerPrec));
+ }
+ }
+ break;
+
+ case kIROp_Param:
+ m_stream->emit(getIRName(inst));
+ break;
+
+ case kIROp_makeArray:
+ case kIROp_makeStruct:
+ {
+ // TODO: initializer-list syntax may not always
+ // be appropriate, depending on the context
+ // of the expression.
+
+ m_stream->emit("{ ");
+ UInt argCount = inst->getOperandCount();
+ for (UInt aa = 0; aa < argCount; ++aa)
+ {
+ if (aa != 0) m_stream->emit(", ");
+ emitIROperand(inst->getOperand(aa), mode, getInfo(EmitOp::General));
+ }
+ m_stream->emit(" }");
+ }
+ break;
+
+ case kIROp_BitCast:
+ {
+ // TODO: we can simplify the logic for arbitrary bitcasts
+ // by always bitcasting the source to a `uint*` type (if it
+ // isn't already) and then bitcasting that to the destination
+ // type (if it isn't already `uint*`.
+ //
+ // For now we are assuming the source type is *already*
+ // a `uint*` type of the appropriate size.
+ //
+// auto fromType = extractBaseType(inst->getOperand(0)->getDataType());
+ auto toType = extractBaseType(inst->getDataType());
+ switch(getTarget())
+ {
+ case CodeGenTarget::GLSL:
+ switch(toType)
+ {
+ default:
+ m_stream->emit("/* unhandled */");
+ break;
+
+ case BaseType::UInt:
+ break;
+
+ case BaseType::Int:
+ emitIRType(inst->getDataType());
+ break;
+
+ case BaseType::Float:
+ m_stream->emit("uintBitsToFloat(");
+ break;
+ }
+ break;
+
+ case CodeGenTarget::HLSL:
+ switch(toType)
+ {
+ default:
+ m_stream->emit("/* unhandled */");
+ break;
+
+ case BaseType::UInt:
+ break;
+ case BaseType::Int:
+ m_stream->emit("(");
+ emitIRType(inst->getDataType());
+ m_stream->emit(")");
+ break;
+ case BaseType::Float:
+ m_stream->emit("asfloat");
+ break;
+ }
+ break;
+
+
+ default:
+ SLANG_UNEXPECTED("unhandled codegen target");
+ break;
+ }
+
+ m_stream->emit("(");
+ emitIROperand(inst->getOperand(0), mode, getInfo(EmitOp::General));
+ m_stream->emit(")");
+ }
+ break;
+
+ default:
+ m_stream->emit("/* unhandled */");
+ break;
+ }
+ maybeCloseParens(needClose);
+}
+
+BaseType CLikeSourceEmitter::extractBaseType(IRType* inType)
+{
+ auto type = inType;
+ for(;;)
+ {
+ if(auto irBaseType = as<IRBasicType>(type))
+ {
+ return irBaseType->getBaseType();
+ }
+ else if(auto vecType = as<IRVectorType>(type))
+ {
+ type = vecType->getElementType();
+ continue;
+ }
+ else
+ {
+ return BaseType::Void;
+ }
+ }
+}
+
+void CLikeSourceEmitter::emitIRInst(IRInst* inst, IREmitMode mode)
+{
+ try
+ {
+ emitIRInstImpl(inst, mode);
+ }
+ // Don't emit any context message for an explicit `AbortCompilationException`
+ // because it should only happen when an error is already emitted.
+ catch(AbortCompilationException&) { throw; }
+ catch(...)
+ {
+ m_context->noteInternalErrorLoc(inst->sourceLoc);
+ throw;
+ }
+}
+
+void CLikeSourceEmitter::emitIRInstImpl(IRInst* inst, IREmitMode mode)
+{
+ if (shouldFoldIRInstIntoUseSites(inst, mode))
+ {
+ return;
+ }
+
+ m_stream->advanceToSourceLocation(inst->sourceLoc);
+
+ switch(inst->op)
+ {
+ default:
+ emitIRInstResultDecl(inst);
+ emitIRInstExpr(inst, mode, getInfo(EmitOp::General));
+ m_stream->emit(";\n");
+ break;
+
+ case kIROp_undefined:
+ {
+ auto type = inst->getDataType();
+ emitIRType(type, getIRName(inst));
+ m_stream->emit(";\n");
+ }
+ break;
+
+ case kIROp_Var:
+ {
+ auto ptrType = cast<IRPtrType>(inst->getDataType());
+ auto valType = ptrType->getValueType();
+
+ auto name = getIRName(inst);
+ emitIRRateQualifiers(inst);
+ emitIRType(valType, name);
+ m_stream->emit(";\n");
+ }
+ break;
+
+ case kIROp_Param:
+ // Don't emit parameters, since they are declared as part of the function.
+ break;
+
+ case kIROp_FieldAddress:
+ // skip during code emit, since it should be
+ // folded into use site(s)
+ break;
+
+ case kIROp_ReturnVoid:
+ m_stream->emit("return;\n");
+ break;
+
+ case kIROp_ReturnVal:
+ m_stream->emit("return ");
+ emitIROperand(((IRReturnVal*) inst)->getVal(), mode, getInfo(EmitOp::General));
+ m_stream->emit(";\n");
+ break;
+
+ case kIROp_discard:
+ m_stream->emit("discard;\n");
+ break;
+
+ case kIROp_swizzleSet:
+ {
+ auto ii = (IRSwizzleSet*)inst;
+ emitIRInstResultDecl(inst);
+ emitIROperand(inst->getOperand(0), mode, getInfo(EmitOp::General));
+ m_stream->emit(";\n");
+
+ auto subscriptOuter = getInfo(EmitOp::General);
+ auto subscriptPrec = getInfo(EmitOp::Postfix);
+ bool needCloseSubscript = maybeEmitParens(subscriptOuter, subscriptPrec);
+
+ emitIROperand(inst, mode, leftSide(subscriptOuter, subscriptPrec));
+ m_stream->emit(".");
+ UInt elementCount = ii->getElementCount();
+ for (UInt ee = 0; ee < elementCount; ++ee)
+ {
+ IRInst* irElementIndex = ii->getElementIndex(ee);
+ SLANG_RELEASE_ASSERT(irElementIndex->op == kIROp_IntLit);
+ IRConstant* irConst = (IRConstant*)irElementIndex;
+
+ UInt elementIndex = (UInt)irConst->value.intVal;
+ SLANG_RELEASE_ASSERT(elementIndex < 4);
+
+ char const* kComponents[] = { "x", "y", "z", "w" };
+ m_stream->emit(kComponents[elementIndex]);
+ }
+ maybeCloseParens(needCloseSubscript);
+
+ m_stream->emit(" = ");
+ emitIROperand(inst->getOperand(1), mode, getInfo(EmitOp::General));
+ m_stream->emit(";\n");
+ }
+ break;
+
+ case kIROp_SwizzledStore:
+ {
+ auto subscriptOuter = getInfo(EmitOp::General);
+ auto subscriptPrec = getInfo(EmitOp::Postfix);
+ bool needCloseSubscript = maybeEmitParens(subscriptOuter, subscriptPrec);
+
+
+ auto ii = cast<IRSwizzledStore>(inst);
+ emitIROperand(ii->getDest(), mode, leftSide(subscriptOuter, subscriptPrec));
+ m_stream->emit(".");
+ UInt elementCount = ii->getElementCount();
+ for (UInt ee = 0; ee < elementCount; ++ee)
+ {
+ IRInst* irElementIndex = ii->getElementIndex(ee);
+ SLANG_RELEASE_ASSERT(irElementIndex->op == kIROp_IntLit);
+ IRConstant* irConst = (IRConstant*)irElementIndex;
+
+ UInt elementIndex = (UInt)irConst->value.intVal;
+ SLANG_RELEASE_ASSERT(elementIndex < 4);
+
+ char const* kComponents[] = { "x", "y", "z", "w" };
+ m_stream->emit(kComponents[elementIndex]);
+ }
+ maybeCloseParens(needCloseSubscript);
+
+ m_stream->emit(" = ");
+ emitIROperand(ii->getSource(), mode, getInfo(EmitOp::General));
+ m_stream->emit(";\n");
+ }
+ break;
+ }
+}
+
+void CLikeSourceEmitter::emitIRSemantics(VarLayout* varLayout)
+{
+ if(varLayout->flags & VarLayoutFlag::HasSemantic)
+ {
+ m_stream->emit(" : ");
+ m_stream->emit(varLayout->semanticName);
+ if(varLayout->semanticIndex)
+ {
+ m_stream->emit(varLayout->semanticIndex);
+ }
+ }
+}
+
+void CLikeSourceEmitter::emitIRSemantics(IRInst* inst)
+{
+ // Don't emit semantics if we aren't translating down to HLSL
+ switch (getTarget())
+ {
+ case CodeGenTarget::HLSL:
+ break;
+
+ default:
+ return;
+ }
+
+ if (auto semanticDecoration = inst->findDecoration<IRSemanticDecoration>())
+ {
+ m_stream->emit(" : ");
+ m_stream->emit(semanticDecoration->getSemanticName());
+ return;
+ }
+
+ if(auto layoutDecoration = inst->findDecoration<IRLayoutDecoration>())
+ {
+ auto layout = layoutDecoration->getLayout();
+ if(auto varLayout = as<VarLayout>(layout))
+ {
+ emitIRSemantics(varLayout);
+ }
+ else if (auto entryPointLayout = as<EntryPointLayout>(layout))
+ {
+ if(auto resultLayout = entryPointLayout->resultLayout)
+ {
+ emitIRSemantics(resultLayout);
+ }
+ }
+ }
+}
+
+VarLayout* CLikeSourceEmitter::getVarLayout(IRInst* var)
+{
+ auto decoration = var->findDecoration<IRLayoutDecoration>();
+ if (!decoration)
+ return nullptr;
+
+ return (VarLayout*) decoration->getLayout();
+}
+
+void CLikeSourceEmitter::emitIRLayoutSemantics(IRInst* inst, char const* uniformSemanticSpelling)
+{
+ auto layout = getVarLayout(inst);
+ if (layout)
+ {
+ emitHLSLRegisterSemantics(layout, uniformSemanticSpelling);
+ }
+}
+
+void CLikeSourceEmitter::emitPhiVarAssignments(UInt argCount, IRUse* args, IRBlock* targetBlock)
+{
+ UInt argCounter = 0;
+ for (auto pp = targetBlock->getFirstParam(); pp; pp = pp->getNextParam())
+ {
+ UInt argIndex = argCounter++;
+
+ if (argIndex >= argCount)
+ {
+ SLANG_UNEXPECTED("not enough arguments for branch");
+ break;
+ }
+
+ IRInst* arg = args[argIndex].get();
+
+ auto outerPrec = getInfo(EmitOp::General);
+ auto prec = getInfo(EmitOp::Assign);
+
+ emitIROperand(pp, IREmitMode::Default, leftSide(outerPrec, prec));
+ m_stream->emit(" = ");
+ emitIROperand(arg, IREmitMode::Default, rightSide(prec, outerPrec));
+ m_stream->emit(";\n");
+ }
+}
+
+void CLikeSourceEmitter::emitRegion(Region* inRegion)
+{
+ // We will use a loop so that we can process sequential (simple)
+ // regions iteratively rather than recursively.
+ // This is effectively an emulation of tail recursion.
+ Region* region = inRegion;
+ while(region)
+ {
+ // What flavor of region are we trying to emit?
+ switch(region->getFlavor())
+ {
+ case Region::Flavor::Simple:
+ {
+ // A simple region consists of a basic block followed
+ // by another region.
+ //
+ auto simpleRegion = (SimpleRegion*) region;
+
+ // We start by outputting all of the non-terminator
+ // instructions in the block.
+ //
+ auto block = simpleRegion->block;
+ auto terminator = block->getTerminator();
+ for (auto inst = block->getFirstInst(); inst != terminator; inst = inst->getNextInst())
+ {
+ emitIRInst(inst, IREmitMode::Default);
+ }
+
+ // Next we have to deal with the terminator instruction
+ // itself. In many cases, the terminator will have been
+ // turned into a block of its own, but certain cases
+ // of terminators are simple enough that we just fold
+ // them into the current block.
+ //
+ m_stream->advanceToSourceLocation(terminator->sourceLoc);
+ switch(terminator->op)
+ {
+ default:
+ // Don't do anything with the terminator, and assume
+ // its behavior has been folded into the next region.
+ break;
+
+ case kIROp_ReturnVal:
+ case kIROp_ReturnVoid:
+ case kIROp_discard:
+ // For extremely simple terminators, we just handle
+ // them here, so that we don't have to allocate
+ // separate `Region`s for them.
+ emitIRInst(terminator, IREmitMode::Default);
+ break;
+
+ // We will also handle any unconditional branches
+ // here, since they may have arguments to pass
+ // to the target block (our encoding of SSA
+ // "phi" operations).
+ //
+ // TODO: A better approach would be to move out of SSA
+ // as an IR pass, and introduce explicit variables to
+ // replace any "phi nodes." This would avoid possible
+ // complications if we ever end up in the bad case where
+ // one of the block arguments on a branch is also
+ // a parameter of the target block, so that the order
+ // of operations is important.
+ //
+ case kIROp_unconditionalBranch:
+ {
+ auto t = (IRUnconditionalBranch*)terminator;
+ UInt argCount = t->getOperandCount();
+ static const UInt kFixedArgCount = 1;
+ emitPhiVarAssignments(
+ argCount - kFixedArgCount,
+ t->getOperands() + kFixedArgCount,
+ t->getTargetBlock());
+ }
+ break;
+ case kIROp_loop:
+ {
+ auto t = (IRLoop*) terminator;
+ UInt argCount = t->getOperandCount();
+ static const UInt kFixedArgCount = 3;
+ emitPhiVarAssignments(
+ argCount - kFixedArgCount,
+ t->getOperands() + kFixedArgCount,
+ t->getTargetBlock());
+
+ }
+ break;
+ }
+
+ // If the terminator required a full region to represent
+ // its behavior in a structured form, then we will move
+ // along to that region now.
+ //
+ // We do this iteratively rather than recursively, by
+ // jumping back to the top of our loop with a new
+ // value for `region`.
+ //
+ region = simpleRegion->nextRegion;
+ continue;
+ }
+
+ // Break and continue regions are trivial to handle, as long as we
+ // don't need to consider multi-level break/continue (which we
+ // don't for now).
+ case Region::Flavor::Break:
+ m_stream->emit("break;\n");
+ break;
+ case Region::Flavor::Continue:
+ m_stream->emit("continue;\n");
+ break;
+
+ case Region::Flavor::If:
+ {
+ auto ifRegion = (IfRegion*) region;
+
+ // TODO: consider simplifying the code in
+ // the case where `ifRegion == null`
+ // so that we output `if(!condition) { elseRegion }`
+ // instead of the current `if(condition) {} else { elseRegion }`
+
+ m_stream->emit("if(");
+ emitIROperand(ifRegion->condition, IREmitMode::Default, getInfo(EmitOp::General));
+ m_stream->emit(")\n{\n");
+ m_stream->indent();
+ emitRegion(ifRegion->thenRegion);
+ m_stream->dedent();
+ m_stream->emit("}\n");
+
+ // Don't emit the `else` region if it would be empty
+ //
+ if(auto elseRegion = ifRegion->elseRegion)
+ {
+ m_stream->emit("else\n{\n");
+ m_stream->indent();
+ emitRegion(elseRegion);
+ m_stream->dedent();
+ m_stream->emit("}\n");
+ }
+
+ // Continue with the region after the `if`.
+ //
+ // TODO: consider just constructing a `SimpleRegion`
+ // around an `IfRegion` to handle this sequencing,
+ // rather than making `IfRegion` serve as both a
+ // conditional and a sequence.
+ //
+ region = ifRegion->nextRegion;
+ continue;
+ }
+ break;
+
+ case Region::Flavor::Loop:
+ {
+ auto loopRegion = (LoopRegion*) region;
+ auto loopInst = loopRegion->loopInst;
+
+ // If the user applied an explicit decoration to the loop,
+ // to control its unrolling behavior, then pass that
+ // along in the output code (if the target language
+ // supports the semantics of the decoration).
+ //
+ if (auto loopControlDecoration = loopInst->findDecoration<IRLoopControlDecoration>())
+ {
+ switch (loopControlDecoration->getMode())
+ {
+ case kIRLoopControl_Unroll:
+ // Note: loop unrolling control is only available in HLSL, not GLSL
+ if(getTarget() == CodeGenTarget::HLSL)
+ {
+ m_stream->emit("[unroll]\n");
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ m_stream->emit("for(;;)\n{\n");
+ m_stream->indent();
+ emitRegion(loopRegion->body);
+ m_stream->dedent();
+ m_stream->emit("}\n");
+
+ // Continue with the region after the loop
+ region = loopRegion->nextRegion;
+ continue;
+ }
+
+ case Region::Flavor::Switch:
+ {
+ auto switchRegion = (SwitchRegion*) region;
+
+ // Emit the start of our statement.
+ m_stream->emit("switch(");
+ emitIROperand(switchRegion->condition, IREmitMode::Default, getInfo(EmitOp::General));
+ m_stream->emit(")\n{\n");
+
+ auto defaultCase = switchRegion->defaultCase;
+ for(auto currentCase : switchRegion->cases)
+ {
+ for(auto caseVal : currentCase->values)
+ {
+ m_stream->emit("case ");
+ emitIROperand(caseVal, IREmitMode::Default, getInfo(EmitOp::General));
+ m_stream->emit(":\n");
+ }
+ if(currentCase.Ptr() == defaultCase)
+ {
+ m_stream->emit("default:\n");
+ }
+
+ m_stream->indent();
+ m_stream->emit("{\n");
+ m_stream->indent();
+ emitRegion(currentCase->body);
+ m_stream->dedent();
+ m_stream->emit("}\n");
+ m_stream->dedent();
+ }
+
+ m_stream->emit("}\n");
+
+ // Continue with the region after the `switch`
+ region = switchRegion->nextRegion;
+ continue;
+ }
+ break;
+ }
+ break;
+ }
+}
+
+/// Emit high-level language statements from a structured region tree.
+void CLikeSourceEmitter::emitRegionTree(RegionTree* regionTree)
+{
+ emitRegion(regionTree->rootRegion);
+}
+
+bool CLikeSourceEmitter::isDefinition(IRFunc* func)
+{
+ // For now, we use a simple approach: a function is
+ // a definition if it has any blocks, and a declaration otherwise.
+ return func->getFirstBlock() != nullptr;
+}
+
+String CLikeSourceEmitter::getIRFuncName(IRFunc* func)
+{
+ if (auto entryPointLayout = asEntryPoint(func))
+ {
+ // GLSL will always need to use `main` as the
+ // name for an entry-point function, but other
+ // targets should try to use the original name.
+ //
+ // TODO: always use `main`, and have any code
+ // that wraps this know to use `main` instead
+ // of the original entry-point name...
+ //
+ if (getTarget() != CodeGenTarget::GLSL)
+ {
+ return getText(entryPointLayout->entryPoint->getName());
+ }
+
+ //
+
+ return "main";
+ }
+ else
+ {
+ return getIRName(func);
+ }
+}
+
+void CLikeSourceEmitter::emitAttributeSingleString(const char* name, FuncDecl* entryPoint, Attribute* attrib)
+{
+ assert(attrib);
+
+ attrib->args.getCount();
+ if (attrib->args.getCount() != 1)
+ {
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute expects single parameter");
+ return;
+ }
+
+ Expr* expr = attrib->args[0];
+
+ auto stringLitExpr = as<StringLiteralExpr>(expr);
+ if (!stringLitExpr)
+ {
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute parameter expecting to be a string ");
+ return;
+ }
+
+ m_stream->emit("[");
+ m_stream->emit(name);
+ m_stream->emit("(\"");
+ m_stream->emit(stringLitExpr->value);
+ m_stream->emit("\")]\n");
+}
+
+void CLikeSourceEmitter::emitAttributeSingleInt(const char* name, FuncDecl* entryPoint, Attribute* attrib)
+{
+ assert(attrib);
+
+ attrib->args.getCount();
+ if (attrib->args.getCount() != 1)
+ {
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute expects single parameter");
+ return;
+ }
+
+ Expr* expr = attrib->args[0];
+
+ auto intLitExpr = as<IntegerLiteralExpr>(expr);
+ if (!intLitExpr)
+ {
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute expects an int");
+ return;
+ }
+
+ m_stream->emit("[");
+ m_stream->emit(name);
+ m_stream->emit("(");
+ m_stream->emit(intLitExpr->value);
+ m_stream->emit(")]\n");
+}
+
+void CLikeSourceEmitter::emitFuncDeclPatchConstantFuncAttribute(IRFunc* irFunc, FuncDecl* entryPoint, PatchConstantFuncAttribute* attrib)
+{
+ SLANG_UNUSED(attrib);
+
+ auto irPatchFunc = irFunc->findDecoration<IRPatchConstantFuncDecoration>();
+ assert(irPatchFunc);
+ if (!irPatchFunc)
+ {
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Unable to find [patchConstantFunc(...)] decoration");
+ return;
+ }
+
+ const String irName = getIRName(irPatchFunc->getFunc());
+
+ m_stream->emit("[patchconstantfunc(\"");
+ m_stream->emit(irName);
+ m_stream->emit("\")]\n");
+}
+
+void CLikeSourceEmitter::emitIREntryPointAttributes_HLSL(IRFunc* irFunc, EntryPointLayout* entryPointLayout)
+{
+ auto profile = m_context->effectiveProfile;
+ auto stage = entryPointLayout->profile.GetStage();
+
+ if(profile.getFamily() == ProfileFamily::DX)
+ {
+ if(profile.GetVersion() >= ProfileVersion::DX_6_1 )
+ {
+ char const* stageName = getStageName(stage);
+ if(stageName)
+ {
+ m_stream->emit("[shader(\"");
+ m_stream->emit(stageName);
+ m_stream->emit("\")]");
+ }
+ }
+ }
+
+ switch (stage)
+ {
+ case Stage::Compute:
+ {
+ static const UInt kAxisCount = 3;
+ UInt sizeAlongAxis[kAxisCount];
+
+ // TODO: this is kind of gross because we are using a public
+ // reflection API function, rather than some kind of internal
+ // utility it forwards to...
+ spReflectionEntryPoint_getComputeThreadGroupSize(
+ (SlangReflectionEntryPoint*)entryPointLayout,
+ kAxisCount,
+ &sizeAlongAxis[0]);
+
+ m_stream->emit("[numthreads(");
+ for (int ii = 0; ii < 3; ++ii)
+ {
+ if (ii != 0) m_stream->emit(", ");
+ m_stream->emit(sizeAlongAxis[ii]);
+ }
+ m_stream->emit(")]\n");
+ }
+ break;
+ case Stage::Geometry:
+ {
+ if (auto attrib = entryPointLayout->entryPoint->FindModifier<MaxVertexCountAttribute>())
+ {
+ m_stream->emit("[maxvertexcount(");
+ m_stream->emit(attrib->value);
+ m_stream->emit(")]\n");
+ }
+ if (auto attrib = entryPointLayout->entryPoint->FindModifier<InstanceAttribute>())
+ {
+ m_stream->emit("[instance(");
+ m_stream->emit(attrib->value);
+ m_stream->emit(")]\n");
+ }
+ break;
+ }
+ case Stage::Domain:
+ {
+ FuncDecl* entryPoint = entryPointLayout->entryPoint;
+ /* [domain("isoline")] */
+ if (auto attrib = entryPoint->FindModifier<DomainAttribute>())
+ {
+ emitAttributeSingleString("domain", entryPoint, attrib);
+ }
+
+ break;
+ }
+ case Stage::Hull:
+ {
+ // Lists these are only attributes for hull shader
+ // https://docs.microsoft.com/en-us/windows/desktop/direct3d11/direct3d-11-advanced-stages-hull-shader-design
+
+ FuncDecl* entryPoint = entryPointLayout->entryPoint;
+
+ /* [domain("isoline")] */
+ if (auto attrib = entryPoint->FindModifier<DomainAttribute>())
+ {
+ emitAttributeSingleString("domain", entryPoint, attrib);
+ }
+ /* [domain("partitioning")] */
+ if (auto attrib = entryPoint->FindModifier<PartitioningAttribute>())
+ {
+ emitAttributeSingleString("partitioning", entryPoint, attrib);
+ }
+ /* [outputtopology("line")] */
+ if (auto attrib = entryPoint->FindModifier<OutputTopologyAttribute>())
+ {
+ emitAttributeSingleString("outputtopology", entryPoint, attrib);
+ }
+ /* [outputcontrolpoints(4)] */
+ if (auto attrib = entryPoint->FindModifier<OutputControlPointsAttribute>())
+ {
+ emitAttributeSingleInt("outputcontrolpoints", entryPoint, attrib);
+ }
+ /* [patchconstantfunc("HSConst")] */
+ if (auto attrib = entryPoint->FindModifier<PatchConstantFuncAttribute>())
+ {
+ emitFuncDeclPatchConstantFuncAttribute(irFunc, entryPoint, attrib);
+ }
+
+ break;
+ }
+ case Stage::Pixel:
+ {
+ if (irFunc->findDecoration<IREarlyDepthStencilDecoration>())
+ {
+ m_stream->emit("[earlydepthstencil]\n");
+ }
+ break;
+ }
+ // TODO: There are other stages that will need this kind of handling.
+ default:
+ break;
+ }
+}
+
+void CLikeSourceEmitter::emitIREntryPointAttributes_GLSL(IRFunc* irFunc, EntryPointLayout* entryPointLayout)
+{
+ auto profile = entryPointLayout->profile;
+ auto stage = profile.GetStage();
+
+ switch (stage)
+ {
+ case Stage::Compute:
+ {
+ static const UInt kAxisCount = 3;
+ UInt sizeAlongAxis[kAxisCount];
+
+ // TODO: this is kind of gross because we are using a public
+ // reflection API function, rather than some kind of internal
+ // utility it forwards to...
+ spReflectionEntryPoint_getComputeThreadGroupSize(
+ (SlangReflectionEntryPoint*)entryPointLayout,
+ kAxisCount,
+ &sizeAlongAxis[0]);
+
+ m_stream->emit("layout(");
+ char const* axes[] = { "x", "y", "z" };
+ for (int ii = 0; ii < 3; ++ii)
+ {
+ if (ii != 0) m_stream->emit(", ");
+ m_stream->emit("local_size_");
+ m_stream->emit(axes[ii]);
+ m_stream->emit(" = ");
+ m_stream->emit(sizeAlongAxis[ii]);
+ }
+ m_stream->emit(") in;");
+ }
+ break;
+ case Stage::Geometry:
+ {
+ if (auto attrib = entryPointLayout->entryPoint->FindModifier<MaxVertexCountAttribute>())
+ {
+ m_stream->emit("layout(max_vertices = ");
+ m_stream->emit(attrib->value);
+ m_stream->emit(") out;\n");
+ }
+ if (auto attrib = entryPointLayout->entryPoint->FindModifier<InstanceAttribute>())
+ {
+ m_stream->emit("layout(invocations = ");
+ m_stream->emit(attrib->value);
+ m_stream->emit(") in;\n");
+ }
+
+ for(auto pp : entryPointLayout->entryPoint->GetParameters())
+ {
+ if(auto inputPrimitiveTypeModifier = pp->FindModifier<HLSLGeometryShaderInputPrimitiveTypeModifier>())
+ {
+ if(as<HLSLTriangleModifier>(inputPrimitiveTypeModifier))
+ {
+ m_stream->emit("layout(triangles) in;\n");
+ }
+ else if(as<HLSLLineModifier>(inputPrimitiveTypeModifier))
+ {
+ m_stream->emit("layout(lines) in;\n");
+ }
+ else if(as<HLSLLineAdjModifier>(inputPrimitiveTypeModifier))
+ {
+ m_stream->emit("layout(lines_adjacency) in;\n");
+ }
+ else if(as<HLSLPointModifier>(inputPrimitiveTypeModifier))
+ {
+ m_stream->emit("layout(points) in;\n");
+ }
+ else if(as<HLSLTriangleAdjModifier>(inputPrimitiveTypeModifier))
+ {
+ m_stream->emit("layout(triangles_adjacency) in;\n");
+ }
+ }
+
+ if(auto outputStreamType = as<HLSLStreamOutputType>(pp->type))
+ {
+ if(as<HLSLTriangleStreamType>(outputStreamType))
+ {
+ m_stream->emit("layout(triangle_strip) out;\n");
+ }
+ else if(as<HLSLLineStreamType>(outputStreamType))
+ {
+ m_stream->emit("layout(line_strip) out;\n");
+ }
+ else if(as<HLSLPointStreamType>(outputStreamType))
+ {
+ m_stream->emit("layout(points) out;\n");
+ }
+ }
+ }
+
+
+ }
+ break;
+ case Stage::Pixel:
+ {
+ if (irFunc->findDecoration<IREarlyDepthStencilDecoration>())
+ {
+ // https://www.khronos.org/opengl/wiki/Early_Fragment_Test
+ m_stream->emit("layout(early_fragment_tests) in;\n");
+ }
+ break;
+ }
+ // TODO: There are other stages that will need this kind of handling.
+ default:
+ break;
+ }
+}
+
+void CLikeSourceEmitter::emitIREntryPointAttributes(IRFunc* irFunc, EntryPointLayout* entryPointLayout)
+{
+ switch(getTarget())
+ {
+ case CodeGenTarget::HLSL:
+ emitIREntryPointAttributes_HLSL(irFunc, entryPointLayout);
+ break;
+
+ case CodeGenTarget::GLSL:
+ emitIREntryPointAttributes_GLSL(irFunc, entryPointLayout);
+ break;
+ }
+}
+
+void CLikeSourceEmitter::emitPhiVarDecls(IRFunc* func)
+{
+ // We will skip the first block, since its parameters are
+ // the parameters of the whole function.
+ auto bb = func->getFirstBlock();
+ if (!bb)
+ return;
+ bb = bb->getNextBlock();
+
+ for (; bb; bb = bb->getNextBlock())
+ {
+ for (auto pp = bb->getFirstParam(); pp; pp = pp->getNextParam())
+ {
+ emitIRTempModifiers(pp);
+ emitIRType(pp->getFullType(), getIRName(pp));
+ m_stream->emit(";\n");
+ }
+ }
+}
+
+/// Emit high-level statements for the body of a function.
+void CLikeSourceEmitter::emitIRFunctionBody(IRGlobalValueWithCode* code)
+{
+ // Compute a structured region tree that can represent
+ // the control flow of our function.
+ //
+ RefPtr<RegionTree> regionTree = generateRegionTreeForFunc(
+ code,
+ m_context->getSink());
+
+ // Now that we've computed the region tree, we have
+ // an opportunity to perform some last-minute transformations
+ // on the code to make sure it follows our rules.
+ //
+ // TODO: it would be better to do these transformations earlier,
+ // so that we can, e.g., dump the final IR code *before* emission
+ // starts, but that gets a bit complicated because we also want
+ // to have the region tree available without having to recompute it.
+ //
+ // For now we are just going to do things the expedient way, but
+ // eventually we should allow an IR module to have side-band
+ // storage for derived structures like the region tree (and logic
+ // for invalidating them when a transformation would break them).
+ //
+ fixValueScoping(regionTree);
+
+ // Now emit high-level code from that structured region tree.
+ //
+ emitRegionTree(regionTree);
+}
+
+void CLikeSourceEmitter::emitIRSimpleFunc(IRFunc* func)
+{
+ auto resultType = func->getResultType();
+
+ // Deal with decorations that need
+ // to be emitted as attributes
+ auto entryPointLayout = asEntryPoint(func);
+ if (entryPointLayout)
+ {
+ emitIREntryPointAttributes(func, entryPointLayout);
+ }
+
+ const CodeGenTarget target = getTarget();
+
+ auto name = getIRFuncName(func);
+
+ emitType(resultType, name);
+
+ m_stream->emit("(");
+ auto firstParam = func->getFirstParam();
+ for( auto pp = firstParam; pp; pp = pp->getNextParam())
+ {
+ if(pp != firstParam)
+ m_stream->emit(", ");
+
+ auto paramName = getIRName(pp);
+ auto paramType = pp->getDataType();
+
+ if (target == CodeGenTarget::HLSL)
+ {
+ if (auto layoutDecor = pp->findDecoration<IRLayoutDecoration>())
+ {
+ Layout* layout = layoutDecor->getLayout();
+ VarLayout* varLayout = as<VarLayout>(layout);
+
+ if (varLayout)
+ {
+ auto var = varLayout->getVariable();
+
+ if (auto primTypeModifier = var->FindModifier<HLSLGeometryShaderInputPrimitiveTypeModifier>())
+ {
+ if (as<HLSLTriangleModifier>(primTypeModifier))
+ m_stream->emit("triangle ");
+ else if (as<HLSLPointModifier>(primTypeModifier))
+ m_stream->emit("point ");
+ else if (as<HLSLLineModifier>(primTypeModifier))
+ m_stream->emit("line ");
+ else if (as<HLSLLineAdjModifier>(primTypeModifier))
+ m_stream->emit("lineadj ");
+ else if (as<HLSLTriangleAdjModifier>(primTypeModifier))
+ m_stream->emit("triangleadj ");
+ }
+ }
+ }
+ }
+
+ emitIRParamType(paramType, paramName);
+
+ emitIRSemantics(pp);
+ }
+ m_stream->emit(")");
+
+ emitIRSemantics(func);
+
+ // TODO: encode declaration vs. definition
+ if(isDefinition(func))
+ {
+ m_stream->emit("\n{\n");
+ m_stream->indent();
+
+ // HACK: forward-declare all the local variables needed for the
+ // parameters of non-entry blocks.
+ emitPhiVarDecls(func);
+
+ // Need to emit the operations in the blocks of the function
+ emitIRFunctionBody(func);
+
+ m_stream->dedent();
+ m_stream->emit("}\n\n");
+ }
+ else
+ {
+ m_stream->emit(";\n\n");
+ }
+}
+
+void CLikeSourceEmitter::emitIRParamType(IRType* type, String const& name)
+{
+ // An `out` or `inout` parameter will have been
+ // encoded as a parameter of pointer type, so
+ // we need to decode that here.
+ //
+ if( auto outType = as<IROutType>(type))
+ {
+ m_stream->emit("out ");
+ type = outType->getValueType();
+ }
+ else if( auto inOutType = as<IRInOutType>(type))
+ {
+ m_stream->emit("inout ");
+ type = inOutType->getValueType();
+ }
+ else if( auto refType = as<IRRefType>(type))
+ {
+ // Note: There is no HLSL/GLSL equivalent for by-reference parameters,
+ // so we don't actually expect to encounter these in user code.
+ m_stream->emit("inout ");
+ type = inOutType->getValueType();
+ }
+
+ emitIRType(type, name);
+}
+
+IRInst* CLikeSourceEmitter::getSpecializedValue(IRSpecialize* specInst)
+{
+ auto base = specInst->getBase();
+ auto baseGeneric = as<IRGeneric>(base);
+ if (!baseGeneric)
+ return base;
+
+ auto lastBlock = baseGeneric->getLastBlock();
+ if (!lastBlock)
+ return base;
+
+ auto returnInst = as<IRReturnVal>(lastBlock->getTerminator());
+ if (!returnInst)
+ return base;
+
+ return returnInst->getVal();
+}
+
+void CLikeSourceEmitter::emitIRFuncDecl(IRFunc* func)
+{
+ // We don't want to emit declarations for operations
+ // that only appear in the IR as stand-ins for built-in
+ // operations on that target.
+ if (isTargetIntrinsic(func))
+ return;
+
+ // Finally, don't emit a declaration for an entry point,
+ // because it might need meta-data attributes attached
+ // to it, and the HLSL compiler will get upset if the
+ // forward declaration doesn't *also* have those
+ // attributes.
+ if(asEntryPoint(func))
+ return;
+
+
+ // A function declaration doesn't have any IR basic blocks,
+ // and as a result it *also* doesn't have the IR `param` instructions,
+ // so we need to emit a declaration entirely from the type.
+
+ auto funcType = func->getDataType();
+ auto resultType = func->getResultType();
+
+ auto name = getIRFuncName(func);
+
+ emitIRType(resultType, name);
+
+ m_stream->emit("(");
+ auto paramCount = funcType->getParamCount();
+ for(UInt pp = 0; pp < paramCount; ++pp)
+ {
+ if(pp != 0)
+ m_stream->emit(", ");
+
+ String paramName;
+ paramName.append("_");
+ paramName.append(Int32(pp));
+ auto paramType = funcType->getParamType(pp);
+
+ emitIRParamType(paramType, paramName);
+ }
+ m_stream->emit(");\n\n");
+}
+
+EntryPointLayout* CLikeSourceEmitter::getEntryPointLayout(IRFunc* func)
+{
+ if( auto layoutDecoration = func->findDecoration<IRLayoutDecoration>() )
+ {
+ return as<EntryPointLayout>(layoutDecoration->getLayout());
+ }
+ return nullptr;
+}
+
+EntryPointLayout* CLikeSourceEmitter::asEntryPoint(IRFunc* func)
+{
+ if (auto layoutDecoration = func->findDecoration<IRLayoutDecoration>())
+ {
+ if (auto entryPointLayout = as<EntryPointLayout>(layoutDecoration->getLayout()))
+ {
+ return entryPointLayout;
+ }
+ }
+
+ return nullptr;
+}
+
+bool CLikeSourceEmitter::isTargetIntrinsic(IRFunc* func)
+{
+ // For now we do this in an overly simplistic
+ // fashion: we say that *any* function declaration
+ // (rather then definition) must be an intrinsic:
+ return !isDefinition(func);
+}
+
+IRFunc* CLikeSourceEmitter::asTargetIntrinsic(IRInst* value)
+{
+ if(!value)
+ return nullptr;
+
+ while (auto specInst = as<IRSpecialize>(value))
+ {
+ value = getSpecializedValue(specInst);
+ }
+
+ if(value->op != kIROp_Func)
+ return nullptr;
+
+ IRFunc* func = (IRFunc*) value;
+ if(!isTargetIntrinsic(func))
+ return nullptr;
+
+ return func;
+}
+
+void CLikeSourceEmitter::emitIRFunc(IRFunc* func)
+{
+ if(!isDefinition(func))
+ {
+ // This is just a function declaration,
+ // and so we want to emit it as such.
+ // (Or maybe not emit it at all).
+
+ // We do not emit the declaration for
+ // functions that appear to be intrinsics/builtins
+ // in the target language.
+ if (isTargetIntrinsic(func))
+ return;
+
+ emitIRFuncDecl(func);
+ }
+ else
+ {
+ // The common case is that what we
+ // have is just an ordinary function,
+ // and we can emit it as such.
+ emitIRSimpleFunc(func);
+ }
+}
+
+void CLikeSourceEmitter::emitIRStruct(IRStructType* structType)
+{
+ // If the selected `struct` type is actually an intrinsic
+ // on our target, then we don't want to emit anything at all.
+ if(auto intrinsicDecoration = findTargetIntrinsicDecoration(structType))
+ {
+ return;
+ }
+
+ m_stream->emit("struct ");
+ m_stream->emit(getIRName(structType));
+ m_stream->emit("\n{\n");
+ m_stream->indent();
+
+ for(auto ff : structType->getFields())
+ {
+ auto fieldKey = ff->getKey();
+ auto fieldType = ff->getFieldType();
+
+ // Filter out fields with `void` type that might
+ // have been introduced by legalization.
+ if(as<IRVoidType>(fieldType))
+ continue;
+
+ // Note: GLSL doesn't support interpolation modifiers on `struct` fields
+ if( getTarget() != CodeGenTarget::GLSL )
+ {
+ emitInterpolationModifiers(fieldKey, fieldType, nullptr);
+ }
+
+ emitIRType(fieldType, getIRName(fieldKey));
+ emitIRSemantics(fieldKey);
+ m_stream->emit(";\n");
+ }
+
+ m_stream->dedent();
+ m_stream->emit("};\n\n");
+}
+
+void CLikeSourceEmitter::emitIRMatrixLayoutModifiers(VarLayout* layout)
+{
+ // When a variable has a matrix type, we want to emit an explicit
+ // layout qualifier based on what the layout has been computed to be.
+ //
+
+ auto typeLayout = layout->typeLayout;
+ while(auto arrayTypeLayout = as<ArrayTypeLayout>(typeLayout))
+ typeLayout = arrayTypeLayout->elementTypeLayout;
+
+ if (auto matrixTypeLayout = typeLayout.as<MatrixTypeLayout>())
+ {
+ auto target = getTarget();
+
+ switch (target)
+ {
+ case CodeGenTarget::HLSL:
+ switch (matrixTypeLayout->mode)
+ {
+ case kMatrixLayoutMode_ColumnMajor:
+ m_stream->emit("column_major ");
+ break;
+
+ case kMatrixLayoutMode_RowMajor:
+ m_stream->emit("row_major ");
+ break;
+ }
+ break;
+
+ case CodeGenTarget::GLSL:
+ // Reminder: the meaning of row/column major layout
+ // in our semantics is the *opposite* of what GLSL
+ // calls them, because what they call "columns"
+ // are what we call "rows."
+ //
+ switch (matrixTypeLayout->mode)
+ {
+ case kMatrixLayoutMode_ColumnMajor:
+ m_stream->emit("layout(row_major)\n");
+ break;
+
+ case kMatrixLayoutMode_RowMajor:
+ m_stream->emit("layout(column_major)\n");
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ }
+}
+
+void CLikeSourceEmitter::maybeEmitGLSLFlatModifier(IRType* valueType)
+{
+ auto tt = valueType;
+ if(auto vecType = as<IRVectorType>(tt))
+ tt = vecType->getElementType();
+ if(auto vecType = as<IRMatrixType>(tt))
+ tt = vecType->getElementType();
+
+ switch(tt->op)
+ {
+ default:
+ break;
+
+ case kIROp_IntType:
+ case kIROp_UIntType:
+ case kIROp_UInt64Type:
+ m_stream->emit("flat ");
+ break;
+ }
+}
+
+void CLikeSourceEmitter::emitInterpolationModifiers(IRInst* varInst, IRType* valueType, VarLayout* layout)
+{
+ bool isGLSL = (getTarget() == CodeGenTarget::GLSL);
+ bool anyModifiers = false;
+
+ for(auto dd : varInst->getDecorations())
+ {
+ if(dd->op != kIROp_InterpolationModeDecoration)
+ continue;
+
+ auto decoration = (IRInterpolationModeDecoration*)dd;
+ auto mode = decoration->getMode();
+
+ switch(mode)
+ {
+ case IRInterpolationMode::NoInterpolation:
+ anyModifiers = true;
+ m_stream->emit(isGLSL ? "flat " : "nointerpolation ");
+ break;
+
+ case IRInterpolationMode::NoPerspective:
+ anyModifiers = true;
+ m_stream->emit("noperspective ");
+ break;
+
+ case IRInterpolationMode::Linear:
+ anyModifiers = true;
+ m_stream->emit(isGLSL ? "smooth " : "linear ");
+ break;
+
+ case IRInterpolationMode::Sample:
+ anyModifiers = true;
+ m_stream->emit("sample ");
+ break;
+
+ case IRInterpolationMode::Centroid:
+ anyModifiers = true;
+ m_stream->emit("centroid ");
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ // If the user didn't explicitly qualify a varying
+ // with integer type, then we need to explicitly
+ // add the `flat` modifier for GLSL.
+ if(!anyModifiers && isGLSL)
+ {
+ // Only emit a default `flat` for fragment
+ // stage varying inputs.
+ //
+ // TODO: double-check that this works for
+ // signature matching even if the producing
+ // stage didn't use `flat`.
+ //
+ // If this ends up being a problem we can instead
+ // output everything with `flat` except for
+ // fragment *outputs* (and maybe vertex inputs).
+ //
+ if(layout && layout->stage == Stage::Fragment
+ && layout->FindResourceInfo(LayoutResourceKind::VaryingInput))
+ {
+ maybeEmitGLSLFlatModifier(valueType);
+ }
+ }
+}
+
+UInt CLikeSourceEmitter::getRayPayloadLocation(IRInst* inst)
+{
+ auto& map = m_context->mapIRValueToRayPayloadLocation;
+ UInt value = 0;
+ if(map.TryGetValue(inst, value))
+ return value;
+
+ value = map.Count();
+ map.Add(inst, value);
+ return value;
+}
+
+UInt CLikeSourceEmitter::getCallablePayloadLocation(IRInst* inst)
+{
+ auto& map = m_context->mapIRValueToCallablePayloadLocation;
+ UInt value = 0;
+ if(map.TryGetValue(inst, value))
+ return value;
+
+ value = map.Count();
+ map.Add(inst, value);
+ return value;
+}
+
+void CLikeSourceEmitter::emitGLSLImageFormatModifier(IRInst* var, IRTextureType* resourceType)
+{
+ // If the user specified a format manually, using `[format(...)]`,
+ // then we will respect that format and emit a matching `layout` modifier.
+ //
+ if(auto formatDecoration = var->findDecoration<IRFormatDecoration>())
+ {
+ auto format = formatDecoration->getFormat();
+ if(format == ImageFormat::unknown)
+ {
+ // If the user explicitly opts out of having a format, then
+ // the output shader will require the extension to support
+ // load/store from format-less images.
+ //
+ // TODO: We should have a validation somewhere in the compiler
+ // that atomic operations are only allowed on images with
+ // explicit formats (and then only on specific formats).
+ // This is really an argument that format should be part of
+ // the image *type* (with a "base type" for images with
+ // unknown format).
+ //
+ requireGLSLExtension("GL_EXT_shader_image_load_formatted");
+ }
+ else
+ {
+ // If there is an explicit format specified, then we
+ // should emit a `layout` modifier using the GLSL name
+ // for the format.
+ //
+ m_stream->emit("layout(");
+ m_stream->emit(getGLSLNameForImageFormat(format));
+ m_stream->emit(")\n");
+ }
+
+ // No matter what, if an explicit `[format(...)]` was given,
+ // then we don't need to emit anything else.
+ //
+ return;
+ }
+
+
+ // When no explicit format is specified, we need to either
+ // emit the image as having an unknown format, or else infer
+ // a format from the type.
+ //
+ // For now our default behavior is to infer (so that unmodified
+ // HLSL input is more likely to generate valid SPIR-V that
+ // runs anywhere), but we provide a flag to opt into
+ // treating images without explicit formats as having
+ // unknown format.
+ //
+ if(this->m_context->compileRequest->useUnknownImageFormatAsDefault)
+ {
+ requireGLSLExtension("GL_EXT_shader_image_load_formatted");
+ return;
+ }
+
+ // At this point we have a resource type like `RWTexture2D<X>`
+ // and we want to infer a reasonable format from the element
+ // type `X` that was specified.
+ //
+ // E.g., if `X` is `float` then we can infer a format like `r32f`,
+ // and so forth. The catch of course is that it is possible to
+ // specify a shader parameter with a type like `RWTexture2D<float4>` but
+ // provide an image at runtime with a format like `rgba8`, so
+ // this inference is never guaranteed to give perfect results.
+ //
+ // If users don't like our inferred result, they need to use a
+ // `[format(...)]` attribute to manually specify what they want.
+ //
+ // TODO: We should consider whether we can expand the space of
+ // allowed types for `X` in `RWTexture2D<X>` to include special
+ // pseudo-types that act just like, e.g., `float4`, but come
+ // with attached/implied format information.
+ //
+ auto elementType = resourceType->getElementType();
+ Int vectorWidth = 1;
+ if(auto elementVecType = as<IRVectorType>(elementType))
+ {
+ if(auto intLitVal = as<IRIntLit>(elementVecType->getElementCount()))
+ {
+ vectorWidth = (Int) intLitVal->getValue();
+ }
+ else
+ {
+ vectorWidth = 0;
+ }
+ elementType = elementVecType->getElementType();
+ }
+ if(auto elementBasicType = as<IRBasicType>(elementType))
+ {
+ m_stream->emit("layout(");
+ switch(vectorWidth)
+ {
+ default: m_stream->emit("rgba"); break;
+
+ case 3:
+ {
+ // TODO: GLSL doesn't support 3-component formats so for now we are going to
+ // default to rgba
+ //
+ // The SPIR-V spec (https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.pdf)
+ // section 3.11 on Image Formats it does not list rgbf32.
+ //
+ // It seems SPIR-V can support having an image with an unknown-at-compile-time
+ // format, so long as the underlying API supports it. Ideally this would mean that we can
+ // just drop all these qualifiers when emitting GLSL for Vulkan targets.
+ //
+ // This raises the question of what to do more long term. For Vulkan hopefully we can just
+ // drop the layout. For OpenGL targets it would seem reasonable to have well-defined rules
+ // for inferring the format (and just document that 3-component formats map to 4-component formats,
+ // but that shouldn't matter because the API wouldn't let the user allocate those 3-component formats anyway),
+ // and add an attribute for specifying the format manually if you really want to override our
+ // inference (e.g., to specify r11fg11fb10f).
+
+ m_stream->emit("rgba");
+ //Emit("rgb");
+ break;
+ }
+
+ case 2: m_stream->emit("rg"); break;
+ case 1: m_stream->emit("r"); break;
+ }
+ switch(elementBasicType->getBaseType())
+ {
+ default:
+ case BaseType::Float: m_stream->emit("32f"); break;
+ case BaseType::Half: m_stream->emit("16f"); break;
+ case BaseType::UInt: m_stream->emit("32ui"); break;
+ case BaseType::Int: m_stream->emit("32i"); break;
+
+ // TODO: Here are formats that are available in GLSL,
+ // but that are not handled by the above cases.
+ //
+ // r11f_g11f_b10f
+ //
+ // rgba16
+ // rgb10_a2
+ // rgba8
+ // rg16
+ // rg8
+ // r16
+ // r8
+ //
+ // rgba16_snorm
+ // rgba8_snorm
+ // rg16_snorm
+ // rg8_snorm
+ // r16_snorm
+ // r8_snorm
+ //
+ // rgba16i
+ // rgba8i
+ // rg16i
+ // rg8i
+ // r16i
+ // r8i
+ //
+ // rgba16ui
+ // rgb10_a2ui
+ // rgba8ui
+ // rg16ui
+ // rg8ui
+ // r16ui
+ // r8ui
+ }
+ m_stream->emit(")\n");
+ }
+}
+
+ /// Emit modifiers that should apply even for a declaration of an SSA temporary.
+void CLikeSourceEmitter::emitIRTempModifiers(IRInst* temp)
+{
+ if(temp->findDecoration<IRPreciseDecoration>())
+ {
+ m_stream->emit("precise ");
+ }
+}
+
+void CLikeSourceEmitter::emitIRVarModifiers(VarLayout* layout,IRInst* varDecl, IRType* varType)
+{
+ // Deal with Vulkan raytracing layout stuff *before* we
+ // do the check for whether `layout` is null, because
+ // the payload won't automatically get a layout applied
+ // (it isn't part of the user-visible interface...)
+ //
+ if(varDecl->findDecoration<IRVulkanRayPayloadDecoration>())
+ {
+ m_stream->emit("layout(location = ");
+ m_stream->emit(getRayPayloadLocation(varDecl));
+ m_stream->emit(")\n");
+ m_stream->emit("rayPayloadNV\n");
+ }
+ if(varDecl->findDecoration<IRVulkanCallablePayloadDecoration>())
+ {
+ m_stream->emit("layout(location = ");
+ m_stream->emit(getCallablePayloadLocation(varDecl));
+ m_stream->emit(")\n");
+ m_stream->emit("callableDataNV\n");
+ }
+
+ if(varDecl->findDecoration<IRVulkanHitAttributesDecoration>())
+ {
+ m_stream->emit("hitAttributeNV\n");
+ }
+
+ if(varDecl->findDecoration<IRGloballyCoherentDecoration>())
+ {
+ switch(getTarget())
+ {
+ default:
+ break;
+
+ case CodeGenTarget::HLSL:
+ m_stream->emit("globallycoherent\n");
+ break;
+
+ case CodeGenTarget::GLSL:
+ m_stream->emit("coherent\n");
+ break;
+ }
+ }
+
+ emitIRTempModifiers(varDecl);
+
+ if (!layout)
+ return;
+
+ emitIRMatrixLayoutModifiers(layout);
+
+ // As a special case, if we are emitting a GLSL declaration
+ // for an HLSL `RWTexture*` then we need to emit a `format` layout qualifier.
+ if(getTarget() == CodeGenTarget::GLSL)
+ {
+ if(auto resourceType = as<IRTextureType>(unwrapArray(varType)))
+ {
+ switch(resourceType->getAccess())
+ {
+ case SLANG_RESOURCE_ACCESS_READ_WRITE:
+ case SLANG_RESOURCE_ACCESS_RASTER_ORDERED:
+ {
+ emitGLSLImageFormatModifier(varDecl, resourceType);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ if(layout->FindResourceInfo(LayoutResourceKind::VaryingInput)
+ || layout->FindResourceInfo(LayoutResourceKind::VaryingOutput))
+ {
+ emitInterpolationModifiers(varDecl, varType, layout);
+ }
+
+ if (getTarget() == CodeGenTarget::GLSL)
+ {
+ // Layout-related modifiers need to come before the declaration,
+ // so deal with them here.
+ emitGLSLLayoutQualifiers(layout, nullptr);
+
+ // try to emit an appropriate leading qualifier
+ for (auto rr : layout->resourceInfos)
+ {
+ switch (rr.kind)
+ {
+ case LayoutResourceKind::Uniform:
+ case LayoutResourceKind::ShaderResource:
+ case LayoutResourceKind::DescriptorTableSlot:
+ m_stream->emit("uniform ");
+ break;
+
+ case LayoutResourceKind::VaryingInput:
+ {
+ m_stream->emit("in ");
+ }
+ break;
+
+ case LayoutResourceKind::VaryingOutput:
+ {
+ m_stream->emit("out ");
+ }
+ break;
+
+ case LayoutResourceKind::RayPayload:
+ {
+ m_stream->emit("rayPayloadInNV ");
+ }
+ break;
+
+ case LayoutResourceKind::CallablePayload:
+ {
+ m_stream->emit("callableDataInNV ");
+ }
+ break;
+
+ case LayoutResourceKind::HitAttributes:
+ {
+ m_stream->emit("hitAttributeNV ");
+ }
+ break;
+
+ default:
+ continue;
+ }
+
+ break;
+ }
+ }
+}
+
+void CLikeSourceEmitter::emitHLSLParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type)
+{
+ if(as<IRTextureBufferType>(type))
+ {
+ m_stream->emit("tbuffer ");
+ }
+ else
+ {
+ m_stream->emit("cbuffer ");
+ }
+ m_stream->emit(getIRName(varDecl));
+
+ auto varLayout = getVarLayout(varDecl);
+ SLANG_RELEASE_ASSERT(varLayout);
+
+ EmitVarChain blockChain(varLayout);
+
+ EmitVarChain containerChain = blockChain;
+ EmitVarChain elementChain = blockChain;
+
+ auto typeLayout = varLayout->typeLayout;
+ if( auto parameterGroupTypeLayout = as<ParameterGroupTypeLayout>(typeLayout) )
+ {
+ containerChain = EmitVarChain(parameterGroupTypeLayout->containerVarLayout, &blockChain);
+ elementChain = EmitVarChain(parameterGroupTypeLayout->elementVarLayout, &blockChain);
+
+ typeLayout = parameterGroupTypeLayout->elementVarLayout->typeLayout;
+ }
+
+ emitHLSLRegisterSemantic(LayoutResourceKind::ConstantBuffer, &containerChain);
+
+ m_stream->emit("\n{\n");
+ m_stream->indent();
+
+ auto elementType = type->getElementType();
+
+ emitIRType(elementType, getIRName(varDecl));
+ m_stream->emit(";\n");
+
+ m_stream->dedent();
+ m_stream->emit("}\n");
+}
+
+void CLikeSourceEmitter::emitArrayBrackets(IRType* inType)
+{
+ // A declaration may require zero, one, or
+ // more array brackets. When writing out array
+ // brackets from left to right, they represent
+ // the structure of the type from the "outside"
+ // in (that is, if we have a 5-element array of
+ // 3-element arrays we should output `[5][3]`),
+ // because of C-style declarator rules.
+ //
+ // This conveniently means that we can print
+ // out all the array brackets with a looping
+ // rather than a recursive structure.
+ //
+ // We will peel the input type like an onion,
+ // looking at one layer at a time until we
+ // reach a non-array type in the middle.
+ //
+ IRType* type = inType;
+ for(;;)
+ {
+ if(auto arrayType = as<IRArrayType>(type))
+ {
+ m_stream->emit("[");
+ emitVal(arrayType->getElementCount(), getInfo(EmitOp::General));
+ m_stream->emit("]");
+
+ // Continue looping on the next layer in.
+ //
+ type = arrayType->getElementType();
+ }
+ else if(auto unsizedArrayType = as<IRUnsizedArrayType>(type))
+ {
+ m_stream->emit("[]");
+
+ // Continue looping on the next layer in.
+ //
+ type = unsizedArrayType->getElementType();
+ }
+ else
+ {
+ // This layer wasn't an array, so we are done.
+ //
+ return;
+ }
+ }
+}
+
+
+void CLikeSourceEmitter::emitGLSLParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type)
+{
+ auto varLayout = getVarLayout(varDecl);
+ SLANG_RELEASE_ASSERT(varLayout);
+
+ EmitVarChain blockChain(varLayout);
+
+ EmitVarChain containerChain = blockChain;
+ EmitVarChain elementChain = blockChain;
+
+ auto typeLayout = varLayout->typeLayout->unwrapArray();
+ if( auto parameterGroupTypeLayout = as<ParameterGroupTypeLayout>(typeLayout) )
+ {
+ containerChain = EmitVarChain(parameterGroupTypeLayout->containerVarLayout, &blockChain);
+ elementChain = EmitVarChain(parameterGroupTypeLayout->elementVarLayout, &blockChain);
+
+ typeLayout = parameterGroupTypeLayout->elementVarLayout->typeLayout;
+ }
+
+ /*
+ With resources backed by 'buffer' on glsl, we want to output 'readonly' if that is a good match
+ for the underlying type. If uniform it's implicit it's readonly
+
+ Here this only happens with isShaderRecord which is a 'constant buffer' (ie implicitly readonly)
+ or IRGLSLShaderStorageBufferType which is read write.
+ */
+
+ emitGLSLLayoutQualifier(LayoutResourceKind::DescriptorTableSlot, &containerChain);
+ emitGLSLLayoutQualifier(LayoutResourceKind::PushConstantBuffer, &containerChain);
+ bool isShaderRecord = emitGLSLLayoutQualifier(LayoutResourceKind::ShaderRecord, &containerChain);
+
+ if( isShaderRecord )
+ {
+ // TODO: A shader record in vk can be potentially read-write. Currently slang doesn't support write access
+ // and readonly buffer generates SPIRV validation error.
+ m_stream->emit("buffer ");
+ }
+ else if(as<IRGLSLShaderStorageBufferType>(type))
+ {
+ // Is writable
+ m_stream->emit("layout(std430) buffer ");
+ }
+ // TODO: what to do with HLSL `tbuffer` style buffers?
+ else
+ {
+ // uniform is implicitly read only
+ m_stream->emit("layout(std140) uniform ");
+ }
+
+ // Generate a dummy name for the block
+ m_stream->emit("_S");
+ m_stream->emit(m_context->uniqueIDCounter++);
+
+ m_stream->emit("\n{\n");
+ m_stream->indent();
+
+ auto elementType = type->getElementType();
+
+ emitIRType(elementType, "_data");
+ m_stream->emit(";\n");
+
+ m_stream->dedent();
+ m_stream->emit("} ");
+
+ m_stream->emit(getIRName(varDecl));
+
+ // If the underlying variable was an array (or array of arrays, etc.)
+ // we need to emit all those array brackets here.
+ emitArrayBrackets(varDecl->getDataType());
+
+ m_stream->emit(";\n");
+}
+
+void CLikeSourceEmitter::emitIRParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type)
+{
+ switch (getTarget())
+ {
+ case CodeGenTarget::HLSL:
+ emitHLSLParameterGroup(varDecl, type);
+ break;
+
+ case CodeGenTarget::GLSL:
+ emitGLSLParameterGroup(varDecl, type);
+ break;
+ }
+}
+
+void CLikeSourceEmitter::emitIRVar(IRVar* varDecl)
+{
+ auto allocatedType = varDecl->getDataType();
+ auto varType = allocatedType->getValueType();
+// auto addressSpace = allocatedType->getAddressSpace();
+
+#if 0
+ switch( varType->op )
+ {
+ case kIROp_ConstantBufferType:
+ case kIROp_TextureBufferType:
+ emitIRParameterGroup(ctx, varDecl, (IRUniformBufferType*) varType);
+ return;
+
+ default:
+ break;
+ }
+#endif
+
+ // Need to emit appropriate modifiers here.
+
+ auto layout = getVarLayout(varDecl);
+
+ emitIRVarModifiers(layout, varDecl, varType);
+
+#if 0
+ switch (addressSpace)
+ {
+ default:
+ break;
+
+ case kIRAddressSpace_GroupShared:
+ emit("groupshared ");
+ break;
+ }
+#endif
+ emitIRRateQualifiers(varDecl);
+
+ emitIRType(varType, getIRName(varDecl));
+
+ emitIRSemantics(varDecl);
+
+ emitIRLayoutSemantics(varDecl);
+
+ m_stream->emit(";\n");
+}
+
+void CLikeSourceEmitter::emitIRStructuredBuffer_GLSL(IRGlobalParam* varDecl, IRHLSLStructuredBufferTypeBase* structuredBufferType)
+{
+ // Shader storage buffer is an OpenGL 430 feature
+ //
+ // TODO: we should require either the extension or the version...
+ requireGLSLVersion(430);
+
+ m_stream->emit("layout(std430");
+
+ auto layout = getVarLayout(varDecl);
+ if (layout)
+ {
+ LayoutResourceKind kind = LayoutResourceKind::DescriptorTableSlot;
+ EmitVarChain chain(layout);
+
+ const UInt index = getBindingOffset(&chain, kind);
+ const UInt space = getBindingSpace(&chain, kind);
+
+ m_stream->emit(", binding = ");
+ m_stream->emit(index);
+ if (space)
+ {
+ m_stream->emit(", set = ");
+ m_stream->emit(space);
+ }
+ }
+
+ m_stream->emit(") ");
+
+ /*
+ If the output type is a buffer, and we can determine it is only readonly we can prefix before
+ buffer with 'readonly'
+
+ The actual structuredBufferType could be
+
+ HLSLStructuredBufferType - This is unambiguously read only
+ HLSLRWStructuredBufferType - Read write
+ HLSLRasterizerOrderedStructuredBufferType - Allows read/write access
+ HLSLAppendStructuredBufferType - Write
+ HLSLConsumeStructuredBufferType - TODO (JS): Its possible that this can be readonly, but we currently don't support on GLSL
+ */
+
+ if (as<IRHLSLStructuredBufferType>(structuredBufferType))
+ {
+ m_stream->emit("readonly ");
+ }
+
+ m_stream->emit("buffer ");
+
+ // Generate a dummy name for the block
+ m_stream->emit("_S");
+ m_stream->emit(m_context->uniqueIDCounter++);
+
+ m_stream->emit(" {\n");
+ m_stream->indent();
+
+
+ auto elementType = structuredBufferType->getElementType();
+ emitIRType(elementType, "_data[]");
+ m_stream->emit(";\n");
+
+ m_stream->dedent();
+ m_stream->emit("} ");
+
+ m_stream->emit(getIRName(varDecl));
+ emitArrayBrackets(varDecl->getDataType());
+
+ m_stream->emit(";\n");
+}
+
+void CLikeSourceEmitter::emitIRByteAddressBuffer_GLSL(IRGlobalParam* varDecl, IRByteAddressBufferTypeBase* byteAddressBufferType)
+{
+ // TODO: A lot of this logic is copy-pasted from `emitIRStructuredBuffer_GLSL`.
+ // It might be worthwhile to share the common code to avoid regressions sneaking
+ // in when one or the other, but not both, gets updated.
+
+ // Shader storage buffer is an OpenGL 430 feature
+ //
+ // TODO: we should require either the extension or the version...
+ requireGLSLVersion(430);
+
+ m_stream->emit("layout(std430");
+
+ auto layout = getVarLayout(varDecl);
+ if (layout)
+ {
+ LayoutResourceKind kind = LayoutResourceKind::DescriptorTableSlot;
+ EmitVarChain chain(layout);
+
+ const UInt index = getBindingOffset(&chain, kind);
+ const UInt space = getBindingSpace(&chain, kind);
+
+ m_stream->emit(", binding = ");
+ m_stream-> emit(index);
+ if (space)
+ {
+ m_stream->emit(", set = ");
+ m_stream->emit(space);
+ }
+ }
+
+ m_stream->emit(") ");
+
+ /*
+ If the output type is a buffer, and we can determine it is only readonly we can prefix before
+ buffer with 'readonly'
+
+ HLSLByteAddressBufferType - This is unambiguously read only
+ HLSLRWByteAddressBufferType - Read write
+ HLSLRasterizerOrderedByteAddressBufferType - Allows read/write access
+ */
+
+ if (as<IRHLSLByteAddressBufferType>(byteAddressBufferType))
+ {
+ m_stream->emit("readonly ");
+ }
+
+ m_stream->emit("buffer ");
+
+ // Generate a dummy name for the block
+ m_stream->emit("_S");
+ m_stream->emit(m_context->uniqueIDCounter++);
+ m_stream->emit("\n{\n");
+ m_stream->indent();
+
+ m_stream->emit("uint _data[];\n");
+
+ m_stream->dedent();
+ m_stream->emit("} ");
+
+ m_stream->emit(getIRName(varDecl));
+ emitArrayBrackets(varDecl->getDataType());
+
+ m_stream->emit(";\n");
+}
+
+void CLikeSourceEmitter::emitIRGlobalVar(IRGlobalVar* varDecl)
+{
+ auto allocatedType = varDecl->getDataType();
+ auto varType = allocatedType->getValueType();
+
+ String initFuncName;
+ if (varDecl->getFirstBlock())
+ {
+ // A global variable with code means it has an initializer
+ // associated with it. Eventually we'd like to emit that
+ // initializer directly as an expression here, but for
+ // now we'll emit it as a separate function.
+
+ initFuncName = getIRName(varDecl);
+ initFuncName.append("_init");
+
+ m_stream->emit("\n");
+ emitIRType(varType, initFuncName);
+ m_stream->emit("()\n{\n");
+ m_stream->indent();
+ emitIRFunctionBody(varDecl);
+ m_stream->dedent();
+ m_stream->emit("}\n");
+ }
+
+ // An ordinary global variable won't have a layout
+ // associated with it, since it is not a shader
+ // parameter.
+ //
+ SLANG_ASSERT(!getVarLayout(varDecl));
+ VarLayout* layout = nullptr;
+
+ // An ordinary global variable (which is not a
+ // shader parameter) may need special
+ // modifiers to indicate it as such.
+ //
+ switch (getTarget())
+ {
+ case CodeGenTarget::HLSL:
+ // HLSL requires the `static` modifier on any
+ // global variables; otherwise they are assumed
+ // to be uniforms.
+ m_stream->emit("static ");
+ break;
+
+ default:
+ break;
+ }
+
+ emitIRVarModifiers(layout, varDecl, varType);
+
+ emitIRRateQualifiers(varDecl);
+ emitIRType(varType, getIRName(varDecl));
+
+ // TODO: These shouldn't be needed for ordinary
+ // global variables.
+ //
+ emitIRSemantics(varDecl);
+ emitIRLayoutSemantics(varDecl);
+
+ if (varDecl->getFirstBlock())
+ {
+ m_stream->emit(" = ");
+ m_stream->emit(initFuncName);
+ m_stream->emit("()");
+ }
+
+ m_stream->emit(";\n\n");
+}
+
+void CLikeSourceEmitter::emitIRGlobalParam(IRGlobalParam* varDecl)
+{
+ auto rawType = varDecl->getDataType();
+
+ auto varType = rawType;
+ if( auto outType = as<IROutTypeBase>(varType) )
+ {
+ varType = outType->getValueType();
+ }
+ if (as<IRVoidType>(varType))
+ return;
+
+ // When a global shader parameter represents a "parameter group"
+ // (either a constant buffer or a parameter block with non-resource
+ // data in it), we will prefer to emit it as an ordinary `cbuffer`
+ // declaration or `uniform` block, even when emitting HLSL for
+ // D3D profiles that support the explicit `ConstantBuffer<T>` type.
+ //
+ // Alternatively, we could make this choice based on profile, and
+ // prefer `ConstantBuffer<T>` on profiles that support it and/or when
+ // the input code used that syntax.
+ //
+ if (auto paramBlockType = as<IRUniformParameterGroupType>(varType))
+ {
+ emitIRParameterGroup(varDecl, paramBlockType);
+ return;
+ }
+
+ if(getTarget() == CodeGenTarget::GLSL)
+ {
+ // There are a number of types that are (or can be)
+ // "first-class" in D3D HLSL, but are second-class in GLSL in
+ // that they require explicit global declarations for each value/object,
+ // and don't support declaration as ordinary variables.
+ //
+ // This includes constant buffers (`uniform` blocks) and well as
+ // structured and byte-address buffers (both mapping to `buffer` blocks).
+ //
+ // We intercept these types, and arrays thereof, to produce the required
+ // global declarations. This assumes that earlier "legalization" passes
+ // already performed the work of pulling fields with these types out of
+ // aggregates.
+ //
+ // Note: this also assumes that these types are not used as function
+ // parameters/results, local variables, etc. Additional legalization
+ // steps are required to guarantee these conditions.
+ //
+ if (auto paramBlockType = as<IRUniformParameterGroupType>(unwrapArray(varType)))
+ {
+ emitGLSLParameterGroup(varDecl, paramBlockType);
+ return;
+ }
+ if( auto structuredBufferType = as<IRHLSLStructuredBufferTypeBase>(unwrapArray(varType)) )
+ {
+ emitIRStructuredBuffer_GLSL(varDecl, structuredBufferType);
+ return;
+ }
+ if( auto byteAddressBufferType = as<IRByteAddressBufferTypeBase>(unwrapArray(varType)) )
+ {
+ emitIRByteAddressBuffer_GLSL(varDecl, byteAddressBufferType);
+ return;
+ }
+
+ // We want to skip the declaration of any system-value variables
+ // when outputting GLSL (well, except in the case where they
+ // actually *require* redeclaration...).
+ //
+ // Note: these won't be variables the user declare explicitly
+ // in their code, but rather variables that we generated as
+ // part of legalizing the varying input/output signature of
+ // an entry point for GL/Vulkan.
+ //
+ // TODO: This could be handled more robustly by attaching an
+ // appropriate decoration to these variables to indicate their
+ // purpose.
+ //
+ if(auto linkageDecoration = varDecl->findDecoration<IRLinkageDecoration>())
+ {
+ if(linkageDecoration->getMangledName().startsWith("gl_"))
+ {
+ // The variable represents an OpenGL system value,
+ // so we will assume that it doesn't need to be declared.
+ //
+ // TODO: handle case where we *should* declare the variable.
+ return;
+ }
+ }
+
+ // When emitting unbounded-size resource arrays with GLSL we need
+ // to use the `GL_EXT_nonuniform_qualifier` extension to ensure
+ // that they are not treated as "implicitly-sized arrays" which
+ // are arrays that have a fixed size that just isn't specified
+ // at the declaration site (instead being inferred from use sites).
+ //
+ // While the extension primarily introduces the `nonuniformEXT`
+ // qualifier that we use to implement `NonUniformResourceIndex`,
+ // it also changes the GLSL language semantics around (resource) array
+ // declarations that don't specify a size.
+ //
+ if( as<IRUnsizedArrayType>(varType) )
+ {
+ if(isResourceType(unwrapArray(varType)))
+ {
+ requireGLSLExtension("GL_EXT_nonuniform_qualifier");
+ }
+ }
+ }
+
+ // Need to emit appropriate modifiers here.
+
+ // We expect/require all shader parameters to
+ // have some kind of layout information associated with them.
+ //
+ auto layout = getVarLayout(varDecl);
+ SLANG_ASSERT(layout);
+
+ emitIRVarModifiers(layout, varDecl, varType);
+
+ emitIRRateQualifiers(varDecl);
+ emitIRType(varType, getIRName(varDecl));
+
+ emitIRSemantics(varDecl);
+
+ emitIRLayoutSemantics(varDecl);
+
+ // A shader parameter cannot have an initializer,
+ // so we do need to consider emitting one here.
+
+ m_stream->emit(";\n\n");
+}
+
+
+void CLikeSourceEmitter::emitIRGlobalConstantInitializer(IRGlobalConstant* valDecl)
+{
+ // We expect to see only a single block
+ auto block = valDecl->getFirstBlock();
+ SLANG_RELEASE_ASSERT(block);
+ SLANG_RELEASE_ASSERT(!block->getNextBlock());
+
+ // We expect the terminator to be a `return`
+ // instruction with a value.
+ auto returnInst = (IRReturnVal*) block->getLastDecorationOrChild();
+ SLANG_RELEASE_ASSERT(returnInst->op == kIROp_ReturnVal);
+
+ // We will emit the value in the `GlobalConstant` mode, which
+ // more or less says to fold all instructions into their use
+ // sites, so that we end up with a single expression tree even
+ // in cases that would otherwise trip up our analysis.
+ //
+ // Note: We are emitting the value as an *operand* here instead
+ // of directly calling `emitIRInstExpr` because we need to handle
+ // cases where the value might *need* to emit as a named referenced
+ // (e.g., when it names another constant directly).
+ //
+ emitIROperand(returnInst->getVal(), IREmitMode::GlobalConstant, getInfo(EmitOp::General));
+}
+
+void CLikeSourceEmitter::emitIRGlobalConstant(IRGlobalConstant* valDecl)
+{
+ auto valType = valDecl->getDataType();
+
+ if( getTarget() != CodeGenTarget::GLSL )
+ {
+ m_stream->emit("static ");
+ }
+ m_stream->emit("const ");
+ emitIRRateQualifiers(valDecl);
+ emitIRType(valType, getIRName(valDecl));
+
+ if (valDecl->getFirstBlock())
+ {
+ // There is an initializer (which we expect for
+ // any global constant...).
+
+ m_stream->emit(" = ");
+
+ // We need to emit the entire initializer as
+ // a single expression.
+ emitIRGlobalConstantInitializer(valDecl);
+ }
+
+
+ m_stream->emit(";\n");
+}
+
+void CLikeSourceEmitter::emitIRGlobalInst(IRInst* inst)
+{
+ m_stream->advanceToSourceLocation(inst->sourceLoc);
+
+ switch(inst->op)
+ {
+ case kIROp_Func:
+ emitIRFunc((IRFunc*) inst);
+ break;
+
+ case kIROp_GlobalVar:
+ emitIRGlobalVar((IRGlobalVar*) inst);
+ break;
+
+ case kIROp_GlobalParam:
+ emitIRGlobalParam((IRGlobalParam*) inst);
+ break;
+
+ case kIROp_GlobalConstant:
+ emitIRGlobalConstant((IRGlobalConstant*) inst);
+ break;
+
+ case kIROp_Var:
+ emitIRVar((IRVar*) inst);
+ break;
+
+ case kIROp_StructType:
+ emitIRStruct(cast<IRStructType>(inst));
+ break;
+
+ default:
+ break;
+ }
+}
+
+void CLikeSourceEmitter::ensureInstOperand(ComputeEmitActionsContext* ctx, IRInst* inst, EmitAction::Level requiredLevel)
+{
+ if(!inst) return;
+
+ if(inst->getParent() == ctx->moduleInst)
+ {
+ ensureGlobalInst(ctx, inst, requiredLevel);
+ }
+}
+
+void CLikeSourceEmitter::ensureInstOperandsRec(ComputeEmitActionsContext* ctx, IRInst* inst)
+{
+ ensureInstOperand(ctx, inst->getFullType());
+
+ UInt operandCount = inst->operandCount;
+ for(UInt ii = 0; ii < operandCount; ++ii)
+ {
+ // TODO: there are some special cases we can add here,
+ // to avoid outputting full definitions in cases that
+ // can get by with forward declarations.
+ //
+ // For example, true pointer types should (in principle)
+ // only need the type they point to to be forward-declared.
+ // Similarly, a `call` instruction only needs the callee
+ // to be forward-declared, etc.
+
+ ensureInstOperand(ctx, inst->getOperand(ii));
+ }
+
+ for(auto child : inst->getDecorationsAndChildren())
+ {
+ ensureInstOperandsRec(ctx, child);
+ }
+}
+
+void CLikeSourceEmitter::ensureGlobalInst(ComputeEmitActionsContext* ctx, IRInst* inst, EmitAction::Level requiredLevel)
+{
+ // Skip certain instructions, since they
+ // don't affect output.
+ switch(inst->op)
+ {
+ case kIROp_WitnessTable:
+ case kIROp_Generic:
+ return;
+
+ default:
+ break;
+ }
+
+ // Have we already processed this instruction?
+ EmitAction::Level existingLevel;
+ if(ctx->mapInstToLevel.TryGetValue(inst, existingLevel))
+ {
+ // If we've already emitted it suitably,
+ // then don't worry about it.
+ if(existingLevel >= requiredLevel)
+ return;
+ }
+
+ EmitAction action;
+ action.level = requiredLevel;
+ action.inst = inst;
+
+ if(requiredLevel == EmitAction::Level::Definition)
+ {
+ if(ctx->openInsts.Contains(inst))
+ {
+ SLANG_UNEXPECTED("circularity during codegen");
+ return;
+ }
+
+ ctx->openInsts.Add(inst);
+
+ ensureInstOperandsRec(ctx, inst);
+
+ ctx->openInsts.Remove(inst);
+ }
+
+ ctx->mapInstToLevel[inst] = requiredLevel;
+ ctx->actions->add(action);
+}
+
+void CLikeSourceEmitter::computeIREmitActions(IRModule* module, List<EmitAction>& ioActions)
+{
+ ComputeEmitActionsContext ctx;
+ ctx.moduleInst = module->getModuleInst();
+ ctx.actions = &ioActions;
+
+ for(auto inst : module->getGlobalInsts())
+ {
+ if( as<IRType>(inst) )
+ {
+ // Don't emit a type unless it is actually used.
+ continue;
+ }
+
+ ensureGlobalInst(&ctx, inst, EmitAction::Level::Definition);
+ }
+}
+
+void CLikeSourceEmitter::executeIREmitActions(List<EmitAction> const& actions)
+{
+ for(auto action : actions)
+ {
+ switch(action.level)
+ {
+ case EmitAction::Level::ForwardDeclaration:
+ emitIRFuncDecl(cast<IRFunc>(action.inst));
+ break;
+
+ case EmitAction::Level::Definition:
+ emitIRGlobalInst(action.inst);
+ break;
+ }
+ }
+}
+
+void CLikeSourceEmitter::emitIRModule(IRModule* module)
+{
+ // The IR will usually come in an order that respects
+ // dependencies between global declarations, but this
+ // isn't guaranteed, so we need to be careful about
+ // the order in which we emit things.
+
+ List<EmitAction> actions;
+
+ computeIREmitActions(module, actions);
+ executeIREmitActions(actions);
+}
+
+} // namespace Slang
diff --git a/source/slang/slang-c-like-source-emitter.h b/source/slang/slang-c-like-source-emitter.h
new file mode 100644
index 000000000..3c8f3dbef
--- /dev/null
+++ b/source/slang/slang-c-like-source-emitter.h
@@ -0,0 +1,379 @@
+// slang-c-like-source-emitter.h
+#ifndef SLANG_C_LIKE_SOURCE_EMITTER_H_INCLUDED
+#define SLANG_C_LIKE_SOURCE_EMITTER_H_INCLUDED
+
+#include "../core/basic.h"
+
+#include "compiler.h"
+
+#include "slang-emit-context.h"
+#include "slang-extension-usage-tracker.h"
+#include "slang-emit-precedence.h"
+
+#include "ir.h"
+#include "ir-insts.h"
+#include "ir-restructure.h"
+
+namespace Slang
+{
+
+struct CLikeSourceEmitter
+{
+ enum class BuiltInCOp
+ {
+ Splat, //< Splat a single value to all values of a vector or matrix type
+ Init, //< Initialize with parameters (must match the type)
+ };
+
+ typedef unsigned int ESemanticMask;
+ enum
+ {
+ kESemanticMask_None = 0,
+ kESemanticMask_NoPackOffset = 1 << 0,
+ kESemanticMask_Default = kESemanticMask_NoPackOffset,
+ };
+
+ // Hack to allow IR emit for global constant to override behavior
+ enum class IREmitMode
+ {
+ Default,
+ GlobalConstant,
+ };
+
+ struct EmitVarChain;
+ struct IRDeclaratorInfo;
+ struct EDeclarator;
+ struct ComputeEmitActionsContext;
+
+ // An action to be performed during code emit.
+ struct EmitAction
+ {
+ enum Level
+ {
+ ForwardDeclaration,
+ Definition,
+ };
+ Level level;
+ IRInst* inst;
+ };
+
+ /// Ctor
+ CLikeSourceEmitter(EmitContext* context);
+
+ /// Get the source manager
+ SourceManager* getSourceManager() { return m_context->getSourceManager(); }
+
+ /// Get the diagnostic sink
+ DiagnosticSink* getSink() { return m_context->getSink();}
+
+ //
+ // Types
+ //
+
+ void emitDeclarator(EDeclarator* declarator);
+
+ void emitGLSLTypePrefix(IRType* type, bool promoteHalfToFloat = false);
+
+ void emitHLSLTextureType(IRTextureTypeBase* texType);
+
+ void emitGLSLTextureOrTextureSamplerType(IRTextureTypeBase* type, char const* baseName);
+
+ void emitGLSLTextureType(IRTextureType* texType);
+
+ void emitGLSLTextureSamplerType(IRTextureSamplerType* type);
+
+ void emitGLSLImageType(IRGLSLImageType* type);
+
+ void emitTextureType(IRTextureType* texType);
+
+ void emitTextureSamplerType(IRTextureSamplerType* type);
+ void emitImageType(IRGLSLImageType* type);
+
+ void emitVectorTypeName(IRType* elementType, IRIntegerValue elementCount);
+
+ void emitVectorTypeImpl(IRVectorType* vecType);
+
+ void emitMatrixTypeImpl(IRMatrixType* matType);
+
+ void emitSamplerStateType(IRSamplerStateTypeBase* samplerStateType);
+
+ void emitStructuredBufferType(IRHLSLStructuredBufferTypeBase* type);
+
+ void emitUntypedBufferType(IRUntypedBufferResourceType* type);
+
+ void emitSimpleTypeImpl(IRType* type);
+
+ void emitArrayTypeImpl(IRArrayType* arrayType, EDeclarator* declarator);
+
+ void emitUnsizedArrayTypeImpl(IRUnsizedArrayType* arrayType, EDeclarator* declarator);
+
+ void emitTypeImpl(IRType* type, EDeclarator* declarator);
+
+ void emitType(IRType* type, const SourceLoc& typeLoc, Name* name, const SourceLoc& nameLoc);
+ void emitType(IRType* type, Name* name);
+ void emitType(IRType* type, String const& name);
+ void emitType(IRType* type);
+
+ //
+ // Expressions
+ //
+
+ bool maybeEmitParens(EmitOpInfo& outerPrec, EmitOpInfo prec);
+
+ void maybeCloseParens(bool needClose);
+
+ bool isTargetIntrinsicModifierApplicable(String const& targetName);
+
+ void emitType(IRType* type, Name* name, SourceLoc const& nameLoc);
+
+ void emitType(IRType* type, NameLoc const& nameAndLoc);
+
+ bool isTargetIntrinsicModifierApplicable(IRTargetIntrinsicDecoration* decoration);
+
+ void emitStringLiteral(const String& value);
+
+ void requireGLSLExtension(const String& name);
+
+ void requireGLSLVersion(ProfileVersion version);
+ void requireGLSLVersion(int version);
+ void setSampleRateFlag();
+
+ void doSampleRateInputCheck(Name* name);
+
+ void emitVal(IRInst* val, const EmitOpInfo& outerPrec);
+
+ UInt getBindingOffset(EmitVarChain* chain, LayoutResourceKind kind);
+ UInt getBindingSpace(EmitVarChain* chain, LayoutResourceKind kind);
+
+ // Emit a single `register` semantic, as appropriate for a given resource-type-specific layout info
+ // Keyword to use in the uniform case (`register` for globals, `packoffset` inside a `cbuffer`)
+ void emitHLSLRegisterSemantic(LayoutResourceKind kind, EmitVarChain* chain, char const* uniformSemanticSpelling = "register");
+
+ // Emit all the `register` semantics that are appropriate for a particular variable layout
+ void emitHLSLRegisterSemantics(EmitVarChain* chain, char const* uniformSemanticSpelling = "register");
+ void emitHLSLRegisterSemantics(VarLayout* varLayout, char const* uniformSemanticSpelling = "register");
+
+ void emitHLSLParameterGroupFieldLayoutSemantics(EmitVarChain* chain);
+
+ void emitHLSLParameterGroupFieldLayoutSemantics(RefPtr<VarLayout> fieldLayout, EmitVarChain* inChain);
+
+ bool emitGLSLLayoutQualifier(LayoutResourceKind kind, EmitVarChain* chain);
+
+ void emitGLSLLayoutQualifiers(RefPtr<VarLayout> layout, EmitVarChain* inChain, LayoutResourceKind filter = LayoutResourceKind::None);
+
+ void emitGLSLVersionDirective();
+
+ void emitGLSLPreprocessorDirectives();
+
+ /// Emit directives to control overall layout computation for the emitted code.
+ void emitLayoutDirectives(TargetRequest* targetReq);
+
+ // Utility code for generating unique IDs as needed
+ // during the emit process (e.g., for declarations
+ // that didn't originally have names, but now need to).
+ UInt allocateUniqueID();
+
+ // IR-level emit logic
+
+ UInt getID(IRInst* value);
+
+ /// "Scrub" a name so that it complies with restrictions of the target language.
+ String scrubName(const String& name);
+
+ String generateIRName(IRInst* inst);
+ String getIRName(IRInst* inst);
+
+ void emitDeclarator(IRDeclaratorInfo* declarator);
+ void emitIRSimpleValue(IRInst* inst);
+
+ CodeGenTarget getTarget();
+
+ bool shouldFoldIRInstIntoUseSites(IRInst* inst, IREmitMode mode);
+
+ void emitIROperand(IRInst* inst, IREmitMode mode, EmitOpInfo const& outerPrec);
+
+ void emitIRArgs(IRInst* inst, IREmitMode mode);
+
+ void emitIRType(IRType* type, String const& name);
+
+ void emitIRType(IRType* type, Name* name);
+
+ void emitIRType(IRType* type);
+
+ void emitIRRateQualifiers(IRRate* rate);
+
+ void emitIRRateQualifiers(IRInst* value);
+
+ void emitIRInstResultDecl(IRInst* inst);
+
+ IRTargetIntrinsicDecoration* findTargetIntrinsicDecoration(IRInst* inst);
+
+ // Check if the string being used to define a target intrinsic
+ // is an "ordinary" name, such that we can simply emit a call
+ // to the new name with the arguments of the old operation.
+ static bool isOrdinaryName(const String& name);
+
+ void emitTargetIntrinsicCallExpr(
+ IRCall* inst,
+ IRFunc* /* func */,
+ IRTargetIntrinsicDecoration* targetIntrinsic,
+ IREmitMode mode,
+ EmitOpInfo const& inOuterPrec);
+
+ void emitIntrinsicCallExpr(
+ IRCall* inst,
+ IRFunc* func,
+ IREmitMode mode,
+ EmitOpInfo const& inOuterPrec);
+
+ void emitIRCallExpr(IRCall* inst, IREmitMode mode, EmitOpInfo outerPrec);
+
+ void emitNot(IRInst* inst, IREmitMode mode, EmitOpInfo& ioOuterPrec, bool* outNeedClose);
+
+ void emitComparison(IRInst* inst, IREmitMode mode, EmitOpInfo& ioOuterPrec, const EmitOpInfo& opPrec, bool* needCloseOut);
+
+ void emitIRInstExpr(IRInst* inst, IREmitMode mode, EmitOpInfo const& inOuterPrec);
+
+ BaseType extractBaseType(IRType* inType);
+
+ void emitIRInst(IRInst* inst, IREmitMode mode);
+
+ void emitIRInstImpl(IRInst* inst, IREmitMode mode);
+
+ void emitIRSemantics(VarLayout* varLayout);
+
+ void emitIRSemantics(IRInst* inst);
+
+ VarLayout* getVarLayout(IRInst* var);
+
+ void emitIRLayoutSemantics(IRInst* inst, char const* uniformSemanticSpelling = "register");
+
+ // When we are about to traverse an edge from one block to another,
+ // we need to emit the assignments that conceptually occur "along"
+ // the edge. In traditional SSA these are the phi nodes in the
+ // target block, while in our representation these use the arguments
+ // to the branch instruction to fill in the parameters of the target.
+ void emitPhiVarAssignments(UInt argCount, IRUse* args, IRBlock* targetBlock);
+
+ /// Emit high-level language statements from a structured region.
+ void emitRegion(Region* inRegion);
+
+ /// Emit high-level language statements from a structured region tree.
+ void emitRegionTree(RegionTree* regionTree);
+
+ // Is an IR function a definition? (otherwise it is a declaration)
+ bool isDefinition(IRFunc* func);
+
+ String getIRFuncName(IRFunc* func);
+
+ void emitAttributeSingleString(const char* name, FuncDecl* entryPoint, Attribute* attrib);
+
+ void emitAttributeSingleInt(const char* name, FuncDecl* entryPoint, Attribute* attrib);
+
+ void emitFuncDeclPatchConstantFuncAttribute(IRFunc* irFunc, FuncDecl* entryPoint, PatchConstantFuncAttribute* attrib);
+
+ void emitIREntryPointAttributes_HLSL(IRFunc* irFunc, EntryPointLayout* entryPointLayout);
+
+ void emitIREntryPointAttributes_GLSL(IRFunc* irFunc, EntryPointLayout* entryPointLayout);
+
+ void emitIREntryPointAttributes(IRFunc* irFunc, EntryPointLayout* entryPointLayout);
+
+ void emitPhiVarDecls(IRFunc* func);
+
+ /// Emit high-level statements for the body of a function.
+ void emitIRFunctionBody(IRGlobalValueWithCode* code);
+
+ void emitIRSimpleFunc(IRFunc* func);
+
+ void emitIRParamType(IRType* type, String const& name);
+
+ IRInst* getSpecializedValue(IRSpecialize* specInst);
+
+ void emitIRFuncDecl(IRFunc* func);
+
+ EntryPointLayout* getEntryPointLayout(IRFunc* func);
+
+ EntryPointLayout* asEntryPoint(IRFunc* func);
+
+ // Detect if the given IR function represents a
+ // declaration of an intrinsic/builtin for the
+ // current code-generation target.
+ bool isTargetIntrinsic(IRFunc* func);
+
+ // Check whether a given value names a target intrinsic,
+ // and return the IR function representing the intrinsic
+ // if it does.
+ IRFunc* asTargetIntrinsic(IRInst* value);
+
+ void emitIRFunc(IRFunc* func);
+
+ void emitIRStruct(IRStructType* structType);
+
+ void emitIRMatrixLayoutModifiers(VarLayout* layout);
+
+ // Emit the `flat` qualifier if the underlying type
+ // of the variable is an integer type.
+ void maybeEmitGLSLFlatModifier(IRType* valueType);
+
+ void emitInterpolationModifiers(IRInst* varInst, IRType* valueType, VarLayout* layout);
+
+ UInt getRayPayloadLocation(IRInst* inst);
+
+ UInt getCallablePayloadLocation(IRInst* inst);
+
+ void emitGLSLImageFormatModifier(IRInst* var, IRTextureType* resourceType);
+
+ /// Emit modifiers that should apply even for a declaration of an SSA temporary.
+ void emitIRTempModifiers(IRInst* temp);
+
+ void emitIRVarModifiers(VarLayout* layout, IRInst* varDecl, IRType* varType);
+
+ void emitHLSLParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type);
+
+ /// Emit the array brackets that go on the end of a declaration of the given type.
+ void emitArrayBrackets(IRType* inType);
+
+ void emitGLSLParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type);
+
+ void emitIRParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type);
+
+ void emitIRVar(IRVar* varDecl);
+
+ void emitIRStructuredBuffer_GLSL(IRGlobalParam* varDecl, IRHLSLStructuredBufferTypeBase* structuredBufferType);
+
+ void emitIRByteAddressBuffer_GLSL(IRGlobalParam* varDecl, IRByteAddressBufferTypeBase* byteAddressBufferType);
+
+ void emitIRGlobalVar(IRGlobalVar* varDecl);
+ void emitIRGlobalParam(IRGlobalParam* varDecl);
+ void emitIRGlobalConstantInitializer(IRGlobalConstant* valDecl);
+
+ void emitIRGlobalConstant(IRGlobalConstant* valDecl);
+
+ void emitIRGlobalInst(IRInst* inst);
+
+ void ensureInstOperand(ComputeEmitActionsContext* ctx, IRInst* inst, EmitAction::Level requiredLevel = EmitAction::Level::Definition);
+
+ void ensureInstOperandsRec(ComputeEmitActionsContext* ctx, IRInst* inst);
+
+ void ensureGlobalInst(ComputeEmitActionsContext* ctx, IRInst* inst, EmitAction::Level requiredLevel);
+
+ void computeIREmitActions(IRModule* module, List<EmitAction>& ioActions);
+
+ void executeIREmitActions(List<EmitAction> const& actions);
+ void emitIRModule(IRModule* module);
+
+ protected:
+
+ void _requireHalf();
+ void _emitCVecType(IROp op, Int size);
+ void _emitCMatType(IROp op, IRIntegerValue rowCount, IRIntegerValue colCount);
+
+ void _emitCFunc(BuiltInCOp cop, IRType* type);
+ void _maybeEmitGLSLCast(IRType* castType, IRInst* inst, IREmitMode mode);
+
+ EmitContext* m_context;
+ SourceStream* m_stream;
+};
+
+}
+#endif
diff --git a/source/slang/slang-emit-context.cpp b/source/slang/slang-emit-context.cpp
new file mode 100644
index 000000000..b330b86f9
--- /dev/null
+++ b/source/slang/slang-emit-context.cpp
@@ -0,0 +1,7 @@
+// slang-emit-context.cpp
+#include "slang-emit-context.h"
+
+namespace Slang {
+
+
+} // namespace Slang
diff --git a/source/slang/slang-emit-context.h b/source/slang/slang-emit-context.h
new file mode 100644
index 000000000..75e65feee
--- /dev/null
+++ b/source/slang/slang-emit-context.h
@@ -0,0 +1,79 @@
+// slang-emit-context.h
+#ifndef SLANG_EMIT_CONTEXT_H_INCLUDED
+#define SLANG_EMIT_CONTEXT_H_INCLUDED
+
+#include "../core/basic.h"
+
+#include "compiler.h"
+#include "type-layout.h"
+#include "slang-source-stream.h"
+#include "slang-extension-usage-tracker.h"
+
+namespace Slang
+{
+
+// Shared state for an entire emit session
+struct EmitContext
+{
+ DiagnosticSink* getSink() { return compileRequest->getSink(); }
+ LineDirectiveMode getLineDirectiveMode() { return compileRequest->getLineDirectiveMode(); }
+ SourceManager* getSourceManager() { return compileRequest->getSourceManager(); }
+ void noteInternalErrorLoc(SourceLoc loc) { return getSink()->noteInternalErrorLoc(loc); }
+
+ BackEndCompileRequest* compileRequest = nullptr;
+
+ // The entry point we are being asked to compile
+ EntryPoint* entryPoint;
+
+ // The layout for the entry point
+ EntryPointLayout* entryPointLayout;
+
+ // The target language we want to generate code for
+ CodeGenTarget target;
+
+ // Where source is written to
+ SourceStream* stream;
+
+ // We only want to emit each `import`ed module one time, so
+ // we maintain a set of already-emitted modules.
+ HashSet<ModuleDecl*> modulesAlreadyEmitted;
+
+ // We track the original global-scope layout so that we can
+ // find layout information for `import`ed parameters.
+ //
+ // TODO: This will probably change if we represent imports
+ // explicitly in the layout data.
+ StructTypeLayout* globalStructLayout;
+
+ ProgramLayout* programLayout;
+
+ ModuleDecl* program;
+
+ ExtensionUsageTracker extensionUsageTracker;
+
+ UInt uniqueIDCounter = 1;
+ Dictionary<IRInst*, UInt> mapIRValueToID;
+ Dictionary<Decl*, UInt> mapDeclToID;
+
+ HashSet<String> irDeclsVisited;
+
+ HashSet<String> irTupleTypes;
+
+ // The "effective" profile that is being used to emit code,
+ // combining information from the target and entry point.
+ Profile effectiveProfile;
+
+ // Map a string name to the number of times we have seen this
+ // name used so far during code emission.
+ Dictionary<String, UInt> uniqueNameCounters;
+
+ // Map an IR instruction to the name that we've decided
+ // to use for it when emitting code.
+ Dictionary<IRInst*, String> mapInstToName;
+
+ Dictionary<IRInst*, UInt> mapIRValueToRayPayloadLocation;
+ Dictionary<IRInst*, UInt> mapIRValueToCallablePayloadLocation;
+};
+
+}
+#endif
diff --git a/source/slang/slang-emit-precedence.cpp b/source/slang/slang-emit-precedence.cpp
new file mode 100644
index 000000000..43a573f20
--- /dev/null
+++ b/source/slang/slang-emit-precedence.cpp
@@ -0,0 +1,13 @@
+// slang-emit-precedence.cpp
+#include "slang-emit-precedence.h"
+
+namespace Slang {
+
+#define SLANG_OP_INFO_EXPAND(op, name, precedence) {name, kEPrecedence_##precedence##_Left, kEPrecedence_##precedence##_Right, },
+
+/* static */const EmitOpInfo EmitOpInfo::s_infos[int(EmitOp::CountOf)] =
+{
+ SLANG_OP_INFO(SLANG_OP_INFO_EXPAND)
+};
+
+} // namespace Slang
diff --git a/source/slang/slang-emit-precedence.h b/source/slang/slang-emit-precedence.h
new file mode 100644
index 000000000..30783f685
--- /dev/null
+++ b/source/slang/slang-emit-precedence.h
@@ -0,0 +1,156 @@
+// slang-emit-precedence.h
+#ifndef SLANG_EMIT_PRECEDENCE_H_INCLUDED
+#define SLANG_EMIT_PRECEDENCE_H_INCLUDED
+
+#include "../core/basic.h"
+
+namespace Slang
+{
+
+// Macros for setting up precedence
+#define SLANG_PRECEDENCE_LEFT(NAME) \
+ kEPrecedence_##NAME##_Left, \
+ kEPrecedence_##NAME##_Right,
+
+#define SLANG_PRECEDENCE_RIGHT(NAME) \
+ kEPrecedence_##NAME##_Right, \
+ kEPrecedence_##NAME##_Left,
+
+#define SLANG_PRECEDENCE_NON_ASSOC(NAME) \
+ kEPrecedence_##NAME##_Left, \
+ kEPrecedence_##NAME##_Right = kEPrecedence_##NAME##_Left,
+
+#define SLANG_PRECEDENCE_EXPAND(NAME, ASSOC) SLANG_PRECEDENCE_##ASSOC(NAME)
+
+// x macro of precedence of types in order.
+// Used because in header, need to prefix macros to avoid clashes, and this style allows for prefixing without additional clutter
+#define SLANG_PRECEDENCE(x) \
+ x(None, NON_ASSOC) \
+ x(Comma, LEFT) \
+ \
+ x(General, NON_ASSOC) \
+ \
+ x(Assign, RIGHT) \
+ \
+ x(Conditional, RIGHT) \
+ \
+ x(Or, LEFT) \
+ x(And, LEFT) \
+ x(BitOr, LEFT) \
+ x(BitXor, LEFT) \
+ x(BitAnd, LEFT) \
+ \
+ x(Equality, LEFT) \
+ x(Relational, LEFT) \
+ x(Shift, LEFT) \
+ x(Additive, LEFT) \
+ x(Multiplicative, LEFT) \
+ x(Prefix, RIGHT) \
+ x(Postfix, LEFT) \
+ x(Atomic, NON_ASSOC)
+
+// Precedence enum produced from the SLANG_PRECEDENCE macro
+enum EPrecedence
+{
+ SLANG_PRECEDENCE(SLANG_PRECEDENCE_EXPAND)
+};
+
+// Macro for define OpInfo and an associated enum type. Order or macro parameters is
+// Op, OpName, Precedence
+#define SLANG_OP_INFO(x) \
+ x(None, "", None) \
+ \
+ x(Comma, ",", Comma) \
+ \
+ x(General, "", General) \
+ \
+ x(Assign, "=", Assign) \
+ x(AddAssign, "+=", Assign) \
+ x(SubAssign, "-=", Assign) \
+ x(MulAssign, "*=", Assign) \
+ x(DivAssign, "/=", Assign) \
+ x(ModAssign, "%=", Assign) \
+ x(LshAssign, "<<=", Assign) \
+ x(RshAssign, ">>=", Assign) \
+ x(OrAssign, "|=", Assign) \
+ x(AndAssign, "&=", Assign) \
+ x(XorAssign, "^=", Assign) \
+ \
+ x(Conditional, "?:", Conditional) \
+ \
+ x(Or, "||", Or) \
+ x(And, "&&", And) \
+ x(BitOr, "|", BitOr) \
+ x(BitXor, "^", BitXor) \
+ x(BitAnd, "&", BitAnd) \
+ \
+ x(Eql, "==", Equality) \
+ x(Neq, "!=", Equality) \
+ \
+ x(Less, "<", Relational) \
+ x(Greater, ">", Relational) \
+ x(Leq, "<=", Relational) \
+ x(Geq, ">=", Relational) \
+ \
+ x(Lsh, "<<", Shift) \
+ x(Rsh, ">>", Shift) \
+ \
+ x(Add, "+", Additive) \
+ x(Sub, "-", Additive) \
+ \
+ x(Mul, "*", Multiplicative) \
+ x(Div, "/", Multiplicative) \
+ x(Mod, "%", Multiplicative) \
+ \
+ x(Prefix, "", Prefix) \
+ x(Postfix, "", Postfix) \
+ x(Atomic, "", Atomic)
+
+#define SLANG_OP_INFO_ENUM(op, name, precedence) op,
+
+enum class EmitOp
+{
+ SLANG_OP_INFO(SLANG_OP_INFO_ENUM)
+ CountOf,
+};
+
+// Info on an op for emit purposes
+struct EmitOpInfo
+{
+ SLANG_FORCE_INLINE static const EmitOpInfo& get(EmitOp inOp) { return s_infos[int(inOp)]; }
+
+ char const* op;
+ EPrecedence leftPrecedence;
+ EPrecedence rightPrecedence;
+
+ static const EmitOpInfo s_infos[int(EmitOp::CountOf)];
+};
+
+SLANG_FORCE_INLINE const EmitOpInfo& getInfo(EmitOp op) { return EmitOpInfo::s_infos[int(op)]; }
+
+SLANG_INLINE EmitOpInfo leftSide(EmitOpInfo const& outerPrec, EmitOpInfo const& prec)
+{
+ EmitOpInfo result;
+ result.op = nullptr;
+ result.leftPrecedence = outerPrec.leftPrecedence;
+ result.rightPrecedence = prec.leftPrecedence;
+ return result;
+}
+
+SLANG_INLINE EmitOpInfo rightSide(EmitOpInfo const& prec, EmitOpInfo const& outerPrec)
+{
+ EmitOpInfo result;
+ result.op = nullptr;
+ result.leftPrecedence = prec.rightPrecedence;
+ result.rightPrecedence = outerPrec.rightPrecedence;
+ return result;
+}
+
+// Precedence macros no longer needed
+#undef SLANG_PRECEDENCE_EXPAND
+#undef SLANG_PRECEDENCE_NON_ASSOC
+#undef SLANG_PRECEDENCE_RIGHT
+#undef SLANG_PRECEDENCE_LEFT
+
+}
+#endif
diff --git a/source/slang/slang-extension-usage-tracker.cpp b/source/slang/slang-extension-usage-tracker.cpp
new file mode 100644
index 000000000..f679e15db
--- /dev/null
+++ b/source/slang/slang-extension-usage-tracker.cpp
@@ -0,0 +1,44 @@
+// slang-extension-usage-tracker.cpp
+#include "slang-extension-usage-tracker.h"
+
+namespace Slang {
+
+void ExtensionUsageTracker::requireGLSLExtension(const String& name)
+{
+ if (m_glslExtensionsRequired.Contains(name))
+ return;
+
+ StringBuilder& sb = m_glslExtensionRequireLines;
+
+ sb.append("#extension ");
+ sb.append(name);
+ sb.append(" : require\n");
+
+ m_glslExtensionsRequired.Add(name);
+}
+
+void ExtensionUsageTracker::requireGLSLVersion(ProfileVersion version)
+{
+ // Check if this profile is newer
+ if ((UInt)version > (UInt)m_glslProfileVersion)
+ {
+ m_glslProfileVersion = version;
+ }
+}
+
+void ExtensionUsageTracker::requireGLSLHalfExtension()
+{
+ if (!m_hasGLSLHalfExtension)
+ {
+ // https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_shader_16bit_storage.txt
+ requireGLSLExtension("GL_EXT_shader_16bit_storage");
+
+ // https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_shader_explicit_arithmetic_types.txt
+ requireGLSLExtension("GL_EXT_shader_explicit_arithmetic_types");
+
+ m_hasGLSLHalfExtension = true;
+ }
+}
+
+
+} // namespace Slang
diff --git a/source/slang/slang-extension-usage-tracker.h b/source/slang/slang-extension-usage-tracker.h
new file mode 100644
index 000000000..32002261d
--- /dev/null
+++ b/source/slang/slang-extension-usage-tracker.h
@@ -0,0 +1,34 @@
+// slang-extension-usage-tracker.h
+#ifndef SLANG_EXTENSION_USAGE_TRACKER_H_INCLUDED
+#define SLANG_EXTENSION_USAGE_TRACKER_H_INCLUDED
+
+#include "../core/basic.h"
+
+#include "compiler.h"
+
+namespace Slang
+{
+
+class ExtensionUsageTracker
+{
+public:
+
+ void requireGLSLExtension(const String& name);
+ void requireGLSLVersion(ProfileVersion version);
+ void requireGLSLHalfExtension();
+
+ ProfileVersion getRequiredGLSLProfileVersion() const { return m_glslProfileVersion; }
+ const StringBuilder& getGLSLExtensionRequireLines() const { return m_glslExtensionRequireLines; }
+
+protected:
+ // Record the GLSL extensions we have already emitted a `#extension` for
+ HashSet<String> m_glslExtensionsRequired;
+ StringBuilder m_glslExtensionRequireLines;
+
+ ProfileVersion m_glslProfileVersion = ProfileVersion::GLSL_110;
+
+ bool m_hasGLSLHalfExtension = false;
+};
+
+}
+#endif
diff --git a/source/slang/slang-mangled-lexer.cpp b/source/slang/slang-mangled-lexer.cpp
new file mode 100644
index 000000000..f1f5ec903
--- /dev/null
+++ b/source/slang/slang-mangled-lexer.cpp
@@ -0,0 +1,184 @@
+// slang-mangled-lexer.cpp
+#include "slang-mangled-lexer.h"
+
+#include <assert.h>
+
+namespace Slang {
+
+UInt MangledLexer::readCount()
+{
+ int c = _peek();
+ if (!_isDigit((char)c))
+ {
+ SLANG_UNEXPECTED("bad name mangling");
+ UNREACHABLE_RETURN(0);
+ }
+ _next();
+
+ if (c == '0')
+ return 0;
+
+ UInt count = 0;
+ for (;;)
+ {
+ count = count * 10 + c - '0';
+ c = _peek();
+ if (!_isDigit((char)c))
+ return count;
+
+ _next();
+ }
+}
+
+void MangledLexer::readGenericParam()
+{
+ switch (_peek())
+ {
+ case 'T':
+ case 'C':
+ _next();
+ break;
+
+ case 'v':
+ _next();
+ readType();
+ break;
+
+ default:
+ SLANG_UNEXPECTED("bad name mangling");
+ break;
+ }
+}
+
+void MangledLexer::readGenericParams()
+{
+ _expect("g");
+ UInt paramCount = readCount();
+ for (UInt pp = 0; pp < paramCount; pp++)
+ {
+ readGenericParam();
+ }
+}
+
+void MangledLexer::readType()
+{
+ int c = _peek();
+ switch (c)
+ {
+ case 'V':
+ case 'b':
+ case 'i':
+ case 'u':
+ case 'U':
+ case 'h':
+ case 'f':
+ case 'd':
+ _next();
+ break;
+
+ case 'v':
+ _next();
+ readSimpleIntVal();
+ readType();
+ break;
+
+ default:
+ readNamedType();
+ break;
+ }
+}
+
+void MangledLexer::readVal()
+{
+ switch (_peek())
+ {
+ case 'k':
+ _next();
+ readCount();
+ break;
+
+ case 'K':
+ _next();
+ readRawStringSegment();
+ break;
+
+ default:
+ readType();
+ break;
+ }
+
+}
+
+void MangledLexer::readGenericArgs()
+{
+ _expect("G");
+ UInt argCount = readCount();
+ for (UInt aa = 0; aa < argCount; aa++)
+ {
+ readGenericArg();
+ }
+}
+
+UnownedStringSlice MangledLexer::readSimpleName()
+{
+ UnownedStringSlice result;
+ for (;;)
+ {
+ int c = _peek();
+
+ if (c == 'g')
+ {
+ readGenericParams();
+ continue;
+ }
+ else if (c == 'G')
+ {
+ readGenericArgs();
+ continue;
+ }
+ else if (c == 'X')
+ {
+ readExtensionSpec();
+ continue;
+ }
+
+ if (!_isDigit((char)c))
+ return result;
+
+ // Read the length part
+ UInt count = readCount();
+ if (count > UInt(m_end - m_cursor))
+ {
+ SLANG_UNEXPECTED("bad name mangling");
+ UNREACHABLE_RETURN(result);
+ }
+
+ result = UnownedStringSlice(m_cursor, m_cursor + count);
+ m_cursor += count;
+ }
+}
+
+UnownedStringSlice MangledLexer::readRawStringSegment()
+{
+ // Read the length part
+ UInt count = readCount();
+ if (count > UInt(m_end - m_cursor))
+ {
+ SLANG_UNEXPECTED("bad name mangling");
+ UNREACHABLE_RETURN(UnownedStringSlice());
+ }
+
+ auto result = UnownedStringSlice(m_cursor, m_cursor + count);
+ m_cursor += count;
+ return result;
+}
+
+UInt MangledLexer::readParamCount()
+{
+ _expect("p");
+ UInt count = readCount();
+ _expect("p");
+ return count;
+}
+
+} // namespace Slang
diff --git a/source/slang/slang-mangled-lexer.h b/source/slang/slang-mangled-lexer.h
new file mode 100644
index 000000000..4890ae80f
--- /dev/null
+++ b/source/slang/slang-mangled-lexer.h
@@ -0,0 +1,125 @@
+// slang-mangled-lexer.h
+#ifndef SLANG_MANGLED_LEXER_H_INCLUDED
+#define SLANG_MANGLED_LEXER_H_INCLUDED
+
+#include "../core/basic.h"
+
+#include "compiler.h"
+
+namespace Slang
+{
+
+/* A lexer like utility class used for decoding mangled names.
+Expects names to be correctly constructed - any errors will cause asserts/failures */
+class MangledLexer
+{
+public:
+ /// Reads a count at current position
+ UInt readCount();
+
+ void readGenericParam();
+
+ void readGenericParams();
+
+ SLANG_INLINE void readSimpleIntVal();
+
+ UnownedStringSlice readRawStringSegment();
+
+ void readNamedType();
+
+ void readType();
+
+ void readVal();
+
+ void readGenericArg() { readVal(); }
+
+ void readGenericArgs();
+
+ SLANG_INLINE void readExtensionSpec();
+
+ UnownedStringSlice readSimpleName();
+
+ UInt readParamCount();
+
+ /// Ctor
+ SLANG_FORCE_INLINE MangledLexer(String const& str);
+
+private:
+
+ // Call at the beginning of a mangled name,
+ // to strip off the main prefix
+ void _start() { _expect("_S"); }
+
+ static bool _isDigit(char c) { return (c >= '0') && (c <= '9'); }
+
+ /// Returns the character at the current position
+ char _peek() { return *m_cursor; }
+ // Returns the current character and moves to next character.
+ char _next() { return *m_cursor++; }
+
+ SLANG_INLINE void _expect(char c);
+
+ void _expect(char const* str)
+ {
+ while (char c = *str++)
+ _expect(c);
+ }
+
+ char const* m_cursor = nullptr;
+ char const* m_begin = nullptr;
+ char const* m_end = nullptr;
+};
+
+// -------------------------------------------------------------------------- -
+SLANG_FORCE_INLINE MangledLexer::MangledLexer(String const& str)
+ : m_cursor(str.begin())
+ , m_begin(str.begin())
+ , m_end(str.end())
+{
+ _start();
+}
+
+// ---------------------------------------------------------------------------
+SLANG_INLINE void MangledLexer::readSimpleIntVal()
+{
+ int c = _peek();
+ if (_isDigit((char)c))
+ {
+ _next();
+ }
+ else
+ {
+ readVal();
+ }
+}
+
+// ---------------------------------------------------------------------------
+SLANG_INLINE void MangledLexer::readNamedType()
+{
+ // TODO: handle types with more complicated names
+ readRawStringSegment();
+}
+
+// ---------------------------------------------------------------------------
+SLANG_INLINE void MangledLexer::readExtensionSpec()
+{
+ _expect("X");
+ readType();
+}
+
+// ---------------------------------------------------------------------------
+SLANG_INLINE void MangledLexer::_expect(char c)
+{
+ if (_peek() == c)
+ {
+ _next();
+ }
+ else
+ {
+ // ERROR!
+ SLANG_UNEXPECTED("mangled name error");
+ }
+}
+
+}
+#endif
diff --git a/source/slang/slang-source-stream.cpp b/source/slang/slang-source-stream.cpp
new file mode 100644
index 000000000..e561176ae
--- /dev/null
+++ b/source/slang/slang-source-stream.cpp
@@ -0,0 +1,396 @@
+// emit.cpp
+#include "slang-source-stream.h"
+
+// Disable warnings about sprintf
+#ifdef _WIN32
+# pragma warning(disable:4996)
+#endif
+
+// Note: using C++ stdio just to get a locale-independent
+// way to format floating-point values.
+//
+// TODO: Go ahead and implement the Dragon4 algorithm so
+// that we can print floating-point values to arbitrary
+// precision as needed.
+#include <sstream>
+
+namespace Slang {
+
+SourceStream::SourceStream(SourceManager* sourceManager, LineDirectiveMode lineDirectiveMode)
+{
+ m_lineDirectiveMode = lineDirectiveMode;
+ this->m_sourceManager = sourceManager;
+}
+
+String SourceStream::getContentAndClear()
+{
+ String content(getContent());
+ clearContent();
+ return content;
+}
+
+void SourceStream::emitRawTextSpan(char const* textBegin, char const* textEnd)
+{
+ // TODO(tfoley): Need to make "corelib" not use `int` for pointer-sized things...
+ auto len = textEnd - textBegin;
+ m_builder.Append(textBegin, len);
+}
+
+void SourceStream::emitRawText(char const* text)
+{
+ emitRawTextSpan(text, text + strlen(text));
+}
+
+void SourceStream::_emitTextSpan(char const* textBegin, char const* textEnd)
+{
+ // Don't change anything given an empty string
+ if (textBegin == textEnd)
+ return;
+
+ // If the source location has changed in a way that required update,
+ // do it now!
+ _flushSourceLocationChange();
+
+ // Note: we don't want to emit indentation on a line that is empty.
+ // The logic in `Emit(textBegin, textEnd)` below will have broken
+ // the text into lines, so we can simply check if a line consists
+ // of just a newline.
+ if (m_isAtStartOfLine && *textBegin != '\n')
+ {
+ // We are about to emit text (other than a newline)
+ // at the start of a line, so we will emit the proper
+ // amount of indentation to keep things looking nice.
+ m_isAtStartOfLine = false;
+ for (Int ii = 0; ii < m_indentLevel; ++ii)
+ {
+ char const* indentString = " ";
+ size_t indentStringSize = strlen(indentString);
+ emitRawTextSpan(indentString, indentString + indentStringSize);
+
+ // We will also update our tracking location, just in
+ // case other logic needs it.
+ //
+ // TODO: We may need to have a switch that controls whether
+ // we are in "pretty-printing" mode or "follow the locations
+ // in the original code" mode.
+ m_loc.column += indentStringSize;
+ }
+ }
+
+ // Emit the raw text
+ emitRawTextSpan(textBegin, textEnd);
+
+ // Update our logical position
+ auto len = int(textEnd - textBegin);
+ m_loc.column += len;
+}
+
+void SourceStream::indent()
+{
+ m_indentLevel++;
+}
+
+void SourceStream::dedent()
+{
+ m_indentLevel--;
+}
+
+void SourceStream::emit(char const* textBegin, char const* textEnd)
+{
+ char const* spanBegin = textBegin;
+ char const* spanEnd = spanBegin;
+ for (;;)
+ {
+ if (spanEnd == textEnd)
+ {
+ // We have a whole range of text waiting to be flushed
+ _emitTextSpan(spanBegin, spanEnd);
+ return;
+ }
+
+ auto c = *spanEnd++;
+
+ if (c == '\n')
+ {
+ // At the end of a line, we need to update our tracking
+ // information on code positions
+ _emitTextSpan(spanBegin, spanEnd);
+ m_loc.line++;
+ m_loc.column = 1;
+ m_isAtStartOfLine = true;
+
+ // Start a new span for emit purposes
+ spanBegin = spanEnd;
+ }
+ }
+}
+
+void SourceStream::emit(char const* text)
+{
+ emit(text, text + strlen(text));
+}
+
+void SourceStream::emit(const String& text)
+{
+ emit(text.begin(), text.end());
+}
+
+void SourceStream::emit(const UnownedStringSlice& text)
+{
+ emit(text.begin(), text.end());
+}
+
+void SourceStream::emit(Name* name)
+{
+ emit(getText(name));
+}
+
+void SourceStream::emit(const NameLoc& nameAndLoc)
+{
+ advanceToSourceLocation(nameAndLoc.loc);
+ emit(getText(nameAndLoc.name));
+}
+
+void SourceStream::emitName(Name* name, const SourceLoc& locIn)
+{
+ advanceToSourceLocation(locIn);
+ emit(name);
+}
+
+void SourceStream::emitName(const NameLoc& nameAndLoc)
+{
+ emitName(nameAndLoc.name, nameAndLoc.loc);
+}
+
+void SourceStream::emitName(Name* name)
+{
+ emitName(name, SourceLoc());
+}
+
+void SourceStream::emit(IntegerLiteralValue value)
+{
+ char buffer[32];
+ sprintf(buffer, "%lld", (long long int)value);
+ emit(buffer);
+}
+
+void SourceStream::emit(UInt value)
+{
+ char buffer[32];
+ sprintf(buffer, "%llu", (unsigned long long)(value));
+ emit(buffer);
+}
+
+void SourceStream::emit(int value)
+{
+ char buffer[16];
+ sprintf(buffer, "%d", value);
+ emit(buffer);
+}
+
+void SourceStream::emit(double value)
+{
+ // There are a few different requirements here that we need to deal with:
+ //
+ // 1) We need to print something that is valid syntax in the target language
+ // (this means that hex floats are off the table for now)
+ //
+ // 2) We need our printing to be independent of the current global locale in C,
+ // so that we don't depend on the application leaving it as the default,
+ // and we also don't revert any changes they make.
+ // (this means that `sprintf` and friends are off the table)
+ //
+ // 3) We need to be sure that floating-point literals specified by the user will
+ // "round-trip" and turn into the same value when parsed back in. This means
+ // that we need to print a reasonable number of digits of precision.
+ //
+ // For right now, the easiest option that can balance these is to use
+ // the C++ standard library `iostream`s, because they support an explicit locale,
+ // and can (hopefully) print floating-point numbers accurately.
+ //
+ // Eventually, the right move here would be to implement proper floating-point
+ // number formatting ourselves, but that would require extensive testing to
+ // make sure we get it right.
+
+ std::ostringstream stream;
+ stream.imbue(std::locale::classic());
+ stream.setf(std::ios::fixed, std::ios::floatfield);
+ stream.precision(20);
+ stream << value;
+
+ emit(stream.str().c_str());
+}
+
+void SourceStream::advanceToSourceLocation(const SourceLoc& sourceLocation)
+{
+ advanceToSourceLocation(getSourceManager()->getHumaneLoc(sourceLocation));
+}
+
+void SourceStream::advanceToSourceLocation(const HumaneSourceLoc& sourceLocation)
+{
+ // Skip invalid locations
+ if (sourceLocation.line <= 0)
+ return;
+
+ m_needToUpdateSourceLocation = true;
+ m_nextSourceLocation = sourceLocation;
+}
+
+void SourceStream::_flushSourceLocationChange()
+{
+ if (!m_needToUpdateSourceLocation)
+ return;
+
+ // Note: the order matters here, because trying to update
+ // the source location may involve outputting text that
+ // advances the location, and outputting text is what
+ // triggers this flush operation.
+ m_needToUpdateSourceLocation = false;
+ _emitLineDirectiveIfNeeded(m_nextSourceLocation);
+}
+
+void SourceStream::_emitLineDirectiveAndUpdateSourceLocation(const HumaneSourceLoc& sourceLocation)
+{
+ _emitLineDirective(sourceLocation);
+
+ HumaneSourceLoc newLoc = sourceLocation;
+ newLoc.column = 1;
+
+ m_loc = newLoc;
+}
+
+void SourceStream::_emitLineDirectiveIfNeeded(const HumaneSourceLoc& sourceLocation)
+{
+ // Don't do any of this work if the user has requested that we
+ // not emit line directives.
+ auto mode = getLineDirectiveMode();
+ switch (mode)
+ {
+ case LineDirectiveMode::None:
+ return;
+
+ case LineDirectiveMode::Default:
+ default:
+ break;
+ }
+
+ // Ignore invalid source locations
+ if (sourceLocation.line <= 0)
+ return;
+
+ // If we are currently emitting code at a source location with
+ // a differnet file or line, *or* if the source location is
+ // somehow later on the line than what we want to emit,
+ // then we need to emit a new `#line` directive.
+ if (sourceLocation.pathInfo.foundPath != m_loc.pathInfo.foundPath
+ || sourceLocation.line != m_loc.line
+ || sourceLocation.column < m_loc.column)
+ {
+ // Special case: if we are in the same file, and within a small number
+ // of lines of the target location, then go ahead and output newlines
+ // to get us caught up.
+ enum { kSmallLineCount = 3 };
+ auto lineDiff = sourceLocation.line - m_loc.line;
+ if (sourceLocation.pathInfo.foundPath == m_loc.pathInfo.foundPath
+ && sourceLocation.line > m_loc.line
+ && lineDiff <= kSmallLineCount)
+ {
+ for (int ii = 0; ii < lineDiff; ++ii)
+ {
+ emit("\n");
+ }
+ SLANG_RELEASE_ASSERT(sourceLocation.line == m_loc.line);
+ }
+ else
+ {
+ // Go ahead and output a `#line` directive to get us caught up
+ _emitLineDirectiveAndUpdateSourceLocation(sourceLocation);
+ }
+ }
+}
+
+void SourceStream::_emitLineDirective(const HumaneSourceLoc& sourceLocation)
+{
+ emitRawText("\n#line ");
+
+ char buffer[16];
+ sprintf(buffer, "%llu", (unsigned long long)sourceLocation.line);
+ emitRawText(buffer);
+
+ // Only emit the path part of a `#line` directive if needed
+ if (sourceLocation.pathInfo.foundPath != m_loc.pathInfo.foundPath)
+ {
+ emitRawText(" ");
+
+ auto mode = getLineDirectiveMode();
+ switch (mode)
+ {
+ default:
+ case LineDirectiveMode::None:
+ SLANG_UNEXPECTED("should not be trying to emit '#line' directive");
+ return;
+ case LineDirectiveMode::GLSL:
+ {
+ auto path = sourceLocation.pathInfo.foundPath;
+
+ // GLSL doesn't support the traditional form of a `#line` directive without
+ // an extension. Rather than depend on that extension we will output
+ // a directive in the traditional GLSL fashion.
+ //
+ // TODO: Add some kind of configuration where we require the appropriate
+ // extension and then emit a traditional line directive.
+
+ int id = 0;
+ if (!m_mapGLSLSourcePathToID.TryGetValue(path, id))
+ {
+ id = m_glslSourceIDCount++;
+ m_mapGLSLSourcePathToID.Add(path, id);
+ }
+
+ sprintf(buffer, "%d", id);
+ emitRawText(buffer);
+ break;
+ }
+ case LineDirectiveMode::Default:
+ case LineDirectiveMode::Standard:
+ {
+ // The simple case is to emit the path for the current source
+ // location. We need to be a little bit careful with this,
+ // because the path might include backslash characters if we
+ // are on Windows, and we want to canonicalize those over
+ // to forward slashes.
+ //
+ // TODO: Canonicalization like this should be done centrally
+ // in a module that tracks source files.
+
+ emitRawText("\"");
+ const auto& path = sourceLocation.pathInfo.foundPath;
+ for (auto c : path)
+ {
+ char charBuffer[] = { c, 0 };
+ switch (c)
+ {
+ default:
+ emitRawText(charBuffer);
+ break;
+
+ // The incoming file path might use `/` and/or `\\` as
+ // a directory separator. We want to canonicalize this.
+ //
+ // TODO: should probably canonicalize paths to not use backslash somewhere else
+ // in the compilation pipeline...
+ case '\\':
+ emitRawText("/");
+ break;
+ }
+ }
+ emitRawText("\"");
+ break;
+ }
+ }
+ }
+
+ emitRawText("\n");
+}
+
+} // namespace Slang
diff --git a/source/slang/slang-source-stream.h b/source/slang/slang-source-stream.h
new file mode 100644
index 000000000..8dcd29c8d
--- /dev/null
+++ b/source/slang/slang-source-stream.h
@@ -0,0 +1,113 @@
+// slang-source-stream.h
+#ifndef SLANG_SOURCE_STREAM_H_INCLUDED
+#define SLANG_SOURCE_STREAM_H_INCLUDED
+
+#include "../core/basic.h"
+
+#include "compiler.h"
+
+namespace Slang
+{
+
+/* Class that encapsulates a stream of source. Facilities provided...
+
+* Management of the buffer that holds the source content as it is constructed
+* output line directives
+ + Supports GLSL as well as C/CPP/HLSL style directives
+* Support for line indention */
+class SourceStream
+{
+public:
+ /// Emits without span without any extra processing
+ void emitRawTextSpan(char const* textBegin, char const* textEnd);
+ void emitRawText(char const* text);
+
+ /// Emit different types into the stream
+ void emit(char const* textBegin, char const* textEnd);
+ void emit(char const* text);
+ void emit(const String& text);
+ void emit(const UnownedStringSlice& text);
+ void emit(Name* name);
+ void emit(const NameLoc& nameAndLoc);
+ void emit(IntegerLiteralValue value);
+ void emit(UInt value);
+ void emit(int value);
+ void emit(double value);
+
+ /// Emit names (doing so can also advance to a new source location)
+ void emitName(const NameLoc& nameAndLoc);
+ void emitName(Name* name, const SourceLoc& loc);
+ void emitName(Name* name);
+
+ /// Indent the text
+ void indent();
+ /// Dedent (the opposite of indenting) the text
+ void dedent();
+
+ /// Move the current source location to that specified
+ void advanceToSourceLocation(const SourceLoc& sourceLocation);
+ void advanceToSourceLocation(const HumaneSourceLoc& sourceLocation);
+
+ /// Get the content as a string
+ String getContent() { return m_builder.ProduceString(); }
+ /// Clear the content
+ void clearContent() { m_builder.Clear(); }
+ /// Get the content as a string and clear the internal representation
+ String getContentAndClear();
+
+ /// Get the line directive mode used
+ LineDirectiveMode getLineDirectiveMode() const { return m_lineDirectiveMode; }
+ /// Get the source manager user
+ SourceManager* getSourceManager() const { return m_sourceManager; }
+
+ /// Ctor
+ SourceStream(SourceManager* sourceManager, LineDirectiveMode lineDirectiveMode);
+
+protected:
+ void _emitTextSpan(char const* textBegin, char const* textEnd);
+ void _flushSourceLocationChange();
+
+ // Emit a `#line` directive to the output, and also
+ // ensure that source location tracking information
+ // is correct based on the directive we just output.
+ void _emitLineDirectiveAndUpdateSourceLocation(const HumaneSourceLoc& sourceLocation);
+
+ void _emitLineDirectiveIfNeeded(const HumaneSourceLoc& sourceLocation);
+
+ // Emit a `#line` directive to the output.
+ // Doesn't update state of source-location tracking.
+ void _emitLineDirective(const HumaneSourceLoc& sourceLocation);
+
+ // The string of code we've built so far.
+ // TODO(JS): We could store the text in chunks, and then only sew together into one buffer
+ // when we are done. Doing so would not require copies/reallocs until the full buffer has been
+ // produced. A downside to doing this is that it won't be so simple to debug by trying to
+ // look at the current contents of the buffer
+ StringBuilder m_builder;
+
+ // Current source position for tracking purposes...
+ HumaneSourceLoc m_loc;
+ HumaneSourceLoc m_nextSourceLocation;
+ bool m_needToUpdateSourceLocation = false;
+
+ // Are we at the start of a line, so that we should indent
+ // before writing any other text?
+ bool m_isAtStartOfLine = true;
+
+ // How far are we indented?
+ Int m_indentLevel = 0;
+
+ SourceManager* m_sourceManager = nullptr;
+
+ // For GLSL output, we can't emit traditional `#line` directives
+ // with a file path in them, so we maintain a map that associates
+ // each path with a unique integer, and then we output those
+ // instead.
+ Dictionary<String, int> m_mapGLSLSourcePathToID;
+ int m_glslSourceIDCount = 0;
+
+ LineDirectiveMode m_lineDirectiveMode;
+};
+
+}
+#endif
diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj
index 128b0b481..f85aad7e0 100644
--- a/source/slang/slang.vcxproj
+++ b/source/slang/slang.vcxproj
@@ -218,7 +218,13 @@
<ClInclude Include="profile-defs.h" />
<ClInclude Include="profile.h" />
<ClInclude Include="reflection.h" />
+ <ClInclude Include="slang-c-like-source-emitter.h" />
+ <ClInclude Include="slang-emit-context.h" />
+ <ClInclude Include="slang-emit-precedence.h" />
+ <ClInclude Include="slang-extension-usage-tracker.h" />
<ClInclude Include="slang-file-system.h" />
+ <ClInclude Include="slang-mangled-lexer.h" />
+ <ClInclude Include="slang-source-stream.h" />
<ClInclude Include="source-loc.h" />
<ClInclude Include="stmt-defs.h" />
<ClInclude Include="syntax-base-defs.h" />
@@ -271,7 +277,13 @@
<ClCompile Include="preprocessor.cpp" />
<ClCompile Include="profile.cpp" />
<ClCompile Include="reflection.cpp" />
+ <ClCompile Include="slang-c-like-source-emitter.cpp" />
+ <ClCompile Include="slang-emit-context.cpp" />
+ <ClCompile Include="slang-emit-precedence.cpp" />
+ <ClCompile Include="slang-extension-usage-tracker.cpp" />
<ClCompile Include="slang-file-system.cpp" />
+ <ClCompile Include="slang-mangled-lexer.cpp" />
+ <ClCompile Include="slang-source-stream.cpp" />
<ClCompile Include="slang-stdlib.cpp" />
<ClCompile Include="slang.cpp" />
<ClCompile Include="source-loc.cpp" />
diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters
index b88fbcc25..f08395da7 100644
--- a/source/slang/slang.vcxproj.filters
+++ b/source/slang/slang.vcxproj.filters
@@ -153,9 +153,27 @@
<ClInclude Include="reflection.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="slang-c-like-source-emitter.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="slang-emit-context.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="slang-emit-precedence.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="slang-extension-usage-tracker.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="slang-file-system.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="slang-mangled-lexer.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="slang-source-stream.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="source-loc.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -308,9 +326,27 @@
<ClCompile Include="reflection.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="slang-c-like-source-emitter.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="slang-emit-context.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="slang-emit-precedence.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="slang-extension-usage-tracker.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="slang-file-system.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="slang-mangled-lexer.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="slang-source-stream.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="slang-stdlib.cpp">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/source/slang/type-layout.cpp b/source/slang/type-layout.cpp
index ba6085e0a..92f7d6af3 100644
--- a/source/slang/type-layout.cpp
+++ b/source/slang/type-layout.cpp
@@ -814,6 +814,19 @@ LayoutRulesFamilyImpl* getDefaultLayoutRulesFamilyForTarget(TargetRequest* targe
case CodeGenTarget::SPIRVAssembly:
return &kGLSLLayoutRulesFamilyImpl;
+
+ case CodeGenTarget::CPPSource:
+ case CodeGenTarget::CSource:
+ {
+ // We just need to decide here what style of layout is appropriate, in terms of memory
+ // and binding. That in terms of the actual binding that will be injected into functions
+ // in the form of a BindContext. For now we'll go with HLSL layout -
+ // that we may want to rethink that with the use of arrays and binding VK style binding might be
+ // more appropriate in some ways.
+
+ return &kHLSLLayoutRulesFamilyImpl;
+ }
+
default:
return nullptr;
}