summaryrefslogtreecommitdiffstats
path: root/source/slang/ir.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/slang/ir.cpp')
-rw-r--r--source/slang/ir.cpp595
1 files changed, 591 insertions, 4 deletions
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);
+ }
+ }
+
+
}