summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2017-10-06 14:31:42 -0700
committerGitHub <noreply@github.com>2017-10-06 14:31:42 -0700
commitfaf26afdcfd343c79517f56f215bf8dcd5e0b992 (patch)
tree070bbc0acb2ed5c9a5e522ea522fbcfdc77af78c
parent3693bff4a2b822a2b29ac563d464d1012a3a3eda (diff)
Perform some transformations on IR to legalize for GLSL (#200)
When outputting GLSL from a Slang or HLSL entry point, we need to translate any parameters or results of an entry-point function into global declarations of `in` or `out` parameters, as needed by GLSL. This change adds these transformations at the IR level, so that they don't need to complicate the emit logic. More detailed changes: - Make `render0` test use IR. It passes out of the box. - Fix test runner to not always dump diffs on failures I accidentally initialized an option to `true` instead of `false` when working on debugging the Travis CI failures. - Special-case output for component-wise multiplication to handle GLSL `matrixCompMul()` - Handle GLSL vs. HLSL output for calls to `mul()` - Output proper `layout(std140)` on GLSL constant buffer declarations - Require appropriate GLSL extension when emitting explicit `layout(offset = ...)` on constant buffer members - TODO: Need to avoid requiring this extension in cases where the offsets are what would be computed anyway. Realistically, should probably be emitting code with explicit padding, etc. to guarantee layouts. - Add an IR-based pass to translate entry point functions by eliminating their input/output parameters and replacing them with global variables. - Demangle names when calling target intrinsics The lowering to the IR will turn a call like `sin(foo)` into a call to a function declaration with a mangled name like `_S3sin...`. This works fine when the user is calling their own functions, since the name mangling will apply to both the definition and use sites, but for builtin functions it obviously isn't what we want. This change makes it so that we demangle the name of an instrinsic function just enough so that we can extract the original simple name, and make a call using that. These changes do nor provide 100% of what we need when translating to GLSL, so the `cross-compile-entry-point` test *still* hasn't been flipped over to use the IR (even though that is the test case I've been using to develop these changes).
-rw-r--r--source/core/slang-string.h9
-rw-r--r--source/slang/bytecode.cpp4
-rw-r--r--source/slang/emit.cpp298
-rw-r--r--source/slang/ir-insts.h2
-rw-r--r--source/slang/ir.cpp595
-rw-r--r--source/slang/ir.h63
-rw-r--r--source/slang/lower-to-ir.cpp9
-rw-r--r--tests/render/render0.hlsl2
-rw-r--r--tools/slang-test/main.cpp2
9 files changed, 946 insertions, 38 deletions
diff --git a/source/core/slang-string.h b/source/core/slang-string.h
index 23ab54c23..3f2b85d28 100644
--- a/source/core/slang-string.h
+++ b/source/core/slang-string.h
@@ -134,6 +134,15 @@ namespace Slang
struct UnownedStringSlice
{
public:
+ UnownedStringSlice()
+ : beginData(0)
+ , endData(0)
+ {}
+
+ UnownedStringSlice(char const* b, char const* e)
+ : beginData(b)
+ , endData(e)
+ {}
char const* begin() const
{
diff --git a/source/slang/bytecode.cpp b/source/slang/bytecode.cpp
index 854d9bf34..8d8d609b9 100644
--- a/source/slang/bytecode.cpp
+++ b/source/slang/bytecode.cpp
@@ -956,7 +956,7 @@ BytecodeGenerationPtr<BCModule> generateBytecodeForModule(
// for the module, where the registers represent the
// values being computed at the global scope.
UInt symbolCount = 0;
- for( auto gv : irModule->globalValues )
+ for( auto gv = irModule->getFirstGlobalValue(); gv; gv = gv->getNextValue() )
{
Int globalID = Int(symbolCount++);
@@ -975,7 +975,7 @@ BytecodeGenerationPtr<BCModule> generateBytecodeForModule(
bcModule->symbolCount = symbolCount;
bcModule->symbols = bcSymbols;
- for( auto gv : irModule->globalValues )
+ for( auto gv = irModule->getFirstGlobalValue(); gv; gv = gv->getNextValue() )
{
UInt symbolIndex = *context->mapInstToLocalID.TryGetValue(gv);
diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp
index 2d42a79ed..938d54d36 100644
--- a/source/slang/emit.cpp
+++ b/source/slang/emit.cpp
@@ -460,6 +460,12 @@ struct EmitVisitor
Emit(text.begin(), text.end());
}
+ void emit(UnownedStringSlice const& text)
+ {
+ Emit(text.begin(), text.end());
+ }
+
+
void emit(Name* name)
{
emit(getText(name));
@@ -3546,6 +3552,14 @@ struct EmitVisitor
switch(info.kind)
{
case LayoutResourceKind::Uniform:
+ // Explicit offsets require a GLSL extension.
+ //
+ // TODO: We really need to fix this so that we
+ // only output an explicit offset for things
+ // that are layed out differently than they
+ // would normally be...
+ requireGLSLExtension("GL_ARB_enhanced_layouts");
+
Emit("layout(offset = ");
Emit(info.index);
Emit(")\n");
@@ -4466,6 +4480,165 @@ emitDeclImpl(decl, nullptr);
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");
+ }
+
+ int readCount()
+ {
+ int c = peek();
+ if(!isDigit(c))
+ {
+ SLANG_UNEXPECTED("bad name mangling");
+ return 0;
+ }
+ get();
+
+ if(c == '0')
+ return 0;
+
+ int count = 0;
+ for(;;)
+ {
+ count = count*10 + c - '0';
+ c = peek();
+ if(!isDigit(c))
+ return count;
+
+ get();
+ }
+ }
+
+ UnownedStringSlice readSimpleName()
+ {
+ UnownedStringSlice result;
+ for(;;)
+ {
+ int c = peek();
+ if(!isDigit(c))
+ return result;
+
+ // Read the length part
+ int count = readCount();
+ if(count > (end_ - cursor_))
+ {
+ SLANG_UNEXPECTED("bad name mangling");
+ return result;
+ }
+
+ result = UnownedStringSlice(cursor_, cursor_ + count);
+ cursor_ += count;
+ }
+ }
+ };
+
+ void emitIntrinsicCallExpr(
+ EmitContext* context,
+ IRCall* inst,
+ IRFunc* func)
+ {
+ // TODO: we need to inspect the mangled name,
+ // and construct a suitable expression from it...
+
+ UnmangleContext um(func->mangledName);
+
+ um.startUnmangling();
+
+ auto name = um.readSimpleName();
+
+ // TODO: need to detect if name represents
+ // a member function, etc.
+
+ emit(name);
+ emit("(");
+ UInt argCount = inst->getArgCount();
+ for( UInt aa = 1; aa < argCount; ++aa )
+ {
+ if(aa != 1) emit(", ");
+ emitIROperand(context, inst->getArg(aa));
+ }
+ emit(")");
+ }
+
+ void emitIRCallExpr(
+ EmitContext* context,
+ IRCall* inst)
+ {
+ // We want to detect any call to an intrinsic operation,
+ // that we can emit it directly without mangling, etc.
+ auto funcValue = inst->getArg(0);
+ if(auto irFunc = asTargetIntrinsic(context, funcValue))
+ {
+ emitIntrinsicCallExpr(context, inst, irFunc);
+ }
+ else
+ {
+ emitIROperand(context, funcValue);
+ emit("(");
+ UInt argCount = inst->getArgCount();
+ for( UInt aa = 1; aa < argCount; ++aa )
+ {
+ if(aa != 1) emit(", ");
+ emitIROperand(context, inst->getArg(aa));
+ }
+ emit(")");
+ }
+ }
+
void emitIRInstExpr(
EmitContext* context,
IRValue* value)
@@ -4544,19 +4717,20 @@ emitDeclImpl(decl, nullptr);
#define CASE(OPCODE, OP) \
case OPCODE: \
emitIROperand(context, inst->getArg(0)); \
- emit("" #OP " "); \
+ emit(" " #OP " "); \
emitIROperand(context, inst->getArg(1)); \
break
CASE(kIROp_Add, +);
CASE(kIROp_Sub, -);
- CASE(kIROp_Mul, *);
CASE(kIROp_Div, /);
CASE(kIROp_Mod, %);
CASE(kIROp_Lsh, <<);
CASE(kIROp_Rsh, >>);
+ // TODO: Need to pull out component-wise
+ // comparison cases for matrices/vectors
CASE(kIROp_Eql, ==);
CASE(kIROp_Neq, !=);
CASE(kIROp_Greater, >);
@@ -4573,6 +4747,30 @@ emitDeclImpl(decl, nullptr);
#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 is this a matrix product?
+ if(getTarget(context) == CodeGenTarget::GLSL
+ && inst->type->As<MatrixExpressionType>())
+ {
+ emit("matrixCompMult(");
+ emitIROperand(context, inst->getArg(0));
+ emit(", ");
+ emitIROperand(context, inst->getArg(1));
+ emit(")");
+ }
+ else
+ {
+ // Default handling is to just rely on infix
+ // `operator*`.
+ emitIROperand(context, inst->getArg(0));
+ emit(" * ");
+ emitIROperand(context, inst->getArg(1));
+ }
+ break;
+
case kIROp_Not:
{
if (inst->getType()->Equals(getSession()->getBoolType()))
@@ -4624,15 +4822,7 @@ emitDeclImpl(decl, nullptr);
case kIROp_Call:
{
- emitIROperand(context, inst->getArg(0));
- emit("(");
- UInt argCount = inst->getArgCount();
- for( UInt aa = 1; aa < argCount; ++aa )
- {
- if(aa != 1) emit(", ");
- emitIROperand(context, inst->getArg(aa));
- }
- emit(")");
+ emitIRCallExpr(context, (IRCall*)inst);
}
break;
@@ -4666,11 +4856,28 @@ emitDeclImpl(decl, nullptr);
case kIROp_Mul_Vector_Matrix:
case kIROp_Mul_Matrix_Vector:
case kIROp_Mul_Matrix_Matrix:
- emit("mul(");
- emitIROperand(context, inst->getArg(0));
- emit(", ");
- emitIROperand(context, inst->getArg(1));
- emit(")");
+ if(getTarget(context) == 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.
+ //
+ emitIROperand(context, inst->getArg(1));
+ emit(" * ");
+ emitIROperand(context, inst->getArg(0));
+ }
+ else
+ {
+ emit("mul(");
+ emitIROperand(context, inst->getArg(0));
+ emit(", ");
+ emitIROperand(context, inst->getArg(1));
+ emit(")");
+ }
break;
case kIROp_swizzle:
@@ -5201,6 +5408,7 @@ emitDeclImpl(decl, nullptr);
return nullptr;
}
+#if 0
void emitGLSLEntryPointFunc(
EmitContext* context,
IRFunc* func)
@@ -5274,6 +5482,7 @@ emitDeclImpl(decl, nullptr);
emit("}\n");
}
+#endif
bool isEntryPoint(IRFunc* func)
{
@@ -5296,10 +5505,31 @@ emitDeclImpl(decl, nullptr);
return !isDefinition(func);
}
+ // Check whether a given value names a target intrinsic,
+ // and return the IR function representing the instrinsic
+ // if it does.
+ IRFunc* asTargetIntrinsic(
+ EmitContext* ctxt,
+ IRValue* value)
+ {
+ if(!value)
+ return nullptr;
+
+ if(value->op != kIROp_Func)
+ return nullptr;
+
+ IRFunc* func = (IRFunc*) value;
+ if(!isTargetIntrinsic(ctxt, func))
+ return nullptr;
+
+ return func;
+ }
+
void emitIRFunc(
EmitContext* context,
IRFunc* func)
{
+#if 0
if( getTarget(context) == CodeGenTarget::GLSL
&& isEntryPoint(func) )
{
@@ -5312,7 +5542,9 @@ emitDeclImpl(decl, nullptr);
emitGLSLEntryPointFunc(context, func);
}
- else if(!isDefinition(func))
+ else
+#endif
+ if(!isDefinition(func))
{
// This is just a function declaration,
// and so we want to emit it as such.
@@ -5431,6 +5663,14 @@ emitDeclImpl(decl, nullptr);
emit("uniform ");
break;
+ case LayoutResourceKind::VertexInput:
+ emit("in ");
+ break;
+
+ case LayoutResourceKind::FragmentOutput:
+ emit("out ");
+ break;
+
default:
continue;
}
@@ -5520,7 +5760,16 @@ emitDeclImpl(decl, nullptr);
emitGLSLLayoutQualifier(*info);
}
- emit("uniform ");
+ if(type->As<GLSLShaderStorageBufferType>())
+ {
+ emit("layout(std430) buffer ");
+ }
+ // TODO: what to do with HLSL `tbuffer` style buffers?
+ else
+ {
+ emit("layout(std140) uniform ");
+ }
+
emit(getIRName(varDecl));
emit("\n{\n");
@@ -5852,7 +6101,7 @@ emitDeclImpl(decl, nullptr);
EmitContext* context,
IRModule* module)
{
- for (auto gv : module->globalValues)
+ for( auto gv = module->getFirstGlobalValue(); gv; gv = gv->getNextValue() )
{
emitIRUsedTypesForValue(context, gv);
}
@@ -5864,7 +6113,7 @@ emitDeclImpl(decl, nullptr);
{
emitIRUsedTypesForModule(context, module);
- for (auto gv : module->globalValues)
+ for( auto gv = module->getFirstGlobalValue(); gv; gv = gv->getNextValue() )
{
emitIRGlobalInst(context, gv);
}
@@ -5906,6 +6155,7 @@ String emitEntryPoint(
CodeGenTarget target)
{
auto translationUnit = entryPoint->getTranslationUnit();
+ auto session = entryPoint->compileRequest->mSession;
SharedEmitContext sharedContext;
sharedContext.target = target;
@@ -6007,6 +6257,14 @@ String emitEntryPoint(
// need to link in (and then inline) target-specific implementations
// for the library functions that the user called.
+ switch(target)
+ {
+ case CodeGenTarget::GLSL:
+ legalizeEntryPointsForGLSL(session, lowered);
+ break;
+ }
+
+
// TODO: do we want to emit directly from IR, or translate the
// IR back into AST for emission?
diff --git a/source/slang/ir-insts.h b/source/slang/ir-insts.h
index 3a7bbb503..6f6af13cb 100644
--- a/source/slang/ir-insts.h
+++ b/source/slang/ir-insts.h
@@ -11,6 +11,7 @@
#include "compiler.h"
#include "ir.h"
#include "syntax.h"
+#include "type-layout.h"
namespace Slang {
@@ -40,6 +41,7 @@ struct IREntryPointDecoration : IRDecoration
enum { kDecorationOp = kIRDecorationOp_EntryPoint };
Profile profile;
+ EntryPointLayout* layout;
};
// Associates a compute-shader entry point function
diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp
index ee28227e3..a79dc3992 100644
--- a/source/slang/ir.cpp
+++ b/source/slang/ir.cpp
@@ -600,26 +600,172 @@ namespace Slang
IRModule* IRBuilder::createModule()
{
- return new IRModule();
+ auto module = new IRModule();
+ module->session = getSession();
+ return module;
}
+ void IRGlobalValue::insertBefore(IRGlobalValue* other)
+ {
+ assert(other);
+ insertBefore(other, other->parentModule);
+ }
+
+ void IRGlobalValue::insertBefore(IRGlobalValue* other, IRModule* module)
+ {
+ assert(other || module);
+
+ if(!other) other = module->firstGlobalValue;
+ if(!module) module = other->parentModule;
+
+ assert(module);
+
+ auto nn = other;
+ auto pp = other ? other->prevGlobalValue : nullptr;
+
+ if(pp)
+ {
+ pp->nextGlobalValue = this;
+ }
+ else
+ {
+ module->firstGlobalValue = this;
+ }
+
+ if(nn)
+ {
+ nn->prevGlobalValue = this;
+ }
+ else
+ {
+ module->lastGlobalValue = this;
+ }
+
+ this->prevGlobalValue = pp;
+ this->nextGlobalValue = nn;
+ this->parentModule = module;
+ }
+
+ void IRGlobalValue::insertAtStart(IRModule* module)
+ {
+ insertBefore(module->firstGlobalValue, module);
+ }
+
+ void IRGlobalValue::insertAfter(IRGlobalValue* other)
+ {
+ assert(other);
+ insertAfter(other, other->parentModule);
+ }
+
+ void IRGlobalValue::insertAfter(IRGlobalValue* other, IRModule* module)
+ {
+ assert(other || module);
+
+ if(!other) other = module->lastGlobalValue;
+ if(!module) module = other->parentModule;
+
+ assert(module);
+
+ auto pp = other;
+ auto nn = other ? other->nextGlobalValue : nullptr;
+
+ if(pp)
+ {
+ pp->nextGlobalValue = this;
+ }
+ else
+ {
+ module->firstGlobalValue = this;
+ }
+
+ if(nn)
+ {
+ nn->prevGlobalValue = this;
+ }
+ else
+ {
+ module->lastGlobalValue = this;
+ }
+
+ this->prevGlobalValue = pp;
+ this->nextGlobalValue = nn;
+ this->parentModule = module;
+ }
+
+ void IRGlobalValue::insertAtEnd(IRModule* module)
+ {
+ assert(module);
+ insertAfter(module->lastGlobalValue, module);
+ }
+
+ void IRGlobalValue::removeFromParent()
+ {
+ auto module = parentModule;
+ if(!module)
+ return;
+
+ auto pp = this->prevGlobalValue;
+ auto nn = this->nextGlobalValue;
+
+ if(pp)
+ {
+ pp->nextGlobalValue = nn;
+ }
+ else
+ {
+ module->firstGlobalValue = nn;
+ }
+
+ if( nn )
+ {
+ nn->prevGlobalValue = pp;
+ }
+ else
+ {
+ module->lastGlobalValue = pp;
+ }
+ }
+
+ void IRGlobalValue::moveToEnd()
+ {
+ auto module = parentModule;
+ removeFromParent();
+ insertAtEnd(module);
+ }
+
+
+
+ void addGlobalValue(
+ IRModule* module,
+ IRGlobalValue* value)
+ {
+ if(!module)
+ return;
+
+ value->parentModule = module;
+ value->insertAfter(module->lastGlobalValue, module);
+ }
IRFunc* IRBuilder::createFunc()
{
- return createValue<IRFunc>(
+ IRFunc* func = createValue<IRFunc>(
this,
kIROp_Func,
nullptr);
+ addGlobalValue(getModule(), func);
+ return func;
}
IRGlobalVar* IRBuilder::createGlobalVar(
IRType* valueType)
{
auto ptrType = getSession()->getPtrType(valueType);
- return createValue<IRGlobalVar>(
+ IRGlobalVar* globalVar = createValue<IRGlobalVar>(
this,
kIROp_global_var,
ptrType);
+ addGlobalValue(getModule(), globalVar);
+ return globalVar;
}
IRBlock* IRBuilder::createBlock()
@@ -1646,7 +1792,7 @@ namespace Slang
IRDumpContext* context,
IRModule* module)
{
- for (auto gv : module->globalValues)
+ for( auto gv = module->getFirstGlobalValue(); gv; gv = gv->getNextValue() )
{
dumpIRGlobalValue(context, gv);
}
@@ -1676,4 +1822,445 @@ namespace Slang
}
+ //
+ //
+ //
+
+ void IRValue::replaceUsesWith(IRValue* other)
+ {
+ // We will walk through the list of uses for the current
+ // instruction, and make them point to the other inst.
+ IRUse* ff = firstUse;
+
+ // No uses? Nothing to do.
+ if(!ff)
+ return;
+
+ IRUse* uu = ff;
+ for(;;)
+ {
+ // The uses had better all be uses of this
+ // instruction, or invariants are broken.
+ assert(uu->usedValue == this);
+
+ // Swap this use over to use the other value.
+ uu->usedValue = other;
+
+ // Try to move to the next use, but bail
+ // out if we are at the last one.
+ IRUse* next = uu->nextUse;
+ if( !next )
+ break;
+
+ uu = next;
+ }
+
+ // We are at the last use (and there must
+ // be at least one, because we handled
+ // the case of an empty list earlier).
+ assert(uu);
+
+ // Our job at this point is to splice
+ // our list of uses onto the other
+ // value's uses.
+ //
+ // If the value already had uses, then
+ // we need to patch our new list onto
+ // the front.
+ if( auto nn = other->firstUse )
+ {
+ uu->nextUse = nn;
+ nn->prevLink = &uu->nextUse;
+ }
+
+ // No matter what, our list of
+ // uses will become the start
+ // of the list of uses for
+ // `other`
+ other->firstUse = ff;
+ ff->prevLink = &other->firstUse;
+
+ // And `this` will have no uses any more.
+ this->firstUse = nullptr;
+ }
+
+ void IRValue::deallocate()
+ {
+ // Run destructor to be sure...
+ this->~IRValue();
+
+ // And then free the memory
+ free((void*) this);
+ }
+
+ // Insert this instruction into the same basic block
+ // as `other`, right before it.
+ void IRInst::insertBefore(IRInst* other)
+ {
+ // Make sure this instruction has been removed from any previous parent
+ this->removeFromParent();
+
+ auto bb = other->parentBlock;
+ assert(bb);
+
+ auto pp = other->prevInst;
+ if( pp )
+ {
+ pp->nextInst = this;
+ }
+ else
+ {
+ bb->firstInst = this;
+ }
+
+ this->prevInst = pp;
+ this->nextInst = other;
+ this->parentBlock = bb;
+
+ other->prevInst = this;
+ }
+
+ // Remove this instruction from its parent block,
+ // and then destroy it (it had better have no uses!)
+ void IRInst::removeFromParent()
+ {
+ // If we don't currently have a parent, then
+ // we are doing fine.
+ if(!parentBlock)
+ return;
+
+ auto bb = parentBlock;
+ auto pp = prevInst;
+ auto nn = nextInst;
+
+ if(pp)
+ {
+ SLANG_ASSERT(pp->parentBlock == bb);
+ pp->nextInst = nn;
+ }
+ else
+ {
+ bb->firstInst = nn;
+ }
+
+ if(nn)
+ {
+ SLANG_ASSERT(nn->parentBlock == bb);
+ nn->prevInst = pp;
+ }
+ else
+ {
+ bb->lastInst = pp;
+ }
+
+ prevInst = nullptr;
+ nextInst = nullptr;
+ parentBlock = nullptr;
+ }
+
+ // Remove this instruction from its parent block,
+ // and then destroy it (it had better have no uses!)
+ void IRInst::removeAndDeallocate()
+ {
+ removeFromParent();
+ deallocate();
+ }
+
+
+ //
+ // Legalization of entry points for GLSL:
+ //
+
+ IRGlobalVar* addGlobalVariable(
+ IRModule* module,
+ Type* valueType)
+ {
+ auto session = module->session;
+
+ SharedIRBuilder shared;
+ shared.module = module;
+ shared.session = session;
+
+ IRBuilder builder;
+ builder.shared = &shared;
+
+ RefPtr<PtrType> ptrType = session->getPtrType(valueType);
+
+ return builder.createGlobalVar(valueType);
+ }
+
+ void moveValueBefore(
+ IRGlobalValue* valueToMove,
+ IRGlobalValue* placeBefore)
+ {
+ valueToMove->removeFromParent();
+ valueToMove->insertBefore(placeBefore);
+ }
+
+ void legalizeEntryPointForGLSL(
+ Session* session,
+ IRFunc* func,
+ IREntryPointDecoration* entryPointInfo)
+ {
+ auto module = func->parentModule;
+
+ auto entryPointLayout = entryPointInfo->layout;
+
+ // We require that the entry-point function has no uses,
+ // because otherwise we'd invalidate the signature
+ // at all existing call sites.
+ //
+ // TODO: the right thing to do here is to split any
+ // function that both gets called as an entry point
+ // and as an ordinary function.
+ assert(!func->firstUse);
+
+ // We create a dummy IR builder, since some of
+ // the functions require it.
+ //
+ // TODO: make some of these free functions...
+ //
+ SharedIRBuilder shared;
+ shared.module = module;
+ shared.session = session;
+ IRBuilder builder;
+ builder.shared = &shared;
+
+ // We will start by looking at the return type of the
+ // function, because that will enable us to do an
+ // early-out check to avoid more work.
+ //
+ // Specifically, we need to check if the function has
+ // a `void` return type, because there is no work
+ // to be done on its return value in that case.
+ auto resultType = func->getResultType();
+ if( resultType->Equals(session->getVoidType()) )
+ {
+ // In this case, the function doesn't return a value
+ // so we don't need to transform its `return` sites.
+ //
+ // We can also use this opportunity to quickly
+ // check if the function has any parameters, and if
+ // it doesn't use the chance to bail out immediately.
+ if( func->getParamCount() == 0 )
+ {
+ // This function is already legal for GLSL
+ // (at least in terms of parameter/result signature),
+ // so we won't bother doing anything at all.
+ return;
+ }
+
+ // If the function does have parameters, then we need
+ // to let the logic later in this function handle them.
+ }
+ else
+ {
+ // Function returns a value, so we need
+ // to introduce a new global variable
+ // to hold that value, and then replace
+ // any `returnVal` instructions with
+ // code to write to that variable.
+
+ auto resultVariable = addGlobalVariable(module, resultType);
+ moveValueBefore(resultVariable, func);
+
+ // We need to transfer layout information from the entry point
+ // down to the variable:
+ builder.addLayoutDecoration(resultVariable, entryPointLayout->resultLayout);
+
+ for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() )
+ {
+ for( auto ii = bb->getFirstInst(); ii; ii = ii->nextInst )
+ {
+ if(ii->op != kIROp_ReturnVal)
+ continue;
+
+ IRReturnVal* returnInst = (IRReturnVal*) ii;
+ IRValue* resultValue = returnInst->getVal();
+
+
+
+ // `store <resultVariable> <resultValue>`
+ IRStore* storeInst = createInst<IRStore>(
+ &builder,
+ kIROp_Store,
+ nullptr,
+ resultVariable,
+ resultValue);
+
+ // `returnVoid`
+ IRReturnVoid* returnVoid = createInst<IRReturnVoid>(
+ &builder,
+ kIROp_ReturnVoid,
+ nullptr);
+
+ // Put the two new instructions before the old one
+ storeInst->insertBefore(returnInst);
+ returnVoid->insertBefore(returnInst);
+
+ // and then remove the old one.
+ returnInst->removeAndDeallocate();
+
+ // Make sure to resume our iteration at an
+ // appropriate instruciton, since we deleted
+ // the one we had been using.
+ ii = returnVoid;
+ }
+ }
+ }
+
+ // Next we will walk through any parameters of the entry-point function,
+ // and turn them into global variables.
+ if( auto firstBlock = func->getFirstBlock() )
+ {
+ IRInst* insertBeforeInst = firstBlock->getFirstInst();
+
+ UInt paramCounter = 0;
+ for( auto pp = firstBlock->getFirstParam(); pp; pp = pp->getNextParam() )
+ {
+ UInt paramIndex = paramCounter++;
+
+ // We assume that the entry-point layout includes information
+ // on each parameter, and that these arrays are kept aligned.
+ // Note that this means that any transformations that mess
+ // with function signatures will need to also update layout info...
+ //
+ assert(entryPointLayout->fields.Count() > paramIndex);
+ auto paramLayout = entryPointLayout->fields[paramIndex];
+
+ // We need to create a global variable that will replace the parameter.
+ // It seems superficially obvious that the variable should have
+ // the same type as the parameter.
+ // However, if the parameter was a pointer, in order to
+ // support `out` or `in out` parameter passing, we need
+ // to be sure to allocate a variable of the pointed-to
+ // type instead.
+ //
+ // We also need to replace uses of the parameter with
+ // uses of the variable, and the exact logic there
+ // will differ a bit between the pointer and non-pointer
+ // cases.
+ auto paramType = pp->getType();
+
+ // TODO: We need to distinguish any true pointers in the
+ // user's code from pointers that only exist for
+ // parameter-passing. This `PtrType` here should actually
+ // be `OutTypeBase`, but I'm not confident that all
+ // the other code is handling that correctly...
+ if(auto paramPtrType = paramType->As<PtrType>() )
+ {
+ // Okay, we have the more interesting case here,
+ // where the parameter was being passed by reference.
+ // This actually makes our life pretty easy, though,
+ // since we can simply replace any uses of the existing
+ // pointer with the global variable (since it will
+ // be a pointer to storage).
+
+ // We start by creating the global variable, using
+ // the pointed-to type:
+ auto valueType = paramPtrType->getValueType();
+ auto paramVariable = addGlobalVariable(module, paramType);
+ moveValueBefore(paramVariable, func);
+
+ // TODO: We need to special-case `in out` variables here,
+ // because they actually need to be lowered to *two*
+ // global variables, not just one. We then need
+ // to emit logic to initialize the output variable
+ // based on the input at the start of the entry point,
+ // and then use the output variable thereafter.
+ //
+ // TODO: Actually, I need to double-check that it is
+ // legal in GLSL to use shader input/output parameters
+ // as temporaries in general; if not then we'd need
+ // to introduce a temporary no matter what.
+
+ // Next we attach the layout information from the
+ // original parameter to the new global variable,
+ // so that we can lay it out correctly when generating
+ // target code:
+ builder.addLayoutDecoration(paramVariable, paramLayout);
+
+ // And finally, we go ahead and replace all the
+ // uses of the parameter (which was a pointer) with
+ // uses of the new global variable's address.
+ pp->replaceUsesWith(paramVariable);
+ }
+ else
+ {
+ // This is the "easy" case where the parameter wasn't
+ // being passed by reference. We start by just creating
+ // a variable of the appropriate type, and attaching
+ // the required layout information to it.
+ auto paramVariable = addGlobalVariable(module, paramType);
+ moveValueBefore(paramVariable, func);
+ builder.addLayoutDecoration(paramVariable, paramLayout);
+
+ // Next we need to replace uses of the parameter with
+ // references to the variable. We are going to do that
+ // somewhat naively, by simply loading the variable
+ // at the start.
+
+ IRInst* loadInst = builder.emitLoad(paramVariable);
+ loadInst->insertBefore(insertBeforeInst);
+
+ pp->replaceUsesWith(loadInst);
+ }
+ }
+
+ // At this point we should have eliminated all uses of the
+ // parameters of the entry block. Also, our control-flow
+ // rules mean that the entry block cannot be the target
+ // of any branches in the code, so there can't be
+ // any control-flow ops that try to match the parameter
+ // list.
+ //
+ // We can safely go through and destroy the parameters
+ // themselves, and then clear out the parameter list.
+ for( auto pp = firstBlock->getFirstParam(); pp; )
+ {
+ auto next = pp->getNextParam();
+ pp->deallocate();
+ pp = next;
+ }
+ firstBlock->firstParam = nullptr;
+ firstBlock->lastParam = nullptr;
+ }
+
+ // Finally, we need to patch up the type of the entry point,
+ // because it is no longer accurate.
+
+ auto voidFuncType = new FuncType();
+ voidFuncType->resultType = session->getVoidType();
+ func->type = voidFuncType;
+
+ // TODO: we should technically be constructing
+ // a new `EntryPointLayout` here to reflect
+ // the way that things have been moved around.
+ }
+
+ void legalizeEntryPointsForGLSL(
+ Session* session,
+ IRModule* module)
+ {
+ // We need to walk through all the global entry point
+ // declarations, and transform them to comply with
+ // GLSL rules.
+ for( auto globalValue = module->getFirstGlobalValue(); globalValue; globalValue = globalValue->getNextValue())
+ {
+ // Is the global value a function?
+ if(globalValue->op != kIROp_Func)
+ continue;
+ IRFunc* func = (IRFunc*) globalValue;
+
+ // Is the function an entry point?
+ IREntryPointDecoration* entryPointDecoration = func->findDecoration<IREntryPointDecoration>();
+ if(!entryPointDecoration)
+ continue;
+
+ // Okay, we need to legalize this one.
+ legalizeEntryPointForGLSL(session, func, entryPointDecoration);
+ }
+ }
+
+
}
diff --git a/source/slang/ir.h b/source/slang/ir.h
index 82684ae17..4fd165c33 100644
--- a/source/slang/ir.h
+++ b/source/slang/ir.h
@@ -15,6 +15,7 @@ class Decl;
class FuncType;
class Layout;
class Type;
+class Session;
struct IRFunc;
struct IRInst;
@@ -133,7 +134,7 @@ struct IRValue
Type* getType() { return type; }
// The linked list of decorations attached to this value
- IRDecoration* firstDecoration;
+ IRDecoration* firstDecoration = nullptr;
// Look up a decoration in the list of decorations
IRDecoration* findDecorationImpl(IRDecorationOp op);
@@ -144,9 +145,16 @@ struct IRValue
}
// The first use of this value (start of a linked list)
- IRUse* firstUse;
+ IRUse* firstUse = nullptr;
+ // Replace all uses of this value with `other`, so
+ // that this value will now have no uses.
+ void replaceUsesWith(IRValue* other);
+
+ // Free a value (which needs to have been removed
+ // from its parent, had its uses eliminated, etc.)
+ void deallocate();
};
// Instructions are values that can be executed,
@@ -180,6 +188,18 @@ struct IRInst : IRValue
{
return getArgs()[index].usedValue;
}
+
+ // Insert this instruction into the same basic block
+ // as `other`, right before it.
+ void insertBefore(IRInst* other);
+
+ // Remove this instruction from its parent block,
+ // but don't delete it, or replace uses.
+ void removeFromParent();
+
+ // Remove this instruction from its parent block,
+ // and then destroy it (it had better have no uses!)
+ void removeAndDeallocate();
};
typedef int64_t IRIntegerValue;
@@ -268,7 +288,27 @@ struct IRBlock : IRValue
typedef FuncType IRFuncType;
struct IRGlobalValue : IRValue
-{};
+{
+ IRModule* parentModule;
+
+ IRGlobalValue* nextGlobalValue;
+ IRGlobalValue* prevGlobalValue;
+
+ IRGlobalValue* getNextValue() { return nextGlobalValue; }
+ IRGlobalValue* getPrevValue() { return prevGlobalValue; }
+
+ void insertBefore(IRGlobalValue* other);
+ void insertBefore(IRGlobalValue* other, IRModule* module);
+ void insertAtStart(IRModule* module);
+
+ void insertAfter(IRGlobalValue* other);
+ void insertAfter(IRGlobalValue* other, IRModule* module);
+ void insertAtEnd(IRModule* module);
+
+ void removeFromParent();
+
+ void moveToEnd();
+};
// A function is a parent to zero or more blocks of instructions.
//
@@ -311,14 +351,16 @@ struct IRFunc : IRGlobalValue
// A module is a parent to functions, global variables, types, etc.
struct IRModule : RefObject
{
- // The designated entry-point function, if any
- IRFunc* entryPoint;
+ // The compilation session in use.
+ Session* session;
// A list of all the functions and other
// global values declared in this module.
- List<IRGlobalValue*> globalValues;
+ IRGlobalValue* firstGlobalValue = nullptr;
+ IRGlobalValue* lastGlobalValue = nullptr;
- // TODO: need a symbol of all the global variables too
+ IRGlobalValue* getFirstGlobalValue() { return firstGlobalValue; }
+ IRGlobalValue* getlastGlobalValue() { return lastGlobalValue; }
};
void printSlangIRAssembly(StringBuilder& builder, IRModule* module);
@@ -326,6 +368,13 @@ String getSlangIRAssembly(IRModule* module);
void dumpIR(IRModule* module);
+// IR transformations
+
+// Transform shader entry points so that they conform to GLSL rules.
+void legalizeEntryPointsForGLSL(
+ Session* session,
+ IRModule* module);
+
}
diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp
index 502d63320..b9307b007 100644
--- a/source/slang/lower-to-ir.cpp
+++ b/source/slang/lower-to-ir.cpp
@@ -2117,8 +2117,6 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
// TODO: need to handle global with initializer!
}
- getBuilder()->getModule()->globalValues.Add(irGlobal);
-
return globalVal;
}
@@ -2837,7 +2835,11 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
getBuilder()->addHighLevelDeclDecoration(irFunc, decl);
- getBuilder()->getModule()->globalValues.Add(irFunc);
+ // For convenience, ensure that any additional global
+ // values that were emitted while outputting the function
+ // body appear before the function itself in the list
+ // of global values.
+ irFunc->moveToEnd();
return LoweredValInfo::simple(irFunc);
}
@@ -2939,6 +2941,7 @@ static void lowerEntryPointToIR(
auto entryPointDecoration = builder->addDecoration<IREntryPointDecoration>(irFunc);
entryPointDecoration->profile = profile;
+ entryPointDecoration->layout = entryPointLayout;
// Attach layout information here.
builder->addLayoutDecoration(irFunc, entryPointLayout);
diff --git a/tests/render/render0.hlsl b/tests/render/render0.hlsl
index e6849fe60..833788ad1 100644
--- a/tests/render/render0.hlsl
+++ b/tests/render/render0.hlsl
@@ -1,4 +1,4 @@
-//TEST(smoke):COMPARE_HLSL_RENDER:
+//TEST(smoke):COMPARE_HLSL_RENDER:-xslang -use-ir
// Starting with a basic test for the ability to render stuff...
cbuffer Uniforms
diff --git a/tools/slang-test/main.cpp b/tools/slang-test/main.cpp
index eeb34b657..0bf2ff6af 100644
--- a/tools/slang-test/main.cpp
+++ b/tools/slang-test/main.cpp
@@ -54,7 +54,7 @@ struct Options
// Dump expected/actual output on failures, for debugging.
// This is especially intended for use in continuous
// integration builds.
- bool dumpOutputOnFailure = true;
+ bool dumpOutputOnFailure = false;
// kind of output to generate
OutputMode outputMode = kOutputMode_Default;