[VPlan] Add new VPScalarCastRecipe, use for IV & step trunc. (#78113)
Add a new recipe to model scalar cast instructions, without relying on an underlying instruction. This allows creating scalar casts, without relying on an underlying instruction (like the current VPReplicateRecipe). The new recipe is used to explicitly model both truncating the induction step and the VPDerivedIVRecipe, thus simplifying both the recipe and code needed to introduce it. Truncating VPWidenIntOrFpInductionRecipes should also be modeled using the new recipe, as follow-up. PR: https://github.com/llvm/llvm-project/pull/78113
This commit is contained in:
@@ -9277,12 +9277,6 @@ void VPDerivedIVRecipe::execute(VPTransformState &State) {
|
||||
State.Builder, CanonicalIV, getStartValue()->getLiveInIRValue(), Step,
|
||||
Kind, cast_if_present<BinaryOperator>(FPBinOp));
|
||||
DerivedIV->setName("offset.idx");
|
||||
if (TruncResultTy) {
|
||||
assert(TruncResultTy != DerivedIV->getType() &&
|
||||
Step->getType()->isIntegerTy() &&
|
||||
"Truncation requires an integer step");
|
||||
DerivedIV = State.Builder.CreateTrunc(DerivedIV, TruncResultTy);
|
||||
}
|
||||
assert(DerivedIV != CanonicalIV && "IV didn't need transforming?");
|
||||
|
||||
State.set(this, DerivedIV, VPIteration(0, 0));
|
||||
|
||||
@@ -859,6 +859,7 @@ public:
|
||||
case VPRecipeBase::VPWidenIntOrFpInductionSC:
|
||||
case VPRecipeBase::VPWidenPointerInductionSC:
|
||||
case VPRecipeBase::VPReductionPHISC:
|
||||
case VPRecipeBase::VPScalarCastSC:
|
||||
return true;
|
||||
case VPRecipeBase::VPInterleaveSC:
|
||||
case VPRecipeBase::VPBranchOnMaskSC:
|
||||
@@ -1338,6 +1339,34 @@ public:
|
||||
Type *getResultType() const { return ResultTy; }
|
||||
};
|
||||
|
||||
/// VPScalarCastRecipe is a recipe to create scalar cast instructions.
|
||||
class VPScalarCastRecipe : public VPSingleDefRecipe {
|
||||
Instruction::CastOps Opcode;
|
||||
|
||||
Type *ResultTy;
|
||||
|
||||
Value *generate(VPTransformState &State, unsigned Part);
|
||||
|
||||
public:
|
||||
VPScalarCastRecipe(Instruction::CastOps Opcode, VPValue *Op, Type *ResultTy)
|
||||
: VPSingleDefRecipe(VPDef::VPScalarCastSC, {Op}), Opcode(Opcode),
|
||||
ResultTy(ResultTy) {}
|
||||
|
||||
~VPScalarCastRecipe() override = default;
|
||||
|
||||
VP_CLASSOF_IMPL(VPDef::VPScalarCastSC)
|
||||
|
||||
void execute(VPTransformState &State) override;
|
||||
|
||||
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
|
||||
void print(raw_ostream &O, const Twine &Indent,
|
||||
VPSlotTracker &SlotTracker) const override;
|
||||
#endif
|
||||
|
||||
/// Returns the result type of the cast.
|
||||
Type *getResultType() const { return ResultTy; }
|
||||
};
|
||||
|
||||
/// A recipe for widening Call instructions.
|
||||
class VPWidenCallRecipe : public VPSingleDefRecipe {
|
||||
/// ID of the vector intrinsic to call when widening the call. If set the
|
||||
@@ -2254,10 +2283,9 @@ public:
|
||||
}
|
||||
|
||||
/// Check if the induction described by \p Kind, /p Start and \p Step is
|
||||
/// canonical, i.e. has the same start, step (of 1), and type as the
|
||||
/// canonical IV.
|
||||
/// canonical, i.e. has the same start and step (of 1) as the canonical IV.
|
||||
bool isCanonical(InductionDescriptor::InductionKind Kind, VPValue *Start,
|
||||
VPValue *Step, Type *Ty) const;
|
||||
VPValue *Step) const;
|
||||
};
|
||||
|
||||
/// A recipe for generating the active lane mask for the vector loop that is
|
||||
@@ -2320,10 +2348,6 @@ public:
|
||||
/// an IV with different start and step values, using Start + CanonicalIV *
|
||||
/// Step.
|
||||
class VPDerivedIVRecipe : public VPSingleDefRecipe {
|
||||
/// If not nullptr, the result of the induction will get truncated to
|
||||
/// TruncResultTy.
|
||||
Type *TruncResultTy;
|
||||
|
||||
/// Kind of the induction.
|
||||
const InductionDescriptor::InductionKind Kind;
|
||||
/// If not nullptr, the floating point induction binary operator. Must be set
|
||||
@@ -2332,10 +2356,9 @@ class VPDerivedIVRecipe : public VPSingleDefRecipe {
|
||||
|
||||
public:
|
||||
VPDerivedIVRecipe(const InductionDescriptor &IndDesc, VPValue *Start,
|
||||
VPCanonicalIVPHIRecipe *CanonicalIV, VPValue *Step,
|
||||
Type *TruncResultTy)
|
||||
VPCanonicalIVPHIRecipe *CanonicalIV, VPValue *Step)
|
||||
: VPSingleDefRecipe(VPDef::VPDerivedIVSC, {Start, CanonicalIV, Step}),
|
||||
TruncResultTy(TruncResultTy), Kind(IndDesc.getKind()),
|
||||
Kind(IndDesc.getKind()),
|
||||
FPBinOp(dyn_cast_or_null<FPMathOperator>(IndDesc.getInductionBinOp())) {
|
||||
}
|
||||
|
||||
@@ -2354,8 +2377,7 @@ public:
|
||||
#endif
|
||||
|
||||
Type *getScalarType() const {
|
||||
return TruncResultTy ? TruncResultTy
|
||||
: getStartValue()->getLiveInIRValue()->getType();
|
||||
return getStartValue()->getLiveInIRValue()->getType();
|
||||
}
|
||||
|
||||
VPValue *getStartValue() const { return getOperand(0); }
|
||||
|
||||
@@ -230,7 +230,13 @@ Type *VPTypeAnalysis::inferScalarType(const VPValue *V) {
|
||||
return V->getUnderlyingValue()->getType();
|
||||
})
|
||||
.Case<VPWidenCastRecipe>(
|
||||
[](const VPWidenCastRecipe *R) { return R->getResultType(); });
|
||||
[](const VPWidenCastRecipe *R) { return R->getResultType(); })
|
||||
.Case<VPScalarCastRecipe>(
|
||||
[](const VPScalarCastRecipe *R) { return R->getResultType(); })
|
||||
.Case<VPExpandSCEVRecipe>([](const VPExpandSCEVRecipe *R) {
|
||||
return R->getSCEV()->getType();
|
||||
});
|
||||
|
||||
assert(ResultTy && "could not infer type for the given VPValue");
|
||||
CachedTypes[V] = ResultTy;
|
||||
return ResultTy;
|
||||
|
||||
@@ -117,6 +117,7 @@ bool VPRecipeBase::mayHaveSideEffects() const {
|
||||
switch (getVPDefID()) {
|
||||
case VPDerivedIVSC:
|
||||
case VPPredInstPHISC:
|
||||
case VPScalarCastSC:
|
||||
return false;
|
||||
case VPInstructionSC:
|
||||
switch (cast<VPInstruction>(this)->getOpcode()) {
|
||||
@@ -1096,9 +1097,6 @@ void VPDerivedIVRecipe::print(raw_ostream &O, const Twine &Indent,
|
||||
getCanonicalIV()->printAsOperand(O, SlotTracker);
|
||||
O << " * ";
|
||||
getStepValue()->printAsOperand(O, SlotTracker);
|
||||
|
||||
if (TruncResultTy)
|
||||
O << " (truncated to " << *TruncResultTy << ")";
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1117,13 +1115,7 @@ void VPScalarIVStepsRecipe::execute(VPTransformState &State) {
|
||||
|
||||
// Ensure step has the same type as that of scalar IV.
|
||||
Type *BaseIVTy = BaseIV->getType()->getScalarType();
|
||||
if (BaseIVTy != Step->getType()) {
|
||||
// TODO: Also use VPDerivedIVRecipe when only the step needs truncating, to
|
||||
// avoid separate truncate here.
|
||||
assert(Step->getType()->isIntegerTy() &&
|
||||
"Truncation requires an integer step");
|
||||
Step = State.Builder.CreateTrunc(Step, BaseIVTy);
|
||||
}
|
||||
assert(BaseIVTy == Step->getType() && "Types of BaseIV and Step must match!");
|
||||
|
||||
// We build scalar steps for both integer and floating-point induction
|
||||
// variables. Here, we determine the kind of arithmetic we will perform.
|
||||
@@ -1469,6 +1461,58 @@ void VPReplicateRecipe::print(raw_ostream &O, const Twine &Indent,
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Checks if \p C is uniform across all VFs and UFs. It is considered as such
|
||||
/// if it is either defined outside the vector region or its operand is known to
|
||||
/// be uniform across all VFs and UFs (e.g. VPDerivedIV or VPCanonicalIVPHI).
|
||||
/// TODO: Uniformity should be associated with a VPValue and there should be a
|
||||
/// generic way to check.
|
||||
static bool isUniformAcrossVFsAndUFs(VPScalarCastRecipe *C) {
|
||||
return C->isDefinedOutsideVectorRegions() ||
|
||||
isa<VPDerivedIVRecipe>(C->getOperand(0)) ||
|
||||
isa<VPCanonicalIVPHIRecipe>(C->getOperand(0));
|
||||
}
|
||||
|
||||
Value *VPScalarCastRecipe ::generate(VPTransformState &State, unsigned Part) {
|
||||
assert(vputils::onlyFirstLaneUsed(this) &&
|
||||
"Codegen only implemented for first lane.");
|
||||
switch (Opcode) {
|
||||
case Instruction::SExt:
|
||||
case Instruction::ZExt:
|
||||
case Instruction::Trunc: {
|
||||
// Note: SExt/ZExt not used yet.
|
||||
Value *Op = State.get(getOperand(0), VPIteration(Part, 0));
|
||||
return State.Builder.CreateCast(Instruction::CastOps(Opcode), Op, ResultTy);
|
||||
}
|
||||
default:
|
||||
llvm_unreachable("opcode not implemented yet");
|
||||
}
|
||||
}
|
||||
|
||||
void VPScalarCastRecipe ::execute(VPTransformState &State) {
|
||||
bool IsUniformAcrossVFsAndUFs = isUniformAcrossVFsAndUFs(this);
|
||||
for (unsigned Part = 0; Part != State.UF; ++Part) {
|
||||
Value *Res;
|
||||
// Only generate a single instance, if the recipe is uniform across UFs and
|
||||
// VFs.
|
||||
if (Part > 0 && IsUniformAcrossVFsAndUFs)
|
||||
Res = State.get(this, VPIteration(0, 0));
|
||||
else
|
||||
Res = generate(State, Part);
|
||||
State.set(this, Res, VPIteration(Part, 0));
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
|
||||
void VPScalarCastRecipe ::print(raw_ostream &O, const Twine &Indent,
|
||||
VPSlotTracker &SlotTracker) const {
|
||||
O << Indent << "SCALAR-CAST ";
|
||||
printAsOperand(O, SlotTracker);
|
||||
O << " = " << Instruction::getOpcodeName(Opcode) << " ";
|
||||
printOperands(O, SlotTracker);
|
||||
O << " to " << *ResultTy;
|
||||
}
|
||||
#endif
|
||||
|
||||
void VPBranchOnMaskRecipe::execute(VPTransformState &State) {
|
||||
assert(State.Instance && "Branch on Mask works only on single instance.");
|
||||
|
||||
@@ -1587,10 +1631,10 @@ void VPCanonicalIVPHIRecipe::print(raw_ostream &O, const Twine &Indent,
|
||||
#endif
|
||||
|
||||
bool VPCanonicalIVPHIRecipe::isCanonical(
|
||||
InductionDescriptor::InductionKind Kind, VPValue *Start, VPValue *Step,
|
||||
Type *Ty) const {
|
||||
// The types must match and it must be an integer induction.
|
||||
if (Ty != getScalarType() || Kind != InductionDescriptor::IK_IntInduction)
|
||||
InductionDescriptor::InductionKind Kind, VPValue *Start,
|
||||
VPValue *Step) const {
|
||||
// Must be an integer induction.
|
||||
if (Kind != InductionDescriptor::IK_IntInduction)
|
||||
return false;
|
||||
// Start must match the start value of this canonical induction.
|
||||
if (Start != getStartValue())
|
||||
|
||||
@@ -491,17 +491,39 @@ void VPlanTransforms::removeDeadRecipes(VPlan &Plan) {
|
||||
|
||||
static VPValue *createScalarIVSteps(VPlan &Plan, const InductionDescriptor &ID,
|
||||
ScalarEvolution &SE, Instruction *TruncI,
|
||||
Type *IVTy, VPValue *StartV,
|
||||
VPValue *Step) {
|
||||
VPValue *StartV, VPValue *Step) {
|
||||
VPBasicBlock *HeaderVPBB = Plan.getVectorLoopRegion()->getEntryBasicBlock();
|
||||
auto IP = HeaderVPBB->getFirstNonPhi();
|
||||
VPCanonicalIVPHIRecipe *CanonicalIV = Plan.getCanonicalIV();
|
||||
Type *TruncTy = TruncI ? TruncI->getType() : IVTy;
|
||||
VPValue *BaseIV = CanonicalIV;
|
||||
if (!CanonicalIV->isCanonical(ID.getKind(), StartV, Step, TruncTy)) {
|
||||
BaseIV = new VPDerivedIVRecipe(ID, StartV, CanonicalIV, Step,
|
||||
TruncI ? TruncI->getType() : nullptr);
|
||||
HeaderVPBB->insert(BaseIV->getDefiningRecipe(), IP);
|
||||
VPSingleDefRecipe *BaseIV = CanonicalIV;
|
||||
if (!CanonicalIV->isCanonical(ID.getKind(), StartV, Step)) {
|
||||
BaseIV = new VPDerivedIVRecipe(ID, StartV, CanonicalIV, Step);
|
||||
HeaderVPBB->insert(BaseIV, IP);
|
||||
}
|
||||
|
||||
// Truncate base induction if needed.
|
||||
VPTypeAnalysis TypeInfo(SE.getContext());
|
||||
Type *ResultTy = TypeInfo.inferScalarType(BaseIV);
|
||||
if (TruncI) {
|
||||
Type *TruncTy = TruncI->getType();
|
||||
assert(ResultTy->getScalarSizeInBits() > TruncTy->getScalarSizeInBits() &&
|
||||
"Not truncating.");
|
||||
assert(ResultTy->isIntegerTy() && "Truncation requires an integer type");
|
||||
BaseIV = new VPScalarCastRecipe(Instruction::Trunc, BaseIV, TruncTy);
|
||||
HeaderVPBB->insert(BaseIV, IP);
|
||||
ResultTy = TruncTy;
|
||||
}
|
||||
|
||||
// Truncate step if needed.
|
||||
Type *StepTy = TypeInfo.inferScalarType(Step);
|
||||
if (ResultTy != StepTy) {
|
||||
assert(StepTy->getScalarSizeInBits() > ResultTy->getScalarSizeInBits() &&
|
||||
"Not truncating.");
|
||||
assert(StepTy->isIntegerTy() && "Truncation requires an integer type");
|
||||
Step = new VPScalarCastRecipe(Instruction::Trunc, Step, ResultTy);
|
||||
auto *VecPreheader =
|
||||
cast<VPBasicBlock>(HeaderVPBB->getSingleHierarchicalPredecessor());
|
||||
VecPreheader->appendRecipe(Step->getDefiningRecipe());
|
||||
}
|
||||
|
||||
VPScalarIVStepsRecipe *Steps = new VPScalarIVStepsRecipe(ID, BaseIV, Step);
|
||||
@@ -523,9 +545,9 @@ void VPlanTransforms::optimizeInductions(VPlan &Plan, ScalarEvolution &SE) {
|
||||
continue;
|
||||
|
||||
const InductionDescriptor &ID = WideIV->getInductionDescriptor();
|
||||
VPValue *Steps = createScalarIVSteps(
|
||||
Plan, ID, SE, WideIV->getTruncInst(), WideIV->getPHINode()->getType(),
|
||||
WideIV->getStartValue(), WideIV->getStepValue());
|
||||
VPValue *Steps =
|
||||
createScalarIVSteps(Plan, ID, SE, WideIV->getTruncInst(),
|
||||
WideIV->getStartValue(), WideIV->getStepValue());
|
||||
|
||||
// Update scalar users of IV to use Step instead.
|
||||
if (!HasOnlyVectorVFs)
|
||||
|
||||
@@ -350,6 +350,7 @@ public:
|
||||
VPInterleaveSC,
|
||||
VPReductionSC,
|
||||
VPReplicateSC,
|
||||
VPScalarCastSC,
|
||||
VPScalarIVStepsSC,
|
||||
VPVectorPointerSC,
|
||||
VPWidenCallSC,
|
||||
|
||||
@@ -83,12 +83,14 @@ define void @cast_variable_step(i64 %step) {
|
||||
; VF4: middle.block:
|
||||
;
|
||||
; IC2-LABEL: @cast_variable_step(
|
||||
; IC2: [[TRUNC_STEP:%.+]] = trunc i64 %step to i32
|
||||
; IC2: br label %vector.body
|
||||
|
||||
; IC2-LABEL: vector.body:
|
||||
; IC2-NEXT: [[INDEX:%.+]] = phi i64 [ 0, %vector.ph ]
|
||||
; IC2-NEXT: [[MUL:%.+]] = mul i64 %index, %step
|
||||
; IC2-NEXT: [[OFFSET_IDX:%.+]] = add i64 10, [[MUL]]
|
||||
; IC2-NEXT: [[TRUNC_OFF:%.+]] = trunc i64 [[OFFSET_IDX]] to i32
|
||||
; IC2-NEXT: [[TRUNC_STEP:%.+]] = trunc i64 %step to i32
|
||||
; IC2-NEXT: [[STEP0:%.+]] = mul i32 0, [[TRUNC_STEP]]
|
||||
; IC2-NEXT: [[T0:%.+]] = add i32 [[TRUNC_OFF]], [[STEP0]]
|
||||
; IC2-NEXT: [[STEP1:%.+]] = mul i32 1, [[TRUNC_STEP]]
|
||||
|
||||
@@ -184,14 +184,15 @@ exit:
|
||||
; DBG-NEXT: No successors
|
||||
; DBG-EMPTY:
|
||||
; DBG-NEXT: vector.ph:
|
||||
; DBG-NEXT: SCALAR-CAST vp<[[CAST:%.+]]> = trunc ir<1> to i32
|
||||
; DBG-NEXT: Successor(s): vector loop
|
||||
; DBG-EMPTY:
|
||||
; DBG-NEXT: <x1> vector loop: {
|
||||
; DBG-NEXT: vector.body:
|
||||
; DBG-NEXT: EMIT vp<[[CAN_IV:%.+]]> = CANONICAL-INDUCTION
|
||||
; DBG-NEXT: FIRST-ORDER-RECURRENCE-PHI ir<%for> = phi ir<0>, vp<[[SCALAR_STEPS:.+]]>
|
||||
; DBG-NEXT: vp<[[DERIVED_IV:%.+]]> = DERIVED-IV ir<0> + vp<[[CAN_IV]]> * ir<1> (truncated to i32)
|
||||
; DBG-NEXT: vp<[[SCALAR_STEPS]]> = SCALAR-STEPS vp<[[DERIVED_IV]]>, ir<1>
|
||||
; DBG-NEXT: SCALAR-CAST vp<[[TRUNC_IV:%.+]]> = trunc vp<[[CAN_IV]]> to i32
|
||||
; DBG-NEXT: vp<[[SCALAR_STEPS]]> = SCALAR-STEPS vp<[[TRUNC_IV]]>, vp<[[CAST]]>
|
||||
; DBG-NEXT: EMIT vp<[[SPLICE:%.+]]> = first-order splice ir<%for>, vp<[[SCALAR_STEPS]]>
|
||||
; DBG-NEXT: CLONE store vp<[[SPLICE]]>, ir<%dst>
|
||||
; DBG-NEXT: EMIT vp<[[IV_INC:%.+]]> = add nuw vp<[[CAN_IV]]>, vp<[[VFxUF]]>
|
||||
|
||||
@@ -43,7 +43,7 @@ define void @test(i16 %x, i64 %y, ptr %ptr) {
|
||||
; CHECK-NEXT: [[V3:%.*]] = add i8 [[V2]], 1
|
||||
; CHECK-NEXT: [[CMP15:%.*]] = icmp slt i8 [[V3]], 5
|
||||
; CHECK-NEXT: [[IV_NEXT]] = add i64 [[IV]], [[INC]]
|
||||
; CHECK-NEXT: br i1 [[CMP15]], label [[LOOP]], label [[LOOP_EXIT]], !llvm.loop [[LOOP2:![0-9]+]]
|
||||
; CHECK-NEXT: br i1 [[CMP15]], label [[LOOP]], label [[LOOP_EXIT]], !llvm.loop [[LOOP3:![0-9]+]]
|
||||
; CHECK: loop.exit:
|
||||
; CHECK-NEXT: [[DIV_1:%.*]] = udiv i64 [[Y]], [[ADD]]
|
||||
; CHECK-NEXT: [[V1:%.*]] = add i64 [[DIV_1]], 1
|
||||
|
||||
Reference in New Issue
Block a user