summaryrefslogtreecommitdiffstats
path: root/source/slang
diff options
context:
space:
mode:
Diffstat (limited to 'source/slang')
-rw-r--r--source/slang/emit.cpp114
-rw-r--r--source/slang/ir-inst-defs.h2
-rw-r--r--source/slang/ir-insts.h16
-rw-r--r--source/slang/ir-ssa.cpp848
-rw-r--r--source/slang/ir-ssa.h9
-rw-r--r--source/slang/ir.cpp261
-rw-r--r--source/slang/ir.h62
-rw-r--r--source/slang/lower-to-ir.cpp37
-rw-r--r--source/slang/slang.vcxproj2
-rw-r--r--source/slang/slang.vcxproj.filters2
-rw-r--r--source/slang/vm.cpp40
11 files changed, 1381 insertions, 12 deletions
diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp
index f9f24fcf6..eaabd520c 100644
--- a/source/slang/emit.cpp
+++ b/source/slang/emit.cpp
@@ -6074,6 +6074,14 @@ emitDeclImpl(decl, nullptr);
emit(";\n");
break;
+ case kIROp_undefined:
+ {
+ auto type = inst->getType();
+ emitIRType(ctx, type, getIRName(inst));
+ emit(";\n");
+ }
+ break;
+
case kIROp_Var:
{
auto ptrType = inst->getType();
@@ -6202,6 +6210,37 @@ emitDeclImpl(decl, nullptr);
}
}
+ // 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)
+ {
+ assert(!"not enough arguments for branch");
+ break;
+ }
+
+ IRValue* arg = args[argIndex].usedValue;
+
+ emitIROperand(ctx, pp);
+ emit(" = ");
+ emitIROperand(ctx, arg);
+ emit(";\n");
+ }
+ }
+
// We want to emit a range of code in the IR, represented
// by the blocks that are logically in the interval [begin, end)
// which we consider as a single-entry multiple-exit region.
@@ -6305,6 +6344,14 @@ emitDeclImpl(decl, nullptr);
auto breakBlock = t->getBreakBlock();
auto continueBlock = t->getContinueBlock();
+ UInt argCount = t->getArgCount();
+ static const UInt kFixedArgCount = 3;
+ emitPhiVarAssignments(
+ ctx,
+ argCount - kFixedArgCount,
+ t->getArgs() + kFixedArgCount,
+ targetBlock);
+
if (auto loopControlDecoration = t->findDecoration<IRLoopControlDecoration>())
{
switch (loopControlDecoration->mode)
@@ -6388,7 +6435,20 @@ emitDeclImpl(decl, nullptr);
break;
case kIROp_break:
- emit("break;\n");
+ {
+ auto t = (IRBreak*)terminator;
+ auto targetBlock = t->getTargetBlock();
+
+ UInt argCount = t->getArgCount();
+ static const UInt kFixedArgCount = 1;
+ emitPhiVarAssignments(
+ ctx,
+ argCount - kFixedArgCount,
+ t->getArgs() + kFixedArgCount,
+ targetBlock);
+
+ emit("break;\n");
+ }
return;
case kIROp_continue:
@@ -6405,6 +6465,15 @@ emitDeclImpl(decl, nullptr);
{
auto continueInst = (IRContinue*) terminator;
auto targetBlock = continueInst->getTargetBlock();
+
+ UInt argCount = continueInst->getArgCount();
+ static const UInt kFixedArgCount = 1;
+ emitPhiVarAssignments(
+ ctx,
+ argCount - kFixedArgCount,
+ continueInst->getArgs() + kFixedArgCount,
+ targetBlock);
+
IRBlock* loopHead = nullptr;
ctx->shared->irMapContinueTargetToLoopHead.TryGetValue(targetBlock, loopHead);
SLANG_ASSERT(loopHead);
@@ -6422,10 +6491,16 @@ emitDeclImpl(decl, nullptr);
auto t = (IRLoopTest*)terminator;
auto afterBlock = t->getTrueBlock();
+ auto exitBlock = t->getFalseBlock();
emit("if(");
emitIROperand(ctx, t->getCondition());
- emit(")\n{} else break;\n");
+ emit(")\n{} else {\n");
+ emitIRStmtsForBlocks(
+ ctx,
+ exitBlock,
+ nullptr);
+ emit("}\n");
// Continue with the block after the test
block = afterBlock;
@@ -6440,6 +6515,16 @@ emitDeclImpl(decl, nullptr);
// block, or a backward edge to the top
// of a loop.
auto t = (IRUnconditionalBranch*)terminator;
+ auto targetBlock = t->getTargetBlock();
+
+ UInt argCount = t->getArgCount();
+ static const UInt kFixedArgCount = 1;
+ emitPhiVarAssignments(
+ ctx,
+ argCount - kFixedArgCount,
+ t->getArgs() + kFixedArgCount,
+ targetBlock);
+
block = t->getTargetBlock();
}
break;
@@ -6758,6 +6843,27 @@ emitDeclImpl(decl, nullptr);
}
}
+ 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())
+ {
+ emitIRType(ctx, pp->getType(), getIRName(pp));
+ emit(";\n");
+ }
+ }
+ }
+
void emitIRSimpleFunc(
EmitContext* ctx,
IRFunc* func)
@@ -6816,6 +6922,10 @@ emitDeclImpl(decl, nullptr);
{
emit("\n{\n");
+ // HACK: forward-declare all the local variables needed for the
+ // prameters of non-entry blocks.
+ emitPhiVarDecls(ctx, func);
+
// Need to emit the operations in the blocks of the function
emitIRStmtsForBlocks(ctx, func->getFirstBlock(), nullptr);
diff --git a/source/slang/ir-inst-defs.h b/source/slang/ir-inst-defs.h
index fa02da683..38515e6f0 100644
--- a/source/slang/ir-inst-defs.h
+++ b/source/slang/ir-inst-defs.h
@@ -96,6 +96,8 @@ INST(IntLit, integer_constant, 0, 0)
INST(FloatLit, float_constant, 0, 0)
INST(decl_ref, decl_ref, 0, 0)
+INST(undefined, undefined, 0, 0)
+
INST(specialize, specialize, 2, 0)
INST(lookup_interface_method, lookup_interface_method, 2, 0)
INST(lookup_witness_table, lookup_witness_table, 2, 0)
diff --git a/source/slang/ir-insts.h b/source/slang/ir-insts.h
index 46a804a1c..266374545 100644
--- a/source/slang/ir-insts.h
+++ b/source/slang/ir-insts.h
@@ -326,6 +326,15 @@ struct IRWitnessTable : IRGlobalValue
IRValueList<IRWitnessTableEntry> entries;
};
+// An instruction that yields an undefined value.
+//
+// Note that we make this an instruction rather than a value,
+// so that we will be able to identify a variable that is
+// used when undefined.
+struct IRUndefined : IRInst
+{
+};
+
// Description of an instruction to be used for global value numbering
struct IRInstKey
{
@@ -394,6 +403,7 @@ struct IRBuilder
IRValue* getBoolValue(bool value);
IRValue* getIntValue(IRType* type, IRIntegerValue value);
IRValue* getFloatValue(IRType* type, IRFloatingPointValue value);
+
IRValue* getDeclRefVal(
DeclRefBase const& declRef);
IRValue* getTypeVal(IRType* type); // create an IR value that represents a type
@@ -443,6 +453,10 @@ struct IRBuilder
UInt argCount,
IRValue* const* args);
+ IRUndefined* emitUndefined(IRType* type);
+
+
+
IRModule* createModule();
IRFunc* createFunc();
@@ -458,6 +472,8 @@ struct IRBuilder
IRBlock* createBlock();
IRBlock* emitBlock();
+ IRParam* createParam(
+ IRType* type);
IRParam* emitParam(
IRType* type);
diff --git a/source/slang/ir-ssa.cpp b/source/slang/ir-ssa.cpp
new file mode 100644
index 000000000..366ecc279
--- /dev/null
+++ b/source/slang/ir-ssa.cpp
@@ -0,0 +1,848 @@
+// ir-ssa.cpp
+#include "ir-ssa.h"
+
+#include "ir.h"
+#include "ir-insts.h"
+
+namespace Slang {
+
+// Track information on a phi node we are in
+// the process of constructing.
+struct PhiInfo : RefObject
+{
+ // The phi node will be represented as a parameter
+ // to a (non-entry) basic block.
+ IRParam* phi;
+
+ // The original variable that this phi will be replacing.
+ IRVar* var;
+
+ // The operands to the phi will be stored as uses here,
+ // because our IR parameters don't have operands.
+ //
+ // Once we've collected all the values we plan to use,
+ // we will turn this into argument in predecessor blocks
+ // that branch to this one.
+ //
+ // The order of elements in this list must match the
+ // order in which the predecessor blocks get enumerated.
+ List<IRUse> operands;
+
+ // Did we end up removing this phi because it was determined
+ // to be trivial?
+ bool wasRemoved = false;
+};
+
+// Information about a basic block that we generate/use
+// during SSA construction.
+struct SSABlockInfo : RefObject
+{
+ // Map a promotable variable to the value to
+ // use for that variable
+ Dictionary<IRVar*, IRValue*> valueForVar;
+
+ // The underlying basic block.
+ IRBlock* block;
+
+ // Have we processed all the instructions in the
+ // body of this block (so that we would have
+ // found any stores to SSA variables)?
+ bool isFilled = false;
+
+ // Have we filled all the predecessors of
+ // this block, so that we can actually perform
+ // look up in them?
+ bool isSealed = false;
+
+ // An IR builder to use when we want to construct
+ // stuff in the context of this block
+ IRBuilder builder;
+
+ // Phi nodes we are creating for this block.
+ List<PhiInfo*> phis;
+
+ // Arguments that this block needs to pass along
+ // to the phi nodes defined by is sucessor
+ List<IRValue*> successorArgs;
+};
+
+// State for constructing SSA form for a global value
+// with code (usually a function).
+struct ConstructSSAContext
+{
+ // The value that we want to rewrite into SSA form
+ // (usually an IR function)
+ IRGlobalValueWithCode* globalVal;
+
+ // Variables that we've identified for promotion
+ // to SSA values.
+ List<IRVar*> promotableVars;
+
+ // Information about each basic block
+ Dictionary<IRBlock*, RefPtr<SSABlockInfo>> blockInfos;
+
+ // IR building state to use during the operation
+ SharedIRBuilder sharedBuilder;
+
+
+ Dictionary<IRParam*, RefPtr<PhiInfo>> phiInfos;
+
+ PhiInfo* getPhiInfo(IRParam* phi)
+ {
+ return *phiInfos.TryGetValue(phi);
+ }
+};
+
+// Is the given variable one that we can promote to SSA form?
+bool isPromotableVar(
+ ConstructSSAContext* /*context*/,
+ IRVar* var)
+{
+ // If the variable is only used directly as the pointer
+ // operand of load and store instructions, then it should
+ // be promote-able.
+ //
+ // For now, we won't deal with cases where the variable
+ // is an aggregate an we sometimes pull out individual
+ // fields or elements. This is an important extension,
+ // but we probably also need to think about scalarizing
+ // any aggregates when we promote them to registers.
+
+ for (auto u = var->firstUse; u; u = u->nextUse)
+ {
+ auto user = u->user;
+ switch (user->op)
+ {
+ default:
+ // If the variable gets used by any operation
+ // we can't account for directly, then it isn't
+ // promotable.
+ return false;
+
+ case kIROp_Load:
+ {
+ // A load has only a single argument, so
+ // it had better be our pointer.
+ assert(u == &((IRLoad*) user)->ptr);
+ }
+ break;
+
+ case kIROp_Store:
+ {
+ auto storeInst = (IRStore*)user;
+
+ // We don't want to promote a variable if
+ // its address gets stored into another
+ // variable, so check for that case.
+ if (u == &storeInst->val)
+ return false;
+
+ // Otherwise our variable is being used
+ // as the destination for the store, and
+ // that is okay by us.
+ assert(u == &storeInst->ptr);
+ }
+ break;
+ }
+ }
+
+ // If all of the uses passed our checking, then
+ // we are good to go.
+ return true;
+}
+
+// Identify local variables that can be promoted to SSA form
+void identifyPromotableVars(
+ ConstructSSAContext* context)
+{
+ for (auto bb = context->globalVal->getFirstBlock(); bb; bb = bb->getNextBlock())
+ {
+ for (auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst())
+ {
+ if (ii->op != kIROp_Var)
+ continue;
+
+ IRVar* var = (IRVar*)ii;
+
+ if (isPromotableVar(context, var))
+ {
+ context->promotableVars.Add(var);
+ }
+ }
+ }
+}
+
+IRVar* asPromotableVar(
+ ConstructSSAContext* context,
+ IRValue* value)
+{
+ if (value->op != kIROp_Var)
+ return nullptr;
+
+ IRVar* var = (IRVar*)value;
+ if (!context->promotableVars.Contains(var))
+ return nullptr;
+
+ return var;
+}
+
+// Try to read the value of an SSA variable
+// in the context of the given block. If
+// the variable is defined in the block, then
+// that value will be used. If not, this all
+// may recursively work its way up through
+// the predecessors of the block.
+IRValue* readVar(
+ ConstructSSAContext* context,
+ SSABlockInfo* blockInfo,
+ IRVar* var);
+
+
+// Add a phi node to represent the given variable
+PhiInfo* addPhi(
+ ConstructSSAContext* context,
+ SSABlockInfo* blockInfo,
+ IRVar* var)
+{
+ auto builder = &blockInfo->builder;
+ IRParam* phi = builder->createParam(var->getType()->getValueType());
+
+ RefPtr<PhiInfo> phiInfo = new PhiInfo();
+ context->phiInfos.Add(phi, phiInfo);
+
+ phiInfo->phi = phi;
+ phiInfo->var = var;
+
+ blockInfo->phis.Add(phiInfo);
+
+ return phiInfo;
+}
+
+IRValue* tryRemoveTrivialPhi(
+ ConstructSSAContext* /*context*/,
+ PhiInfo* phiInfo)
+{
+ auto phi = phiInfo->phi;
+
+ // We are going to check if all of the operands
+ // to the phi are either the same, or are equal
+ // to the phi itself.
+
+ IRValue* same = nullptr;
+ for (auto u : phiInfo->operands)
+ {
+ auto usedVal = u.usedValue;
+ assert(usedVal);
+
+ if (usedVal == same || usedVal == phi)
+ {
+ // Either this is a self-reference, or it refers
+ // to the same value we've seen already.
+ continue;
+ }
+ if (same != nullptr)
+ {
+ // We've found at least two distinct values
+ // other than the phi itself, so this phi
+ // indeed appears to be non-trivial.
+ //
+ // We will keep the phi around.
+ return phi;
+ }
+ else
+ {
+ // This value is distinct from the phi itself,
+ // so we need to track its value.
+ same = usedVal;
+ }
+ }
+
+ if (!same)
+ {
+ // There were no operands other than the phi itself.
+ // This implies that the value at the use sites should
+ // actually be undefined.
+
+ assert(!"unimplemented");
+ }
+
+ // replace uses of the phi (including its possible uses
+ // of itself) with the unique non-phi value.
+ phi->replaceUsesWith(same);
+
+ // We will mark the phi as removed in the `PhiInfo` structure,
+ // so that we can avoid adding it to the block later.
+ phiInfo->wasRemoved = true;
+
+ // TODO: we need a way to record that this parameter
+ // is no longer to be used...
+
+ // TODO: need to recursively consider chances to simplify
+ // other phi nodes now.
+
+ return same;
+}
+
+IRValue* addPhiOperands(
+ ConstructSSAContext* context,
+ SSABlockInfo* blockInfo,
+ PhiInfo* phiInfo)
+{
+ auto var = phiInfo->var;
+
+ auto block = blockInfo->block;
+
+ List<IRValue*> operandValues;
+ for (auto predBlock : block->getPredecessors())
+ {
+ // Precondition: if we have multiple predecessors, then
+ // each must have only one successor (no critical edges).
+ //
+ assert(predBlock->getSuccessors().getCount() == 1);
+
+ auto predInfo = *context->blockInfos.TryGetValue(predBlock);
+
+ auto phiOperand = readVar(context, predInfo, var);
+
+ operandValues.Add(phiOperand);
+ }
+
+ // The `IRUse` type needs to stay at a stable location
+ // since they get threaded into lists. We allocate the
+ // list with its final size so that we can preserve the
+ // required invariant.
+
+ UInt operandCount = operandValues.Count();
+ phiInfo->operands.SetSize(operandCount);
+ for(UInt ii = 0; ii < operandCount; ++ii)
+ {
+ phiInfo->operands[ii].init(nullptr, operandValues[ii]);
+ }
+
+ return tryRemoveTrivialPhi(context, phiInfo);
+}
+
+void writeVar(
+ ConstructSSAContext* /*context*/,
+ SSABlockInfo* blockInfo,
+ IRVar* var,
+ IRValue* val)
+{
+ blockInfo->valueForVar[var] = val;
+}
+
+void maybeSealBlock(
+ ConstructSSAContext* context,
+ SSABlockInfo* blockInfo)
+{
+ // We can't seal a block that has already been sealed.
+ if (blockInfo->isSealed)
+ return;
+
+ // We can't seal a block until all of its predecessors
+ // have been filled.
+ for (auto pp : blockInfo->block->getPredecessors())
+ {
+ auto predInfo = *context->blockInfos.TryGetValue(pp);
+ if (!predInfo->isFilled)
+ return;
+ }
+
+ // All the checks passed, so it seems like we can be sealed.
+
+ // We will loop over any incomplete phis that have been recoreded
+ // for this block, and complete them here.
+ //
+ // Note that we are doing the "inefficient" loop where we compute
+ // the count on each iteration to account for the possibility that
+ // new incomplete phis will get added while we are working.
+ for (UInt ii = 0; ii < blockInfo->phis.Count(); ++ii)
+ {
+ auto incompletePhi = blockInfo->phis[ii];
+ addPhiOperands(context, blockInfo, incompletePhi);
+ }
+
+ // After we've completed all our incomplete phis, we can mark this
+ // block as sealed and move along.
+ blockInfo->isSealed = true;
+}
+
+IRValue* readVarRec(
+ ConstructSSAContext* context,
+ SSABlockInfo* blockInfo,
+ IRVar* var)
+{
+ IRValue* val = nullptr;
+ if (!blockInfo->isSealed)
+ {
+ // If block isn't sealed, we need to
+ // speculatively add a phi to it.
+ // This phi may get removed later, once
+ // we are able to seal this block.
+
+ PhiInfo* phiInfo = addPhi(context, blockInfo, var);
+ val = phiInfo->phi;
+ }
+ else
+ {
+ // If the block is sealed, then we are free to look at
+ // it predecessor list, and use that to decide what to do.
+ auto predecessors = blockInfo->block->getPredecessors();
+
+ //
+ IRBlock* firstPred = nullptr;
+ bool multiplePreds = false;
+ for (auto pp : predecessors)
+ {
+ if (!firstPred)
+ {
+ // A candidate for the sole predecessor
+ firstPred = pp;
+ }
+ else if (pp == firstPred)
+ {
+ // Same as existing predecessor
+ }
+ else
+ {
+ // Multiple unique predecessors
+ multiplePreds = true;
+ }
+ }
+
+ if (!firstPred)
+ {
+ // The block had *no* predecssors. This will commonly
+ // happen for the entry block, but could also conceivably
+ // happen for a block that is somehow disconnected
+ // from the CFG and thus unreachable.
+
+ // We would only reach this function (`readVarRec`) if
+ // a local lookup in the block had already failed, so
+ // at this point we are dealing with an undefined value.
+
+ auto type = var->getType()->getValueType();
+ val = blockInfo->builder.emitUndefined(type);
+ }
+ else if (!multiplePreds)
+ {
+ // There is only a single predecessor for this block,
+ // so there is no need to insert a phi. Instead, we
+ // just perform the lookup step recursively in
+ // the predecessor.
+ auto predInfo = *context->blockInfos.TryGetValue(firstPred);
+ val = readVar(context, predInfo, var);
+ }
+ else
+ {
+ // The default/fallback case requires us to create
+ // a phi node in the current block, and then look
+ // up the appropriate operands in the predecessor
+ // blocks, which will eventually become the operands
+ // that drive the phi.
+
+ // Create the phi node for the given variable
+ PhiInfo* phiInfo = addPhi(context, blockInfo, var);
+
+ // Mark the phi as the value for the variable inside
+ // this block
+ writeVar(context, blockInfo, var, phiInfo->phi);
+
+ // Now add operands to the phi and maybe simplify
+ // it, based on what gets found.
+
+ val = addPhiOperands(context, blockInfo, phiInfo);
+ }
+ }
+
+ // Whatever value we find, we need to mark it as the
+ // value for the given variable in this block
+ writeVar(context, blockInfo, var, val);
+
+ return val;
+}
+
+
+IRValue* readVar(
+ ConstructSSAContext* context,
+ SSABlockInfo* blockInfo,
+ IRVar* var)
+{
+ // In the easy case, there will be a preceeding
+ // store in the same block, so we can use
+ // that local value.
+ IRValue* val = nullptr;
+ if (blockInfo->valueForVar.TryGetValue(var, val))
+ {
+ // Hooray, we found a value to use, and we
+ // can proceed without too many complications.
+ return val;
+ }
+
+ // Otherwise we need to try to non-trivial/recursive
+ // case of lookup.
+ return readVarRec(context, blockInfo, var);
+}
+
+void processBlock(
+ ConstructSSAContext* context,
+ IRBlock* block,
+ SSABlockInfo* blockInfo)
+{
+ // Before starting, check if this block can be sealed
+ maybeSealBlock(context, blockInfo);
+
+ // Walk the instructions in the block, and either
+ // leave them as-is, or replace them with a value
+ // that we look up with local/global value numbering
+
+ IRInst* next = nullptr;
+ for (auto ii = block->getFirstInst(); ii; ii = next)
+ {
+ next = ii->getNextInst();
+
+ // Any new instructions we create to represent
+ // the new value will get inserted before whatever
+ // instruction we are working with.
+ blockInfo->builder.insertBeforeInst = ii;
+
+ switch (ii->op)
+ {
+ default:
+ // Ordinary instruction -> leave as-is
+ break;
+
+ case kIROp_Store:
+ {
+ auto storeInst = (IRStore*)ii;
+ auto ptrArg = storeInst->ptr.usedValue;
+ auto valArg = storeInst->val.usedValue;
+
+ if (auto var = asPromotableVar(context, ptrArg))
+ {
+ // We are storing to a promotable variable,
+ // so we want to register the value being
+ // stored as the value for the given SSA
+ // variable.
+ writeVar(context, blockInfo, var, valArg);
+
+ // Also eliminate the store instruction,
+ // since it is no longer needed.
+ storeInst->removeAndDeallocate();
+ }
+ }
+ break;
+
+ case kIROp_Load:
+ {
+ IRLoad* loadInst = (IRLoad*)ii;
+ auto ptrArg = loadInst->ptr.usedValue;
+
+ if (auto var = asPromotableVar(context, ptrArg))
+ {
+ // We are loading from a promotable variable.
+ // Look up the value in the context of this
+ // block.
+ auto val = readVar(context, blockInfo, var);
+
+ // We can just replace all uses of this
+ // load instruction with the given value.
+ loadInst->replaceUsesWith(val);
+
+ // Also eliminate the load instruction,
+ // since it is no longer needed.
+ loadInst->removeAndDeallocate();
+ }
+ }
+ break;
+ }
+ }
+
+ blockInfo->builder.insertBeforeInst = block->lastInst;
+
+ // Once we are done with all of the instructions
+ // in a block, we can mark it as "filled," which
+ // means we can actually consider lookups into
+ // it.
+ blockInfo->isFilled = true;
+
+ // Having filled this block might allow us to seal some
+ // of its successor(s)
+ for (auto ss : block->getSuccessors())
+ {
+ auto successorInfo = *context->blockInfos.TryGetValue(ss);
+ maybeSealBlock(context, successorInfo);
+ }
+}
+
+static void breakCriticalEdges(
+ ConstructSSAContext* context)
+{
+ // A critical edge is an edge P -> S where
+ // P has multiple sucessors, and S has multiple
+ // predecessors.
+ //
+ // In the context of our CFG representation, such an edge
+ // will be an `IRUse` in the terminator instruction of block P,
+ // which refers to block S.
+ //
+ // We will make a pass over the CFG to collect all the critical
+ // edges, and then we will break them in a follow-up pass.
+
+ List<IRUse*> criticalEdges;
+
+ auto globalVal = context->globalVal;
+ for (auto pred = globalVal->getFirstBlock(); pred; pred = pred->getNextBlock())
+ {
+ auto successors = pred->getSuccessors();
+ if (successors.getCount() <= 1)
+ continue;
+
+ auto succIter = successors.begin();
+ auto succEnd = successors.end();
+
+ for (; succIter != succEnd; ++succIter)
+ {
+ auto succ = *succIter;
+
+ // For the edge to be critical, the successor must have
+ // more than one predecessor.
+ // More than that, we require that it has more than one
+ // *unique* predecessor, to handle the case where multiple
+ // cases of a `switch` might lead to the same block.
+ //
+ // To implement this, we test if it has any predecessor
+ // other than `pred` which we already know about.
+
+ bool multiplePreds = false;
+ for (auto pp : succ->getPredecessors())
+ {
+ if (pp != pred)
+ {
+ multiplePreds = true;
+ break;
+ }
+ }
+ if (!multiplePreds)
+ continue;
+
+ // We have found a critical edge from `pred` to `succ`.
+ //
+ // Furthermore, the `IRUse` embedded in `succIter` represents
+ // that edge directly.
+ auto edgeUse = succIter.use;
+ criticalEdges.Add(edgeUse);
+ }
+ }
+
+ // Now we will iterate over the critical edges and break each
+ // one by inserting a new block. Note that we do not try
+ // to break the edges while doing the initial walk, because
+ // that would change the CFG while we are walking it.
+
+ for (auto edgeUse : criticalEdges)
+ {
+ auto pred = (IRBlock*) edgeUse->user->parent;
+ assert(pred->op == kIROp_Block);
+
+ auto succ = (IRBlock*)edgeUse->usedValue;
+ assert(succ->op == kIROp_Block);
+
+ IRBuilder builder;
+ builder.sharedBuilder = &context->sharedBuilder;
+ builder.curFunc = globalVal;
+ builder.curBlock = pred;
+
+ // Create a new block that will sit "along" the edge
+ IRBlock* edgeBlock = builder.createBlock();
+
+ // The predecessor block should now branch to
+ // the edge block.
+ edgeUse->set(edgeBlock);
+
+ // The edge block should branch (unconditionally)
+ // to the successor block.
+ builder.curBlock = edgeBlock;
+ builder.emitBranch(succ);
+
+ // Insert the new block into the block list
+ // for the function.
+ //
+ // In principle, the order of this list shouldn't
+ // affect the semantics of a program, but we
+ // might want to be careful about ordering anyway.
+ edgeBlock->insertAfter(pred);
+ }
+}
+
+// Construct SSA form for a global value with code
+void constructSSA(ConstructSSAContext* context)
+{
+ // First, detect and and break any critical edges in the CFG,
+ // because our representation of SSA form doesn't allow for them.
+ breakCriticalEdges(context);
+
+
+ // Figure out what variables we can promote to
+ // SSA temporaries.
+ identifyPromotableVars(context);
+
+ // If none of the variables are promote-able,
+ // then we can exit without making any changes
+ if (context->promotableVars.Count() == 0)
+ return;
+
+ // We are going to walk the blocks in order,
+ // and try to process each, by replacing loads
+ // and stores of promotable variables with simple values.
+
+ auto globalVal = context->globalVal;
+ for (auto bb = globalVal->firstBlock; bb; bb = bb->nextBlock)
+ {
+ auto blockInfo = new SSABlockInfo();
+ blockInfo->block = bb;
+
+ blockInfo->builder.sharedBuilder = &context->sharedBuilder;
+ blockInfo->builder.curBlock = bb;
+ blockInfo->builder.curFunc = globalVal;
+ blockInfo->builder.insertBeforeInst = bb->lastInst;
+
+ context->blockInfos.Add(bb, blockInfo);
+ }
+ for (auto bb = globalVal->firstBlock; bb; bb = bb->nextBlock)
+ {
+ auto blockInfo = * context->blockInfos.TryGetValue(bb);
+ processBlock(context, bb, blockInfo);
+ }
+
+ // We need to transfer the logical arguments to our phi nodes
+ // from the phi nodes back to the predecessor blocks that will
+ // pass them in.
+ for (auto bb = globalVal->firstBlock; bb; bb = bb->nextBlock)
+ {
+ auto blockInfo = *context->blockInfos.TryGetValue(bb);
+
+ for (auto phiInfo : blockInfo->phis)
+ {
+ // If we eliminated this phi, then we had better not
+ // include it in the result.
+ if (phiInfo->wasRemoved)
+ continue;
+
+ // We should add the phi as an explicit parameter of
+ // the given block.
+ bb->addParam(phiInfo->phi);
+
+ UInt predCounter = 0;
+ for (auto pp : bb->getPredecessors())
+ {
+ UInt predIndex = predCounter++;
+ auto predInfo = *context->blockInfos.TryGetValue(pp);
+
+ IRValue* operandVal = phiInfo->operands[predIndex].usedValue;
+
+ phiInfo->operands[predIndex].clear();
+
+ predInfo->successorArgs.Add(operandVal);
+ }
+ }
+ }
+
+ // Some blocks may now need to pass along arguments to their sucessor,
+ // which have been stored into the `SSABlockInfo::successorArgs` field.
+ for (auto bb = globalVal->firstBlock; bb; bb = bb->nextBlock)
+ {
+ auto blockInfo = * context->blockInfos.TryGetValue(bb);
+
+ // Sanity check: all blocks should be filled and sealed.
+ assert(blockInfo->isSealed);
+ assert(blockInfo->isFilled);
+
+ // Don't do any work for blocks that don't need to pass along
+ // values to the sucessor block.
+ auto addedArgCount = blockInfo->successorArgs.Count();
+ if (addedArgCount == 0)
+ continue;
+
+ // We need to replace the terminator instruction with one that
+ // has additional arguments.
+
+ IRTerminatorInst* oldTerminator = (IRTerminatorInst*) bb->getLastInst();
+ assert(isTerminatorInst(oldTerminator));
+
+ blockInfo->builder.insertBeforeInst = nullptr;
+
+ auto oldArgCount = oldTerminator->argCount;
+ auto newArgCount = oldArgCount + addedArgCount;
+
+ List<IRValue*> newArgs;
+ for (UInt aa = 0; aa < oldArgCount; ++aa)
+ {
+ newArgs.Add(oldTerminator->getArg(aa));
+ }
+ for (UInt aa = 0; aa < addedArgCount; ++aa)
+ {
+ newArgs.Add(blockInfo->successorArgs[aa]);
+ }
+
+ IRTerminatorInst* newTerminator = (IRTerminatorInst*)blockInfo->builder.emitIntrinsicInst(
+ oldTerminator->type,
+ oldTerminator->op,
+ newArgCount,
+ newArgs.Buffer());
+
+ // Swap decorations over to the new instruction
+ newTerminator->firstDecoration = oldTerminator->firstDecoration;
+ oldTerminator->firstDecoration = nullptr;
+
+ // A terminator better not have uses, so we shouldn't have
+ // to replace them.
+ assert(!oldTerminator->firstUse);
+
+
+ // Okay, we should be clear to remove the old terminator
+ oldTerminator->removeAndDeallocate();
+ }
+
+ // Now we should be able to go through and remove
+ // of of the variables
+ for (auto var : context->promotableVars)
+ {
+ var->removeAndDeallocate();
+ }
+}
+
+// Construct SSA form for a global value with code
+void constructSSA(IRModule* module, IRGlobalValueWithCode* globalVal)
+{
+ ConstructSSAContext context;
+ context.globalVal = globalVal;
+
+ context.sharedBuilder.module = module;
+ context.sharedBuilder.session = module->session;
+
+ constructSSA(&context);
+}
+
+void constructSSA(IRModule* module, IRGlobalValue* globalVal)
+{
+ switch (globalVal->op)
+ {
+ case kIROp_Func:
+ case kIROp_global_var:
+ constructSSA(module, (IRGlobalValueWithCode*)globalVal);
+
+ default:
+ break;
+ }
+}
+
+void constructSSA(IRModule* module)
+{
+ for (auto gv = module->getFirstGlobalValue(); gv; gv = gv->getNextValue())
+ {
+ constructSSA(module, gv);
+ }
+}
+
+}
diff --git a/source/slang/ir-ssa.h b/source/slang/ir-ssa.h
new file mode 100644
index 000000000..b9a05c2ff
--- /dev/null
+++ b/source/slang/ir-ssa.h
@@ -0,0 +1,9 @@
+// ir-ssa.h
+#pragma once
+
+struct IRModule;
+
+namespace Slang
+{
+ void constructSSA(IRModule* module);
+}
diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp
index 77aac3f5a..634632f0d 100644
--- a/source/slang/ir.cpp
+++ b/source/slang/ir.cpp
@@ -28,16 +28,27 @@ namespace Slang
{
// TODO: need to make this faster by using a dictionary...
- if (0) {}
+ static const struct {
+ char const* mnemonic;
+ IROp op;
+ } kOps[] = {
#define INST(ID, MNEMONIC, ARG_COUNT, FLAGS) \
- else if(strcmp(name, #MNEMONIC) == 0) return kIROp_##ID;
+ { #MNEMONIC, kIROp_##ID },
#define PSEUDO_INST(ID) \
- else if(strcmp(name, #ID) == 0) return kIRPseudoOp_##ID;
+ { #ID, kIRPseudoOp_##ID },
#include "ir-inst-defs.h"
+ };
+
+ for (auto ee : kOps)
+ {
+ if (strcmp(name, ee.mnemonic) == 0)
+ return ee.op;
+ }
+
return IROp(kIROp_Invalid);
}
@@ -125,6 +136,228 @@ namespace Slang
lastParam = param;
}
+ // The predecessors of a block should all show up as users
+ // of its value, so rather than explicitly store the CFG,
+ // we will recover it on demand from the use-def information.
+ //
+ // Note: we are really iterating over incoming/outgoing *edges*
+ // for a block, because there might be multiple uses of a block,
+ // if more than one way of an N-way branch targets the same block.
+
+ // Get the list of successor blocks for an instruction,
+ // which we expect to be the last instruction in a block.
+ static IRBlock::SuccessorList getSuccessors(IRInst* terminator)
+ {
+ // If the block somehow isn't terminated, then
+ // there is no way to read its successors, so
+ // we return an empty list.
+ if (!terminator || !isTerminatorInst(terminator))
+ return IRBlock::SuccessorList(nullptr, nullptr);
+
+ // Otherwise, based on the opcode of the terminator
+ // instruction, we will build up our list of uses.
+ IRUse* begin = nullptr;
+ IRUse* end = nullptr;
+ UInt stride = 1;
+
+ auto args = terminator->getArgs();
+ switch (terminator->op)
+ {
+ case kIROp_ReturnVal:
+ case kIROp_ReturnVoid:
+ case kIROp_unreachable:
+ case kIROp_discard:
+ break;
+
+ case kIROp_unconditionalBranch:
+ case kIROp_break:
+ case kIROp_continue:
+ case kIROp_loop:
+ // unconditonalBranch <block>
+ begin = args + 0;
+ end = begin + 1;
+ break;
+
+ case kIROp_conditionalBranch:
+ case kIROp_if:
+ case kIROp_ifElse:
+ case kIROp_loopTest:
+ // conditionalBranch <condition> <trueBlock> <falseBlock>
+ begin = args + 1;
+ end = begin + 2;
+ break;
+
+ case kIROp_switch:
+ // switch <val> <break> <default> <caseVal1> <caseBlock1> ...
+ begin = args + 4;
+ end = args + terminator->getArgCount() + 1;
+ stride = 2;
+ break;
+
+ default:
+ assert(!"unepxected");
+ return IRBlock::SuccessorList(nullptr, nullptr);
+ }
+
+ return IRBlock::SuccessorList(begin, end, stride);
+ }
+
+ void IRBlock::insertAfter(IRBlock* other)
+ {
+ assert(other);
+ insertAfter(other, other->parentFunc);
+ }
+
+ void IRBlock::insertAfter(IRBlock* other, IRGlobalValueWithCode* func)
+ {
+ assert(other || func);
+
+ if (!other) other = func->lastBlock;
+ if (!func) func = other->parentFunc;
+
+ assert(func);
+
+ auto pp = other;
+ auto nn = other ? other->nextBlock : nullptr;
+
+ if (pp)
+ {
+ pp->nextBlock = this;
+ }
+ else
+ {
+ func->firstBlock = this;
+ }
+
+ if (nn)
+ {
+ nn->prevBlock = this;
+ }
+ else
+ {
+ func->lastBlock = this;
+ }
+
+ this->prevBlock = pp;
+ this->nextBlock = nn;
+ this->parentFunc = func;
+ }
+
+ static IRUse* adjustPredecessorUse(IRUse* use)
+ {
+ // We will search until we either find a
+ // suitable use, or run out of uses.
+ for (;use; use = use->nextUse)
+ {
+ // We only want to deal with uses that represent
+ // a "sucessor" operand to some terminator instruction.
+ // We will re-use the logic for getting the successor
+ // list from such an instruction.
+
+ auto successorList = getSuccessors((IRInst*) use->user);
+
+ if(use >= successorList.begin_
+ && use < successorList.end_)
+ {
+ UInt index = (use - successorList.begin_);
+ if ((index % successorList.stride) == 0)
+ {
+ // This use is in the range of the sucessor list,
+ // and so it represents a real edge between
+ // blocks.
+ return use;
+ }
+ }
+ }
+
+ // If we ran out of uses, then we are at the end
+ // of the list of incoming edges.
+ return nullptr;
+ }
+
+ IRBlock::PredecessorList IRBlock::getPredecessors()
+ {
+ // We want to iterate over the predecessors of this block.
+ // First, we resign ourselves to iterating over the
+ // incoming edges, rather than the blocks themselves.
+ // This might sound like a trival distinction, but it is
+ // possible for there to be multiple edges between two
+ // blocks (as for a `switch` with multiple cases that
+ // map to the same code). Any client that wants just
+ // the unique predecessor blocks needs to deal with
+ // the deduplication themselves.
+ //
+ // Next, we note that for any predecessor edge, there will
+ // be a use of this block in the terminator instruction of
+ // the predecessor. We basically just want to iterate over
+ // the users of this block, then, but we need to be careful
+ // to rule out anything that doesn't actually represent
+ // an edge. The `adjustPredecessorUse` function will be
+ // used to search for a use that actually represents an edge.
+
+ return PredecessorList(
+ adjustPredecessorUse(firstUse));
+ }
+
+ UInt IRBlock::PredecessorList::getCount()
+ {
+ UInt count = 0;
+ for (auto ii : *this)
+ {
+ (void)ii;
+ count++;
+ }
+ return count;
+ }
+
+ void IRBlock::PredecessorList::Iterator::operator++()
+ {
+ if (!use) return;
+ use = adjustPredecessorUse(use->nextUse);
+ }
+
+ IRBlock* IRBlock::PredecessorList::Iterator::operator*()
+ {
+ if (!use) return nullptr;
+ return (IRBlock*)use->user->parent;
+ }
+
+ IRBlock::SuccessorList IRBlock::getSuccessors()
+ {
+ // The successors of a block will all be listed
+ // as operands of its terminator instruction.
+ // Depending on the terminator, we might have
+ // different numbers of operands to deal with.
+ //
+ // (We might also have to deal with a "stride"
+ // in the case where the basic-block operands
+ // are mixed up with non-block operands)
+
+ auto terminator = getLastInst();
+ return Slang::getSuccessors(terminator);
+ }
+
+ UInt IRBlock::SuccessorList::getCount()
+ {
+ UInt count = 0;
+ for (auto ii : *this)
+ {
+ (void)ii;
+ count++;
+ }
+ return count;
+ }
+
+ void IRBlock::SuccessorList::Iterator::operator++()
+ {
+ use += stride;
+ }
+
+ IRBlock* IRBlock::SuccessorList::Iterator::operator*()
+ {
+ return (IRBlock*)use->usedValue;
+ }
+
// IRFunc
IRType* IRFunc::getResultType() { return getType()->getResultType(); }
@@ -609,6 +842,18 @@ namespace Slang
&value);
}
+ IRUndefined* IRBuilder::emitUndefined(IRType* type)
+ {
+ auto inst = createInst<IRUndefined>(
+ this,
+ kIROp_undefined,
+ type);
+
+ addInst(inst);
+
+ return inst;
+ }
+
IRValue* IRBuilder::getDeclRefVal(
DeclRefBase const& declRef)
{
@@ -1004,13 +1249,20 @@ namespace Slang
return bb;
}
- IRParam* IRBuilder::emitParam(
+ IRParam* IRBuilder::createParam(
IRType* type)
{
auto param = createValue<IRParam>(
this,
kIROp_Param,
type);
+ return param;
+ }
+
+ IRParam* IRBuilder::emitParam(
+ IRType* type)
+ {
+ auto param = createParam(type);
if (auto bb = curBlock)
{
@@ -3283,6 +3535,7 @@ namespace Slang
case kIROp_Func:
case kIROp_witness_table:
return cloneGlobalValue(this, (IRGlobalValue*) originalValue);
+
case kIROp_boolConst:
{
IRConstant* c = (IRConstant*)originalValue;
diff --git a/source/slang/ir.h b/source/slang/ir.h
index 1b4529a3c..028468308 100644
--- a/source/slang/ir.h
+++ b/source/slang/ir.h
@@ -375,6 +375,68 @@ struct IRBlock : IRValue
IRGlobalValueWithCode* getParent() { return parentFunc; }
+ void insertAfter(IRBlock* other);
+ void insertAfter(IRBlock* other, IRGlobalValueWithCode* func);
+
+ struct PredecessorList
+ {
+ PredecessorList(IRUse* begin) : b(begin) {}
+ IRUse* b;
+
+ UInt getCount();
+
+ struct Iterator
+ {
+ Iterator(IRUse* use) : use(use) {}
+
+ IRBlock* operator*();
+
+ void operator++();
+
+ bool operator!=(Iterator const& that)
+ {
+ return use != that.use;
+ }
+
+ IRUse* use;
+ };
+
+ Iterator begin() { return Iterator(b); }
+ Iterator end() { return Iterator(nullptr); }
+ };
+
+ struct SuccessorList
+ {
+ SuccessorList(IRUse* begin, IRUse* end, UInt stride = 1) : begin_(begin), end_(end), stride(stride) {}
+ IRUse* begin_;
+ IRUse* end_;
+ UInt stride;
+
+ UInt getCount();
+
+ struct Iterator
+ {
+ Iterator(IRUse* use, UInt stride) : use(use), stride(stride) {}
+
+ IRBlock* operator*();
+
+ void operator++();
+
+ bool operator!=(Iterator const& that)
+ {
+ return use != that.use;
+ }
+
+ IRUse* use;
+ UInt stride;
+ };
+
+ Iterator begin() { return Iterator(begin_, stride); }
+ Iterator end() { return Iterator(end_, stride); }
+ };
+
+ PredecessorList getPredecessors();
+ SuccessorList getSuccessors();
};
// For right now, we will represent the type of
diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp
index bde50aa89..5c6f62c38 100644
--- a/source/slang/lower-to-ir.cpp
+++ b/source/slang/lower-to-ir.cpp
@@ -5,6 +5,7 @@
#include "ir.h"
#include "ir-insts.h"
+#include "ir-ssa.h"
#include "mangle.h"
#include "type-layout.h"
#include "visitor.h"
@@ -4201,6 +4202,42 @@ IRModule* generateIRForTranslationUnit(
ensureDecl(context, decl);
}
+ // We will perform certain "mandatory" optimization passes now.
+ // These passes serve two purposes:
+ //
+ // 1. To simplify the code that we use in backend compilation,
+ // or when serializing/deserializing modules, so that we can
+ // amortize this effort when we compile multiple entry points
+ // that use the same module(s).
+ //
+ // 2. To ensure certain semantic properties that can't be
+ // validated without dataflow information. For example, we want
+ // to detect when a variable might be used before it is initialized.
+
+ // Note: if you need to debug the IR that is created before
+ // any mandatory optimizations have been applied, then
+ // uncomment this line while debugging.
+
+ // dumpIR(module);
+
+ // First, attempt to promote local variables to SSA
+ // temporaries whenever possible.
+ constructSSA(module);
+
+ // TODO: Do basic constant folding and DCE
+
+ // TODO: give error messages if any `undefined` or
+ // `unreachable` instructions remain.
+
+ // TODO: consider doing some more aggressive optimizations
+ // (in particular specialization of generics) here, so
+ // that we can avoid doing them downstream.
+ //
+ // Note: doing specialization or inlining involving code
+ // from other modules potentially makes the IR we generate
+ // "fragile" in that we'd now need to recompile when
+ // a module we depend on changes.
+
// If we are being sked to dump IR during compilation,
// then we can dump the initial IR for the module here.
if(compileRequest->shouldDumpIR)
diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj
index 23400bb23..1ff88020f 100644
--- a/source/slang/slang.vcxproj
+++ b/source/slang/slang.vcxproj
@@ -179,6 +179,7 @@
<ClInclude Include="expr-defs.h" />
<ClInclude Include="ir-inst-defs.h" />
<ClInclude Include="ir-insts.h" />
+ <ClInclude Include="ir-ssa.h" />
<ClInclude Include="ir.h" />
<ClInclude Include="legalize-types.h" />
<ClInclude Include="lexer.h" />
@@ -217,6 +218,7 @@
<ClCompile Include="dxc-support.cpp" />
<ClCompile Include="emit.cpp" />
<ClCompile Include="ir-legalize-types.cpp" />
+ <ClCompile Include="ir-ssa.cpp" />
<ClCompile Include="ir.cpp" />
<ClCompile Include="legalize-types.cpp" />
<ClCompile Include="lexer.cpp" />
diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters
index 4c463a62a..e0bf59025 100644
--- a/source/slang/slang.vcxproj.filters
+++ b/source/slang/slang.vcxproj.filters
@@ -44,6 +44,7 @@
<ClInclude Include="vm.h" />
<ClInclude Include="mangle.h" />
<ClInclude Include="legalize-types.h" />
+ <ClInclude Include="ir-ssa.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="check.cpp" />
@@ -73,6 +74,7 @@
<ClCompile Include="dxc-support.cpp" />
<ClCompile Include="ir-legalize-types.cpp" />
<ClCompile Include="legalize-types.cpp" />
+ <ClCompile Include="ir-ssa.cpp" />
</ItemGroup>
<ItemGroup>
<CustomBuild Include="core.meta.slang" />
diff --git a/source/slang/vm.cpp b/source/slang/vm.cpp
index ffc455232..c486615c9 100644
--- a/source/slang/vm.cpp
+++ b/source/slang/vm.cpp
@@ -1003,17 +1003,45 @@ void resumeThread(
VMType type = decodeType(frame, &ip);
UInt argCount = decodeUInt(&ip);
- Int destinationBlock = decodeSInt(&ip);
- for( UInt aa = 2; aa < argCount; ++aa )
+ Int destinationBlockIndex = decodeSInt(&ip);
+
+ auto func = frame->func;
+ auto& destinationBlock = func->bcFunc->blocks[destinationBlockIndex];
+
+ // We may be passing arguments through to the destination
+ // block, so any remaining operands of the branch instruction
+ // need to be used to fill in the parameter registers of
+ // the destination block.
+
+ UInt paramCount = destinationBlock.paramCount;
+
+ // There might be additional operands, because some branches
+ // also include information on merge points and break/continue labels.
+ UInt remainingArgCount = argCount - 1;
+ assert(remainingArgCount >= paramCount);
+
+ UInt extraArgCount = remainingArgCount - paramCount;
+
+ for (UInt ee = 0; ee < extraArgCount; ++ee)
{
decodeOperandPtr<void>(frame, &ip);
}
- // TODO: we need to deal with the case of
- // passing arguments to the block, which
- // means copying between the registers...
+ auto baseRegIndex = destinationBlock.params - func->bcFunc->regs;
+ for( UInt pp = 0; pp < paramCount; ++pp )
+ {
+ auto regIndex = baseRegIndex + pp;
+
+ void* argPtr = decodeOperandPtr<void>(frame, &ip);
+ void* regPtr = getRegPtrImpl(frame, regIndex);
+
+ VMType regType = func->regs[regIndex].type;
+ memcpy(regPtr, argPtr, regType.getSize());
+ }
+
+ // Now simply jump to the destination block.
//
- ip = frame->func->bcFunc->blocks[destinationBlock].code;
+ ip = destinationBlock.code;
}
break;