-
Notifications
You must be signed in to change notification settings - Fork 14.6k
[VPlan] Add initial anlysis to infer scalar type of VPValues. #69013
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 12 commits
22b23c7
30a8968
58b145e
d89b7d8
4ed3b6d
529805c
a75c562
1483f63
79bcce9
532d75e
5bbd764
74525ad
722cc3d
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 |
---|---|---|
|
@@ -1167,6 +1167,8 @@ class VPWidenRecipe : public VPRecipeWithIRFlags, public VPValue { | |
/// Produce widened copies of all Ingredients. | ||
void execute(VPTransformState &State) override; | ||
|
||
unsigned getOpcode() const { return Opcode; } | ||
|
||
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) | ||
/// Print the recipe. | ||
void print(raw_ostream &O, const Twine &Indent, | ||
|
@@ -1458,7 +1460,7 @@ class VPWidenIntOrFpInductionRecipe : public VPHeaderPHIRecipe { | |
bool isCanonical() const; | ||
|
||
/// Returns the scalar type of the induction. | ||
const Type *getScalarType() const { | ||
Type *getScalarType() const { | ||
return Trunc ? Trunc->getType() : IV->getType(); | ||
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 wait for separate subsequent patch: type of all header phi's should arguably be derived from their start value. If it gets truncated, let the start value be truncated in VPlan. Similar for DerivedIV. 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. Agreed! |
||
} | ||
}; | ||
|
@@ -2080,8 +2082,8 @@ class VPCanonicalIVPHIRecipe : public VPHeaderPHIRecipe { | |
#endif | ||
|
||
/// Returns the scalar type of the induction. | ||
const Type *getScalarType() const { | ||
return getOperand(0)->getLiveInIRValue()->getType(); | ||
Type *getScalarType() const { | ||
return getStartValue()->getLiveInIRValue()->getType(); | ||
} | ||
|
||
/// Returns true if the recipe only uses the first lane of operand \p Op. | ||
|
@@ -2192,6 +2194,11 @@ class VPDerivedIVRecipe : public VPRecipeBase, public VPValue { | |
VPSlotTracker &SlotTracker) const override; | ||
#endif | ||
|
||
Type *getScalarType() const { | ||
return TruncResultTy ? TruncResultTy | ||
: getStartValue()->getLiveInIRValue()->getType(); | ||
} | ||
|
||
VPValue *getStartValue() const { return getOperand(0); } | ||
VPValue *getCanonicalIV() const { return getOperand(1); } | ||
VPValue *getStepValue() const { return getOperand(2); } | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
//===- VPlanAnalysis.cpp - Various Analyses working on VPlan ----*- C++ -*-===// | ||
// | ||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
// See https://llvm.org/LICENSE.txt for license information. | ||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#include "VPlanAnalysis.h" | ||
#include "VPlan.h" | ||
#include "llvm/ADT/TypeSwitch.h" | ||
|
||
using namespace llvm; | ||
|
||
#define DEBUG_TYPE "vplan" | ||
|
||
Type *VPTypeAnalysis::inferScalarTypeForRecipe(const VPBlendRecipe *R) { | ||
Type *ResTy = inferScalarType(R->getIncomingValue(0)); | ||
for (unsigned I = 1, E = R->getNumIncomingValues(); I != E; ++I) { | ||
VPValue *Inc = R->getIncomingValue(I); | ||
assert(inferScalarType(Inc) == ResTy && | ||
"different types inferred for different incoming values"); | ||
CachedTypes[Inc] = ResTy; | ||
} | ||
return ResTy; | ||
} | ||
|
||
Type *VPTypeAnalysis::inferScalarTypeForRecipe(const VPInstruction *R) { | ||
switch (R->getOpcode()) { | ||
case Instruction::Select: { | ||
Type *ResTy = inferScalarType(R->getOperand(1)); | ||
VPValue *OtherV = R->getOperand(2); | ||
assert(inferScalarType(OtherV) == ResTy && | ||
"different types inferred for different operands"); | ||
CachedTypes[OtherV] = ResTy; | ||
return ResTy; | ||
} | ||
case VPInstruction::FirstOrderRecurrenceSplice: { | ||
Type *ResTy = inferScalarType(R->getOperand(0)); | ||
VPValue *OtherV = R->getOperand(1); | ||
assert(inferScalarType(OtherV) == ResTy && | ||
"different types inferred for different operands"); | ||
CachedTypes[OtherV] = ResTy; | ||
return ResTy; | ||
} | ||
default: | ||
break; | ||
} | ||
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. Ah, this works w/o return nor unreachable at the end? Very well. Perhaps worth a comment. Switch below should be consistent, i.e., with an unreachable default but no return at the unreachable end. 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 updated it to move the unreachable out of the switch here as well |
||
llvm_unreachable("Unhandled opcode!"); | ||
} | ||
|
||
Type *VPTypeAnalysis::inferScalarTypeForRecipe(const VPWidenRecipe *R) { | ||
unsigned Opcode = R->getOpcode(); | ||
switch (Opcode) { | ||
case Instruction::ICmp: | ||
case Instruction::FCmp: | ||
return IntegerType::get(Ctx, 1); | ||
case Instruction::UDiv: | ||
case Instruction::SDiv: | ||
case Instruction::SRem: | ||
case Instruction::URem: | ||
case Instruction::Add: | ||
case Instruction::FAdd: | ||
case Instruction::Sub: | ||
case Instruction::FSub: | ||
case Instruction::Mul: | ||
case Instruction::FMul: | ||
case Instruction::FDiv: | ||
case Instruction::FRem: | ||
case Instruction::Shl: | ||
case Instruction::LShr: | ||
case Instruction::AShr: | ||
case Instruction::And: | ||
case Instruction::Or: | ||
case Instruction::Xor: { | ||
Type *ResTy = inferScalarType(R->getOperand(0)); | ||
assert(ResTy == inferScalarType(R->getOperand(1)) && | ||
"types for both operands must match for binary op"); | ||
CachedTypes[R->getOperand(1)] = ResTy; | ||
return ResTy; | ||
} | ||
case Instruction::FNeg: | ||
case Instruction::Freeze: | ||
return inferScalarType(R->getOperand(0)); | ||
default: | ||
break; | ||
} | ||
|
||
// Type inference not implemented for opcode. | ||
LLVM_DEBUG(dbgs() << "LV: Found unhandled opcode: " | ||
<< Instruction::getOpcodeName(Opcode)); | ||
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. nit: better dump the whole underlying instruction than/in addition to only its opcode. nit: can dump them for other unhandled cases of VPInstruction and replicate recipe above and below. |
||
llvm_unreachable("Unhandled opcode!"); | ||
} | ||
|
||
Type *VPTypeAnalysis::inferScalarTypeForRecipe(const VPWidenCallRecipe *R) { | ||
auto &CI = *cast<CallInst>(R->getUnderlyingInstr()); | ||
return CI.getType(); | ||
} | ||
|
||
Type *VPTypeAnalysis::inferScalarTypeForRecipe( | ||
const VPWidenMemoryInstructionRecipe *R) { | ||
assert(!R->isStore() && "Store recipes should not define any values"); | ||
return cast<LoadInst>(&R->getIngredient())->getType(); | ||
} | ||
|
||
Type *VPTypeAnalysis::inferScalarTypeForRecipe(const VPWidenSelectRecipe *R) { | ||
Type *ResTy = inferScalarType(R->getOperand(1)); | ||
VPValue *OtherV = R->getOperand(2); | ||
assert(inferScalarType(OtherV) == ResTy && | ||
"different types inferred for different operands"); | ||
CachedTypes[OtherV] = ResTy; | ||
return ResTy; | ||
} | ||
|
||
Type *VPTypeAnalysis::inferScalarTypeForRecipe(const VPReplicateRecipe *R) { | ||
switch (R->getUnderlyingInstr()->getOpcode()) { | ||
case Instruction::Call: { | ||
unsigned CallIdx = R->getNumOperands() - (R->isPredicated() ? 2 : 1); | ||
return cast<Function>(R->getOperand(CallIdx)->getLiveInIRValue()) | ||
->getReturnType(); | ||
} | ||
case Instruction::UDiv: | ||
case Instruction::SDiv: | ||
case Instruction::SRem: | ||
case Instruction::URem: | ||
case Instruction::Add: | ||
case Instruction::FAdd: | ||
case Instruction::Sub: | ||
case Instruction::FSub: | ||
case Instruction::Mul: | ||
case Instruction::FMul: | ||
case Instruction::FDiv: | ||
case Instruction::FRem: | ||
case Instruction::Shl: | ||
case Instruction::LShr: | ||
case Instruction::AShr: | ||
case Instruction::And: | ||
case Instruction::Or: | ||
case Instruction::Xor: { | ||
Type *ResTy = inferScalarType(R->getOperand(0)); | ||
assert(ResTy == inferScalarType(R->getOperand(1)) && | ||
"inferred types for operands of binary op don't match"); | ||
CachedTypes[R->getOperand(1)] = ResTy; | ||
return ResTy; | ||
} | ||
case Instruction::Select: { | ||
Type *ResTy = inferScalarType(R->getOperand(1)); | ||
assert(ResTy == inferScalarType(R->getOperand(2)) && | ||
"inferred types for operands of select op don't match"); | ||
CachedTypes[R->getOperand(2)] = ResTy; | ||
return ResTy; | ||
} | ||
case Instruction::ICmp: | ||
case Instruction::FCmp: | ||
return IntegerType::get(Ctx, 1); | ||
case Instruction::Alloca: | ||
case Instruction::BitCast: | ||
case Instruction::Trunc: | ||
case Instruction::SExt: | ||
case Instruction::ZExt: | ||
case Instruction::FPExt: | ||
case Instruction::FPTrunc: | ||
case Instruction::ExtractValue: | ||
case Instruction::SIToFP: | ||
case Instruction::UIToFP: | ||
case Instruction::FPToSI: | ||
case Instruction::FPToUI: | ||
case Instruction::PtrToInt: | ||
case Instruction::IntToPtr: | ||
return R->getUnderlyingInstr()->getType(); | ||
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. fall through? 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, thanks! |
||
case Instruction::Freeze: | ||
case Instruction::FNeg: | ||
case Instruction::GetElementPtr: | ||
return inferScalarType(R->getOperand(0)); | ||
case Instruction::Load: | ||
return cast<LoadInst>(R->getUnderlyingInstr())->getType(); | ||
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. Does it matter that the underlying value is a LoadInst? Also supply the type of values being stored? 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.
It's an extra consistency check.
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.
LoadInst deserves an extra consistency check that truncation and extends do not?
Stores cannot be reached indirectly, but one can query their type directly, and one did provide support for querying the type of a widened stores above and interleaved stored below. Treatment should be consistent regardless of how stores are handled (replicated, widened, interleaved). If querying the type of stores is forbidden, it should be documented. 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 interface returns the type of the defined value of the inputs (the public interface computes the type for a VPValue). As stores won't define a result, no type can be queried for them. Does that make sense? |
||
default: | ||
break; | ||
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. switches with return/unreachable cases/default should be handled consistently. 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. Should be handled consistently now, by sinking |
||
} | ||
|
||
llvm_unreachable("Unhandled instruction"); | ||
} | ||
|
||
Type *VPTypeAnalysis::inferScalarType(const VPValue *V) { | ||
if (Type *CachedTy = CachedTypes.lookup(V)) | ||
return CachedTy; | ||
|
||
if (V->isLiveIn()) | ||
return V->getLiveInIRValue()->getType(); | ||
|
||
Type *ResultTy = | ||
TypeSwitch<const VPRecipeBase *, Type *>(V->getDefiningRecipe()) | ||
.Case<VPCanonicalIVPHIRecipe, VPFirstOrderRecurrencePHIRecipe, | ||
VPReductionPHIRecipe, VPWidenPointerInductionRecipe>( | ||
[this](const auto *R) { | ||
// Handle header phi recipes, except VPWienIntOrFpInduction | ||
// which needs special handling due it being possibly truncated. | ||
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. nit/TODO: consider inferring/caching type of siblings, e.g., backedge value, here and in cases below. 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. Added TODO, thanks |
||
return inferScalarType(R->getStartValue()); | ||
}) | ||
.Case<VPWidenIntOrFpInductionRecipe, VPDerivedIVRecipe>( | ||
[](const auto *R) { return R->getScalarType(); }) | ||
.Case<VPPredInstPHIRecipe, VPWidenPHIRecipe, VPScalarIVStepsRecipe, | ||
VPWidenGEPRecipe>([this](const VPRecipeBase *R) { | ||
return inferScalarType(R->getOperand(0)); | ||
}) | ||
.Case<VPBlendRecipe, VPInstruction, VPWidenRecipe, VPReplicateRecipe, | ||
VPWidenCallRecipe, VPWidenMemoryInstructionRecipe, | ||
VPWidenSelectRecipe>( | ||
[this](const auto *R) { return inferScalarTypeForRecipe(R); }) | ||
.Case<VPInterleaveRecipe>([V](const VPInterleaveRecipe *R) { | ||
// TODO: Use info from interleave group. | ||
return V->getUnderlyingValue()->getType(); | ||
}) | ||
.Case<VPWidenCastRecipe>( | ||
[](const VPWidenCastRecipe *R) { return R->getResultType(); }); | ||
assert(ResultTy && "could not infer type for the given VPValue"); | ||
CachedTypes[V] = ResultTy; | ||
return ResultTy; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
//===- VPlanAnalysis.h - Various Analyses working on VPlan ------*- C++ -*-===// | ||
// | ||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
// See https://llvm.org/LICENSE.txt for license information. | ||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#ifndef LLVM_TRANSFORMS_VECTORIZE_VPLANANALYSIS_H | ||
#define LLVM_TRANSFORMS_VECTORIZE_VPLANANALYSIS_H | ||
|
||
#include "llvm/ADT/DenseMap.h" | ||
|
||
namespace llvm { | ||
|
||
class LLVMContext; | ||
class VPValue; | ||
class VPBlendRecipe; | ||
class VPInterleaveRecipe; | ||
class VPInstruction; | ||
class VPReductionPHIRecipe; | ||
class VPWidenRecipe; | ||
class VPWidenCallRecipe; | ||
class VPWidenCastRecipe; | ||
class VPWidenIntOrFpInductionRecipe; | ||
class VPWidenMemoryInstructionRecipe; | ||
struct VPWidenSelectRecipe; | ||
class VPReplicateRecipe; | ||
class Type; | ||
|
||
/// An analysis for type-inference for VPValues. | ||
/// It infers the scalar type for a given VPValue by bottom-up traversing | ||
/// through defining recipes until root nodes with known types are reached (e.g. | ||
/// live-ins or load recipes). The types are then propagated top down through | ||
/// operations. | ||
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 cache cannot be flushed so any changes made to VPlan after invoking the analysis render it obsolete and a new analysis must be constructed. 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. Extended comment, thanks! |
||
/// Note that the analysis caches the inferred types. A new analysis object must | ||
/// be constructed once a VPlan has been modified in a way that invalidates any | ||
/// of the previously inferred types. | ||
class VPTypeAnalysis { | ||
DenseMap<const VPValue *, Type *> CachedTypes; | ||
LLVMContext &Ctx; | ||
|
||
Type *inferScalarTypeForRecipe(const VPBlendRecipe *R); | ||
Type *inferScalarTypeForRecipe(const VPInstruction *R); | ||
Type *inferScalarTypeForRecipe(const VPWidenCallRecipe *R); | ||
Type *inferScalarTypeForRecipe(const VPWidenRecipe *R); | ||
Type *inferScalarTypeForRecipe(const VPWidenIntOrFpInductionRecipe *R); | ||
Type *inferScalarTypeForRecipe(const VPWidenMemoryInstructionRecipe *R); | ||
Type *inferScalarTypeForRecipe(const VPWidenSelectRecipe *R); | ||
Type *inferScalarTypeForRecipe(const VPReplicateRecipe *R); | ||
|
||
public: | ||
VPTypeAnalysis(LLVMContext &Ctx) : Ctx(Ctx) {} | ||
|
||
/// Infer the type of \p V. Returns the scalar type of \p V. | ||
Type *inferScalarType(const VPValue *V); | ||
}; | ||
|
||
} // end namespace llvm | ||
|
||
#endif // LLVM_TRANSFORMS_VECTORIZE_VPLANANALYSIS_H |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
//===----------------------------------------------------------------------===// | ||
|
||
#include "VPlan.h" | ||
#include "VPlanAnalysis.h" | ||
#include "llvm/ADT/STLExtras.h" | ||
#include "llvm/ADT/SmallVector.h" | ||
#include "llvm/ADT/Twine.h" | ||
|
@@ -738,7 +739,19 @@ void VPWidenRecipe::execute(VPTransformState &State) { | |
<< Instruction::getOpcodeName(Opcode)); | ||
llvm_unreachable("Unhandled instruction!"); | ||
} // end of switch. | ||
|
||
#if !defined(NDEBUG) | ||
// Verify that VPlan type inference results agree with the type of the | ||
// generated values. | ||
VPTypeAnalysis A(State.Builder.GetInsertBlock()->getContext()); | ||
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. Caching types is unhelpful and even harmful time-wise if a VPTypeAnalysis is built for every call of inferScalarType(). 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. Will do! |
||
for (unsigned Part = 0; Part < State.UF; ++Part) { | ||
assert(VectorType::get(A.inferScalarType(this), State.VF) == | ||
State.get(this, Part)->getType() && | ||
"inferred type and type from generated instructions do not match"); | ||
} | ||
#endif | ||
} | ||
|
||
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) | ||
void VPWidenRecipe::print(raw_ostream &O, const Twine &Indent, | ||
VPSlotTracker &SlotTracker) const { | ||
|
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.
Check should appear in execute, of every recipe (fine to add others later), but easier for replicate recipe to have it here, once?
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 it is simpler here, as the current structure of VPReplicateRecipe::execute uses earlier exits for different cases