-
Notifications
You must be signed in to change notification settings - Fork 17.7k
[VPlan] Assert vplan-verify-each result and fix LastActiveLane verification #182254
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
896ef73
9401b48
e9059ef
ba01b63
db8dbb3
04e4ee7
08155c7
b503231
90160d8
0f62428
bb065bb
84eb197
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -72,8 +72,10 @@ struct VPlanTransforms { | |
| dbgs() << Plan << '\n'; | ||
| } | ||
| #endif | ||
| if (VerifyEachVPlan && EnableVerify) | ||
| verifyVPlanIsValid(Plan); | ||
| if (VerifyEachVPlan && EnableVerify) { | ||
| if (!verifyVPlanIsValid(Plan)) | ||
| report_fatal_error("Broken VPlan found, compilation aborted!"); | ||
|
Comment on lines
+76
to
+77
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If possible, it may be good to add a C++ unit test that passes an invalid VPlan to a transform invoked via the macro, to add test coverage for the reporting.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 0f62428 |
||
| } | ||
| }}; | ||
|
|
||
| return std::forward<PassTy>(Pass)(Plan, std::forward<ArgsTy>(Args)...); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -31,7 +31,6 @@ namespace { | |||||
| class VPlanVerifier { | ||||||
| const VPDominatorTree &VPDT; | ||||||
| VPTypeAnalysis &TypeInfo; | ||||||
| bool VerifyLate; | ||||||
|
|
||||||
| SmallPtrSet<BasicBlock *, 8> WrappedIRBBs; | ||||||
|
|
||||||
|
|
@@ -40,11 +39,6 @@ class VPlanVerifier { | |||||
| // VPHeaderPHIRecipes. | ||||||
| bool verifyPhiRecipes(const VPBasicBlock *VPBB); | ||||||
|
|
||||||
| /// Verify that \p EVL is used correctly. The user must be either in | ||||||
| /// EVL-based recipes as a last operand or VPInstruction::Add which is | ||||||
| /// incoming value into EVL's recipe. | ||||||
| bool verifyEVLRecipe(const VPInstruction &EVL) const; | ||||||
|
lukel97 marked this conversation as resolved.
Outdated
|
||||||
|
|
||||||
| /// Verify that \p LastActiveLane's operand is guaranteed to be a prefix-mask. | ||||||
| bool verifyLastActiveLaneRecipe(const VPInstruction &LastActiveLane) const; | ||||||
|
|
||||||
|
|
@@ -67,9 +61,8 @@ class VPlanVerifier { | |||||
| bool verifyRegionRec(const VPRegionBlock *Region); | ||||||
|
|
||||||
| public: | ||||||
| VPlanVerifier(VPDominatorTree &VPDT, VPTypeAnalysis &TypeInfo, | ||||||
| bool VerifyLate) | ||||||
| : VPDT(VPDT), TypeInfo(TypeInfo), VerifyLate(VerifyLate) {} | ||||||
| VPlanVerifier(VPDominatorTree &VPDT, VPTypeAnalysis &TypeInfo) | ||||||
| : VPDT(VPDT), TypeInfo(TypeInfo) {} | ||||||
|
|
||||||
| bool verify(const VPlan &Plan); | ||||||
| }; | ||||||
|
|
@@ -124,7 +117,7 @@ bool VPlanVerifier::verifyPhiRecipes(const VPBasicBlock *VPBB) { | |||||
| RecipeI++; | ||||||
| } | ||||||
|
|
||||||
| if (!VerifyLate && NumActiveLaneMaskPhiRecipes > 1) { | ||||||
| if (!VPBB->getPlan()->isUnrolled() && NumActiveLaneMaskPhiRecipes > 1) { | ||||||
| errs() << "There should be no more than one VPActiveLaneMaskPHIRecipe"; | ||||||
| return false; | ||||||
| } | ||||||
|
|
@@ -146,106 +139,24 @@ bool VPlanVerifier::verifyPhiRecipes(const VPBasicBlock *VPBB) { | |||||
| return true; | ||||||
| } | ||||||
|
|
||||||
| bool VPlanVerifier::verifyEVLRecipe(const VPInstruction &EVL) const { | ||||||
| if (EVL.getOpcode() != VPInstruction::ExplicitVectorLength) { | ||||||
| errs() << "verifyEVLRecipe should only be called on " | ||||||
| "VPInstruction::ExplicitVectorLength\n"; | ||||||
| return false; | ||||||
| } | ||||||
| auto VerifyEVLUse = [&](const VPRecipeBase &R, | ||||||
| const unsigned ExpectedIdx) -> bool { | ||||||
| SmallVector<const VPValue *> Ops(R.operands()); | ||||||
| unsigned UseCount = count(Ops, &EVL); | ||||||
| if (UseCount != 1 || Ops[ExpectedIdx] != &EVL) { | ||||||
| errs() << "EVL is used as non-last operand in EVL-based recipe\n"; | ||||||
| return false; | ||||||
| } | ||||||
| static bool isKnownMonotonic(VPValue *V) { | ||||||
| VPValue *X, *Y; | ||||||
| if (match(V, m_Add(m_VPValue(X), m_VPValue(Y)))) | ||||||
| return isKnownMonotonic(X) && isKnownMonotonic(Y); | ||||||
|
Comment on lines
+145
to
+146
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this would also need to check that the Add is NUW, if it would wrap it would not be monotonic, even if both operands are
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in db8dbb3
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like this actually causes verifier failures in test/Transforms/LoopVectorize/first-order-recurrence-tail-folding.ll, %10 is used as a prefix mask in a lastactivelane but %step.add doesn't have unsigned wrap: Will take a look to see if we're missing a nuw on the step.add from unrolling.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would be good to add a test case; in general, if the wide IV had nuw, I * think* that should also be preserve-able for the steps, and if it was canonical
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think so too. However I'm noticing we actually drop the nuw on the wide IV after #163538. I believe the original reasoning was that with tail folding we might end up with poison lanes because the VTC > TC.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I went down a bit of a rabbit hole here. IIUC we check for the canonical IV overflowing with tail folding because vscale can be a non-power-of-2. AFAIK the plan is to remove non-power-of-2 vscales in #145098, and if that lands we can remove all the overflow checks so the increments will always have NUW. Until then I think it's quite tricky to infer NUW. Can we relax the NUW check here and add a TODO to add it back if/when #145098 lands? |
||||||
| if (match(V, m_StepVector())) | ||||||
| return true; | ||||||
| }; | ||||||
| auto VerifyEVLUseInVecEndPtr = [&EVL](auto &VEPRs) { | ||||||
| if (all_of(VEPRs, [&EVL](VPUser *U) { | ||||||
| auto *VEPR = cast<VPVectorEndPointerRecipe>(U); | ||||||
| return match(VEPR->getOffset(), | ||||||
| m_c_Mul(m_SpecificSInt(VEPR->getStride()), | ||||||
| m_Sub(m_Specific(&EVL), m_One()))); | ||||||
| })) | ||||||
| return true; | ||||||
| errs() << "Expected VectorEndPointer with EVL operand\n"; | ||||||
| return false; | ||||||
| }; | ||||||
| return all_of(EVL.users(), [&](VPUser *U) { | ||||||
| return TypeSwitch<const VPUser *, bool>(U) | ||||||
| .Case([&](const VPWidenIntrinsicRecipe *S) { | ||||||
| return VerifyEVLUse(*S, S->getNumOperands() - 1); | ||||||
| }) | ||||||
| .Case<VPWidenStoreEVLRecipe, VPReductionEVLRecipe, | ||||||
| VPWidenIntOrFpInductionRecipe, VPWidenPointerInductionRecipe>( | ||||||
| [&](const VPRecipeBase *S) { return VerifyEVLUse(*S, 2); }) | ||||||
| .Case([&](const VPScalarIVStepsRecipe *R) { | ||||||
| if (R->getNumOperands() != 3) { | ||||||
| errs() << "Unrolling with EVL tail folding not yet supported\n"; | ||||||
| return false; | ||||||
| } | ||||||
| return VerifyEVLUse(*R, 2); | ||||||
| }) | ||||||
| .Case<VPWidenLoadEVLRecipe, VPVectorEndPointerRecipe, | ||||||
| VPInterleaveEVLRecipe>( | ||||||
| [&](const VPRecipeBase *R) { return VerifyEVLUse(*R, 1); }) | ||||||
| .Case( | ||||||
| [&](const VPInstructionWithType *S) { return VerifyEVLUse(*S, 0); }) | ||||||
| .Case([&](const VPInstruction *I) { | ||||||
| if (I->getOpcode() == Instruction::PHI || | ||||||
| I->getOpcode() == Instruction::ICmp) | ||||||
| return VerifyEVLUse(*I, 1); | ||||||
| if (I->getOpcode() == Instruction::Sub) { | ||||||
| // If Sub has a single user that's a SingleDefRecipe (which is | ||||||
| // expected to be a Mul), filter its users, in turn, to get | ||||||
| // VectorEndPointerRecipes, and verify that all the offsets match | ||||||
| // (EVL - 1) * Stride. | ||||||
| if (auto *Def = dyn_cast_if_present<VPSingleDefRecipe>( | ||||||
| I->getSingleUser())) { | ||||||
| auto VEPRs = make_filter_range(Def->users(), | ||||||
| IsaPred<VPVectorEndPointerRecipe>); | ||||||
| if (!VEPRs.empty()) | ||||||
| return VerifyEVLUseInVecEndPtr(VEPRs); | ||||||
| } | ||||||
| return VerifyEVLUse(*I, 1); | ||||||
| } | ||||||
| switch (I->getOpcode()) { | ||||||
| case Instruction::Add: | ||||||
| break; | ||||||
| case Instruction::UIToFP: | ||||||
| case Instruction::Trunc: | ||||||
| case Instruction::ZExt: | ||||||
| case Instruction::Mul: | ||||||
| case Instruction::Shl: | ||||||
| case Instruction::FMul: | ||||||
| case VPInstruction::Broadcast: | ||||||
| case VPInstruction::PtrAdd: | ||||||
| // Opcodes above can only use EVL after wide inductions have been | ||||||
| // expanded. | ||||||
| if (!VerifyLate) { | ||||||
| errs() << "EVL used by unexpected VPInstruction\n"; | ||||||
| return false; | ||||||
| } | ||||||
| break; | ||||||
| default: | ||||||
| errs() << "EVL used by unexpected VPInstruction\n"; | ||||||
| return false; | ||||||
| } | ||||||
| if (!VerifyLate && | ||||||
| !isa<VPCurrentIterationPHIRecipe>(*I->users().begin())) { | ||||||
| errs() << "Result of VPInstruction::Add with EVL operand is " | ||||||
| "not used by VPCurrentIterationPHIRecipe\n"; | ||||||
| return false; | ||||||
| } | ||||||
| return true; | ||||||
| }) | ||||||
| .Default([&](const VPUser *U) { | ||||||
| errs() << "EVL has unexpected user\n"; | ||||||
| return false; | ||||||
| }); | ||||||
| }); | ||||||
| if (auto *WidenIV = dyn_cast<VPWidenIntOrFpInductionRecipe>(V)) | ||||||
|
eas marked this conversation as resolved.
|
||||||
| return match(WidenIV->getStartValue(), m_ZeroInt()) && | ||||||
| match(WidenIV->getStepValue(), m_One()); | ||||||
|
lukel97 marked this conversation as resolved.
Outdated
|
||||||
| if (auto *Steps = dyn_cast<VPScalarIVStepsRecipe>(V)) | ||||||
| return match(Steps->getOperand(0), | ||||||
| m_CombineOr( | ||||||
| m_CanonicalIV(), | ||||||
| m_DerivedIV(m_ZeroInt(), m_CanonicalIV(), m_One()))) && | ||||||
| match(Steps->getStepValue(), m_One()); | ||||||
| if (isa<VPWidenCanonicalIVRecipe>(V)) | ||||||
| return true; | ||||||
| return vputils::isUniformAcrossVFsAndUFs(V); | ||||||
| } | ||||||
|
|
||||||
| bool VPlanVerifier::verifyLastActiveLaneRecipe( | ||||||
|
|
@@ -259,18 +170,19 @@ bool VPlanVerifier::verifyLastActiveLaneRecipe( | |||||
| } | ||||||
|
|
||||||
| const VPlan &Plan = *LastActiveLane.getParent()->getPlan(); | ||||||
| // All operands must be prefix-mask. Currently we check for header masks or | ||||||
| // EVL-derived masks, as those are currently the only operands in practice, | ||||||
| // but this may need updating in the future. | ||||||
| // All operands must be prefix-mask. This means an icmp ult/ule LHS, RHS where | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can the changes in this function be done in a separate PR or are they explicitly tied to the removal of the 'VerifyLate' parameter?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The VerifyLate parameter bit was split off and landed in #182799 |
||||||
| // the LHS is monotonically increasing and RHS is uniform. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 0f62428 |
||||||
| for (VPValue *Op : LastActiveLane.operands()) { | ||||||
| if (vputils::isHeaderMask(Op, Plan)) | ||||||
| continue; | ||||||
|
|
||||||
| // Masks derived from EVL are also fine. | ||||||
| auto BroadcastOrEVL = | ||||||
| m_CombineOr(m_Broadcast(m_EVL(m_VPValue())), m_EVL(m_VPValue())); | ||||||
| if (match(Op, m_CombineOr(m_ICmp(m_StepVector(), BroadcastOrEVL), | ||||||
| m_ICmp(BroadcastOrEVL, m_StepVector())))) | ||||||
| CmpPredicate Pred; | ||||||
| VPValue *LHS, *RHS; | ||||||
| if (match(Op, m_ICmp(Pred, m_VPValue(LHS), m_VPValue(RHS))) && | ||||||
| (Pred == CmpInst::ICMP_ULE || Pred == CmpInst::ICMP_ULT) && | ||||||
| isKnownMonotonic(LHS) && | ||||||
| (vputils::isUniformAcrossVFsAndUFs(RHS) || | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the only case that it missing from
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah it's just EVL. I think it would be nice to avoid having the UF = 1 invariant in isUniformAcrossVFsAndUFs for EVL though. I just restricted the isSingleScalar to EVL in the verifier instead in b503231 if that works for you |
||||||
| vputils::isSingleScalar(RHS))) | ||||||
|
eas marked this conversation as resolved.
Outdated
|
||||||
| continue; | ||||||
|
|
||||||
| errs() << "LastActiveLane operand "; | ||||||
|
|
@@ -372,12 +284,6 @@ bool VPlanVerifier::verifyVPBasicBlock(const VPBasicBlock *VPBB) { | |||||
| } | ||||||
| if (const auto *VPI = dyn_cast<VPInstruction>(&R)) { | ||||||
| switch (VPI->getOpcode()) { | ||||||
| case VPInstruction::ExplicitVectorLength: | ||||||
|
lukel97 marked this conversation as resolved.
Outdated
|
||||||
| if (!verifyEVLRecipe(*VPI)) { | ||||||
| errs() << "EVL VPValue is not used correctly\n"; | ||||||
| return false; | ||||||
| } | ||||||
| break; | ||||||
| case VPInstruction::LastActiveLane: | ||||||
| if (!verifyLastActiveLaneRecipe(*VPI)) | ||||||
| return false; | ||||||
|
|
@@ -569,9 +475,9 @@ bool VPlanVerifier::verify(const VPlan &Plan) { | |||||
| return true; | ||||||
| } | ||||||
|
|
||||||
| bool llvm::verifyVPlanIsValid(const VPlan &Plan, bool VerifyLate) { | ||||||
| bool llvm::verifyVPlanIsValid(const VPlan &Plan) { | ||||||
| VPDominatorTree VPDT(const_cast<VPlan &>(Plan)); | ||||||
| VPTypeAnalysis TypeInfo(Plan); | ||||||
| VPlanVerifier Verifier(VPDT, TypeInfo, VerifyLate); | ||||||
| VPlanVerifier Verifier(VPDT, TypeInfo); | ||||||
| return Verifier.verify(Plan); | ||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How come this isn't also guarded by
EnableVerifylike in the vplan code? I was expecting something similar here:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just an assert that verifies that the plan is valid at this specific point, independent of running the verifier after all transforms. This should mirror how verification is handled for LLVM IR verification
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But in a release build does the IR verifier report a fatal error for unverified IR? If so, then if we mirror that behaviour this should also be a fatal error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep,
verifyFunctionfor IR similarly just returns a boolean, instead of reporting a fatal error:llvm-project/llvm/lib/IR/Verifier.cpp
Line 7885 in 58ac25e
It is used in asserts so those won't raise a fatal error in release builds (e.g. the assert used in LV). If the verifier is enabled to run after each pass, that raises a fatal error.