diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2018-03-03 07:16:08 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-03-03 07:16:08 -0800 |
| commit | 1fef9b4abfce5ace686a6acc772c605503f825fd (patch) | |
| tree | 292adbedff59c0db68550ff7e8c14cb3b9edc694 | |
| parent | 41dc26b9ef501e23563bdb0705ceecb15fd6c18d (diff) | |
IR: next phase of "everything is an instruction" (#433)
The main practical change here is that things that used to be `IRValue`s, like literals, are now being expressed as instructions in the global scope.
In order to validate that things are actually being handled correctly, this change introduces an explicit "validation" pass that can be run on the IR to check for different invariants (although it doesn't check many of the important ones right now). I've left the validation pass turned off by default, but with a command-line flag to enable it. We may want to make it be on by default in debug builds, just to keep us honest. The main invariant for the moment is that when on IR instruction is used as an operand to another, it had better come from the same IR module.
Some of the existing passes were violating this rule, in particular when it came to cloning of witness tables related to global generic parameter substitution. Those features can in theory be handled better now by allowing `specialize` instructions at other scopes, but I didn't want to over-complicate this change, so I make just enough fixes to ensure that these steps always clone witness tables they get from the "symbols" on an IR specialization context. In order for this to work when recursively specializing, I had to ensure that the logic for generic specialization had a notion of a "parent" specialization context that it would fall back to to perform cloning when necessary.
This change keeps the logic that was caching and re-using the instructions for literal values within a module, but adds some logic that isn't really being tested right now for picking the right parent instruction to insert a constant instruction into. This logic doesn't trigger right now because all of the cases we are using it on have zero operands (and so they always get "hoisted" to the global scope), but eventually for things like types we want to be able to support instructions with operands (e.g., `vector<float, 4>`) and handle the case where some of those operands come from different scopes (e.g., when nested inside a generic).
The final change here is mostly cosmetic: the `IRBuilder` is now more abstract about where insertion occurs: it tracks a single `IRParentInst` to insert into, and then an optional `IRInst` to insert before. In the common case, that parent is an `IRBlock`, but it could conceivably also be the global scope, or a witness table, etc. Use sites where we used to change those fields directly now use distinct methods `setInsertInto(parent)` and `setInsertBefore(inst)` which capture the two cases we care about. Accessors are also defined to extract the current block (if the current parent is a block), and the current "function" (global value with code, if the current parent is a global value with code, or a block inside one).
With this work in place, it should be possible for a follow-on change to start putting `specialize` instructions at the global scope and thus clean up some of the on-the-fly specialization work. This work should also help with some of the requirements around a distinct IR-level type system and more explicit generics.
| -rw-r--r-- | slang.h | 1 | ||||
| -rw-r--r-- | source/slang/compiler.h | 1 | ||||
| -rw-r--r-- | source/slang/diagnostic-defs.h | 4 | ||||
| -rw-r--r-- | source/slang/diagnostics.cpp | 6 | ||||
| -rw-r--r-- | source/slang/diagnostics.h | 4 | ||||
| -rw-r--r-- | source/slang/emit.cpp | 5 | ||||
| -rw-r--r-- | source/slang/ir-constexpr.cpp | 6 | ||||
| -rw-r--r-- | source/slang/ir-inst-defs.h | 60 | ||||
| -rw-r--r-- | source/slang/ir-insts.h | 23 | ||||
| -rw-r--r-- | source/slang/ir-legalize-types.cpp | 3 | ||||
| -rw-r--r-- | source/slang/ir-ssa.cpp | 15 | ||||
| -rw-r--r-- | source/slang/ir-validate.cpp | 182 | ||||
| -rw-r--r-- | source/slang/ir-validate.h | 35 | ||||
| -rw-r--r-- | source/slang/ir.cpp | 509 | ||||
| -rw-r--r-- | source/slang/ir.h | 15 | ||||
| -rw-r--r-- | source/slang/lower-to-ir.cpp | 44 | ||||
| -rw-r--r-- | source/slang/options.cpp | 4 | ||||
| -rw-r--r-- | source/slang/slang.natvis | 6 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj | 2 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj.filters | 2 | ||||
| -rw-r--r-- | tests/ir/loop.slang.expected | 81 |
21 files changed, 804 insertions, 204 deletions
@@ -1241,6 +1241,7 @@ namespace slang #include "source/slang/ir-constexpr.cpp" #include "source/slang/ir-legalize-types.cpp" #include "source/slang/ir-ssa.cpp" +#include "source/slang/ir-validate.cpp" #include "source/slang/legalize-types.cpp" #include "source/slang/lexer.cpp" #include "source/slang/mangle.cpp" diff --git a/source/slang/compiler.h b/source/slang/compiler.h index a91e03c55..6d0475a05 100644 --- a/source/slang/compiler.h +++ b/source/slang/compiler.h @@ -275,6 +275,7 @@ namespace Slang bool shouldDumpIntermediates = false; bool shouldDumpIR = false; + bool shouldValidateIR = false; bool shouldSkipCodegen = false; // How should `#line` directives be emitted (if at all)? diff --git a/source/slang/diagnostic-defs.h b/source/slang/diagnostic-defs.h index 06e553072..14efdb9a9 100644 --- a/source/slang/diagnostic-defs.h +++ b/source/slang/diagnostic-defs.h @@ -286,7 +286,9 @@ DIAGNOSTIC(40005, Error, topLevelModuleUsedWithoutSpecifyingBinding, "top level DIAGNOSTIC(49999, Error, unknownSystemValueSemantic, "unknown system-value semantic '$0'") -DIAGNOSTIC(40006, Error, needCompileTimeConstant, "expected a compile-time constant"); +DIAGNOSTIC(40006, Error, needCompileTimeConstant, "expected a compile-time constant") + +DIAGNOSTIC(40007, Internal, irValidationFailed, "IR validation failed: $0") // // 5xxxx - Target code generation. diff --git a/source/slang/diagnostics.cpp b/source/slang/diagnostics.cpp index 64713072d..03acf50dc 100644 --- a/source/slang/diagnostics.cpp +++ b/source/slang/diagnostics.cpp @@ -93,6 +93,12 @@ SourceLoc const& getDiagnosticPos(TypeExp const& typeExp) return typeExp.exp->loc; } +SourceLoc const& getDiagnosticPos(IRInst* inst) +{ + return inst->sourceLoc; +} + + // Take the format string for a diagnostic message, along with its arguments, and turn it into a static void formatDiagnosticMessage(StringBuilder& sb, char const* format, int argCount, DiagnosticArg const* const* args) { diff --git a/source/slang/diagnostics.h b/source/slang/diagnostics.h index f92df030b..9bc3b3d8c 100644 --- a/source/slang/diagnostics.h +++ b/source/slang/diagnostics.h @@ -94,11 +94,13 @@ namespace Slang inline SourceLoc const& getDiagnosticPos(SourceLoc const& pos) { return pos; } class SyntaxNode; - class ShaderClosure; SourceLoc const& getDiagnosticPos(SyntaxNode const* syntax); SourceLoc const& getDiagnosticPos(Token const& token); SourceLoc const& getDiagnosticPos(TypeExp const& typeExp); + struct IRInst; + SourceLoc const& getDiagnosticPos(IRInst* inst); + template<typename T> SourceLoc getDiagnosticPos(RefPtr<T> const& ptr) { diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp index 189c1c4bd..fa090121f 100644 --- a/source/slang/emit.cpp +++ b/source/slang/emit.cpp @@ -3,6 +3,7 @@ #include "ir-insts.h" #include "ir-ssa.h" +#include "ir-validate.h" #include "legalize-types.h" #include "lower-to-ir.h" #include "mangle.h" @@ -8200,6 +8201,7 @@ String emitEntryPoint( typeLegalizationContext.session = entryPoint->compileRequest->mSession; IRModule* irModule = getIRModule(irSpecializationState); + auto compileRequest = translationUnit->compileRequest; typeLegalizationContext.irModule = irModule; @@ -8208,6 +8210,8 @@ String emitEntryPoint( entryPoint, &sharedContext.extensionUsageTracker); + validateIRModuleIfEnabled(compileRequest, irModule); + // If the user specified the flag that they want us to dump // IR, then do it here, for the target-specific, but // un-specialized IR. @@ -8254,6 +8258,7 @@ String emitEntryPoint( // so that we can work with the individual fields). constructSSA(irModule); + validateIRModuleIfEnabled(compileRequest, irModule); // After all of the required optimization and legalization // passes have been performed, we can emit target code from diff --git a/source/slang/ir-constexpr.cpp b/source/slang/ir-constexpr.cpp index 8463851ac..ca64f5f04 100644 --- a/source/slang/ir-constexpr.cpp +++ b/source/slang/ir-constexpr.cpp @@ -234,8 +234,7 @@ bool propagateConstExprBackward( IRBuilder builder; builder.sharedBuilder = &sharedBuilder; - builder.curFunc = code; - builder.curBlock = nullptr; + builder.setInsertInto(code); bool anyChanges = false; for(;;) @@ -432,9 +431,6 @@ void propagateConstExpr( context.sharedBuilder.module = module; context.sharedBuilder.session = session; context.builder.sharedBuilder = &context.sharedBuilder; - context.builder.curFunc = nullptr; - context.builder.curBlock = nullptr; - // We need to propagate information both forward and backward. diff --git a/source/slang/ir-inst-defs.h b/source/slang/ir-inst-defs.h index cac3c086e..fbb3912d8 100644 --- a/source/slang/ir-inst-defs.h +++ b/source/slang/ir-inst-defs.h @@ -115,22 +115,25 @@ INST(makeStruct, makeStruct, 0, 0) INST(Call, call, 1, 0) -INST(Module, module, 0, PARENT) +/*IRParentInst*/ -INST(Block, block, 0, PARENT) + INST(Module, module, 0, PARENT) -/*IRGlobalValue*/ + INST(Block, block, 0, PARENT) - /*IRGlobalValueWithCode*/ - INST(Func, func, 0, PARENT) - INST(global_var, global_var, 0, 0) - INST(global_constant, global_constant, 0, 0) - INST_RANGE(GlobalValueWithCode, Func, global_constant) + /*IRGlobalValue*/ - INST(witness_table, witness_table, 0, 0) + /*IRGlobalValueWithCode*/ + INST(Func, func, 0, PARENT) + INST(global_var, global_var, 0, 0) + INST(global_constant, global_constant, 0, 0) + INST_RANGE(GlobalValueWithCode, Func, global_constant) + + INST(witness_table, witness_table, 0, 0) INST_RANGE(GlobalValue, Func, witness_table) +INST_RANGE(ParentInst, Module, witness_table) INST(witness_table_entry, witness_table_entry, 2, 0) @@ -241,37 +244,10 @@ INST(BitOr, or , 2, 0) INST(And, logicalAnd, 2, 0) INST(Or, logicalOr, 2, 0) -#if 0 -INST(Assign, assign, 2, 0) -INST(AddAssign, addAssign, 2, 0) -INST(SubAssign, subAssign, 2, 0) -INST(SubAssign, subAssign, 2, 0) - -INTRINSIC(SubAssign) -INTRINSIC(MulAssign) -INTRINSIC(DivAssign) -INTRINSIC(ModAssign) -INTRINSIC(LshAssign) -INTRINSIC(RshAssign) -INTRINSIC(OrAssign) -INTRINSIC(AndAssign) -INTRINSIC(XorAssign) -INTRINSIC(Pos) -#endif - INST(Neg, neg, 1, 0) INST(Not, not, 1, 0) INST(BitNot, bitnot, 1, 0) -#if 0 -INTRINSIC(PreInc) -INTRINSIC(PreDec) -INTRINSIC(PostInc) -INTRINSIC(PostDec) - -INTRINSIC(Sequence) -#endif - INST(Select, select, 3, 0) INST(Dot, dot, 2, 0) @@ -280,18 +256,6 @@ INST(Mul_Vector_Matrix, mulVectorMatrix, 2, 0) INST(Mul_Matrix_Vector, mulMatrixVector, 2, 0) INST(Mul_Matrix_Matrix, mulMatrixMatrix, 2, 0) -#if 0 -INTRINSIC(Mul_Scalar_Scalar) -INTRINSIC(Mul_Vector_Scalar) -INTRINSIC(Mul_Scalar_Vector) -INTRINSIC(Mul_Matrix_Scalar) -INTRINSIC(Mul_Scalar_Matrix) -INTRINSIC(InnerProduct_Vector_Vector) -INTRINSIC(InnerProduct_Vector_Matrix) -INTRINSIC(InnerProduct_Matrix_Vector) -INTRINSIC(InnerProduct_Matrix_Matrix) -#endif - // Texture sampling operation of the form `t.Sample(s,u)` INST(Sample, sample, 3, 0) diff --git a/source/slang/ir-insts.h b/source/slang/ir-insts.h index a3fd97351..a7109948f 100644 --- a/source/slang/ir-insts.h +++ b/source/slang/ir-insts.h @@ -430,16 +430,23 @@ struct IRBuilder IRModule* getModule() { return sharedBuilder->module; } - // The current function and block being inserted into - // (or `null` if we aren't inserting). - IRGlobalValueWithCode* curFunc = nullptr; - IRBlock* curBlock = nullptr; + // The current parent being inserted into (this might + // be the global scope, a function, a block inside + // a function, etc.) + IRParentInst* insertIntoParent = nullptr; // - // An instruction in the current block that we should insert before - IRInst* insertBeforeInst = nullptr; + // An instruction in the current parent that we should insert before + IRInst* insertBeforeInst = nullptr; - IRGlobalValueWithCode* getFunc() { return curFunc; } - IRBlock* getBlock() { return curBlock; } + // Get the current basic block we are inserting into (if any) + IRBlock* getBlock(); + + // Get the current function (or other value with code) + // that we are inserting into (if any). + IRGlobalValueWithCode* getFunc(); + + void setInsertInto(IRParentInst* insertInto); + void setInsertBefore(IRInst* insertBefore); IRBuilderSourceLocRAII* sourceLocInfo = nullptr; diff --git a/source/slang/ir-legalize-types.cpp b/source/slang/ir-legalize-types.cpp index 4570ecf81..7e380e237 100644 --- a/source/slang/ir-legalize-types.cpp +++ b/source/slang/ir-legalize-types.cpp @@ -762,8 +762,7 @@ static LegalVal legalizeInst( // instructions generated will be placed after // the location of the original instruction. auto builder = context->builder; - builder->curBlock = as<IRBlock>(inst->getParent()); - builder->insertBeforeInst = inst->getNextInst(); + builder->setInsertBefore(inst->getNextInst()); LegalVal legalVal = legalizeInst( context, diff --git a/source/slang/ir-ssa.cpp b/source/slang/ir-ssa.cpp index ef1f8c4d8..60ecddfbd 100644 --- a/source/slang/ir-ssa.cpp +++ b/source/slang/ir-ssa.cpp @@ -563,7 +563,7 @@ void processBlock( // Any new instructions we create to represent // the new value will get inserted before whatever // instruction we are working with. - blockInfo->builder.insertBeforeInst = ii; + blockInfo->builder.setInsertBefore(ii); switch (ii->op) { @@ -617,7 +617,7 @@ void processBlock( } } - blockInfo->builder.insertBeforeInst = block->getLastChild(); + blockInfo->builder.setInsertBefore(block->getLastChild()); // Once we are done with all of the instructions // in a block, we can mark it as "filled," which @@ -709,8 +709,7 @@ static void breakCriticalEdges( IRBuilder builder; builder.sharedBuilder = &context->sharedBuilder; - builder.curFunc = globalVal; - builder.curBlock = pred; + builder.setInsertInto(pred); // Create a new block that will sit "along" the edge IRBlock* edgeBlock = builder.createBlock(); @@ -723,7 +722,7 @@ static void breakCriticalEdges( // The edge block should branch (unconditionally) // to the successor block. - builder.curBlock = edgeBlock; + builder.setInsertInto(edgeBlock); builder.emitBranch(succ); // Insert the new block into the block list @@ -763,9 +762,7 @@ void constructSSA(ConstructSSAContext* context) blockInfo->block = bb; blockInfo->builder.sharedBuilder = &context->sharedBuilder; - blockInfo->builder.curBlock = bb; - blockInfo->builder.curFunc = globalVal; - blockInfo->builder.insertBeforeInst = bb->getLastInst(); + blockInfo->builder.setInsertBefore(bb->getLastInst()); context->blockInfos.Add(bb, blockInfo); } @@ -830,7 +827,7 @@ void constructSSA(ConstructSSAContext* context) IRTerminatorInst* oldTerminator = bb->getTerminator(); assert(oldTerminator); - blockInfo->builder.insertBeforeInst = nullptr; + blockInfo->builder.setInsertInto(bb); auto oldArgCount = oldTerminator->getOperandCount(); auto newArgCount = oldArgCount + addedArgCount; diff --git a/source/slang/ir-validate.cpp b/source/slang/ir-validate.cpp new file mode 100644 index 000000000..95b8f2dff --- /dev/null +++ b/source/slang/ir-validate.cpp @@ -0,0 +1,182 @@ +// ir-validate.cpp +#include "ir-validate.h" + +#include "ir.h" +#include "ir-insts.h" + +namespace Slang +{ + struct IRValidateContext + { + // The IR module we are validating. + IRModule* module; + + // A diagnostic sink to send errors to if anything is invalid. + DiagnosticSink* sink; + + DiagnosticSink* getSink() { return sink; } + + // A set of instructions we've seen, to help confirm that + // values are defined before they are used in a given block. + HashSet<IRInst*> seenInsts; + }; + + void validateIRInst( + IRValidateContext* context, + IRInst* inst); + + void validate(IRValidateContext* context, bool condition, IRInst* inst, char const* message) + { + if (!condition) + { + context->getSink()->diagnose(inst, Diagnostics::irValidationFailed, message); + } + } + + void validateIRInstChildren( + IRValidateContext* context, + IRParentInst* parent) + { + IRInst* prevChild = nullptr; + for (auto child = parent->getFirstChild(); child; child = child->getNextInst()) + { + // We need to check the integrity of the parent/next/prev links of + // all of our instructions + validate(context, child->parent == parent, child, "parent link"); + validate(context, child->prev == prevChild, child, "next/prev link"); + + // Recursively validate the instruction itself. + validateIRInst(context, child); + + prevChild = child; + } + } + + void validateIRInstOperand( + IRValidateContext* context, + IRInst* inst, + IRUse* operandUse) + { + // The `IRUse` for the operand had better have `inst` as its user. + validate(context, operandUse->getUser() == inst, inst, "operand user"); + + // The value we are using needs to fit into one of a few cases. + // + // * If the parent of `inst` and of `operand` is the same block, then + // we require that `operand` is defined before `inst` + // + // * If the parents of `inst` and `operand` are both blocks in the + // same functin, then the block defining `operand` must dominate + // the block defining `inst`. + // + // * Otherwise, we simply require that the parent of `operand` be + // an ancestor (transitive parent) of `inst`. + + auto instParent = inst->getParent(); + + auto operandValue = operandUse->get(); + auto operandParent = operandValue->getParent(); + + if (auto instParentBlock = as<IRBlock>(instParent)) + { + if (auto operandParentBlock = as<IRBlock>(operandParent)) + { + if (instParentBlock == operandParentBlock) + { + // If `operandValue` precedes `inst`, then we should + // have already seen it, because we scan parent instructions + // in order. + validate(context, context->seenInsts.Contains(operandValue), inst, "def must come before use in same block"); + return; + } + + auto instFunc = instParentBlock->getParent(); + auto operandFunc = operandParentBlock->getParent(); + if (instFunc == operandFunc) + { + // The two instructions are defined in different blocks of + // the same function (or another value with code). We need + // to validate that `operandParentBlock` dominates `instParentBlock`. + // + // TODO: implement this validation once we compute dominator trees. + // + // validate(context, operandParentBlock->dominates(instParentBlock), inst, "def must dominate use"); + return; + } + } + } + + // If the special cases above did not trigger, then either the two values + // are nested in the same parent, but that parent isn't a block, or they + // are nested in distinct parents, and those parents aren't both children + // of a function. + // + // In either case, we need to enforce that the parent of `operand` needs + // to be an ancestor of `inst`. + // + for (auto pp = instParent; pp; pp = pp->getParent()) + { + if (pp == operandParent) + return; + } + // + // We failed to find `operandParent` while walking the ancestors of `inst`, + // so something had gone wrong. + validate(context, false, inst, "def must be ancestor of use"); + } + + void validateIRInstOperands( + IRValidateContext* context, + IRInst* inst) + { + UInt operandCount = inst->getOperandCount(); + for (UInt ii = 0; ii < operandCount; ++ii) + { + validateIRInstOperand(context, inst, inst->getOperands() + ii); + } + } + + void validateIRInst( + IRValidateContext* context, + IRInst* inst) + { + // Validate that any operands of the instruction are used appropriately + validateIRInstOperands(context, inst); + context->seenInsts.Add(inst); + + // If `inst` is itself a parent instruction, then we need to recursively + // validate its children. + if (auto parent = as<IRParentInst>(inst)) + { + validateIRInstChildren(context, parent); + } + } + + void validateIRModule(IRModule* module, DiagnosticSink* sink) + { + IRValidateContext contextStorage; + IRValidateContext* context = &contextStorage; + context->module = module; + context->sink = sink; + + auto moduleInst = module->moduleInst; + + validate(context, moduleInst != nullptr, moduleInst, "module instruction"); + validate(context, moduleInst->parent == nullptr, moduleInst, "module instruction parent"); + validate(context, moduleInst->prev == nullptr, moduleInst, "module instruction prev"); + validate(context, moduleInst->next == nullptr, moduleInst, "module instruction next"); + + validateIRInst(context, module->moduleInst); + } + + void validateIRModuleIfEnabled( + CompileRequest* compileRequest, + IRModule* module) + { + if (!compileRequest->shouldValidateIR) + return; + + auto sink = &compileRequest->mSink; + validateIRModule(module, sink); + } +} diff --git a/source/slang/ir-validate.h b/source/slang/ir-validate.h new file mode 100644 index 000000000..0ebc69019 --- /dev/null +++ b/source/slang/ir-validate.h @@ -0,0 +1,35 @@ +// ir-validate.h +#pragma once + +namespace Slang +{ + class CompileRequest; + class DiagnosticSink; + struct IRModule; + + + // Validate that an IR module obeys the invariants we need to enforce. + // For example: + // + // * Confirm that linked lists for children and for use-def chains are consistent + // (e.g., x.next.prev == x) + // + // * Confirm that parent/child relationships are correct (e.g., if is `x` is in + // `y.children`, then `x.parent == y` + // + // * Confirm that every operand of an instruction is valid to reference (i.e., it + // must either be defined earlier in the same block, in a different block that + // dominates the current one, or in a parent instruction of the block. + // + // * Confirm that every block ends with a terminator, and there are no terminators + // elsewhere in a block. + // + // * Confirm that all the parameters of a block come before any "ordinary" instructions. + void validateIRModule(IRModule* module, DiagnosticSink* sink); + + // A wrapper that calls `validateIRModule` only when IR validation is enabled + // for the given compile request. + void validateIRModuleIfEnabled( + CompileRequest* compileRequest, + IRModule* module); +} diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp index b4d19c0d5..aeea83760 100644 --- a/source/slang/ir.cpp +++ b/source/slang/ir.cpp @@ -193,6 +193,48 @@ namespace Slang } } + IRInst* IRBlock::getFirstOrdinaryInst() + { + // Find the last parameter (if any) of the block + auto lastParam = getLastParam(); + if (lastParam) + { + // If there is a last parameter, then the + // instructions after it are the ordinary + // instructions. + return lastParam->getNextInst(); + } + else + { + // If there isn't a last parameter, then + // there must not have been *any* parameters, + // and so the first instruction in the block + // is also the first ordinary one. + return getFirstInst(); + } + } + + IRInst* IRBlock::getLastOrdinaryInst() + { + // Under normal circumstances, the last instruction + // in the block is also the last ordinary instruction. + // However, there is the special case of a block with + // only parameters (which might happen as a temporary + // state while we are building IR). + auto inst = getLastInst(); + + // If the last instruction is a parameter, then + // there are no ordinary instructions, so the last + // one is a null pointer. + if (as<IRParam>(inst)) + return nullptr; + + // Otherwise the last instruction is the last "ordinary" + // instruction as well. + return inst; + } + + // 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. @@ -423,6 +465,38 @@ namespace Slang // + IRBlock* IRBuilder::getBlock() + { + return as<IRBlock>(insertIntoParent); + } + + // Get the current function (or other value with code) + // that we are inserting into (if any). + IRGlobalValueWithCode* IRBuilder::getFunc() + { + auto pp = insertIntoParent; + if (auto block = as<IRBlock>(pp)) + { + pp = pp->getParent(); + } + return as<IRGlobalValueWithCode>(pp); + } + + + void IRBuilder::setInsertInto(IRParentInst* insertInto) + { + insertIntoParent = insertInto; + insertBeforeInst = nullptr; + } + + void IRBuilder::setInsertBefore(IRInst* insertBefore) + { + SLANG_ASSERT(insertBefore); + insertIntoParent = insertBefore->parent; + insertBeforeInst = insertBefore; + } + + // Add an instruction into the current scope void IRBuilder::addInst( IRInst* inst) @@ -430,11 +504,10 @@ namespace Slang if(insertBeforeInst) { inst->insertBefore(insertBeforeInst); - return; } - else if (curBlock) + else if (insertIntoParent) { - inst->insertAtEnd(curBlock); + inst->insertAtEnd(insertIntoParent); } else { @@ -442,6 +515,192 @@ namespace Slang } } + // Given two parent instructions, pick the better one to use as as + // insertion location for a "hoistable" instruction. + // + IRParentInst* mergeCandidateParentsForHoistableInst(IRParentInst* left, IRParentInst* right) + { + // If either `left` or `right` is a block, then we need to be + // a bit careful, because blocks can see other values just using + // the dominance relationship, without a direct parent-child relationship. + // + // First, check if each of `left` and `right` is a block. + // + auto leftBlock = as<IRBlock>(left); + auto rightBlock = as<IRBlock>(right); + // + // As a special case, if both of these are blocks in the same parent, + // then we need to pick between them based on dominance. + // + if (leftBlock && rightBlock && (leftBlock->getParent() == rightBlock->getParent())) + { + // We assume that the order of basic blocks in a function is compatible + // with the dominance relationship (that is, if A dominates B, then + // A comes before B in the list of blocks), so it suffices to pick + // the *later* of the two blocks. + // + // There are ways we could try to speed up this search, but no matter + // what it will be O(n) in the number of blocks, unless we build + // an explicit dominator tree, which is infeasible during IR building. + // Thus we just do a simple linear walk here. + // + // We will start at `leftBlock` and walk forward, until either... + // + for (auto ll = leftBlock; ll; ll = ll->getNextBlock()) + { + // ... we see `rightBlock` (in which case `rightBlock` came later), or ... + // + if (ll == rightBlock) return rightBlock; + } + // + // ... we run out of blocks (in which case `leftBlock` came later). + // + return leftBlock; + } + + // + // If the special case above doesn't apply, then `left` or `right` might + // still be a block, but they aren't blocks nested in the same function. + // We will find the first non-block ancestor of `left` and/or `right`. + // This will either be the inst itself (it is isn't a block), or + // its immediate parent (if it *is* a block). + // + auto leftNonBlock = leftBlock ? leftBlock->getParent() : left; + auto rightNonBlock = rightBlock ? rightBlock->getParent() : right; + + // If either side is null, then take the non-null one. + // + if (!leftNonBlock) return right; + if (!rightNonBlock) return left; + + // If the non-block on the left or right is a descendent of + // the other, then that is what we should use. + // + IRParentInst* parentNonBlock = nullptr; + for (auto ll = leftNonBlock; ll; ll = ll->getParent()) + { + if (ll == rightNonBlock) + { + parentNonBlock = leftNonBlock; + break; + } + } + for (auto rr = rightNonBlock; rr; rr = rr->getParent()) + { + if (rr == leftNonBlock) + { + SLANG_ASSERT(!parentNonBlock); + parentNonBlock = rightNonBlock; + break; + } + } + + // As a matter of validity in the IR, we expect one + // of the two to be an ancestor (in the non-block case), + // because otherwise we'd be violating the basic dominance + // assumptions. + // + SLANG_ASSERT(parentNonBlock); + + // As a fallback, try to use the left parent as a default + // in case things go badly. + // + if (!parentNonBlock) + { + parentNonBlock = leftNonBlock; + } + + IRParentInst* parent = parentNonBlock; + + // At this point we've found a non-block parent where we + // could stick things, but we have to fix things up in + // case we should be inserting into a block beneath + // that non-block parent. + if (leftBlock && (parentNonBlock == leftNonBlock)) + { + // We have a left block, and have picked its parent. + + // It cannot be the case that there is a right block + // with the same parent, or else our special case + // would have triggered at the start. + SLANG_ASSERT(!rightBlock || (parentNonBlock != rightNonBlock)); + + parent = leftBlock; + } + else if (rightBlock && (parentNonBlock == rightNonBlock)) + { + // We have a right block, and have picked its parent. + + // We already tested above, so we know there isn't a + // matching situation on the left side. + + parent = rightBlock; + } + + // Okay, we've picked the parent we want to insert into, + // *but* one last special case arises, because an `IRGlobalValueWithCode` + // is not actually a suitable place to insert instructions. + // Furthermore, there is no actual need to insert instructions at + // that scope, because any parameters, etc. are actually attached + // to the block(s) within the function. + if (auto parentFunc = as<IRGlobalValueWithCode>(parent)) + { + // Insert in the parent of the function (or other value with code). + // We know that the parent must be able to hold ordinary instructions, + // because it was able to hold this `IRGlobalValueWithCode` + parent = parentFunc->getParent(); + } + + return parent; + } + + // Given an instruction that represents a constant, a type, etc. + // Try to "hoist" it as far toward the global scope as possible + // to insert it at a location where it will be maximally visible. + // + void addHoistableInst( + IRBuilder* builder, + IRInst* inst) + { + // Start with the assumption that we would insert this instruction + // into the global scope (the instruction that represents the module) + IRParentInst* parent = builder->getModule()->getModuleInst(); + + // The above decision might be invalid, because there might be + // one or more operands of the instruction that are defined in + // more deeply nested parents than the global scope. + // + // Therefore, we will scan the operands of the instruction, and + // look at the parents that define them. + // + UInt operandCount = inst->getOperandCount(); + for (UInt ii = 0; ii < operandCount; ++ii) + { + auto operand = inst->getOperand(ii); + auto operandParent = operand->getParent(); + + parent = mergeCandidateParentsForHoistableInst(parent, operandParent); + } + + // We better have ended up with a place to insert. + SLANG_ASSERT(parent); + + // If we have chosen to insert into the same parent that the + // IRBuilder is configured to use, then respect its `insertBeforeInst` + // setting. + if (parent == builder->insertIntoParent) + { + builder->addInst(inst); + return; + } + + // Otherwise, we just want to insert at the end of the chosen parent. + // + // TODO: be careful about inserting after the terminator of a block... + + inst->insertAtEnd(parent); + } + static void maybeSetSourceLoc( IRBuilder* builder, IRInst* value) @@ -747,11 +1006,6 @@ namespace Slang UInt valueSize, void const* value) { - // First, we need to pick a good insertion point - // for the instruction, which we do by looking - // at its operands. - // - IRConstant keyInst; memset(&keyInst, 0, sizeof(keyInst)); keyInst.op = op; @@ -781,6 +1035,8 @@ namespace Slang key.inst = irValue; builder->sharedBuilder->constantMap.Add(key, irValue); + addHoistableInst(builder, irValue); + return irValue; } @@ -839,6 +1095,9 @@ namespace Slang kIROp_decl_ref, nullptr); irValue->declRef = DeclRef<Decl>(declRef.decl, declRef.substitutions); + + addHoistableInst(this, irValue); + return irValue; } @@ -1130,13 +1389,19 @@ namespace Slang IRBlock* IRBuilder::emitBlock() { + // Create a block auto bb = createBlock(); - auto f = this->curFunc; + // If we are emitting into a function + // (or another value with code), then + // append the block to the function and + // set this block as the new parent for + // subsequent instructions we insert. + auto f = getFunc(); if (f) { f->addBlock(bb); - this->curBlock = bb; + setInsertInto(bb); } return bb; } @@ -1155,8 +1420,7 @@ namespace Slang IRType* type) { auto param = createParam(type); - - if (auto bb = curBlock) + if (auto bb = getBlock()) { bb->addParam(param); } @@ -2236,16 +2500,45 @@ namespace Slang dump(context, opInfo->name); UInt argCount = inst->getOperandCount(); + UInt ii = 0; + + // Special case: make printing of `call` a bit + // nicer to look at + if (inst->op == kIROp_Call && argCount > 0) + { + dump(context, " "); + auto argVal = inst->getOperand(ii++); + dumpOperand(context, argVal); + } + + bool first = true; dump(context, "("); - for (UInt ii = 0; ii < argCount; ++ii) + for (; ii < argCount; ++ii) { - if (ii != 0) + if (!first) dump(context, ", "); auto argVal = inst->getOperand(ii); dumpOperand(context, argVal); + + first = false; } + + // Special cases: literals and other instructions with no real operands + switch (inst->op) + { + case kIROp_IntLit: + case kIROp_FloatLit: + case kIROp_boolConst: + case kIROp_decl_ref: + dumpOperand(context, inst); + break; + + default: + break; + } + dump(context, ")"); dump(context, "\n"); @@ -3613,7 +3906,7 @@ namespace Slang shared.session = session; IRBuilder builder; builder.sharedBuilder = &shared; - builder.curFunc = func; + builder.setInsertInto(func); // We will start by looking at the return type of the // function, because that will enable us to do an @@ -3671,7 +3964,7 @@ namespace Slang IRInst* returnValue = returnInst->getVal(); // Make sure we add these instructions to the right block - builder.curBlock = bb; + builder.setInsertInto(bb); // Write to our global variable(s) from the value being returned. assign(&builder, resultGlobal, ScalarizedVal::value(returnValue)); @@ -3694,6 +3987,10 @@ namespace Slang // and turn them into global variables. if( auto firstBlock = func->getFirstBlock() ) { + // Any initialization code we insert for parameters needs + // to be at the start of the "ordinary" instructions in the block: + builder.setInsertBefore(firstBlock->getFirstOrdinaryInst()); + UInt paramCounter = 0; for( auto pp = firstBlock->getFirstParam(); pp; pp = pp->getNextParam() ) { @@ -3721,11 +4018,6 @@ namespace Slang // cases. auto paramType = pp->getDataType(); - // Any initialization code we insert nees to be at the start - // of the block: - builder.curBlock = firstBlock; - builder.insertBeforeInst = firstBlock->getFirstInst(); - // First we will special-case stage input/outputs that // don't fit into the standard varying model. // For right now we are only doing special-case handling @@ -3787,8 +4079,7 @@ namespace Slang // Okay, we have a declaration, and we want to modify it! - builder.curBlock = bb; - builder.insertBeforeInst = ii; + builder.setInsertBefore(ii); assign(&builder, globalOutputVal, ScalarizedVal::value(ii->getOperand(2))); } @@ -3857,8 +4148,7 @@ namespace Slang break; } - builder.curBlock = bb; - builder.insertBeforeInst = terminatorInst; + builder.setInsertBefore(terminatorInst); assign(&builder, globalOutputVal, localVal); } @@ -3898,6 +4188,7 @@ namespace Slang for( auto pp = firstBlock->getFirstParam(); pp; ) { auto next = pp->getNextParam(); + pp->removeFromParent(); pp->deallocate(); pp = next; } @@ -4503,7 +4794,7 @@ namespace Slang // Next we are going to clone the actual code. IRBuilder builderStorage = *context->builder; IRBuilder* builder = &builderStorage; - builder->curFunc = clonedValue; + builder->setInsertInto(clonedValue); // We will walk through the blocks of the function, and clone each of them. @@ -4544,7 +4835,7 @@ namespace Slang { assert(cb); - builder->curBlock = cb; + builder->setInsertInto(cb); for (auto oi = ob->getFirstInst(); oi; oi = oi->getNextInst()) { cloneInst(context, builder, oi); @@ -5006,8 +5297,7 @@ namespace Slang RefPtr<GlobalGenericParamSubstitution> createGlobalGenericParamSubstitution( EntryPointRequest * entryPointRequest, ProgramLayout * programLayout, - IRSpecContext* context, - IRModule* originalIRModule); + IRSpecContext* context); struct IRSpecializationState { @@ -5070,7 +5360,7 @@ namespace Slang context->builder = &sharedContext->builderStorage; // Create the GlobalGenericParamSubstitution for substituting global generic types // into user-provided type arguments - auto globalParamSubst = createGlobalGenericParamSubstitution(entryPointRequest, programLayout, context, originalIRModule); + auto globalParamSubst = createGlobalGenericParamSubstitution(entryPointRequest, programLayout, context); context->subst.globalGenParamSubstitutions = globalParamSubst; @@ -5193,6 +5483,8 @@ namespace Slang struct IRGenericSpecContext : IRSpecContextBase { + IRSpecContextBase* parent = nullptr; + IRSharedSpecContext* getShared() { return shared; } // Override the "maybe clone" logic so that we always clone @@ -5216,7 +5508,10 @@ namespace Slang if (context->getSymbols().TryGetValue(mangledName, symbol)) { - return symbol->irGlobalValue; + // Note: the symbols always come from the source module, + // not the destination module, so we may need to clone + // them if we are doing an initialize specialization pass. + return cloneValue(context, symbol->irGlobalValue); } else { @@ -5231,7 +5526,8 @@ namespace Slang subtypeWitness->sup)); if (context->getSymbols().TryGetValue(genericName, symbol)) { - auto specInst = context->builder->emitSpecializeInst(subtypeWitness->sup, symbol->irGlobalValue, subDeclRef->declRef); + auto clonedSymbol = cloneValue(context, symbol->irGlobalValue); + auto specInst = context->builder->emitSpecializeInst(subtypeWitness->sup, clonedSymbol, subDeclRef->declRef); return specInst; } else @@ -5363,7 +5659,14 @@ namespace Slang break; default: - return originalVal; + if (parent) + { + return parent->maybeCloneValue(originalVal); + } + else + { + return originalVal; + } } } @@ -5463,11 +5766,17 @@ namespace Slang } IRFunc* getSpecializedFunc( - IRSharedSpecContext* sharedContext, - IRFunc* genericFunc, - DeclRef<Decl> specDeclRef); + IRSharedSpecContext* sharedContext, + IRSpecContextBase* parentContext, + IRFunc* genericFunc, + DeclRef<Decl> specDeclRef); - IRWitnessTable* specializeWitnessTable(IRSharedSpecContext * sharedContext, IRWitnessTable* originalTable, DeclRef<Decl> specDeclRef, IRWitnessTable* dstTable) + IRWitnessTable* specializeWitnessTable( + IRSharedSpecContext* sharedContext, + IRSpecContextBase* parentContext, + IRWitnessTable* originalTable, + DeclRef<Decl> specDeclRef, + IRWitnessTable* dstTable) { // First, we want to see if an existing specialization // has already been made. To do that we will need to @@ -5503,6 +5812,7 @@ namespace Slang IRGenericSpecContext context; context.shared = sharedContext; + context.parent = parentContext; context.builder = &sharedContext->builderStorage; context.subst = specDeclRef.substitutions; context.subst.genericSubstitutions = newSubst; @@ -5521,7 +5831,7 @@ namespace Slang if (entry->satisfyingVal.get()->op == kIROp_Func) { IRFunc* func = (IRFunc*)entry->satisfyingVal.get(); - auto specFunc = getSpecializedFunc(sharedContext, func, specDeclRef); + auto specFunc = getSpecializedFunc(sharedContext, parentContext, func, specDeclRef); entry->satisfyingVal.set(specFunc); insertGlobalValueSymbol(sharedContext, specFunc); } @@ -5536,9 +5846,10 @@ namespace Slang } IRFunc* getSpecializedFunc( - IRSharedSpecContext* sharedContext, - IRFunc* genericFunc, - DeclRef<Decl> specDeclRef) + IRSharedSpecContext* sharedContext, + IRSpecContextBase* parentContext, + IRFunc* genericFunc, + DeclRef<Decl> specDeclRef) { // First, we want to see if an existing specialization // has already been made. To do that we will need to @@ -5586,6 +5897,7 @@ namespace Slang IRGenericSpecContext context; context.shared = sharedContext; + context.parent = parentContext; context.builder = &sharedContext->builderStorage; context.subst = specDeclRef.substitutions; context.subst.genericSubstitutions = newSubst; @@ -5784,7 +6096,7 @@ namespace Slang // // We will first find or construct a specialized version // of the callee funciton/ - auto specFunc = getSpecializedFunc(sharedContext, genericFunc, specDeclRef); + auto specFunc = getSpecializedFunc(sharedContext, nullptr, genericFunc, specDeclRef); // // Then we will replace the use sites for the `specialize` // instruction with uses of the specialized function. @@ -5797,7 +6109,7 @@ namespace Slang { // specialize a witness table auto originalTable = (IRWitnessTable*)genericVal; - auto specWitnessTable = specializeWitnessTable(sharedContext, originalTable, specDeclRef, nullptr); + auto specWitnessTable = specializeWitnessTable(sharedContext, nullptr, originalTable, specDeclRef, nullptr); witnessTables.AddIfNotExists(specWitnessTable->mangledName, specWitnessTable); specInst->replaceUsesWith(specWitnessTable); specInst->removeAndDeallocate(); @@ -5823,7 +6135,7 @@ namespace Slang IRWitnessTable* genTable = nullptr; if (witnessTables.TryGetValue(genName, genTable)) { - witnessTable = specializeWitnessTable(sharedContext, genTable, srcDeclRef, nullptr); + witnessTable = specializeWitnessTable(sharedContext, nullptr, genTable, srcDeclRef, nullptr); witnessTables.AddIfNotExists(witnessTable->mangledName, witnessTable); } } @@ -5890,11 +6202,23 @@ namespace Slang RefPtr<GlobalGenericParamSubstitution> createGlobalGenericParamSubstitution( EntryPointRequest * entryPointRequest, ProgramLayout * programLayout, - IRSpecContext* context, - IRModule* originalIRModule) + IRSpecContext* context) { RefPtr<GlobalGenericParamSubstitution> globalParamSubst; GlobalGenericParamSubstitution * curTailSubst = nullptr; + + // Because we can't currently put `specialize` instructions inside + // witness tables, or at the global scope, we will track a set of + // witness tables that we need to clone, and then specialize + // from the original module(s) to get what we need. + + struct WitnessTableCloneWorkItem + { + IRWitnessTable* dstTable; + IRWitnessTable* originalTable; + }; + List<WitnessTableCloneWorkItem> witnessTablesToClone; + struct WitnessTableSpecializationWorkItem { IRWitnessTable* dstTable; @@ -5902,6 +6226,10 @@ namespace Slang DeclRef<Decl> specDeclRef; }; List<WitnessTableSpecializationWorkItem> witnessTablesToSpecailize; + + Dictionary<Name*, IRWitnessTable*> witnessTablesByName; + auto namePool = entryPointRequest->compileRequest->getNamePool(); + for (auto param : programLayout->globalGenericParams) { auto paramSubst = new GlobalGenericParamSubstitution(); @@ -5920,41 +6248,64 @@ namespace Slang { if (subtypeWitness->sub->EqualsVal(paramSubst->actualType)) { - auto witnessTableName = getMangledNameForConformanceWitness(subtypeWitness->sub, subtypeWitness->sup); - auto findWitnessTableByName = [&](String name) -> IRGlobalValue* + auto witnessTableName = namePool->getName(getMangledNameForConformanceWitness(subtypeWitness->sub, subtypeWitness->sup)); + auto findWitnessTableByName = [&](Name* name) -> IRWitnessTable* { - for (auto ii : originalIRModule->getGlobalInsts()) - { - auto gv = as<IRGlobalValue>(ii); - if (!gv) - continue; + RefPtr<IRSpecSymbol> symbol; + if (!context->getSymbols().TryGetValue(name, symbol)) + return nullptr; - if (getText(gv->mangledName) == name) - return gv; - } - return nullptr; + return (IRWitnessTable*) symbol->irGlobalValue; }; - auto table = findWitnessTableByName(witnessTableName); + + auto findCloneOfWitnessTableByName = [&](Name* name) -> IRWitnessTable* + { + IRWitnessTable* clonedTable = nullptr; + if (witnessTablesByName.TryGetValue(name, clonedTable)) + return clonedTable; + + IRWitnessTable* originalTable = findWitnessTableByName(name); + if (!originalTable) + return nullptr; + + clonedTable = context->builder->createWitnessTable(); + + WitnessTableCloneWorkItem cloneWorkItem; + cloneWorkItem.originalTable = originalTable; + cloneWorkItem.dstTable = clonedTable; + witnessTablesToClone.Add(cloneWorkItem); + + return clonedTable; + }; + + // First look for a non-generic witness table that matches + auto table = findCloneOfWitnessTableByName(witnessTableName); if (!table) { + // If we didn't find a non-generic table, then maybe we are looking at + // a specialization of a generic witness table. if (auto subDeclRefType = subtypeWitness->sub.As<DeclRefType>()) { auto defaultSubst = createDefaultSubstitutions(entryPointRequest->compileRequest->mSession, subDeclRefType->declRef.getDecl()); - auto genericWitnessTableName = getMangledNameForConformanceWitness(DeclRef<Decl>(subDeclRefType->declRef.getDecl(), defaultSubst), subtypeWitness->sup); - table = findWitnessTableByName(genericWitnessTableName); - SLANG_ASSERT(table); - WitnessTableSpecializationWorkItem workItem; - workItem.srcTable = (IRWitnessTable*)cloneGlobalValue(context, (IRWitnessTable*)(table)); - workItem.dstTable = context->builder->createWitnessTable(); - workItem.dstTable->mangledName = context->getModule()->session->getNameObj(getMangledNameForConformanceWitness(subDeclRefType->declRef, subtypeWitness->sup)); - workItem.specDeclRef = subDeclRefType->declRef; - witnessTablesToSpecailize.Add(workItem); - table = workItem.dstTable; + auto genericWitnessTableName = namePool->getName( + getMangledNameForConformanceWitness(DeclRef<Decl>(subDeclRefType->declRef.getDecl(), defaultSubst), subtypeWitness->sup)); + + IRWitnessTable* genericTable = findCloneOfWitnessTableByName(genericWitnessTableName); + SLANG_ASSERT(genericTable); + + WitnessTableSpecializationWorkItem specializeWorkItem; + specializeWorkItem.srcTable = genericTable; + specializeWorkItem.dstTable = context->builder->createWitnessTable(); + specializeWorkItem.dstTable->mangledName = context->getModule()->session->getNameObj(getMangledNameForConformanceWitness(subDeclRefType->declRef, subtypeWitness->sup)); + specializeWorkItem.specDeclRef = subDeclRefType->declRef; + + witnessTablesToSpecailize.Add(specializeWorkItem); + table = specializeWorkItem.dstTable; } } - else - table = cloneGlobalValue(context, (IRWitnessTable*)(table)); + // We expect to find the table no matter what. SLANG_ASSERT(table); + IRProxyVal * tableVal = new IRProxyVal(); tableVal->inst.init(nullptr, table); paramSubst->witnessTables.Add(KeyValuePair<RefPtr<Type>, RefPtr<Val>>(subtypeWitness->sup, tableVal)); @@ -5962,12 +6313,26 @@ namespace Slang } } } + + for (auto workItem : witnessTablesToClone) + { + cloneWitnessTableWithoutRegistering( + context, + workItem.originalTable, + workItem.dstTable); + } + for (auto workItem : witnessTablesToSpecailize) { int diff = 0; - specializeWitnessTable(context->shared, workItem.srcTable, - workItem.specDeclRef.SubstituteImpl(SubstitutionSet(nullptr, nullptr, globalParamSubst), &diff), workItem.dstTable); + specializeWitnessTable( + context->shared, + context, + workItem.srcTable, + workItem.specDeclRef.SubstituteImpl(SubstitutionSet(nullptr, nullptr, globalParamSubst), &diff), + workItem.dstTable); } + return globalParamSubst; } diff --git a/source/slang/ir.h b/source/slang/ir.h index 95a7a97d1..35a6915f6 100644 --- a/source/slang/ir.h +++ b/source/slang/ir.h @@ -431,6 +431,11 @@ struct IRParentInst : IRInst IRInst* getFirstChild() { return children.first; } IRInst* getLastChild() { return children.last; } IRInstListBase getChildren() { return children; } + + static bool isaImpl(IROp op) + { + return (op >= kIROp_FirstParentInst) && (op <= kIROp_LastParentInst); + } }; // A basic block is a parent instruction that adds the constraint @@ -475,6 +480,16 @@ struct IRBlock : IRParentInst void addParam(IRParam* param); + // The "ordinary" instructions come after the parameters + IRInst* getFirstOrdinaryInst(); + IRInst* getLastOrdinaryInst(); + IRInstList<IRInst> getOrdinaryInsts() + { + return IRInstList<IRInst>( + getFirstOrdinaryInst(), + getLastOrdinaryInst()); + } + // The parent of a basic block is assumed to be a // value with code (e.g., a function, global variable // with initializer, etc.). diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp index 9508bb86d..cfd1c89a4 100644 --- a/source/slang/lower-to-ir.cpp +++ b/source/slang/lower-to-ir.cpp @@ -7,6 +7,7 @@ #include "ir-constexpr.h" #include "ir-insts.h" #include "ir-ssa.h" +#include "ir-validate.h" #include "mangle.h" #include "type-layout.h" #include "visitor.h" @@ -1931,8 +1932,8 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor> { auto builder = getBuilder(); - auto prevBlock = builder->curBlock; - auto parentFunc = prevBlock ? prevBlock->getParent() : builder->curFunc; + auto prevBlock = builder->getBlock(); + auto parentFunc = prevBlock ? prevBlock->getParent() : builder->getFunc(); // If the previous block doesn't already have // a terminator instruction, then be sure to @@ -1944,8 +1945,7 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor> parentFunc->addBlock(block); - builder->curFunc = parentFunc; - builder->curBlock = block; + builder->setInsertInto(block); } // Start a new block at the current location. @@ -2495,7 +2495,7 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor> // Remember the initial block so that we can add to it // after we've collected all the `case`s - auto initialBlock = builder->curBlock; + auto initialBlock = builder->getBlock(); // Next, create a block to use as the target for any `break` statements auto breakLabel = createBlock(); @@ -2504,8 +2504,7 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor> // that we can find it for nested statements. context->shared->breakLabels.Add(stmt, breakLabel); - builder->curFunc = initialBlock->getParent(); - builder->curBlock = nullptr; + builder->setInsertInto(initialBlock->getParent()); // Iterate over the body of the statement, looking // for `case` or `default` statements: @@ -2525,10 +2524,11 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor> // Double check that we aren't in the initial // block, so we don't get tripped up on an // empty `switch`. - if(builder->curBlock != initialBlock) + auto curBlock = builder->getBlock(); + if(curBlock != initialBlock) { // Is the block already terminated? - if(!builder->curBlock->getTerminator()) + if(!curBlock->getTerminator()) { // Not terminated, so add one. builder->emitBreak(breakLabel); @@ -2542,7 +2542,7 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor> // Now that we've collected the cases, we are // prepared to emit the `switch` instruction // itself. - builder->curBlock = initialBlock; + builder->setInsertInto(initialBlock); builder->emitSwitch( conditionVal, breakLabel, @@ -2819,9 +2819,13 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> context->irBuilder->createWitnessTableEntry(witnessTable, context->irBuilder->getDeclRefVal(subInheritanceDeclRef), cpyTable); - // HACK: we are re-using the entries in a pre-existing table here, - // which is not how things are supposed to work. - cpyTable->children = witnessTable->children; + // We need to copy all the entries from the original table to this new table. + for (auto entry : witnessTable->getEntries()) + { + context->irBuilder->createWitnessTableEntry(cpyTable, + entry->requirementKey.get(), + entry->satisfyingVal.get()); + } witnessTablesDictionary.Add(cpyTable->mangledName, cpyTable); walkInheritanceHierarchyAndCreateWitnessTableCopies(witnessTable, subType, subInheritanceDeclRef.getDecl()); @@ -3024,7 +3028,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> IRBuilder subBuilderStorage = *getBuilder(); IRBuilder* subBuilder = &subBuilderStorage; - subBuilder->curFunc = irGlobal; + subBuilder->setInsertInto(irGlobal); IRGenContext subContextStorage = *context; IRGenContext* subContext = &subContextStorage; @@ -3034,7 +3038,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> // TODO: set up a parent IR decl to put the instructions into IRBlock* entryBlock = subBuilder->emitBlock(); - subBuilder->curBlock = entryBlock; + subBuilder->setInsertInto(entryBlock); LoweredValInfo initVal = lowerLValueExpr(subContext, initExpr); subContext->irBuilder->emitReturn(getSimpleVal(subContext, initVal)); @@ -3570,7 +3574,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> // need to create an IR function here IRFunc* irFunc = subBuilder->createFunc(); - subBuilder->curFunc = irFunc; + subBuilder->setInsertInto(irFunc); trySetMangledName(irFunc, decl); @@ -3673,7 +3677,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> // This is a function definition, so we need to actually // construct IR for the body... IRBlock* entryBlock = subBuilder->emitBlock(); - subBuilder->curBlock = entryBlock; + subBuilder->setInsertInto(entryBlock); UInt paramTypeIndex = 0; for( auto paramInfo : parameterLists.params ) @@ -3790,7 +3794,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> // We need to carefully add a terminator instruction to the end // of the body, in case the user didn't do so. - if (!subContext->irBuilder->curBlock->getTerminator()) + if (!subContext->irBuilder->getBlock()->getTerminator()) { if (irResultType->Equals(context->getSession()->getVoidType())) { @@ -4227,6 +4231,8 @@ IRModule* generateIRForTranslationUnit( ensureDecl(context, decl); } + validateIRModuleIfEnabled(compileRequest, module); + // We will perform certain "mandatory" optimization passes now. // These passes serve two purposes: // @@ -4267,6 +4273,8 @@ IRModule* generateIRForTranslationUnit( // "fragile" in that we'd now need to recompile when // a module we depend on changes. + validateIRModuleIfEnabled(compileRequest, module); + // 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/options.cpp b/source/slang/options.cpp index fc4eea3fc..5929adabb 100644 --- a/source/slang/options.cpp +++ b/source/slang/options.cpp @@ -276,6 +276,10 @@ struct OptionsParser { requestImpl->shouldDumpIR = true; } + else if(argStr == "-validate-ir" ) + { + requestImpl->shouldValidateIR = true; + } else if(argStr == "-skip-codegen" ) { requestImpl->shouldSkipCodegen = true; diff --git a/source/slang/slang.natvis b/source/slang/slang.natvis index 1dd3b5ef5..489005620 100644 --- a/source/slang/slang.natvis +++ b/source/slang/slang.natvis @@ -92,6 +92,7 @@ </ArrayItems> </Expand> </Synthetic> + <Item Name="[parent]">parent</Item> <Synthetic Name="[uses]"> <Expand> <LinkedListItems> @@ -117,6 +118,7 @@ </LinkedListItems> </Expand> </Synthetic> + <Item Name="[parent]">parent</Item> <Synthetic Name="[uses]"> <Expand> <LinkedListItems> @@ -142,6 +144,7 @@ </LinkedListItems> </Expand> </Synthetic> + <Item Name="[parent]">parent</Item> <Synthetic Name="[uses]"> <Expand> <LinkedListItems> @@ -159,7 +162,8 @@ <Expand> <Item Name="[op]">op</Item> <Item Name="[type]">type</Item> - <ExpandedItem>declRef</ExpandedItem> + <Item Name="[declRef]">declRef</Item> + <Item Name="[parent]">parent</Item> </Expand> </Type> <Type Name="Slang::IRUse"> diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj index c36045c24..a0ca926be 100644 --- a/source/slang/slang.vcxproj +++ b/source/slang/slang.vcxproj @@ -183,6 +183,7 @@ <ClInclude Include="ir-ssa.h" /> <ClInclude Include="ir-type-defs.h" /> <ClInclude Include="ir-types.h" /> + <ClInclude Include="ir-validate.h" /> <ClInclude Include="ir.h" /> <ClInclude Include="legalize-types.h" /> <ClInclude Include="lexer.h" /> @@ -225,6 +226,7 @@ <ClCompile Include="ir-constexpr.cpp" /> <ClCompile Include="ir-legalize-types.cpp" /> <ClCompile Include="ir-ssa.cpp" /> + <ClCompile Include="ir-validate.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 ee4576d0a..55140a4da 100644 --- a/source/slang/slang.vcxproj.filters +++ b/source/slang/slang.vcxproj.filters @@ -50,6 +50,7 @@ <ClInclude Include="ir-types.h" /> <ClInclude Include="ir-type-defs.h" /> <ClInclude Include="type-system-shared.h" /> + <ClInclude Include="ir-validate.h" /> </ItemGroup> <ItemGroup> <ClCompile Include="check.cpp" /> @@ -83,6 +84,7 @@ <ClCompile Include="memory_pool.cpp" /> <ClCompile Include="ir-constexpr.cpp" /> <ClCompile Include="type-system-shared.cpp" /> + <ClCompile Include="ir-validate.cpp" /> </ItemGroup> <ItemGroup> <CustomBuild Include="core.meta.slang" /> diff --git a/tests/ir/loop.slang.expected b/tests/ir/loop.slang.expected index 0ff22c2b5..2a44ab8de 100644 --- a/tests/ir/loop.slang.expected +++ b/tests/ir/loop.slang.expected @@ -4,53 +4,56 @@ standard error = { ir_global_var @_SV01s : Ptr<@ThreadGroup vector<float,4>[64]>; ir_global_var @_SV05input : Ptr<StructuredBuffer<vector<float,4>>>; +let %1 : int = integer_constant(1) +let %2 : int = integer_constant(64) ir_func @_S031GroupMemoryBarrierWithGroupSyncp0pV : () -> void; ir_global_var @_SV06output : Ptr<RWStructuredBuffer<vector<float,4>>>; +let %3 : int = integer_constant(0) ir_func @_S04mainp3puuuV : (uint, uint, uint) -> void { -block %1( - param %2 : uint, - param %3 : uint, - param %4 : uint): - let %5 : Ptr<vector<float,4>> = getElementPtr(@_SV01s, %3) - let %6 : StructuredBuffer<vector<float,4>> = load(@_SV05input) - let %7 : vector<float,4> = bufferLoad(%6, %2) - store(%5, %7) - let %8 : @ConstExpr uint = construct(1) - loop(%9, %10, %11, %8) - -block %9( - param %12 : uint): - let %13 : @ConstExpr uint = construct(64) - let %14 : bool = cmpLT(%12, %13) - ifElse(%14, %15, %10, %15) - -block %15: - call(@_S031GroupMemoryBarrierWithGroupSyncp0pV) - let %16 : Ptr<vector<float,4>> = getElementPtr(@_SV01s, %3) - let %17 : vector<float,4> = load(%16) - let %18 : uint = sub(%3, %12) - let %19 : Ptr<vector<float,4>> = getElementPtr(@_SV01s, %18) +block %4( + param %5 : uint, + param %6 : uint, + param %7 : uint): + let %8 : Ptr<vector<float,4>> = getElementPtr(@_SV01s, %6) + let %9 : StructuredBuffer<vector<float,4>> = load(@_SV05input) + let %10 : vector<float,4> = bufferLoad(%9, %5) + store(%8, %10) + let %11 : @ConstExpr uint = construct(1) + loop(%12, %13, %14, %11) + +block %12( + param %15 : uint): + let %16 : @ConstExpr uint = construct(64) + let %17 : bool = cmpLT(%15, %16) + ifElse(%17, %18, %13, %18) + +block %18: + call @_S031GroupMemoryBarrierWithGroupSyncp0pV() + let %19 : Ptr<vector<float,4>> = getElementPtr(@_SV01s, %6) let %20 : vector<float,4> = load(%19) - let %21 : vector<float,4> = add(%17, %20) - store(%16, %21) - unconditionalBranch(%11) - -block %11: - let %22 : @ConstExpr uint = construct(1) - let %23 : uint = shl(%12, %22) - unconditionalBranch(%9, %23) - -block %10: - call(@_S031GroupMemoryBarrierWithGroupSyncp0pV) - let %24 : RWStructuredBuffer<vector<float,4>> = load(@_SV06output) - let %25 : Ptr<vector<float,4>> = bufferElementRef(%24, %2) - let %26 : Ptr<vector<float,4>> = getElementPtr(@_SV01s, 0) - let %27 : vector<float,4> = load(%26) - store(%25, %27) + let %21 : uint = sub(%6, %15) + let %22 : Ptr<vector<float,4>> = getElementPtr(@_SV01s, %21) + let %23 : vector<float,4> = load(%22) + let %24 : vector<float,4> = add(%20, %23) + store(%19, %24) + unconditionalBranch(%14) + +block %14: + let %25 : @ConstExpr uint = construct(1) + let %26 : uint = shl(%15, %25) + unconditionalBranch(%12, %26) + +block %13: + call @_S031GroupMemoryBarrierWithGroupSyncp0pV() + let %27 : RWStructuredBuffer<vector<float,4>> = load(@_SV06output) + let %28 : Ptr<vector<float,4>> = bufferElementRef(%27, %5) + let %29 : Ptr<vector<float,4>> = getElementPtr(@_SV01s, 0) + let %30 : vector<float,4> = load(%29) + store(%28, %30) return_void() } |
