diff options
| author | Ellie Hermaszewska <ellieh@nvidia.com> | 2024-10-29 14:49:26 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-10-29 14:49:26 +0800 |
| commit | f65d756bff8d4c5cbc15bd0322a2ae8e6b896a21 (patch) | |
| tree | ea1d61342cd29368e19135000ec2948813096205 /source/slang/slang-ir-validate.cpp | |
| parent | a729c15e9dce9f5116a38afc66329ab2ca4cea54 (diff) | |
format
* format
* Minor test fixes
* enable checking cpp format in ci
Diffstat (limited to 'source/slang/slang-ir-validate.cpp')
| -rw-r--r-- | source/slang/slang-ir-validate.cpp | 652 |
1 files changed, 329 insertions, 323 deletions
diff --git a/source/slang/slang-ir-validate.cpp b/source/slang/slang-ir-validate.cpp index a4a2921fe..36cb518c4 100644 --- a/source/slang/slang-ir-validate.cpp +++ b/source/slang/slang-ir-validate.cpp @@ -1,404 +1,410 @@ // slang-ir-validate.cpp #include "slang-ir-validate.h" -#include "slang-ir.h" -#include "slang-ir-insts.h" #include "slang-ir-dominators.h" +#include "slang-ir-insts.h" #include "slang-ir-util.h" +#include "slang-ir.h" namespace Slang { - struct IRValidateContext - { - // The IR module we are validating. - IRModule* module; +struct IRValidateContext +{ + // The IR module we are validating. + IRModule* module; - RefPtr<IRDominatorTree> domTree; + RefPtr<IRDominatorTree> domTree; - // A diagnostic sink to send errors to if anything is invalid. - DiagnosticSink* sink; + // A diagnostic sink to send errors to if anything is invalid. + DiagnosticSink* sink; - DiagnosticSink* getSink() { return 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; - }; + // 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 validateIRInst(IRValidateContext* context, IRInst* inst); - void validate(IRValidateContext* context, bool condition, IRInst* inst, char const* message) +void validate(IRValidateContext* context, bool condition, IRInst* inst, char const* message) +{ + if (!condition) { - if (!condition) + if (context) { - if (context) - { - context->getSink()->diagnose(inst, Diagnostics::irValidationFailed, message); - } - else - { - SLANG_ASSERT_FAILURE("IR validation failed"); - } + context->getSink()->diagnose(inst, Diagnostics::irValidationFailed, message); + } + else + { + SLANG_ASSERT_FAILURE("IR validation failed"); } } +} - void validateIRInstChildren( - IRValidateContext* context, - IRInst* parent) +void validateIRInstChildren(IRValidateContext* context, IRInst* parent) +{ + // We want to check that child instructions are correctly + // ordered so that decorations come first, then any parameters, + // and then any ordinary instructions. + // + // We will track what we have seen so far with a simple state + // machine, which in valid IR should proceed monitonically + // up through the following states: + // + enum State { - // We want to check that child instructions are correctly - // ordered so that decorations come first, then any parameters, - // and then any ordinary instructions. - // - // We will track what we have seen so far with a simple state - // machine, which in valid IR should proceed monitonically - // up through the following states: - // - enum State - { - kState_Initial = 0, - kState_AfterDecoration, - kState_AfterParam, - kState_AfterOrdinary, - }; - State state = kState_Initial; - - IRInst* prevChild = nullptr; - bool hasSeenTerminatorInst = false; - for(auto child : parent->getDecorationsAndChildren() ) - { - // 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"); + kState_Initial = 0, + kState_AfterDecoration, + kState_AfterParam, + kState_AfterOrdinary, + }; + State state = kState_Initial; - // Recursively validate the instruction itself. - validateIRInst(context, child); + IRInst* prevChild = nullptr; + bool hasSeenTerminatorInst = false; + for (auto child : parent->getDecorationsAndChildren()) + { + // 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"); - if( as<IRDecoration>(child) ) - { - validate(context, state <= kState_AfterDecoration, child, "decorations must come before other child instructions"); - state = kState_AfterDecoration; - } - else if( as<IRParam, IRDynamicCastBehavior::NoUnwrap>(child) ) - { - validate(context, state <= kState_AfterParam, child, "parameters must come before ordinary instructions"); - state = kState_AfterParam; - } - else - { - state = kState_AfterOrdinary; - } + // Recursively validate the instruction itself. + validateIRInst(context, child); - // Do some extra validation around terminator instructions: - // - // * The last instruction of a block should always be a terminator - // * No other instruction should be a terminator - // - if(as<IRBlock>(parent) && (child == parent->getLastDecorationOrChild())) - { - validate(context, as<IRTerminatorInst>(child) != nullptr, child, "last instruction in block must be terminator"); - } - else - { - validate(context, !as<IRTerminatorInst>(child), child, "terminator must be last instruction in a block"); - } - - if (as<IRTerminatorInst>(child)) - { - validate(context, !hasSeenTerminatorInst, child, "block must not contain more than one terminator"); - hasSeenTerminatorInst = true; - } - prevChild = child; + if (as<IRDecoration>(child)) + { + validate( + context, + state <= kState_AfterDecoration, + child, + "decorations must come before other child instructions"); + state = kState_AfterDecoration; + } + else if (as<IRParam, IRDynamicCastBehavior::NoUnwrap>(child)) + { + validate( + context, + state <= kState_AfterParam, + child, + "parameters must come before ordinary instructions"); + state = kState_AfterParam; + } + else + { + state = kState_AfterOrdinary; } - } - - 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. + // Do some extra validation around terminator instructions: // - // * If the parent of `inst` and of `operand` is the same block, then - // we require that `operand` is defined before `inst` + // * The last instruction of a block should always be a terminator + // * No other instruction should be a terminator // - // * 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(); + if (as<IRBlock>(parent) && (child == parent->getLastDecorationOrChild())) + { + validate( + context, + as<IRTerminatorInst>(child) != nullptr, + child, + "last instruction in block must be terminator"); + } + else + { + validate( + context, + !as<IRTerminatorInst>(child), + child, + "terminator must be last instruction in a block"); + } - if( !operandValue ) + if (as<IRTerminatorInst>(child)) { - // A null operand should almost always be an error, but - // we currently have a few cases where this arises. - // - // TODO: plug the leaks. - return; + validate( + context, + !hasSeenTerminatorInst, + child, + "block must not contain more than one terminator"); + hasSeenTerminatorInst = true; } + 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(); + + if (!operandValue) + { + // A null operand should almost always be an error, but + // we currently have a few cases where this arises. + // + // TODO: plug the leaks. + return; + } - auto operandParent = operandValue->getParent(); + auto operandParent = operandValue->getParent(); - auto instParentBlock = getBlock(inst); - if (instParentBlock) + auto instParentBlock = getBlock(inst); + if (instParentBlock) + { + if (auto operandParentBlock = as<IRBlock>(operandParent)) { - if (auto operandParentBlock = as<IRBlock>(operandParent)) + if (instParentBlock == operandParentBlock) { - if (instParentBlock == operandParentBlock) + // If `operandValue` precedes `inst`, then we should + // have already seen it, because we scan parent instructions + // in order. + if (context) { - // If `operandValue` precedes `inst`, then we should - // have already seen it, because we scan parent instructions - // in order. - if (context) - { - validate(context, context->seenInsts.contains(operandValue), inst, "def must come before use in same block"); - } - return; + validate( + context, + context->seenInsts.contains(operandValue), + inst, + "def must come before use in same block"); } - - 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`. - // - if (context && context->domTree) - { - validate( - context, - context->domTree->dominates(operandParentBlock, 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 allow out-of-order def-use in global scope. - bool allInGlobalScope = inst->getParent() && inst->getParent()->getOp() == kIROp_Module; - if (allInGlobalScope) - { - for (UInt i = 0; i < inst->getOperandCount(); i++) + auto instFunc = instParentBlock->getParent(); + auto operandFunc = operandParentBlock->getParent(); + if (instFunc == operandFunc) { - auto op = inst->getOperand(i); - if (!op) - continue; - if (!op->getParent()) - continue; - if (op->getParent()->getOp() != kIROp_Module) + // 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`. + // + if (context && context->domTree) { - allInGlobalScope = false; - break; + validate( + context, + context->domTree->dominates(operandParentBlock, instParentBlock), + inst, + "def must dominate use"); } + return; } } - if (allInGlobalScope) - return; - - // Allow exceptions. - switch (inst->getOp()) - { - case kIROp_DifferentiableTypeDictionaryItem: - 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) + // 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(inst->getFullType()) - validateIRInstOperand(context, inst, &inst->typeUse); - - // Avoid validating decoration operands - // since they don't have to conform to inst visibility - // constraints. - // - if (as<IRDecoration>(inst)) + if (pp == operandParent) return; + } - UInt operandCount = inst->getOperandCount(); - for (UInt ii = 0; ii < operandCount; ++ii) + // We allow out-of-order def-use in global scope. + bool allInGlobalScope = inst->getParent() && inst->getParent()->getOp() == kIROp_Module; + if (allInGlobalScope) + { + for (UInt i = 0; i < inst->getOperandCount(); i++) { - validateIRInstOperand(context, inst, inst->getOperands() + ii); + auto op = inst->getOperand(i); + if (!op) + continue; + if (!op->getParent()) + continue; + if (op->getParent()->getOp() != kIROp_Module) + { + allInGlobalScope = false; + break; + } } } + if (allInGlobalScope) + return; - static thread_local bool _enableIRValidationAtInsert = false; - void disableIRValidationAtInsert() + // Allow exceptions. + switch (inst->getOp()) { - _enableIRValidationAtInsert = false; + case kIROp_DifferentiableTypeDictionaryItem: return; } - void enableIRValidationAtInsert() + // + // 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) +{ + if (inst->getFullType()) + validateIRInstOperand(context, inst, &inst->typeUse); + + // Avoid validating decoration operands + // since they don't have to conform to inst visibility + // constraints. + // + if (as<IRDecoration>(inst)) + return; + + UInt operandCount = inst->getOperandCount(); + for (UInt ii = 0; ii < operandCount; ++ii) { - _enableIRValidationAtInsert = true; + validateIRInstOperand(context, inst, inst->getOperands() + ii); } - void validateIRInstOperands(IRInst* inst) +} + +static thread_local bool _enableIRValidationAtInsert = false; +void disableIRValidationAtInsert() +{ + _enableIRValidationAtInsert = false; +} +void enableIRValidationAtInsert() +{ + _enableIRValidationAtInsert = true; +} +void validateIRInstOperands(IRInst* inst) +{ + if (!_enableIRValidationAtInsert) + return; + switch (inst->getOp()) { - if (!_enableIRValidationAtInsert) - return; - switch (inst->getOp()) - { - case kIROp_loop: - case kIROp_ifElse: - case kIROp_unconditionalBranch: - case kIROp_conditionalBranch: - case kIROp_Switch: - return; - default: - break; - } - - validateIRInstOperands(nullptr, inst); + case kIROp_loop: + case kIROp_ifElse: + case kIROp_unconditionalBranch: + case kIROp_conditionalBranch: + case kIROp_Switch: return; + default: break; } - void validateCodeBody(IRValidateContext* context, IRGlobalValueWithCode* code) + validateIRInstOperands(nullptr, inst); +} + +void validateCodeBody(IRValidateContext* context, IRGlobalValueWithCode* code) +{ + HashSet<IRBlock*> blocks; + for (auto block : code->getBlocks()) + blocks.add(block); + auto validateBranchTarget = [&](IRInst* inst, IRBlock* target) { - HashSet<IRBlock*> blocks; - for (auto block : code->getBlocks()) - blocks.add(block); - auto validateBranchTarget = [&](IRInst* inst, IRBlock* target) - { - validate( - context, - blocks.contains(target), - inst, - "branch inst must have a valid target block that is defined within the same " - "scope."); - }; - for (auto block : code->getBlocks()) + validate( + context, + blocks.contains(target), + inst, + "branch inst must have a valid target block that is defined within the same " + "scope."); + }; + for (auto block : code->getBlocks()) + { + auto terminator = block->getTerminator(); + validate(context, terminator, block, "block must have valid terminator inst."); + switch (terminator->getOp()) { - auto terminator = block->getTerminator(); - validate(context, terminator, block, "block must have valid terminator inst."); - switch (terminator->getOp()) + case kIROp_conditionalBranch: + validateBranchTarget(terminator, as<IRConditionalBranch>(terminator)->getTrueBlock()); + validateBranchTarget(terminator, as<IRConditionalBranch>(terminator)->getFalseBlock()); + break; + case kIROp_loop: + case kIROp_unconditionalBranch: + validateBranchTarget( + terminator, + as<IRUnconditionalBranch>(terminator)->getTargetBlock()); + break; + case kIROp_Switch: { - case kIROp_conditionalBranch: - validateBranchTarget( - terminator, as<IRConditionalBranch>(terminator)->getTrueBlock()); - validateBranchTarget( - terminator, as<IRConditionalBranch>(terminator)->getFalseBlock()); - break; - case kIROp_loop: - case kIROp_unconditionalBranch: - validateBranchTarget(terminator, as<IRUnconditionalBranch>(terminator)->getTargetBlock()); - break; - case kIROp_Switch: + auto switchInst = as<IRSwitch>(terminator); + for (UInt i = 0; i < switchInst->getCaseCount(); i++) { - auto switchInst = as<IRSwitch>(terminator); - for (UInt i = 0; i < switchInst->getCaseCount(); i++) - { - validateBranchTarget(switchInst, switchInst->getCaseLabel(i)); - } - validateBranchTarget(switchInst, switchInst->getDefaultLabel()); - validateBranchTarget(switchInst, switchInst->getBreakLabel()); + validateBranchTarget(switchInst, switchInst->getCaseLabel(i)); } + validateBranchTarget(switchInst, switchInst->getDefaultLabel()); + validateBranchTarget(switchInst, switchInst->getBreakLabel()); } } } +} - void validateIRInst( - IRValidateContext* context, - IRInst* inst) - { - // Validate that any operands of the instruction are used appropriately - validateIRInstOperands(context, inst); - context->seenInsts.add(inst); - - if (auto code = as<IRGlobalValueWithCode>(inst)) - { - context->domTree = computeDominatorTree(code); - validateCodeBody(context, code); - } - - // If `inst` is itself a parent instruction, then we need to recursively - // validate its children. - validateIRInstChildren(context, inst); - - if (as<IRGlobalValueWithCode>(inst)) - context->domTree = nullptr; - } +void validateIRInst(IRValidateContext* context, IRInst* inst) +{ + // Validate that any operands of the instruction are used appropriately + validateIRInstOperands(context, inst); + context->seenInsts.add(inst); - void validateIRInst(IRInst* inst) + if (auto code = as<IRGlobalValueWithCode>(inst)) { - IRValidateContext contextStorage; - IRValidateContext* context = &contextStorage; - DiagnosticSink sink; - context->module = inst->getModule(); - context->sink = &sink; - if (auto func = as<IRFunc>(inst)) - context->domTree = computeDominatorTree(func); - validateIRInst(context, inst); + context->domTree = computeDominatorTree(code); + validateCodeBody(context, code); } - void validateIRModule(IRModule* module, DiagnosticSink* sink) - { - IRValidateContext contextStorage; - IRValidateContext* context = &contextStorage; - context->module = module; - context->sink = sink; + // If `inst` is itself a parent instruction, then we need to recursively + // validate its children. + validateIRInstChildren(context, inst); - auto moduleInst = module->getModuleInst(); + if (as<IRGlobalValueWithCode>(inst)) + context->domTree = nullptr; +} - 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"); +void validateIRInst(IRInst* inst) +{ + IRValidateContext contextStorage; + IRValidateContext* context = &contextStorage; + DiagnosticSink sink; + context->module = inst->getModule(); + context->sink = &sink; + if (auto func = as<IRFunc>(inst)) + context->domTree = computeDominatorTree(func); + validateIRInst(context, inst); +} - validateIRInst(context, moduleInst); - } +void validateIRModule(IRModule* module, DiagnosticSink* sink) +{ + IRValidateContext contextStorage; + IRValidateContext* context = &contextStorage; + context->module = module; + context->sink = sink; - void validateIRModuleIfEnabled( - CompileRequestBase* compileRequest, - IRModule* module) - { - if (!compileRequest->getLinkage()->m_optionSet.getBoolOption(CompilerOptionName::ValidateIr)) - return; + auto moduleInst = module->getModuleInst(); - auto sink = compileRequest->getSink(); - validateIRModule(module, sink); - } + 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"); - void validateIRModuleIfEnabled( - CodeGenContext* codeGenContext, - IRModule* module) - { - if (!codeGenContext->shouldValidateIR()) - return; + validateIRInst(context, moduleInst); +} - auto sink = codeGenContext->getSink(); - validateIRModule(module, sink); - } +void validateIRModuleIfEnabled(CompileRequestBase* compileRequest, IRModule* module) +{ + if (!compileRequest->getLinkage()->m_optionSet.getBoolOption(CompilerOptionName::ValidateIr)) + return; + auto sink = compileRequest->getSink(); + validateIRModule(module, sink); } + +void validateIRModuleIfEnabled(CodeGenContext* codeGenContext, IRModule* module) +{ + if (!codeGenContext->shouldValidateIR()) + return; + + auto sink = codeGenContext->getSink(); + validateIRModule(module, sink); +} + +} // namespace Slang |
