diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2019-05-31 17:20:37 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-05-31 17:20:37 -0400 |
| commit | 6cbc3929a54d37bd23cb5efa8e3320ba02f78b2f (patch) | |
| tree | 5a23cb47782e9e2a77762c90dd35da1005eba8d0 /source/slang/slang-ir-ssa.cpp | |
| parent | b81ff3ef968d1cc4e954b31a1812b3c391d17b02 (diff) | |
Use slang- prefix on slang compiler and core source (#973)
* Prefixing source files in source/slang with slang-
* Prefix source in source/slang with slang- prefix.
* Rename core source files with slang- prefix.
* Update project files.
* Fix problems from automatic merge.
Diffstat (limited to 'source/slang/slang-ir-ssa.cpp')
| -rw-r--r-- | source/slang/slang-ir-ssa.cpp | 1159 |
1 files changed, 1159 insertions, 0 deletions
diff --git a/source/slang/slang-ir-ssa.cpp b/source/slang/slang-ir-ssa.cpp new file mode 100644 index 000000000..9390b1e69 --- /dev/null +++ b/source/slang/slang-ir-ssa.cpp @@ -0,0 +1,1159 @@ +// slang-ir-ssa.cpp +#include "slang-ir-ssa.h" + +#include "slang-ir.h" +#include "slang-ir-clone.h" +#include "slang-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; + + // If this phi ended up being removed as trivial, then + // this will be the value that we replaced it with. + IRInst* replacement = nullptr; +}; + +// 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*, IRInst*> 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<IRInst*> 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; + + // Instructions to remove during cleanup + List<IRInst*> instsToRemove; + + IRBuilder builder; + IRBuilder* getBuilder() { return &builder; } + + + Dictionary<IRParam*, RefPtr<PhiInfo>> phiInfos; + + PhiInfo* getPhiInfo(IRParam* phi) + { + if(auto found = phiInfos.TryGetValue(phi)) + return *found; + return nullptr; + } +}; + +/// Do all uses of this instruction lead to a `load`? +/// +/// Checks if all uses of `inst` are either loads, +/// or get-element-address/get-field-address operations +/// that also lead to loads. +bool allUsesLeadToLoads(IRInst* inst) +{ + for (auto u = inst->firstUse; u; u = u->nextUse) + { + auto user = u->getUser(); + switch (user->op) + { + default: + return false; + + case kIROp_Load: + break; + + case kIROp_getElementPtr: + case kIROp_FieldAddress: + { + // Sanity check: the address being used should + // be the base-address operand, and not the field + // key or index (this should never be a problem). + if (u != &user->getOperands()[0]) + return false; + + if (!allUsesLeadToLoads(user)) + return false; + } + break; + } + } + + // If all of the uses passed our checking, then + // we are good to go. + return true; + +} + +// Is the given variable one that we can promote to SSA form? +bool isPromotableVar( + ConstructSSAContext* /*context*/, + IRVar* var) +{ + // We want to identify variables such that we can always + // determine what they will contain at a point in the + // program by directly inspecting their uses. + // + // The simplest possible answer would be instructions + // that are only ever used as the operand of "full" + // load and store instructions (loads and stores that + // write the entire variable). This is enough to + // promote simple scalar variables to SSA temporaries, + // but falls apart for aggregates and arrays. + // + // A slightly more powerful option (which is what we + // implement for now) is to promote variables when + // all of the stores are "full," and all other uses + // are in the form of a "chain" of `getElmeentAddress` + // or `getFieldAddress` operations that terminates + // with a load. + // + // An even more powerful option (which we do not yet + // implement) would be to handle cases where there are + // "chains" that end with stores, and to treat these + // as partial assignments (where we can still form + // an SSA value by creating a new temporary with just + // one element/field different). This kind of approach + // would be best if it is combined with scalarization, + // so that we don't need to construct aggregate temps. + // + + for (auto u = var->firstUse; u; u = u->nextUse) + { + auto user = u->getUser(); + 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. + SLANG_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. + SLANG_ASSERT(u == &storeInst->ptr); + } + break; + + case kIROp_getElementPtr: + case kIROp_FieldAddress: + { + // Sanity check: the address being used should + // be the base-address operand, and not the field + // key or index (this should never be a problem). + if (u != &user->getOperands()[0]) + return false; + + if (!allUsesLeadToLoads(user)) + return false; + } + 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); + } + } + } +} + +/// If `value` is a promotable variable, then cast and return it. +IRVar* asPromotableVar( + ConstructSSAContext* context, + IRInst* value) +{ + if (value->op != kIROp_Var) + return nullptr; + + IRVar* var = (IRVar*)value; + if (!context->promotableVars.contains(var)) + return nullptr; + + return var; +} + +/// If `value` is a promotable variable or an access chain +/// based on one, then cast and return the variable. +IRVar* asPromotableVarAccessChain( + ConstructSSAContext* context, + IRInst* value) +{ + switch (value->op) + { + case kIROp_Var: + return asPromotableVar(context, value); + + case kIROp_FieldAddress: + case kIROp_getElementPtr: + return asPromotableVarAccessChain(context, value->getOperand(0)); + + default: + return nullptr; + } +} + +/// After looking up the SSA value of avariable in some context, +/// apply whatever "access chain" was applied at the original use site. +/// +/// E.g., if the original operation was *((&a)->b) or *((&a) + i) and we've +/// resolved that the value of the variable `a` should be `v`, then +/// construct v.b or v[i]. +/// +IRInst* applyAccessChain( + ConstructSSAContext* context, + IRBuilder* builder, + IRInst* accessChain, + IRInst* leafVarValue) +{ + switch (accessChain->op) + { + default: + SLANG_UNEXPECTED("unexpected op along access chain"); + UNREACHABLE_RETURN(leafVarValue); + + case kIROp_Var: + return leafVarValue; + + case kIROp_FieldAddress: + { + SLANG_ASSERT(context->instsToRemove.contains(accessChain)); + + auto baseChain = accessChain->getOperand(0); + auto fieldKey = accessChain->getOperand(1); + auto type = cast<IRPtrTypeBase>(accessChain->getDataType())->getValueType(); + auto baseValue = applyAccessChain(context, builder, baseChain, leafVarValue); + return builder->emitFieldExtract( + type, + baseValue, + fieldKey); + } + + case kIROp_getElementPtr: + { + SLANG_ASSERT(context->instsToRemove.contains(accessChain)); + + auto baseChain = accessChain->getOperand(0); + auto index = accessChain->getOperand(1); + auto type = cast<IRPtrTypeBase>(accessChain->getDataType())->getValueType(); + auto baseValue = applyAccessChain(context, builder, baseChain, leafVarValue); + return builder->emitElementExtract( + type, + baseValue, + index); + } + } +} + +// 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. +IRInst* readVar( + ConstructSSAContext* context, + SSABlockInfo* blockInfo, + IRVar* var); + + /// Try to copy any relevant decorations from `var` over to `val`. + /// +static void cloneRelevantDecorations( + IRVar* var, + IRInst* val) +{ + // Copy selected decorations over from the original + // variable to the SSA variable, when doing so is + // required for semantics. + // + for( auto decoration : var->getDecorations() ) + { + switch(decoration->op) + { + default: + // Ignore most decorations. + // + // TODO: Should we include or exclude by default? + break; + + case kIROp_PreciseDecoration: + case kIROp_NameHintDecoration: + // Copy these decorations if the target doesn't already have them, + // but don't make duplicate decorations on the target. + // + if( !val->findDecorationImpl(decoration->op) ) + { + cloneDecoration(decoration, val, var->getModule()); + } + break; + } + } +} + +// Add a phi node to represent the given variable +PhiInfo* addPhi( + ConstructSSAContext* context, + SSABlockInfo* blockInfo, + IRVar* var) +{ + auto builder = &blockInfo->builder; + + auto valueType = var->getDataType()->getValueType(); + if( auto rate = var->getRate() ) + { + valueType = context->getBuilder()->getRateQualifiedType(rate, valueType); + } + IRParam* phi = builder->createParam(valueType); + cloneRelevantDecorations(var, phi); + + RefPtr<PhiInfo> phiInfo = new PhiInfo(); + context->phiInfos.Add(phi, phiInfo); + + phiInfo->phi = phi; + phiInfo->var = var; + + blockInfo->phis.add(phiInfo); + + return phiInfo; +} + +IRInst* 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. + + IRInst* same = nullptr; + for (auto u : phiInfo->operands) + { + auto usedVal = u.get(); + SLANG_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. + SLANG_UNIMPLEMENTED_X("trivial phi"); + } + + // Removing this phi as trivial may make other phi nodes + // become trivial. We will recognize such candidates + // by looking for phi nodes that use this node. + List<PhiInfo*> otherPhis; + for( auto u = phi->firstUse; u; u = u->nextUse ) + { + auto user = u->user; + if(!user) continue; + if(user == phi) continue; + + if( user->op == kIROp_Param ) + { + auto maybeOtherPhi = (IRParam*) user; + if( auto otherPhiInfo = context->getPhiInfo(maybeOtherPhi) ) + { + otherPhis.add(otherPhiInfo); + } + } + } + + // replace uses of the phi (including its possible uses + // of itself) with the unique non-phi value. + phi->replaceUsesWith(same); + + // Clear out the operands to the phi, since they won't + // actually get used in the program any more. + for( auto& u : phiInfo->operands ) + { + u.clear(); + } + + // We will record the value that was used to replace this + // phi, so that we can easily look it up later. + phiInfo->replacement = same; + + // Now that we've cleaned up this phi, we need to consider + // other phis that might have become trivial. + for( auto otherPhi : otherPhis ) + { + tryRemoveTrivialPhi(context, otherPhi); + } + + return same; +} + +IRInst* addPhiOperands( + ConstructSSAContext* context, + SSABlockInfo* blockInfo, + PhiInfo* phiInfo) +{ + auto var = phiInfo->var; + + auto block = blockInfo->block; + + List<IRInst*> operandValues; + for (auto predBlock : block->getPredecessors()) + { + // Precondition: if we have multiple predecessors, then + // each must have only one successor (no critical edges). + // + SLANG_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.getCount(); + phiInfo->operands.setCount(operandCount); + for(UInt ii = 0; ii < operandCount; ++ii) + { + phiInfo->operands[ii].init(phiInfo->phi, operandValues[ii]); + } + + return tryRemoveTrivialPhi(context, phiInfo); +} + +void writeVar( + ConstructSSAContext* /*context*/, + SSABlockInfo* blockInfo, + IRVar* var, + IRInst* 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 (Index ii = 0; ii < blockInfo->phis.getCount(); ++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; +} + +// In some cases we may have a pointer to an IR value that +// represents a phi node that has been replaced with another +// IR value, because we discovered that the phi is no longer +// needed. +// +// The `maybeGetPhiReplacement` function will follow any +// chain of replacements that might be present, so that we +// don't end up referencing a dangling/unused value in +// the code that we generate. +// +IRInst* maybeGetPhiReplacement( + ConstructSSAContext* context, + IRInst* inVal) +{ + IRInst* val = inVal; + + while( val->op == kIROp_Param ) + { + // The value is a parameter, but is it a phi? + IRParam* maybePhi = (IRParam*) val; + RefPtr<PhiInfo> phiInfo = nullptr; + if(!context->phiInfos.TryGetValue(maybePhi, phiInfo)) + break; + + // Okay, this is indeed a phi we are adding, but + // is it one that got replaced? + if(!phiInfo->replacement) + break; + + // The phi we want to use got replaced, so we + // had better use the replacement instead. + val = phiInfo->replacement; + } + + return val; +} + +IRInst* readVarRec( + ConstructSSAContext* context, + SSABlockInfo* blockInfo, + IRVar* var) +{ + IRInst* 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->getDataType()->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); + + // If `val` represents a phi node (block parameter) then + // it is possible that some of the operations above might + // have caused it to be replaced with another value, + // and in that case we had better not return it to + // be referenced in user code. + // + // Note: it is okay for the `valueForVar` map that + // we update in `writeVar` to use the old value, so long + // as we do this replacement logic anywhere we might read + // from that map. + // + val = maybeGetPhiReplacement(context, val); + + return val; +} + + + +IRInst* 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. + IRInst* val = nullptr; + if (blockInfo->valueForVar.TryGetValue(var, val)) + { + // Hooray, we found a value to use, and we + // can proceed without too many complications. + + // Just like in the `readVarRec` case above, we need + // to handle the case where `val` might represent + // a phi node that has subsequently been replaced. + // + val = maybeGetPhiReplacement(context, val); + + 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.setInsertBefore(ii); + + switch (ii->op) + { + default: + // Ordinary instruction -> leave as-is + break; + + case kIROp_Store: + { + auto storeInst = (IRStore*)ii; + auto ptrArg = storeInst->ptr.get(); + auto valArg = storeInst->val.get(); + + 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.get(); + + if (auto var = asPromotableVarAccessChain(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); + + cloneRelevantDecorations(var, val); + + val = applyAccessChain(context, &blockInfo->builder, ptrArg, val); + + // 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; + + case kIROp_getElementPtr: + case kIROp_FieldAddress: + { + auto ptrArg = ii->getOperand(0); + if (auto var = asPromotableVarAccessChain(context, ptrArg)) + { + context->instsToRemove.add(ii); + } + } + break; + + + } + } + + auto terminator = block->getTerminator(); + SLANG_ASSERT(terminator); + blockInfo->builder.setInsertBefore(terminator); + + // 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 = cast<IRBlock>(edgeUse->getUser()->parent); + auto succ = cast<IRBlock>(edgeUse->get()); + + IRBuilder builder; + builder.sharedBuilder = &context->sharedBuilder; + builder.setInsertInto(pred); + + // Create a new block that will sit "along" the edge + IRBlock* edgeBlock = builder.createBlock(); + + edgeUse->debugValidate(); + + // The predecessor block should now branch to + // the edge block. + edgeUse->set(edgeBlock); + + // The edge block should branch (unconditionally) + // to the successor block. + builder.setInsertInto(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.getCount() == 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->getBlocks()) + { + auto blockInfo = new SSABlockInfo(); + blockInfo->block = bb; + + blockInfo->builder.sharedBuilder = &context->sharedBuilder; + blockInfo->builder.setInsertBefore(bb->getLastInst()); + + context->blockInfos.Add(bb, blockInfo); + } + for(auto bb : globalVal->getBlocks()) + { + 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->getBlocks()) + { + auto blockInfo = *context->blockInfos.TryGetValue(bb); + + for (auto phiInfo : blockInfo->phis) + { + // If we replaced this phi with another value, + // then we had better not include it in the result. + if (phiInfo->replacement) + 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); + + IRInst* operandVal = phiInfo->operands[predIndex].get(); + + 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->getBlocks()) + { + auto blockInfo = * context->blockInfos.TryGetValue(bb); + + // Sanity check: all blocks should be filled and sealed. + SLANG_ASSERT(blockInfo->isSealed); + SLANG_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.getCount(); + if (addedArgCount == 0) + continue; + + // We need to replace the terminator instruction with one that + // has additional arguments. + + IRTerminatorInst* oldTerminator = bb->getTerminator(); + SLANG_ASSERT(oldTerminator); + + blockInfo->builder.setInsertInto(bb); + + auto oldArgCount = oldTerminator->getOperandCount(); + auto newArgCount = oldArgCount + addedArgCount; + + List<IRInst*> newArgs; + for (UInt aa = 0; aa < oldArgCount; ++aa) + { + newArgs.add(oldTerminator->getOperand(aa)); + } + for (Index aa = 0; aa < addedArgCount; ++aa) + { + newArgs.add(blockInfo->successorArgs[aa]); + } + + IRTerminatorInst* newTerminator = (IRTerminatorInst*)blockInfo->builder.emitIntrinsicInst( + oldTerminator->getFullType(), + oldTerminator->op, + newArgCount, + newArgs.getBuffer()); + + // Transfer decorations (a terminator should have no children) over to the new instruction. + // + oldTerminator->transferDecorationsTo(newTerminator); + + // A terminator better not have uses, so we shouldn't have + // to replace them. + SLANG_ASSERT(!oldTerminator->firstUse); + + + // Okay, we should be clear to remove the old terminator + oldTerminator->removeAndDeallocate(); + } + + // Remove all the instructions we marked for deletion along + // the way. + // + // Currently these are "access chain" instructions for + // loads from (parts of) variables that got promoted. + for (auto inst : context->instsToRemove) + { + // TODO: do we need to be careful here in case one + // of thes operations still has uses, as part of + // another to-be-remvoed instruction? + + inst->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; + + context.builder.sharedBuilder = &context.sharedBuilder; + context.builder.setInsertInto(module->moduleInst); + + constructSSA(&context); +} + +void constructSSA(IRModule* module, IRInst* globalVal) +{ + switch (globalVal->op) + { + case kIROp_Func: + case kIROp_GlobalVar: + case kIROp_GlobalConstant: + constructSSA(module, (IRGlobalValueWithCode*)globalVal); + + default: + break; + } +} + +void constructSSA(IRModule* module) +{ + for(auto ii : module->getGlobalInsts()) + { + constructSSA(module, ii); + } +} + +} |
