aboutsummaryrefslogtreecommitdiff
path: root/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp')
-rw-r--r--llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp2143
1 files changed, 1479 insertions, 664 deletions
diff --git a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
index aae2fdaf5bec..07baf29ce701 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
@@ -12,12 +12,12 @@
#include "MCTargetDesc/AArch64AddressingModes.h"
#include "Utils/AArch64SMEAttributes.h"
#include "llvm/ADT/DenseMap.h"
-#include "llvm/Analysis/IVDescriptors.h"
#include "llvm/Analysis/LoopInfo.h"
#include "llvm/Analysis/TargetTransformInfo.h"
#include "llvm/CodeGen/BasicTTIImpl.h"
#include "llvm/CodeGen/CostTable.h"
#include "llvm/CodeGen/TargetLowering.h"
+#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/IntrinsicsAArch64.h"
@@ -185,7 +185,7 @@ public:
TailFoldingOption TailFoldingOptionLoc;
-cl::opt<TailFoldingOption, true, cl::parser<std::string>> SVETailFolding(
+static cl::opt<TailFoldingOption, true, cl::parser<std::string>> SVETailFolding(
"sve-tail-folding",
cl::desc(
"Control the use of vectorisation using tail-folding for SVE where the"
@@ -262,29 +262,44 @@ bool AArch64TTIImpl::isMultiversionedFunction(const Function &F) const {
return F.hasFnAttribute("fmv-features");
}
+const FeatureBitset AArch64TTIImpl::InlineInverseFeatures = {
+ AArch64::FeatureExecuteOnly,
+};
+
bool AArch64TTIImpl::areInlineCompatible(const Function *Caller,
const Function *Callee) const {
- SMEAttrs CallerAttrs(*Caller), CalleeAttrs(*Callee);
+ SMECallAttrs CallAttrs(*Caller, *Callee);
// When inlining, we should consider the body of the function, not the
// interface.
- if (CalleeAttrs.hasStreamingBody()) {
- CalleeAttrs.set(SMEAttrs::SM_Compatible, false);
- CalleeAttrs.set(SMEAttrs::SM_Enabled, true);
+ if (CallAttrs.callee().hasStreamingBody()) {
+ CallAttrs.callee().set(SMEAttrs::SM_Compatible, false);
+ CallAttrs.callee().set(SMEAttrs::SM_Enabled, true);
}
- if (CalleeAttrs.isNewZA() || CalleeAttrs.isNewZT0())
+ if (CallAttrs.callee().isNewZA() || CallAttrs.callee().isNewZT0())
return false;
- if (CallerAttrs.requiresLazySave(CalleeAttrs) ||
- CallerAttrs.requiresSMChange(CalleeAttrs) ||
- CallerAttrs.requiresPreservingZT0(CalleeAttrs) ||
- CallerAttrs.requiresPreservingAllZAState(CalleeAttrs)) {
+ if (CallAttrs.requiresLazySave() || CallAttrs.requiresSMChange() ||
+ CallAttrs.requiresPreservingZT0() ||
+ CallAttrs.requiresPreservingAllZAState()) {
if (hasPossibleIncompatibleOps(Callee))
return false;
}
- return BaseT::areInlineCompatible(Caller, Callee);
+ const TargetMachine &TM = getTLI()->getTargetMachine();
+ const FeatureBitset &CallerBits =
+ TM.getSubtargetImpl(*Caller)->getFeatureBits();
+ const FeatureBitset &CalleeBits =
+ TM.getSubtargetImpl(*Callee)->getFeatureBits();
+ // Adjust the feature bitsets by inverting some of the bits. This is needed
+ // for target features that represent restrictions rather than capabilities,
+ // for example a "+execute-only" callee can be inlined into a caller without
+ // "+execute-only", but not vice versa.
+ FeatureBitset EffectiveCallerBits = CallerBits ^ InlineInverseFeatures;
+ FeatureBitset EffectiveCalleeBits = CalleeBits ^ InlineInverseFeatures;
+
+ return (EffectiveCallerBits & EffectiveCalleeBits) == EffectiveCalleeBits;
}
bool AArch64TTIImpl::areTypesABICompatible(
@@ -333,12 +348,14 @@ AArch64TTIImpl::getInlineCallPenalty(const Function *F, const CallBase &Call,
// streaming-mode change, and the call to G from F would also require a
// streaming-mode change, then there is benefit to do the streaming-mode
// change only once and avoid inlining of G into F.
+
SMEAttrs FAttrs(*F);
- SMEAttrs CalleeAttrs(Call);
- if (FAttrs.requiresSMChange(CalleeAttrs)) {
+ SMECallAttrs CallAttrs(Call);
+
+ if (SMECallAttrs(FAttrs, CallAttrs.callee()).requiresSMChange()) {
if (F == Call.getCaller()) // (1)
return CallPenaltyChangeSM * DefaultCallPenalty;
- if (FAttrs.requiresSMChange(SMEAttrs(*Call.getCaller()))) // (2)
+ if (SMECallAttrs(FAttrs, CallAttrs.caller()).requiresSMChange()) // (2)
return InlineCallPenaltyChangeSM * DefaultCallPenalty;
}
@@ -355,7 +372,7 @@ bool AArch64TTIImpl::shouldMaximizeVectorBandwidth(
/// Calculate the cost of materializing a 64-bit value. This helper
/// method might only calculate a fraction of a larger immediate. Therefore it
/// is valid to return a cost of ZERO.
-InstructionCost AArch64TTIImpl::getIntImmCost(int64_t Val) {
+InstructionCost AArch64TTIImpl::getIntImmCost(int64_t Val) const {
// Check if the immediate can be encoded within an instruction.
if (Val == 0 || AArch64_AM::isLogicalImmediate(Val, 64))
return 0;
@@ -370,8 +387,9 @@ InstructionCost AArch64TTIImpl::getIntImmCost(int64_t Val) {
}
/// Calculate the cost of materializing the given constant.
-InstructionCost AArch64TTIImpl::getIntImmCost(const APInt &Imm, Type *Ty,
- TTI::TargetCostKind CostKind) {
+InstructionCost
+AArch64TTIImpl::getIntImmCost(const APInt &Imm, Type *Ty,
+ TTI::TargetCostKind CostKind) const {
assert(Ty->isIntegerTy());
unsigned BitSize = Ty->getPrimitiveSizeInBits();
@@ -398,7 +416,7 @@ InstructionCost AArch64TTIImpl::getIntImmCost(const APInt &Imm, Type *Ty,
InstructionCost AArch64TTIImpl::getIntImmCostInst(unsigned Opcode, unsigned Idx,
const APInt &Imm, Type *Ty,
TTI::TargetCostKind CostKind,
- Instruction *Inst) {
+ Instruction *Inst) const {
assert(Ty->isIntegerTy());
unsigned BitSize = Ty->getPrimitiveSizeInBits();
@@ -466,7 +484,7 @@ InstructionCost AArch64TTIImpl::getIntImmCostInst(unsigned Opcode, unsigned Idx,
InstructionCost
AArch64TTIImpl::getIntImmCostIntrin(Intrinsic::ID IID, unsigned Idx,
const APInt &Imm, Type *Ty,
- TTI::TargetCostKind CostKind) {
+ TTI::TargetCostKind CostKind) const {
assert(Ty->isIntegerTy());
unsigned BitSize = Ty->getPrimitiveSizeInBits();
@@ -516,7 +534,7 @@ AArch64TTIImpl::getIntImmCostIntrin(Intrinsic::ID IID, unsigned Idx,
}
TargetTransformInfo::PopcntSupportKind
-AArch64TTIImpl::getPopcntSupport(unsigned TyWidth) {
+AArch64TTIImpl::getPopcntSupport(unsigned TyWidth) const {
assert(isPowerOf2_32(TyWidth) && "Ty width must be power of 2");
if (TyWidth == 32 || TyWidth == 64)
return TTI::PSK_FastHardware;
@@ -561,7 +579,7 @@ static InstructionCost getHistogramCost(const IntrinsicCostAttributes &ICA) {
InstructionCost
AArch64TTIImpl::getIntrinsicInstrCost(const IntrinsicCostAttributes &ICA,
- TTI::TargetCostKind CostKind) {
+ TTI::TargetCostKind CostKind) const {
// The code-generator is currently not able to handle scalable vectors
// of <vscale x 1 x eltty> yet, so return an invalid cost to avoid selecting
// it. This change will be removed when code-generation for these types is
@@ -868,12 +886,11 @@ AArch64TTIImpl::getIntrinsicInstrCost(const IntrinsicCostAttributes &ICA,
const auto LegalisationCost = getTypeLegalizationCost(RetTy);
if (OpInfoZ.isUniform()) {
- // FIXME: The costs could be lower if the codegen is better.
static const CostTblEntry FshlTbl[] = {
- {Intrinsic::fshl, MVT::v4i32, 3}, // ushr + shl + orr
- {Intrinsic::fshl, MVT::v2i64, 3}, {Intrinsic::fshl, MVT::v16i8, 4},
- {Intrinsic::fshl, MVT::v8i16, 4}, {Intrinsic::fshl, MVT::v2i32, 3},
- {Intrinsic::fshl, MVT::v8i8, 4}, {Intrinsic::fshl, MVT::v4i16, 4}};
+ {Intrinsic::fshl, MVT::v4i32, 2}, // shl + usra
+ {Intrinsic::fshl, MVT::v2i64, 2}, {Intrinsic::fshl, MVT::v16i8, 2},
+ {Intrinsic::fshl, MVT::v8i16, 2}, {Intrinsic::fshl, MVT::v2i32, 2},
+ {Intrinsic::fshl, MVT::v8i8, 2}, {Intrinsic::fshl, MVT::v4i16, 2}};
// Costs for both fshl & fshr are the same, so just pass Intrinsic::fshl
// to avoid having to duplicate the costs.
const auto *Entry =
@@ -940,6 +957,16 @@ AArch64TTIImpl::getIntrinsicInstrCost(const IntrinsicCostAttributes &ICA,
}
break;
}
+ case Intrinsic::experimental_cttz_elts: {
+ EVT ArgVT = getTLI()->getValueType(DL, ICA.getArgTypes()[0]);
+ if (!getTLI()->shouldExpandCttzElements(ArgVT)) {
+ // This will consist of a SVE brkb and a cntp instruction. These
+ // typically have the same latency and half the throughput as a vector
+ // add instruction.
+ return 4;
+ }
+ break;
+ }
default:
break;
}
@@ -984,6 +1011,660 @@ static std::optional<Instruction *> processPhiNode(InstCombiner &IC,
return IC.replaceInstUsesWith(II, NPN);
}
+// A collection of properties common to SVE intrinsics that allow for combines
+// to be written without needing to know the specific intrinsic.
+struct SVEIntrinsicInfo {
+ //
+ // Helper routines for common intrinsic definitions.
+ //
+
+ // e.g. llvm.aarch64.sve.add pg, op1, op2
+ // with IID ==> llvm.aarch64.sve.add_u
+ static SVEIntrinsicInfo
+ defaultMergingOp(Intrinsic::ID IID = Intrinsic::not_intrinsic) {
+ return SVEIntrinsicInfo()
+ .setGoverningPredicateOperandIdx(0)
+ .setOperandIdxInactiveLanesTakenFrom(1)
+ .setMatchingUndefIntrinsic(IID);
+ }
+
+ // e.g. llvm.aarch64.sve.neg inactive, pg, op
+ static SVEIntrinsicInfo defaultMergingUnaryOp() {
+ return SVEIntrinsicInfo()
+ .setGoverningPredicateOperandIdx(1)
+ .setOperandIdxInactiveLanesTakenFrom(0)
+ .setOperandIdxWithNoActiveLanes(0);
+ }
+
+ // e.g. llvm.aarch64.sve.fcvtnt inactive, pg, op
+ static SVEIntrinsicInfo defaultMergingUnaryNarrowingTopOp() {
+ return SVEIntrinsicInfo()
+ .setGoverningPredicateOperandIdx(1)
+ .setOperandIdxInactiveLanesTakenFrom(0);
+ }
+
+ // e.g. llvm.aarch64.sve.add_u pg, op1, op2
+ static SVEIntrinsicInfo defaultUndefOp() {
+ return SVEIntrinsicInfo()
+ .setGoverningPredicateOperandIdx(0)
+ .setInactiveLanesAreNotDefined();
+ }
+
+ // e.g. llvm.aarch64.sve.prf pg, ptr (GPIndex = 0)
+ // llvm.aarch64.sve.st1 data, pg, ptr (GPIndex = 1)
+ static SVEIntrinsicInfo defaultVoidOp(unsigned GPIndex) {
+ return SVEIntrinsicInfo()
+ .setGoverningPredicateOperandIdx(GPIndex)
+ .setInactiveLanesAreUnused();
+ }
+
+ // e.g. llvm.aarch64.sve.cmpeq pg, op1, op2
+ // llvm.aarch64.sve.ld1 pg, ptr
+ static SVEIntrinsicInfo defaultZeroingOp() {
+ return SVEIntrinsicInfo()
+ .setGoverningPredicateOperandIdx(0)
+ .setInactiveLanesAreUnused()
+ .setResultIsZeroInitialized();
+ }
+
+ // All properties relate to predication and thus having a general predicate
+ // is the minimum requirement to say there is intrinsic info to act on.
+ explicit operator bool() const { return hasGoverningPredicate(); }
+
+ //
+ // Properties relating to the governing predicate.
+ //
+
+ bool hasGoverningPredicate() const {
+ return GoverningPredicateIdx != std::numeric_limits<unsigned>::max();
+ }
+
+ unsigned getGoverningPredicateOperandIdx() const {
+ assert(hasGoverningPredicate() && "Propery not set!");
+ return GoverningPredicateIdx;
+ }
+
+ SVEIntrinsicInfo &setGoverningPredicateOperandIdx(unsigned Index) {
+ assert(!hasGoverningPredicate() && "Cannot set property twice!");
+ GoverningPredicateIdx = Index;
+ return *this;
+ }
+
+ //
+ // Properties relating to operations the intrinsic could be transformed into.
+ // NOTE: This does not mean such a transformation is always possible, but the
+ // knowledge makes it possible to reuse existing optimisations without needing
+ // to embed specific handling for each intrinsic. For example, instruction
+ // simplification can be used to optimise an intrinsic's active lanes.
+ //
+
+ bool hasMatchingUndefIntrinsic() const {
+ return UndefIntrinsic != Intrinsic::not_intrinsic;
+ }
+
+ Intrinsic::ID getMatchingUndefIntrinsic() const {
+ assert(hasMatchingUndefIntrinsic() && "Propery not set!");
+ return UndefIntrinsic;
+ }
+
+ SVEIntrinsicInfo &setMatchingUndefIntrinsic(Intrinsic::ID IID) {
+ assert(!hasMatchingUndefIntrinsic() && "Cannot set property twice!");
+ UndefIntrinsic = IID;
+ return *this;
+ }
+
+ bool hasMatchingIROpode() const { return IROpcode != 0; }
+
+ unsigned getMatchingIROpode() const {
+ assert(hasMatchingIROpode() && "Propery not set!");
+ return IROpcode;
+ }
+
+ SVEIntrinsicInfo &setMatchingIROpcode(unsigned Opcode) {
+ assert(!hasMatchingIROpode() && "Cannot set property twice!");
+ IROpcode = Opcode;
+ return *this;
+ }
+
+ //
+ // Properties relating to the result of inactive lanes.
+ //
+
+ bool inactiveLanesTakenFromOperand() const {
+ return ResultLanes == InactiveLanesTakenFromOperand;
+ }
+
+ unsigned getOperandIdxInactiveLanesTakenFrom() const {
+ assert(inactiveLanesTakenFromOperand() && "Propery not set!");
+ return OperandIdxForInactiveLanes;
+ }
+
+ SVEIntrinsicInfo &setOperandIdxInactiveLanesTakenFrom(unsigned Index) {
+ assert(ResultLanes == Uninitialized && "Cannot set property twice!");
+ ResultLanes = InactiveLanesTakenFromOperand;
+ OperandIdxForInactiveLanes = Index;
+ return *this;
+ }
+
+ bool inactiveLanesAreNotDefined() const {
+ return ResultLanes == InactiveLanesAreNotDefined;
+ }
+
+ SVEIntrinsicInfo &setInactiveLanesAreNotDefined() {
+ assert(ResultLanes == Uninitialized && "Cannot set property twice!");
+ ResultLanes = InactiveLanesAreNotDefined;
+ return *this;
+ }
+
+ bool inactiveLanesAreUnused() const {
+ return ResultLanes == InactiveLanesAreUnused;
+ }
+
+ SVEIntrinsicInfo &setInactiveLanesAreUnused() {
+ assert(ResultLanes == Uninitialized && "Cannot set property twice!");
+ ResultLanes = InactiveLanesAreUnused;
+ return *this;
+ }
+
+ // NOTE: Whilst not limited to only inactive lanes, the common use case is:
+ // inactiveLanesAreZeroed =
+ // resultIsZeroInitialized() && inactiveLanesAreUnused()
+ bool resultIsZeroInitialized() const { return ResultIsZeroInitialized; }
+
+ SVEIntrinsicInfo &setResultIsZeroInitialized() {
+ ResultIsZeroInitialized = true;
+ return *this;
+ }
+
+ //
+ // The first operand of unary merging operations is typically only used to
+ // set the result for inactive lanes. Knowing this allows us to deadcode the
+ // operand when we can prove there are no inactive lanes.
+ //
+
+ bool hasOperandWithNoActiveLanes() const {
+ return OperandIdxWithNoActiveLanes != std::numeric_limits<unsigned>::max();
+ }
+
+ unsigned getOperandIdxWithNoActiveLanes() const {
+ assert(hasOperandWithNoActiveLanes() && "Propery not set!");
+ return OperandIdxWithNoActiveLanes;
+ }
+
+ SVEIntrinsicInfo &setOperandIdxWithNoActiveLanes(unsigned Index) {
+ assert(!hasOperandWithNoActiveLanes() && "Cannot set property twice!");
+ OperandIdxWithNoActiveLanes = Index;
+ return *this;
+ }
+
+private:
+ unsigned GoverningPredicateIdx = std::numeric_limits<unsigned>::max();
+
+ Intrinsic::ID UndefIntrinsic = Intrinsic::not_intrinsic;
+ unsigned IROpcode = 0;
+
+ enum PredicationStyle {
+ Uninitialized,
+ InactiveLanesTakenFromOperand,
+ InactiveLanesAreNotDefined,
+ InactiveLanesAreUnused
+ } ResultLanes = Uninitialized;
+
+ bool ResultIsZeroInitialized = false;
+ unsigned OperandIdxForInactiveLanes = std::numeric_limits<unsigned>::max();
+ unsigned OperandIdxWithNoActiveLanes = std::numeric_limits<unsigned>::max();
+};
+
+static SVEIntrinsicInfo constructSVEIntrinsicInfo(IntrinsicInst &II) {
+ // Some SVE intrinsics do not use scalable vector types, but since they are
+ // not relevant from an SVEIntrinsicInfo perspective, they are also ignored.
+ if (!isa<ScalableVectorType>(II.getType()) &&
+ all_of(II.args(), [&](const Value *V) {
+ return !isa<ScalableVectorType>(V->getType());
+ }))
+ return SVEIntrinsicInfo();
+
+ Intrinsic::ID IID = II.getIntrinsicID();
+ switch (IID) {
+ default:
+ break;
+ case Intrinsic::aarch64_sve_fcvt_bf16f32_v2:
+ case Intrinsic::aarch64_sve_fcvt_f16f32:
+ case Intrinsic::aarch64_sve_fcvt_f16f64:
+ case Intrinsic::aarch64_sve_fcvt_f32f16:
+ case Intrinsic::aarch64_sve_fcvt_f32f64:
+ case Intrinsic::aarch64_sve_fcvt_f64f16:
+ case Intrinsic::aarch64_sve_fcvt_f64f32:
+ case Intrinsic::aarch64_sve_fcvtlt_f32f16:
+ case Intrinsic::aarch64_sve_fcvtlt_f64f32:
+ case Intrinsic::aarch64_sve_fcvtx_f32f64:
+ case Intrinsic::aarch64_sve_fcvtzs:
+ case Intrinsic::aarch64_sve_fcvtzs_i32f16:
+ case Intrinsic::aarch64_sve_fcvtzs_i32f64:
+ case Intrinsic::aarch64_sve_fcvtzs_i64f16:
+ case Intrinsic::aarch64_sve_fcvtzs_i64f32:
+ case Intrinsic::aarch64_sve_fcvtzu:
+ case Intrinsic::aarch64_sve_fcvtzu_i32f16:
+ case Intrinsic::aarch64_sve_fcvtzu_i32f64:
+ case Intrinsic::aarch64_sve_fcvtzu_i64f16:
+ case Intrinsic::aarch64_sve_fcvtzu_i64f32:
+ case Intrinsic::aarch64_sve_scvtf:
+ case Intrinsic::aarch64_sve_scvtf_f16i32:
+ case Intrinsic::aarch64_sve_scvtf_f16i64:
+ case Intrinsic::aarch64_sve_scvtf_f32i64:
+ case Intrinsic::aarch64_sve_scvtf_f64i32:
+ case Intrinsic::aarch64_sve_ucvtf:
+ case Intrinsic::aarch64_sve_ucvtf_f16i32:
+ case Intrinsic::aarch64_sve_ucvtf_f16i64:
+ case Intrinsic::aarch64_sve_ucvtf_f32i64:
+ case Intrinsic::aarch64_sve_ucvtf_f64i32:
+ return SVEIntrinsicInfo::defaultMergingUnaryOp();
+
+ case Intrinsic::aarch64_sve_fcvtnt_bf16f32_v2:
+ case Intrinsic::aarch64_sve_fcvtnt_f16f32:
+ case Intrinsic::aarch64_sve_fcvtnt_f32f64:
+ case Intrinsic::aarch64_sve_fcvtxnt_f32f64:
+ return SVEIntrinsicInfo::defaultMergingUnaryNarrowingTopOp();
+
+ case Intrinsic::aarch64_sve_fabd:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_fabd_u);
+ case Intrinsic::aarch64_sve_fadd:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_fadd_u)
+ .setMatchingIROpcode(Instruction::FAdd);
+ case Intrinsic::aarch64_sve_fdiv:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_fdiv_u)
+ .setMatchingIROpcode(Instruction::FDiv);
+ case Intrinsic::aarch64_sve_fmax:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_fmax_u);
+ case Intrinsic::aarch64_sve_fmaxnm:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_fmaxnm_u);
+ case Intrinsic::aarch64_sve_fmin:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_fmin_u);
+ case Intrinsic::aarch64_sve_fminnm:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_fminnm_u);
+ case Intrinsic::aarch64_sve_fmla:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_fmla_u);
+ case Intrinsic::aarch64_sve_fmls:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_fmls_u);
+ case Intrinsic::aarch64_sve_fmul:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_fmul_u)
+ .setMatchingIROpcode(Instruction::FMul);
+ case Intrinsic::aarch64_sve_fmulx:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_fmulx_u);
+ case Intrinsic::aarch64_sve_fnmla:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_fnmla_u);
+ case Intrinsic::aarch64_sve_fnmls:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_fnmls_u);
+ case Intrinsic::aarch64_sve_fsub:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_fsub_u)
+ .setMatchingIROpcode(Instruction::FSub);
+ case Intrinsic::aarch64_sve_add:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_add_u)
+ .setMatchingIROpcode(Instruction::Add);
+ case Intrinsic::aarch64_sve_mla:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_mla_u);
+ case Intrinsic::aarch64_sve_mls:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_mls_u);
+ case Intrinsic::aarch64_sve_mul:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_mul_u)
+ .setMatchingIROpcode(Instruction::Mul);
+ case Intrinsic::aarch64_sve_sabd:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_sabd_u);
+ case Intrinsic::aarch64_sve_sdiv:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_sdiv_u)
+ .setMatchingIROpcode(Instruction::SDiv);
+ case Intrinsic::aarch64_sve_smax:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_smax_u);
+ case Intrinsic::aarch64_sve_smin:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_smin_u);
+ case Intrinsic::aarch64_sve_smulh:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_smulh_u);
+ case Intrinsic::aarch64_sve_sub:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_sub_u)
+ .setMatchingIROpcode(Instruction::Sub);
+ case Intrinsic::aarch64_sve_uabd:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_uabd_u);
+ case Intrinsic::aarch64_sve_udiv:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_udiv_u)
+ .setMatchingIROpcode(Instruction::UDiv);
+ case Intrinsic::aarch64_sve_umax:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_umax_u);
+ case Intrinsic::aarch64_sve_umin:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_umin_u);
+ case Intrinsic::aarch64_sve_umulh:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_umulh_u);
+ case Intrinsic::aarch64_sve_asr:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_asr_u)
+ .setMatchingIROpcode(Instruction::AShr);
+ case Intrinsic::aarch64_sve_lsl:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_lsl_u)
+ .setMatchingIROpcode(Instruction::Shl);
+ case Intrinsic::aarch64_sve_lsr:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_lsr_u)
+ .setMatchingIROpcode(Instruction::LShr);
+ case Intrinsic::aarch64_sve_and:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_and_u)
+ .setMatchingIROpcode(Instruction::And);
+ case Intrinsic::aarch64_sve_bic:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_bic_u);
+ case Intrinsic::aarch64_sve_eor:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_eor_u)
+ .setMatchingIROpcode(Instruction::Xor);
+ case Intrinsic::aarch64_sve_orr:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_orr_u)
+ .setMatchingIROpcode(Instruction::Or);
+ case Intrinsic::aarch64_sve_sqsub:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_sqsub_u);
+ case Intrinsic::aarch64_sve_uqsub:
+ return SVEIntrinsicInfo::defaultMergingOp(Intrinsic::aarch64_sve_uqsub_u);
+
+ case Intrinsic::aarch64_sve_add_u:
+ return SVEIntrinsicInfo::defaultUndefOp().setMatchingIROpcode(
+ Instruction::Add);
+ case Intrinsic::aarch64_sve_and_u:
+ return SVEIntrinsicInfo::defaultUndefOp().setMatchingIROpcode(
+ Instruction::And);
+ case Intrinsic::aarch64_sve_asr_u:
+ return SVEIntrinsicInfo::defaultUndefOp().setMatchingIROpcode(
+ Instruction::AShr);
+ case Intrinsic::aarch64_sve_eor_u:
+ return SVEIntrinsicInfo::defaultUndefOp().setMatchingIROpcode(
+ Instruction::Xor);
+ case Intrinsic::aarch64_sve_fadd_u:
+ return SVEIntrinsicInfo::defaultUndefOp().setMatchingIROpcode(
+ Instruction::FAdd);
+ case Intrinsic::aarch64_sve_fdiv_u:
+ return SVEIntrinsicInfo::defaultUndefOp().setMatchingIROpcode(
+ Instruction::FDiv);
+ case Intrinsic::aarch64_sve_fmul_u:
+ return SVEIntrinsicInfo::defaultUndefOp().setMatchingIROpcode(
+ Instruction::FMul);
+ case Intrinsic::aarch64_sve_fsub_u:
+ return SVEIntrinsicInfo::defaultUndefOp().setMatchingIROpcode(
+ Instruction::FSub);
+ case Intrinsic::aarch64_sve_lsl_u:
+ return SVEIntrinsicInfo::defaultUndefOp().setMatchingIROpcode(
+ Instruction::Shl);
+ case Intrinsic::aarch64_sve_lsr_u:
+ return SVEIntrinsicInfo::defaultUndefOp().setMatchingIROpcode(
+ Instruction::LShr);
+ case Intrinsic::aarch64_sve_mul_u:
+ return SVEIntrinsicInfo::defaultUndefOp().setMatchingIROpcode(
+ Instruction::Mul);
+ case Intrinsic::aarch64_sve_orr_u:
+ return SVEIntrinsicInfo::defaultUndefOp().setMatchingIROpcode(
+ Instruction::Or);
+ case Intrinsic::aarch64_sve_sdiv_u:
+ return SVEIntrinsicInfo::defaultUndefOp().setMatchingIROpcode(
+ Instruction::SDiv);
+ case Intrinsic::aarch64_sve_sub_u:
+ return SVEIntrinsicInfo::defaultUndefOp().setMatchingIROpcode(
+ Instruction::Sub);
+ case Intrinsic::aarch64_sve_udiv_u:
+ return SVEIntrinsicInfo::defaultUndefOp().setMatchingIROpcode(
+ Instruction::UDiv);
+
+ case Intrinsic::aarch64_sve_addqv:
+ case Intrinsic::aarch64_sve_and_z:
+ case Intrinsic::aarch64_sve_bic_z:
+ case Intrinsic::aarch64_sve_brka_z:
+ case Intrinsic::aarch64_sve_brkb_z:
+ case Intrinsic::aarch64_sve_brkn_z:
+ case Intrinsic::aarch64_sve_brkpa_z:
+ case Intrinsic::aarch64_sve_brkpb_z:
+ case Intrinsic::aarch64_sve_cntp:
+ case Intrinsic::aarch64_sve_compact:
+ case Intrinsic::aarch64_sve_eor_z:
+ case Intrinsic::aarch64_sve_eorv:
+ case Intrinsic::aarch64_sve_eorqv:
+ case Intrinsic::aarch64_sve_nand_z:
+ case Intrinsic::aarch64_sve_nor_z:
+ case Intrinsic::aarch64_sve_orn_z:
+ case Intrinsic::aarch64_sve_orr_z:
+ case Intrinsic::aarch64_sve_orv:
+ case Intrinsic::aarch64_sve_orqv:
+ case Intrinsic::aarch64_sve_pnext:
+ case Intrinsic::aarch64_sve_rdffr_z:
+ case Intrinsic::aarch64_sve_saddv:
+ case Intrinsic::aarch64_sve_uaddv:
+ case Intrinsic::aarch64_sve_umaxv:
+ case Intrinsic::aarch64_sve_umaxqv:
+ case Intrinsic::aarch64_sve_cmpeq:
+ case Intrinsic::aarch64_sve_cmpeq_wide:
+ case Intrinsic::aarch64_sve_cmpge:
+ case Intrinsic::aarch64_sve_cmpge_wide:
+ case Intrinsic::aarch64_sve_cmpgt:
+ case Intrinsic::aarch64_sve_cmpgt_wide:
+ case Intrinsic::aarch64_sve_cmphi:
+ case Intrinsic::aarch64_sve_cmphi_wide:
+ case Intrinsic::aarch64_sve_cmphs:
+ case Intrinsic::aarch64_sve_cmphs_wide:
+ case Intrinsic::aarch64_sve_cmple_wide:
+ case Intrinsic::aarch64_sve_cmplo_wide:
+ case Intrinsic::aarch64_sve_cmpls_wide:
+ case Intrinsic::aarch64_sve_cmplt_wide:
+ case Intrinsic::aarch64_sve_cmpne:
+ case Intrinsic::aarch64_sve_cmpne_wide:
+ case Intrinsic::aarch64_sve_facge:
+ case Intrinsic::aarch64_sve_facgt:
+ case Intrinsic::aarch64_sve_fcmpeq:
+ case Intrinsic::aarch64_sve_fcmpge:
+ case Intrinsic::aarch64_sve_fcmpgt:
+ case Intrinsic::aarch64_sve_fcmpne:
+ case Intrinsic::aarch64_sve_fcmpuo:
+ case Intrinsic::aarch64_sve_ld1:
+ case Intrinsic::aarch64_sve_ld1_gather:
+ case Intrinsic::aarch64_sve_ld1_gather_index:
+ case Intrinsic::aarch64_sve_ld1_gather_scalar_offset:
+ case Intrinsic::aarch64_sve_ld1_gather_sxtw:
+ case Intrinsic::aarch64_sve_ld1_gather_sxtw_index:
+ case Intrinsic::aarch64_sve_ld1_gather_uxtw:
+ case Intrinsic::aarch64_sve_ld1_gather_uxtw_index:
+ case Intrinsic::aarch64_sve_ld1q_gather_index:
+ case Intrinsic::aarch64_sve_ld1q_gather_scalar_offset:
+ case Intrinsic::aarch64_sve_ld1q_gather_vector_offset:
+ case Intrinsic::aarch64_sve_ld1ro:
+ case Intrinsic::aarch64_sve_ld1rq:
+ case Intrinsic::aarch64_sve_ld1udq:
+ case Intrinsic::aarch64_sve_ld1uwq:
+ case Intrinsic::aarch64_sve_ld2_sret:
+ case Intrinsic::aarch64_sve_ld2q_sret:
+ case Intrinsic::aarch64_sve_ld3_sret:
+ case Intrinsic::aarch64_sve_ld3q_sret:
+ case Intrinsic::aarch64_sve_ld4_sret:
+ case Intrinsic::aarch64_sve_ld4q_sret:
+ case Intrinsic::aarch64_sve_ldff1:
+ case Intrinsic::aarch64_sve_ldff1_gather:
+ case Intrinsic::aarch64_sve_ldff1_gather_index:
+ case Intrinsic::aarch64_sve_ldff1_gather_scalar_offset:
+ case Intrinsic::aarch64_sve_ldff1_gather_sxtw:
+ case Intrinsic::aarch64_sve_ldff1_gather_sxtw_index:
+ case Intrinsic::aarch64_sve_ldff1_gather_uxtw:
+ case Intrinsic::aarch64_sve_ldff1_gather_uxtw_index:
+ case Intrinsic::aarch64_sve_ldnf1:
+ case Intrinsic::aarch64_sve_ldnt1:
+ case Intrinsic::aarch64_sve_ldnt1_gather:
+ case Intrinsic::aarch64_sve_ldnt1_gather_index:
+ case Intrinsic::aarch64_sve_ldnt1_gather_scalar_offset:
+ case Intrinsic::aarch64_sve_ldnt1_gather_uxtw:
+ return SVEIntrinsicInfo::defaultZeroingOp();
+
+ case Intrinsic::aarch64_sve_prf:
+ case Intrinsic::aarch64_sve_prfb_gather_index:
+ case Intrinsic::aarch64_sve_prfb_gather_scalar_offset:
+ case Intrinsic::aarch64_sve_prfb_gather_sxtw_index:
+ case Intrinsic::aarch64_sve_prfb_gather_uxtw_index:
+ case Intrinsic::aarch64_sve_prfd_gather_index:
+ case Intrinsic::aarch64_sve_prfd_gather_scalar_offset:
+ case Intrinsic::aarch64_sve_prfd_gather_sxtw_index:
+ case Intrinsic::aarch64_sve_prfd_gather_uxtw_index:
+ case Intrinsic::aarch64_sve_prfh_gather_index:
+ case Intrinsic::aarch64_sve_prfh_gather_scalar_offset:
+ case Intrinsic::aarch64_sve_prfh_gather_sxtw_index:
+ case Intrinsic::aarch64_sve_prfh_gather_uxtw_index:
+ case Intrinsic::aarch64_sve_prfw_gather_index:
+ case Intrinsic::aarch64_sve_prfw_gather_scalar_offset:
+ case Intrinsic::aarch64_sve_prfw_gather_sxtw_index:
+ case Intrinsic::aarch64_sve_prfw_gather_uxtw_index:
+ return SVEIntrinsicInfo::defaultVoidOp(0);
+
+ case Intrinsic::aarch64_sve_st1_scatter:
+ case Intrinsic::aarch64_sve_st1_scatter_scalar_offset:
+ case Intrinsic::aarch64_sve_st1_scatter_sxtw:
+ case Intrinsic::aarch64_sve_st1_scatter_sxtw_index:
+ case Intrinsic::aarch64_sve_st1_scatter_uxtw:
+ case Intrinsic::aarch64_sve_st1_scatter_uxtw_index:
+ case Intrinsic::aarch64_sve_st1dq:
+ case Intrinsic::aarch64_sve_st1q_scatter_index:
+ case Intrinsic::aarch64_sve_st1q_scatter_scalar_offset:
+ case Intrinsic::aarch64_sve_st1q_scatter_vector_offset:
+ case Intrinsic::aarch64_sve_st1wq:
+ case Intrinsic::aarch64_sve_stnt1:
+ case Intrinsic::aarch64_sve_stnt1_scatter:
+ case Intrinsic::aarch64_sve_stnt1_scatter_index:
+ case Intrinsic::aarch64_sve_stnt1_scatter_scalar_offset:
+ case Intrinsic::aarch64_sve_stnt1_scatter_uxtw:
+ return SVEIntrinsicInfo::defaultVoidOp(1);
+ case Intrinsic::aarch64_sve_st2:
+ case Intrinsic::aarch64_sve_st2q:
+ return SVEIntrinsicInfo::defaultVoidOp(2);
+ case Intrinsic::aarch64_sve_st3:
+ case Intrinsic::aarch64_sve_st3q:
+ return SVEIntrinsicInfo::defaultVoidOp(3);
+ case Intrinsic::aarch64_sve_st4:
+ case Intrinsic::aarch64_sve_st4q:
+ return SVEIntrinsicInfo::defaultVoidOp(4);
+ }
+
+ return SVEIntrinsicInfo();
+}
+
+static bool isAllActivePredicate(Value *Pred) {
+ // Look through convert.from.svbool(convert.to.svbool(...) chain.
+ Value *UncastedPred;
+ if (match(Pred, m_Intrinsic<Intrinsic::aarch64_sve_convert_from_svbool>(
+ m_Intrinsic<Intrinsic::aarch64_sve_convert_to_svbool>(
+ m_Value(UncastedPred)))))
+ // If the predicate has the same or less lanes than the uncasted
+ // predicate then we know the casting has no effect.
+ if (cast<ScalableVectorType>(Pred->getType())->getMinNumElements() <=
+ cast<ScalableVectorType>(UncastedPred->getType())->getMinNumElements())
+ Pred = UncastedPred;
+ auto *C = dyn_cast<Constant>(Pred);
+ return (C && C->isAllOnesValue());
+}
+
+// Simplify `V` by only considering the operations that affect active lanes.
+// This function should only return existing Values or newly created Constants.
+static Value *stripInactiveLanes(Value *V, const Value *Pg) {
+ auto *Dup = dyn_cast<IntrinsicInst>(V);
+ if (Dup && Dup->getIntrinsicID() == Intrinsic::aarch64_sve_dup &&
+ Dup->getOperand(1) == Pg && isa<Constant>(Dup->getOperand(2)))
+ return ConstantVector::getSplat(
+ cast<VectorType>(V->getType())->getElementCount(),
+ cast<Constant>(Dup->getOperand(2)));
+
+ return V;
+}
+
+static std::optional<Instruction *>
+simplifySVEIntrinsicBinOp(InstCombiner &IC, IntrinsicInst &II,
+ const SVEIntrinsicInfo &IInfo) {
+ const unsigned Opc = IInfo.getMatchingIROpode();
+ assert(Instruction::isBinaryOp(Opc) && "Expected a binary operation!");
+
+ Value *Pg = II.getOperand(0);
+ Value *Op1 = II.getOperand(1);
+ Value *Op2 = II.getOperand(2);
+ const DataLayout &DL = II.getDataLayout();
+
+ // Canonicalise constants to the RHS.
+ if (Instruction::isCommutative(Opc) && IInfo.inactiveLanesAreNotDefined() &&
+ isa<Constant>(Op1) && !isa<Constant>(Op2)) {
+ IC.replaceOperand(II, 1, Op2);
+ IC.replaceOperand(II, 2, Op1);
+ return &II;
+ }
+
+ // Only active lanes matter when simplifying the operation.
+ Op1 = stripInactiveLanes(Op1, Pg);
+ Op2 = stripInactiveLanes(Op2, Pg);
+
+ Value *SimpleII;
+ if (auto FII = dyn_cast<FPMathOperator>(&II))
+ SimpleII = simplifyBinOp(Opc, Op1, Op2, FII->getFastMathFlags(), DL);
+ else
+ SimpleII = simplifyBinOp(Opc, Op1, Op2, DL);
+
+ // An SVE intrinsic's result is always defined. However, this is not the case
+ // for its equivalent IR instruction (e.g. when shifting by an amount more
+ // than the data's bitwidth). Simplifications to an undefined result must be
+ // ignored to preserve the intrinsic's expected behaviour.
+ if (!SimpleII || isa<UndefValue>(SimpleII))
+ return std::nullopt;
+
+ if (IInfo.inactiveLanesAreNotDefined())
+ return IC.replaceInstUsesWith(II, SimpleII);
+
+ Value *Inactive = II.getOperand(IInfo.getOperandIdxInactiveLanesTakenFrom());
+
+ // The intrinsic does nothing (e.g. sve.mul(pg, A, 1.0)).
+ if (SimpleII == Inactive)
+ return IC.replaceInstUsesWith(II, SimpleII);
+
+ // Inactive lanes must be preserved.
+ SimpleII = IC.Builder.CreateSelect(Pg, SimpleII, Inactive);
+ return IC.replaceInstUsesWith(II, SimpleII);
+}
+
+// Use SVE intrinsic info to eliminate redundant operands and/or canonicalise
+// to operations with less strict inactive lane requirements.
+static std::optional<Instruction *>
+simplifySVEIntrinsic(InstCombiner &IC, IntrinsicInst &II,
+ const SVEIntrinsicInfo &IInfo) {
+ if (!IInfo.hasGoverningPredicate())
+ return std::nullopt;
+
+ auto *OpPredicate = II.getOperand(IInfo.getGoverningPredicateOperandIdx());
+
+ // If there are no active lanes.
+ if (match(OpPredicate, m_ZeroInt())) {
+ if (IInfo.inactiveLanesTakenFromOperand())
+ return IC.replaceInstUsesWith(
+ II, II.getOperand(IInfo.getOperandIdxInactiveLanesTakenFrom()));
+
+ if (IInfo.inactiveLanesAreUnused()) {
+ if (IInfo.resultIsZeroInitialized())
+ IC.replaceInstUsesWith(II, Constant::getNullValue(II.getType()));
+
+ return IC.eraseInstFromFunction(II);
+ }
+ }
+
+ // If there are no inactive lanes.
+ if (isAllActivePredicate(OpPredicate)) {
+ if (IInfo.hasOperandWithNoActiveLanes()) {
+ unsigned OpIdx = IInfo.getOperandIdxWithNoActiveLanes();
+ if (!isa<UndefValue>(II.getOperand(OpIdx)))
+ return IC.replaceOperand(II, OpIdx, UndefValue::get(II.getType()));
+ }
+
+ if (IInfo.hasMatchingUndefIntrinsic()) {
+ auto *NewDecl = Intrinsic::getOrInsertDeclaration(
+ II.getModule(), IInfo.getMatchingUndefIntrinsic(), {II.getType()});
+ II.setCalledFunction(NewDecl);
+ return &II;
+ }
+ }
+
+ // Operation specific simplifications.
+ if (IInfo.hasMatchingIROpode() &&
+ Instruction::isBinaryOp(IInfo.getMatchingIROpode()))
+ return simplifySVEIntrinsicBinOp(IC, II, IInfo);
+
+ return std::nullopt;
+}
+
// (from_svbool (binop (to_svbool pred) (svbool_t _) (svbool_t _))))
// => (binop (pred) (from_svbool _) (from_svbool _))
//
@@ -1095,85 +1776,6 @@ instCombineConvertFromSVBool(InstCombiner &IC, IntrinsicInst &II) {
return IC.replaceInstUsesWith(II, EarliestReplacement);
}
-static bool isAllActivePredicate(Value *Pred) {
- // Look through convert.from.svbool(convert.to.svbool(...) chain.
- Value *UncastedPred;
- if (match(Pred, m_Intrinsic<Intrinsic::aarch64_sve_convert_from_svbool>(
- m_Intrinsic<Intrinsic::aarch64_sve_convert_to_svbool>(
- m_Value(UncastedPred)))))
- // If the predicate has the same or less lanes than the uncasted
- // predicate then we know the casting has no effect.
- if (cast<ScalableVectorType>(Pred->getType())->getMinNumElements() <=
- cast<ScalableVectorType>(UncastedPred->getType())->getMinNumElements())
- Pred = UncastedPred;
-
- return match(Pred, m_Intrinsic<Intrinsic::aarch64_sve_ptrue>(
- m_ConstantInt<AArch64SVEPredPattern::all>()));
-}
-
-// Simplify unary operation where predicate has all inactive lanes by replacing
-// instruction with its operand
-static std::optional<Instruction *>
-instCombineSVENoActiveReplace(InstCombiner &IC, IntrinsicInst &II,
- bool hasInactiveVector) {
- int PredOperand = hasInactiveVector ? 1 : 0;
- int ReplaceOperand = hasInactiveVector ? 0 : 1;
- if (match(II.getOperand(PredOperand), m_ZeroInt())) {
- IC.replaceInstUsesWith(II, II.getOperand(ReplaceOperand));
- return IC.eraseInstFromFunction(II);
- }
- return std::nullopt;
-}
-
-// Simplify unary operation where predicate has all inactive lanes or
-// replace unused first operand with undef when all lanes are active
-static std::optional<Instruction *>
-instCombineSVEAllOrNoActiveUnary(InstCombiner &IC, IntrinsicInst &II) {
- if (isAllActivePredicate(II.getOperand(1)) &&
- !isa<llvm::UndefValue>(II.getOperand(0)) &&
- !isa<llvm::PoisonValue>(II.getOperand(0))) {
- Value *Undef = llvm::UndefValue::get(II.getType());
- return IC.replaceOperand(II, 0, Undef);
- }
- return instCombineSVENoActiveReplace(IC, II, true);
-}
-
-// Erase unary operation where predicate has all inactive lanes
-static std::optional<Instruction *>
-instCombineSVENoActiveUnaryErase(InstCombiner &IC, IntrinsicInst &II,
- int PredPos) {
- if (match(II.getOperand(PredPos), m_ZeroInt())) {
- return IC.eraseInstFromFunction(II);
- }
- return std::nullopt;
-}
-
-// Simplify operation where predicate has all inactive lanes by replacing
-// instruction with zeroed object
-static std::optional<Instruction *>
-instCombineSVENoActiveZero(InstCombiner &IC, IntrinsicInst &II) {
- if (match(II.getOperand(0), m_ZeroInt())) {
- Constant *Node;
- Type *RetTy = II.getType();
- if (RetTy->isStructTy()) {
- auto StructT = cast<StructType>(RetTy);
- auto VecT = StructT->getElementType(0);
- SmallVector<llvm::Constant *, 4> ZerVec;
- for (unsigned i = 0; i < StructT->getNumElements(); i++) {
- ZerVec.push_back(VecT->isFPOrFPVectorTy() ? ConstantFP::get(VecT, 0.0)
- : ConstantInt::get(VecT, 0));
- }
- Node = ConstantStruct::get(StructT, ZerVec);
- } else
- Node = RetTy->isFPOrFPVectorTy() ? ConstantFP::get(RetTy, 0.0)
- : ConstantInt::get(II.getType(), 0);
-
- IC.replaceInstUsesWith(II, Node);
- return IC.eraseInstFromFunction(II);
- }
- return std::nullopt;
-}
-
static std::optional<Instruction *> instCombineSVESel(InstCombiner &IC,
IntrinsicInst &II) {
// svsel(ptrue, x, y) => x
@@ -1224,18 +1826,7 @@ static std::optional<Instruction *> instCombineSVECmpNE(InstCombiner &IC,
IntrinsicInst &II) {
LLVMContext &Ctx = II.getContext();
- // Replace by zero constant when all lanes are inactive
- if (auto II_NA = instCombineSVENoActiveZero(IC, II))
- return II_NA;
-
- // Check that the predicate is all active
- auto *Pg = dyn_cast<IntrinsicInst>(II.getArgOperand(0));
- if (!Pg || Pg->getIntrinsicID() != Intrinsic::aarch64_sve_ptrue)
- return std::nullopt;
-
- const auto PTruePattern =
- cast<ConstantInt>(Pg->getOperand(0))->getZExtValue();
- if (PTruePattern != AArch64SVEPredPattern::all)
+ if (!isAllActivePredicate(II.getArgOperand(0)))
return std::nullopt;
// Check that we have a compare of zero..
@@ -1450,7 +2041,7 @@ static std::optional<Instruction *> instCombineRDFFR(InstCombiner &IC,
auto *PTrue = IC.Builder.CreateIntrinsic(Intrinsic::aarch64_sve_ptrue,
{II.getType()}, {AllPat});
auto *RDFFR =
- IC.Builder.CreateIntrinsic(Intrinsic::aarch64_sve_rdffr_z, {}, {PTrue});
+ IC.Builder.CreateIntrinsic(Intrinsic::aarch64_sve_rdffr_z, {PTrue});
RDFFR->takeName(&II);
return IC.replaceInstUsesWith(II, RDFFR);
}
@@ -1460,10 +2051,10 @@ instCombineSVECntElts(InstCombiner &IC, IntrinsicInst &II, unsigned NumElts) {
const auto Pattern = cast<ConstantInt>(II.getArgOperand(0))->getZExtValue();
if (Pattern == AArch64SVEPredPattern::all) {
- Constant *StepVal = ConstantInt::get(II.getType(), NumElts);
- auto *VScale = IC.Builder.CreateVScale(StepVal);
- VScale->takeName(&II);
- return IC.replaceInstUsesWith(II, VScale);
+ Value *Cnt = IC.Builder.CreateElementCount(
+ II.getType(), ElementCount::getScalable(NumElts));
+ Cnt->takeName(&II);
+ return IC.replaceInstUsesWith(II, Cnt);
}
unsigned MinNumElts = getNumElementsFromSVEPredPattern(Pattern);
@@ -1592,10 +2183,6 @@ instCombineSVELD1(InstCombiner &IC, IntrinsicInst &II, const DataLayout &DL) {
Value *PtrOp = II.getOperand(1);
Type *VecTy = II.getType();
- // Replace by zero constant when all lanes are inactive
- if (auto II_NA = instCombineSVENoActiveZero(IC, II))
- return II_NA;
-
if (isAllActivePredicate(Pred)) {
LoadInst *Load = IC.Builder.CreateLoad(VecTy, PtrOp);
Load->copyMetadata(II);
@@ -1649,48 +2236,15 @@ instCombineSVEVectorBinOp(InstCombiner &IC, IntrinsicInst &II) {
auto *OpPredicate = II.getOperand(0);
auto BinOpCode = intrinsicIDToBinOpCode(II.getIntrinsicID());
if (BinOpCode == Instruction::BinaryOpsEnd ||
- !match(OpPredicate, m_Intrinsic<Intrinsic::aarch64_sve_ptrue>(
- m_ConstantInt<AArch64SVEPredPattern::all>())))
+ !isAllActivePredicate(OpPredicate))
return std::nullopt;
auto BinOp = IC.Builder.CreateBinOpFMF(
BinOpCode, II.getOperand(1), II.getOperand(2), II.getFastMathFlags());
return IC.replaceInstUsesWith(II, BinOp);
}
-// Canonicalise operations that take an all active predicate (e.g. sve.add ->
-// sve.add_u).
-static std::optional<Instruction *> instCombineSVEAllActive(IntrinsicInst &II,
- Intrinsic::ID IID) {
- auto *OpPredicate = II.getOperand(0);
- if (!match(OpPredicate, m_Intrinsic<Intrinsic::aarch64_sve_ptrue>(
- m_ConstantInt<AArch64SVEPredPattern::all>())))
- return std::nullopt;
-
- auto *Mod = II.getModule();
- auto *NewDecl = Intrinsic::getOrInsertDeclaration(Mod, IID, {II.getType()});
- II.setCalledFunction(NewDecl);
-
- return &II;
-}
-
-// Simplify operations where predicate has all inactive lanes or try to replace
-// with _u form when all lanes are active
-static std::optional<Instruction *>
-instCombineSVEAllOrNoActive(InstCombiner &IC, IntrinsicInst &II,
- Intrinsic::ID IID) {
- if (match(II.getOperand(0), m_ZeroInt())) {
- // llvm_ir, pred(0), op1, op2 - Spec says to return op1 when all lanes are
- // inactive for sv[func]_m
- return IC.replaceInstUsesWith(II, II.getOperand(1));
- }
- return instCombineSVEAllActive(II, IID);
-}
-
static std::optional<Instruction *> instCombineSVEVectorAdd(InstCombiner &IC,
IntrinsicInst &II) {
- if (auto II_U =
- instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_add_u))
- return II_U;
if (auto MLA = instCombineSVEVectorFuseMulAddSub<Intrinsic::aarch64_sve_mul,
Intrinsic::aarch64_sve_mla>(
IC, II, true))
@@ -1704,9 +2258,6 @@ static std::optional<Instruction *> instCombineSVEVectorAdd(InstCombiner &IC,
static std::optional<Instruction *>
instCombineSVEVectorFAdd(InstCombiner &IC, IntrinsicInst &II) {
- if (auto II_U =
- instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_fadd_u))
- return II_U;
if (auto FMLA =
instCombineSVEVectorFuseMulAddSub<Intrinsic::aarch64_sve_fmul,
Intrinsic::aarch64_sve_fmla>(IC, II,
@@ -1747,9 +2298,6 @@ instCombineSVEVectorFAddU(InstCombiner &IC, IntrinsicInst &II) {
static std::optional<Instruction *>
instCombineSVEVectorFSub(InstCombiner &IC, IntrinsicInst &II) {
- if (auto II_U =
- instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_fsub_u))
- return II_U;
if (auto FMLS =
instCombineSVEVectorFuseMulAddSub<Intrinsic::aarch64_sve_fmul,
Intrinsic::aarch64_sve_fmls>(IC, II,
@@ -1790,9 +2338,6 @@ instCombineSVEVectorFSubU(InstCombiner &IC, IntrinsicInst &II) {
static std::optional<Instruction *> instCombineSVEVectorSub(InstCombiner &IC,
IntrinsicInst &II) {
- if (auto II_U =
- instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_sub_u))
- return II_U;
if (auto MLS = instCombineSVEVectorFuseMulAddSub<Intrinsic::aarch64_sve_mul,
Intrinsic::aarch64_sve_mls>(
IC, II, true))
@@ -1800,51 +2345,6 @@ static std::optional<Instruction *> instCombineSVEVectorSub(InstCombiner &IC,
return std::nullopt;
}
-static std::optional<Instruction *> instCombineSVEVectorMul(InstCombiner &IC,
- IntrinsicInst &II,
- Intrinsic::ID IID) {
- auto *OpPredicate = II.getOperand(0);
- auto *OpMultiplicand = II.getOperand(1);
- auto *OpMultiplier = II.getOperand(2);
-
- // Return true if a given instruction is a unit splat value, false otherwise.
- auto IsUnitSplat = [](auto *I) {
- auto *SplatValue = getSplatValue(I);
- if (!SplatValue)
- return false;
- return match(SplatValue, m_FPOne()) || match(SplatValue, m_One());
- };
-
- // Return true if a given instruction is an aarch64_sve_dup intrinsic call
- // with a unit splat value, false otherwise.
- auto IsUnitDup = [](auto *I) {
- auto *IntrI = dyn_cast<IntrinsicInst>(I);
- if (!IntrI || IntrI->getIntrinsicID() != Intrinsic::aarch64_sve_dup)
- return false;
-
- auto *SplatValue = IntrI->getOperand(2);
- return match(SplatValue, m_FPOne()) || match(SplatValue, m_One());
- };
-
- if (IsUnitSplat(OpMultiplier)) {
- // [f]mul pg %n, (dupx 1) => %n
- OpMultiplicand->takeName(&II);
- return IC.replaceInstUsesWith(II, OpMultiplicand);
- } else if (IsUnitDup(OpMultiplier)) {
- // [f]mul pg %n, (dup pg 1) => %n
- auto *DupInst = cast<IntrinsicInst>(OpMultiplier);
- auto *DupPg = DupInst->getOperand(1);
- // TODO: this is naive. The optimization is still valid if DupPg
- // 'encompasses' OpPredicate, not only if they're the same predicate.
- if (OpPredicate == DupPg) {
- OpMultiplicand->takeName(&II);
- return IC.replaceInstUsesWith(II, OpMultiplicand);
- }
- }
-
- return instCombineSVEVectorBinOp(IC, II);
-}
-
static std::optional<Instruction *> instCombineSVEUnpack(InstCombiner &IC,
IntrinsicInst &II) {
Value *UnpackArg = II.getArgOperand(0);
@@ -1907,9 +2407,9 @@ static std::optional<Instruction *> instCombineSVEUzp1(InstCombiner &IC,
if (TyA == B->getType() &&
RetTy == ScalableVectorType::getDoubleElementsVectorType(TyA)) {
auto *SubVec = IC.Builder.CreateInsertVector(
- RetTy, PoisonValue::get(RetTy), A, IC.Builder.getInt64(0));
- auto *ConcatVec = IC.Builder.CreateInsertVector(
- RetTy, SubVec, B, IC.Builder.getInt64(TyA->getMinNumElements()));
+ RetTy, PoisonValue::get(RetTy), A, uint64_t(0));
+ auto *ConcatVec = IC.Builder.CreateInsertVector(RetTy, SubVec, B,
+ TyA->getMinNumElements());
ConcatVec->takeName(&II);
return IC.replaceInstUsesWith(II, ConcatVec);
}
@@ -1941,10 +2441,6 @@ instCombineLD1GatherIndex(InstCombiner &IC, IntrinsicInst &II) {
Type *Ty = II.getType();
Value *PassThru = ConstantAggregateZero::get(Ty);
- // Replace by zero constant when all lanes are inactive
- if (auto II_NA = instCombineSVENoActiveZero(IC, II))
- return II_NA;
-
// Contiguous gather => masked load.
// (sve.ld1.gather.index Mask BasePtr (sve.index IndexBase 1))
// => (masked.load (gep BasePtr IndexBase) Align Mask zeroinitializer)
@@ -2107,9 +2603,9 @@ static std::optional<Instruction *> instCombineSVEDupqLane(InstCombiner &IC,
auto *WideShuffleMaskTy =
ScalableVectorType::get(IC.Builder.getInt32Ty(), PatternElementCount);
- auto ZeroIdx = ConstantInt::get(IC.Builder.getInt64Ty(), APInt(64, 0));
auto InsertSubvector = IC.Builder.CreateInsertVector(
- II.getType(), PoisonValue::get(II.getType()), InsertEltChain, ZeroIdx);
+ II.getType(), PoisonValue::get(II.getType()), InsertEltChain,
+ uint64_t(0));
auto WideBitcast =
IC.Builder.CreateBitOrPointerCast(InsertSubvector, WideScalableTy);
auto WideShuffleMask = ConstantAggregateZero::get(WideShuffleMaskTy);
@@ -2200,175 +2696,56 @@ static std::optional<Instruction *> instCombineDMB(InstCombiner &IC,
return std::nullopt;
}
+static std::optional<Instruction *> instCombinePTrue(InstCombiner &IC,
+ IntrinsicInst &II) {
+ if (match(II.getOperand(0), m_ConstantInt<AArch64SVEPredPattern::all>()))
+ return IC.replaceInstUsesWith(II, Constant::getAllOnesValue(II.getType()));
+ return std::nullopt;
+}
+
+static std::optional<Instruction *> instCombineSVEUxt(InstCombiner &IC,
+ IntrinsicInst &II,
+ unsigned NumBits) {
+ Value *Passthru = II.getOperand(0);
+ Value *Pg = II.getOperand(1);
+ Value *Op = II.getOperand(2);
+
+ // Convert UXT[BHW] to AND.
+ if (isa<UndefValue>(Passthru) || isAllActivePredicate(Pg)) {
+ auto *Ty = cast<VectorType>(II.getType());
+ auto MaskValue = APInt::getLowBitsSet(Ty->getScalarSizeInBits(), NumBits);
+ auto *Mask = ConstantInt::get(Ty, MaskValue);
+ auto *And = IC.Builder.CreateIntrinsic(Intrinsic::aarch64_sve_and_u, {Ty},
+ {Pg, Op, Mask});
+ return IC.replaceInstUsesWith(II, And);
+ }
+
+ return std::nullopt;
+}
+
+static std::optional<Instruction *>
+instCombineInStreamingMode(InstCombiner &IC, IntrinsicInst &II) {
+ SMEAttrs FnSMEAttrs(*II.getFunction());
+ bool IsStreaming = FnSMEAttrs.hasStreamingInterfaceOrBody();
+ if (IsStreaming || !FnSMEAttrs.hasStreamingCompatibleInterface())
+ return IC.replaceInstUsesWith(
+ II, ConstantInt::getBool(II.getType(), IsStreaming));
+ return std::nullopt;
+}
+
std::optional<Instruction *>
AArch64TTIImpl::instCombineIntrinsic(InstCombiner &IC,
IntrinsicInst &II) const {
+ const SVEIntrinsicInfo &IInfo = constructSVEIntrinsicInfo(II);
+ if (std::optional<Instruction *> I = simplifySVEIntrinsic(IC, II, IInfo))
+ return I;
+
Intrinsic::ID IID = II.getIntrinsicID();
switch (IID) {
default:
break;
case Intrinsic::aarch64_dmb:
return instCombineDMB(IC, II);
- case Intrinsic::aarch64_sve_fcvt_bf16f32_v2:
- case Intrinsic::aarch64_sve_fcvt_f16f32:
- case Intrinsic::aarch64_sve_fcvt_f16f64:
- case Intrinsic::aarch64_sve_fcvt_f32f16:
- case Intrinsic::aarch64_sve_fcvt_f32f64:
- case Intrinsic::aarch64_sve_fcvt_f64f16:
- case Intrinsic::aarch64_sve_fcvt_f64f32:
- case Intrinsic::aarch64_sve_fcvtlt_f32f16:
- case Intrinsic::aarch64_sve_fcvtlt_f64f32:
- case Intrinsic::aarch64_sve_fcvtx_f32f64:
- case Intrinsic::aarch64_sve_fcvtzs:
- case Intrinsic::aarch64_sve_fcvtzs_i32f16:
- case Intrinsic::aarch64_sve_fcvtzs_i32f64:
- case Intrinsic::aarch64_sve_fcvtzs_i64f16:
- case Intrinsic::aarch64_sve_fcvtzs_i64f32:
- case Intrinsic::aarch64_sve_fcvtzu:
- case Intrinsic::aarch64_sve_fcvtzu_i32f16:
- case Intrinsic::aarch64_sve_fcvtzu_i32f64:
- case Intrinsic::aarch64_sve_fcvtzu_i64f16:
- case Intrinsic::aarch64_sve_fcvtzu_i64f32:
- case Intrinsic::aarch64_sve_scvtf:
- case Intrinsic::aarch64_sve_scvtf_f16i32:
- case Intrinsic::aarch64_sve_scvtf_f16i64:
- case Intrinsic::aarch64_sve_scvtf_f32i64:
- case Intrinsic::aarch64_sve_scvtf_f64i32:
- case Intrinsic::aarch64_sve_ucvtf:
- case Intrinsic::aarch64_sve_ucvtf_f16i32:
- case Intrinsic::aarch64_sve_ucvtf_f16i64:
- case Intrinsic::aarch64_sve_ucvtf_f32i64:
- case Intrinsic::aarch64_sve_ucvtf_f64i32:
- return instCombineSVEAllOrNoActiveUnary(IC, II);
- case Intrinsic::aarch64_sve_fcvtnt_bf16f32_v2:
- case Intrinsic::aarch64_sve_fcvtnt_f16f32:
- case Intrinsic::aarch64_sve_fcvtnt_f32f64:
- case Intrinsic::aarch64_sve_fcvtxnt_f32f64:
- return instCombineSVENoActiveReplace(IC, II, true);
- case Intrinsic::aarch64_sve_st1_scatter:
- case Intrinsic::aarch64_sve_st1_scatter_scalar_offset:
- case Intrinsic::aarch64_sve_st1_scatter_sxtw:
- case Intrinsic::aarch64_sve_st1_scatter_sxtw_index:
- case Intrinsic::aarch64_sve_st1_scatter_uxtw:
- case Intrinsic::aarch64_sve_st1_scatter_uxtw_index:
- case Intrinsic::aarch64_sve_st1dq:
- case Intrinsic::aarch64_sve_st1q_scatter_index:
- case Intrinsic::aarch64_sve_st1q_scatter_scalar_offset:
- case Intrinsic::aarch64_sve_st1q_scatter_vector_offset:
- case Intrinsic::aarch64_sve_st1wq:
- case Intrinsic::aarch64_sve_stnt1:
- case Intrinsic::aarch64_sve_stnt1_scatter:
- case Intrinsic::aarch64_sve_stnt1_scatter_index:
- case Intrinsic::aarch64_sve_stnt1_scatter_scalar_offset:
- case Intrinsic::aarch64_sve_stnt1_scatter_uxtw:
- return instCombineSVENoActiveUnaryErase(IC, II, 1);
- case Intrinsic::aarch64_sve_st2:
- case Intrinsic::aarch64_sve_st2q:
- return instCombineSVENoActiveUnaryErase(IC, II, 2);
- case Intrinsic::aarch64_sve_st3:
- case Intrinsic::aarch64_sve_st3q:
- return instCombineSVENoActiveUnaryErase(IC, II, 3);
- case Intrinsic::aarch64_sve_st4:
- case Intrinsic::aarch64_sve_st4q:
- return instCombineSVENoActiveUnaryErase(IC, II, 4);
- case Intrinsic::aarch64_sve_addqv:
- case Intrinsic::aarch64_sve_and_z:
- case Intrinsic::aarch64_sve_bic_z:
- case Intrinsic::aarch64_sve_brka_z:
- case Intrinsic::aarch64_sve_brkb_z:
- case Intrinsic::aarch64_sve_brkn_z:
- case Intrinsic::aarch64_sve_brkpa_z:
- case Intrinsic::aarch64_sve_brkpb_z:
- case Intrinsic::aarch64_sve_cntp:
- case Intrinsic::aarch64_sve_compact:
- case Intrinsic::aarch64_sve_eor_z:
- case Intrinsic::aarch64_sve_eorv:
- case Intrinsic::aarch64_sve_eorqv:
- case Intrinsic::aarch64_sve_nand_z:
- case Intrinsic::aarch64_sve_nor_z:
- case Intrinsic::aarch64_sve_orn_z:
- case Intrinsic::aarch64_sve_orr_z:
- case Intrinsic::aarch64_sve_orv:
- case Intrinsic::aarch64_sve_orqv:
- case Intrinsic::aarch64_sve_pnext:
- case Intrinsic::aarch64_sve_rdffr_z:
- case Intrinsic::aarch64_sve_saddv:
- case Intrinsic::aarch64_sve_uaddv:
- case Intrinsic::aarch64_sve_umaxv:
- case Intrinsic::aarch64_sve_umaxqv:
- case Intrinsic::aarch64_sve_cmpeq:
- case Intrinsic::aarch64_sve_cmpeq_wide:
- case Intrinsic::aarch64_sve_cmpge:
- case Intrinsic::aarch64_sve_cmpge_wide:
- case Intrinsic::aarch64_sve_cmpgt:
- case Intrinsic::aarch64_sve_cmpgt_wide:
- case Intrinsic::aarch64_sve_cmphi:
- case Intrinsic::aarch64_sve_cmphi_wide:
- case Intrinsic::aarch64_sve_cmphs:
- case Intrinsic::aarch64_sve_cmphs_wide:
- case Intrinsic::aarch64_sve_cmple_wide:
- case Intrinsic::aarch64_sve_cmplo_wide:
- case Intrinsic::aarch64_sve_cmpls_wide:
- case Intrinsic::aarch64_sve_cmplt_wide:
- case Intrinsic::aarch64_sve_facge:
- case Intrinsic::aarch64_sve_facgt:
- case Intrinsic::aarch64_sve_fcmpeq:
- case Intrinsic::aarch64_sve_fcmpge:
- case Intrinsic::aarch64_sve_fcmpgt:
- case Intrinsic::aarch64_sve_fcmpne:
- case Intrinsic::aarch64_sve_fcmpuo:
- case Intrinsic::aarch64_sve_ld1_gather:
- case Intrinsic::aarch64_sve_ld1_gather_scalar_offset:
- case Intrinsic::aarch64_sve_ld1_gather_sxtw:
- case Intrinsic::aarch64_sve_ld1_gather_sxtw_index:
- case Intrinsic::aarch64_sve_ld1_gather_uxtw:
- case Intrinsic::aarch64_sve_ld1_gather_uxtw_index:
- case Intrinsic::aarch64_sve_ld1q_gather_index:
- case Intrinsic::aarch64_sve_ld1q_gather_scalar_offset:
- case Intrinsic::aarch64_sve_ld1q_gather_vector_offset:
- case Intrinsic::aarch64_sve_ld1ro:
- case Intrinsic::aarch64_sve_ld1rq:
- case Intrinsic::aarch64_sve_ld1udq:
- case Intrinsic::aarch64_sve_ld1uwq:
- case Intrinsic::aarch64_sve_ld2_sret:
- case Intrinsic::aarch64_sve_ld2q_sret:
- case Intrinsic::aarch64_sve_ld3_sret:
- case Intrinsic::aarch64_sve_ld3q_sret:
- case Intrinsic::aarch64_sve_ld4_sret:
- case Intrinsic::aarch64_sve_ld4q_sret:
- case Intrinsic::aarch64_sve_ldff1:
- case Intrinsic::aarch64_sve_ldff1_gather:
- case Intrinsic::aarch64_sve_ldff1_gather_index:
- case Intrinsic::aarch64_sve_ldff1_gather_scalar_offset:
- case Intrinsic::aarch64_sve_ldff1_gather_sxtw:
- case Intrinsic::aarch64_sve_ldff1_gather_sxtw_index:
- case Intrinsic::aarch64_sve_ldff1_gather_uxtw:
- case Intrinsic::aarch64_sve_ldff1_gather_uxtw_index:
- case Intrinsic::aarch64_sve_ldnf1:
- case Intrinsic::aarch64_sve_ldnt1:
- case Intrinsic::aarch64_sve_ldnt1_gather:
- case Intrinsic::aarch64_sve_ldnt1_gather_index:
- case Intrinsic::aarch64_sve_ldnt1_gather_scalar_offset:
- case Intrinsic::aarch64_sve_ldnt1_gather_uxtw:
- return instCombineSVENoActiveZero(IC, II);
- case Intrinsic::aarch64_sve_prf:
- case Intrinsic::aarch64_sve_prfb_gather_index:
- case Intrinsic::aarch64_sve_prfb_gather_scalar_offset:
- case Intrinsic::aarch64_sve_prfb_gather_sxtw_index:
- case Intrinsic::aarch64_sve_prfb_gather_uxtw_index:
- case Intrinsic::aarch64_sve_prfd_gather_index:
- case Intrinsic::aarch64_sve_prfd_gather_scalar_offset:
- case Intrinsic::aarch64_sve_prfd_gather_sxtw_index:
- case Intrinsic::aarch64_sve_prfd_gather_uxtw_index:
- case Intrinsic::aarch64_sve_prfh_gather_index:
- case Intrinsic::aarch64_sve_prfh_gather_scalar_offset:
- case Intrinsic::aarch64_sve_prfh_gather_sxtw_index:
- case Intrinsic::aarch64_sve_prfh_gather_uxtw_index:
- case Intrinsic::aarch64_sve_prfw_gather_index:
- case Intrinsic::aarch64_sve_prfw_gather_scalar_offset:
- case Intrinsic::aarch64_sve_prfw_gather_sxtw_index:
- case Intrinsic::aarch64_sve_prfw_gather_uxtw_index:
- return instCombineSVENoActiveUnaryErase(IC, II, 0);
case Intrinsic::aarch64_neon_fmaxnm:
case Intrinsic::aarch64_neon_fminnm:
return instCombineMaxMinNM(IC, II);
@@ -2401,39 +2778,12 @@ AArch64TTIImpl::instCombineIntrinsic(InstCombiner &IC,
case Intrinsic::aarch64_sve_ptest_first:
case Intrinsic::aarch64_sve_ptest_last:
return instCombineSVEPTest(IC, II);
- case Intrinsic::aarch64_sve_fabd:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_fabd_u);
case Intrinsic::aarch64_sve_fadd:
return instCombineSVEVectorFAdd(IC, II);
case Intrinsic::aarch64_sve_fadd_u:
return instCombineSVEVectorFAddU(IC, II);
- case Intrinsic::aarch64_sve_fdiv:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_fdiv_u);
- case Intrinsic::aarch64_sve_fmax:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_fmax_u);
- case Intrinsic::aarch64_sve_fmaxnm:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_fmaxnm_u);
- case Intrinsic::aarch64_sve_fmin:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_fmin_u);
- case Intrinsic::aarch64_sve_fminnm:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_fminnm_u);
- case Intrinsic::aarch64_sve_fmla:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_fmla_u);
- case Intrinsic::aarch64_sve_fmls:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_fmls_u);
- case Intrinsic::aarch64_sve_fmul:
- if (auto II_U =
- instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_fmul_u))
- return II_U;
- return instCombineSVEVectorMul(IC, II, Intrinsic::aarch64_sve_fmul_u);
case Intrinsic::aarch64_sve_fmul_u:
- return instCombineSVEVectorMul(IC, II, Intrinsic::aarch64_sve_fmul_u);
- case Intrinsic::aarch64_sve_fmulx:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_fmulx_u);
- case Intrinsic::aarch64_sve_fnmla:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_fnmla_u);
- case Intrinsic::aarch64_sve_fnmls:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_fnmls_u);
+ return instCombineSVEVectorBinOp(IC, II);
case Intrinsic::aarch64_sve_fsub:
return instCombineSVEVectorFSub(IC, II);
case Intrinsic::aarch64_sve_fsub_u:
@@ -2444,57 +2794,12 @@ AArch64TTIImpl::instCombineIntrinsic(InstCombiner &IC,
return instCombineSVEVectorFuseMulAddSub<Intrinsic::aarch64_sve_mul_u,
Intrinsic::aarch64_sve_mla_u>(
IC, II, true);
- case Intrinsic::aarch64_sve_mla:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_mla_u);
- case Intrinsic::aarch64_sve_mls:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_mls_u);
- case Intrinsic::aarch64_sve_mul:
- if (auto II_U =
- instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_mul_u))
- return II_U;
- return instCombineSVEVectorMul(IC, II, Intrinsic::aarch64_sve_mul_u);
- case Intrinsic::aarch64_sve_mul_u:
- return instCombineSVEVectorMul(IC, II, Intrinsic::aarch64_sve_mul_u);
- case Intrinsic::aarch64_sve_sabd:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_sabd_u);
- case Intrinsic::aarch64_sve_smax:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_smax_u);
- case Intrinsic::aarch64_sve_smin:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_smin_u);
- case Intrinsic::aarch64_sve_smulh:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_smulh_u);
case Intrinsic::aarch64_sve_sub:
return instCombineSVEVectorSub(IC, II);
case Intrinsic::aarch64_sve_sub_u:
return instCombineSVEVectorFuseMulAddSub<Intrinsic::aarch64_sve_mul_u,
Intrinsic::aarch64_sve_mls_u>(
IC, II, true);
- case Intrinsic::aarch64_sve_uabd:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_uabd_u);
- case Intrinsic::aarch64_sve_umax:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_umax_u);
- case Intrinsic::aarch64_sve_umin:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_umin_u);
- case Intrinsic::aarch64_sve_umulh:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_umulh_u);
- case Intrinsic::aarch64_sve_asr:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_asr_u);
- case Intrinsic::aarch64_sve_lsl:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_lsl_u);
- case Intrinsic::aarch64_sve_lsr:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_lsr_u);
- case Intrinsic::aarch64_sve_and:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_and_u);
- case Intrinsic::aarch64_sve_bic:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_bic_u);
- case Intrinsic::aarch64_sve_eor:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_eor_u);
- case Intrinsic::aarch64_sve_orr:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_orr_u);
- case Intrinsic::aarch64_sve_sqsub:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_sqsub_u);
- case Intrinsic::aarch64_sve_uqsub:
- return instCombineSVEAllOrNoActive(IC, II, Intrinsic::aarch64_sve_uqsub_u);
case Intrinsic::aarch64_sve_tbl:
return instCombineSVETBL(IC, II);
case Intrinsic::aarch64_sve_uunpkhi:
@@ -2525,6 +2830,16 @@ AArch64TTIImpl::instCombineIntrinsic(InstCombiner &IC,
return instCombineSVEDupqLane(IC, II);
case Intrinsic::aarch64_sve_insr:
return instCombineSVEInsr(IC, II);
+ case Intrinsic::aarch64_sve_ptrue:
+ return instCombinePTrue(IC, II);
+ case Intrinsic::aarch64_sve_uxtb:
+ return instCombineSVEUxt(IC, II, 8);
+ case Intrinsic::aarch64_sve_uxth:
+ return instCombineSVEUxt(IC, II, 16);
+ case Intrinsic::aarch64_sve_uxtw:
+ return instCombineSVEUxt(IC, II, 32);
+ case Intrinsic::aarch64_sme_in_streaming_mode:
+ return instCombineInStreamingMode(IC, II);
}
return std::nullopt;
@@ -2587,7 +2902,7 @@ AArch64TTIImpl::getRegisterBitWidth(TargetTransformInfo::RegisterKind K) const {
bool AArch64TTIImpl::isWideningInstruction(Type *DstTy, unsigned Opcode,
ArrayRef<const Value *> Args,
- Type *SrcOverrideTy) {
+ Type *SrcOverrideTy) const {
// A helper that returns a vector type from the given type. The number of
// elements in type Ty determines the vector width.
auto toVectorTy = [&](Type *ArgTy) {
@@ -2684,7 +2999,7 @@ bool AArch64TTIImpl::isWideningInstruction(Type *DstTy, unsigned Opcode,
// trunc i16 (lshr (add %x, %y), 1) -> i8
//
bool AArch64TTIImpl::isExtPartOfAvgExpr(const Instruction *ExtUser, Type *Dst,
- Type *Src) {
+ Type *Src) const {
// The source should be a legal vector type.
if (!Src->isVectorTy() || !TLI->isTypeLegal(TLI->getValueType(DL, Src)) ||
(Src->isScalableTy() && !ST->hasSVE2()))
@@ -2729,7 +3044,7 @@ InstructionCost AArch64TTIImpl::getCastInstrCost(unsigned Opcode, Type *Dst,
Type *Src,
TTI::CastContextHint CCH,
TTI::TargetCostKind CostKind,
- const Instruction *I) {
+ const Instruction *I) const {
int ISD = TLI->InstructionOpcodeToISD(Opcode);
assert(ISD && "Invalid opcode");
// If the cast is observable, and it is used by a widening instruction (e.g.,
@@ -2785,6 +3100,17 @@ InstructionCost AArch64TTIImpl::getCastInstrCost(unsigned Opcode, Type *Dst,
BF16Tbl, ISD, DstTy.getSimpleVT(), SrcTy.getSimpleVT()))
return AdjustCost(Entry->Cost);
+ // Symbolic constants for the SVE sitofp/uitofp entries in the table below
+ // The cost of unpacking twice is artificially increased for now in order
+ // to avoid regressions against NEON, which will use tbl instructions directly
+ // instead of multiple layers of [s|u]unpk[lo|hi].
+ // We use the unpacks in cases where the destination type is illegal and
+ // requires splitting of the input, even if the input type itself is legal.
+ const unsigned int SVE_EXT_COST = 1;
+ const unsigned int SVE_FCVT_COST = 1;
+ const unsigned int SVE_UNPACK_ONCE = 4;
+ const unsigned int SVE_UNPACK_TWICE = 16;
+
static const TypeConversionCostTblEntry ConversionTbl[] = {
{ISD::TRUNCATE, MVT::v2i8, MVT::v2i64, 1}, // xtn
{ISD::TRUNCATE, MVT::v2i16, MVT::v2i64, 1}, // xtn
@@ -2910,13 +3236,59 @@ InstructionCost AArch64TTIImpl::getCastInstrCost(unsigned Opcode, Type *Dst,
{ISD::UINT_TO_FP, MVT::v4f32, MVT::v4i32, 1},
{ISD::UINT_TO_FP, MVT::v2f64, MVT::v2i64, 1},
+ // SVE: to nxv2f16
+ {ISD::SINT_TO_FP, MVT::nxv2f16, MVT::nxv2i8,
+ SVE_EXT_COST + SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv2f16, MVT::nxv2i16, SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv2f16, MVT::nxv2i32, SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv2f16, MVT::nxv2i64, SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv2f16, MVT::nxv2i8,
+ SVE_EXT_COST + SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv2f16, MVT::nxv2i16, SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv2f16, MVT::nxv2i32, SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv2f16, MVT::nxv2i64, SVE_FCVT_COST},
+
+ // SVE: to nxv4f16
+ {ISD::SINT_TO_FP, MVT::nxv4f16, MVT::nxv4i8,
+ SVE_EXT_COST + SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv4f16, MVT::nxv4i16, SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv4f16, MVT::nxv4i32, SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv4f16, MVT::nxv4i8,
+ SVE_EXT_COST + SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv4f16, MVT::nxv4i16, SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv4f16, MVT::nxv4i32, SVE_FCVT_COST},
+
+ // SVE: to nxv8f16
+ {ISD::SINT_TO_FP, MVT::nxv8f16, MVT::nxv8i8,
+ SVE_EXT_COST + SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv8f16, MVT::nxv8i16, SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv8f16, MVT::nxv8i8,
+ SVE_EXT_COST + SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv8f16, MVT::nxv8i16, SVE_FCVT_COST},
+
+ // SVE: to nxv16f16
+ {ISD::SINT_TO_FP, MVT::nxv16f16, MVT::nxv16i8,
+ SVE_UNPACK_ONCE + 2 * SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv16f16, MVT::nxv16i8,
+ SVE_UNPACK_ONCE + 2 * SVE_FCVT_COST},
+
// Complex: to v2f32
{ISD::SINT_TO_FP, MVT::v2f32, MVT::v2i8, 3},
{ISD::SINT_TO_FP, MVT::v2f32, MVT::v2i16, 3},
- {ISD::SINT_TO_FP, MVT::v2f32, MVT::v2i64, 2},
{ISD::UINT_TO_FP, MVT::v2f32, MVT::v2i8, 3},
{ISD::UINT_TO_FP, MVT::v2f32, MVT::v2i16, 3},
- {ISD::UINT_TO_FP, MVT::v2f32, MVT::v2i64, 2},
+
+ // SVE: to nxv2f32
+ {ISD::SINT_TO_FP, MVT::nxv2f32, MVT::nxv2i8,
+ SVE_EXT_COST + SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv2f32, MVT::nxv2i16, SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv2f32, MVT::nxv2i32, SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv2f32, MVT::nxv2i64, SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv2f32, MVT::nxv2i8,
+ SVE_EXT_COST + SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv2f32, MVT::nxv2i16, SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv2f32, MVT::nxv2i32, SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv2f32, MVT::nxv2i64, SVE_FCVT_COST},
// Complex: to v4f32
{ISD::SINT_TO_FP, MVT::v4f32, MVT::v4i8, 4},
@@ -2924,12 +3296,38 @@ InstructionCost AArch64TTIImpl::getCastInstrCost(unsigned Opcode, Type *Dst,
{ISD::UINT_TO_FP, MVT::v4f32, MVT::v4i8, 3},
{ISD::UINT_TO_FP, MVT::v4f32, MVT::v4i16, 2},
+ // SVE: to nxv4f32
+ {ISD::SINT_TO_FP, MVT::nxv4f32, MVT::nxv4i8,
+ SVE_EXT_COST + SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv4f32, MVT::nxv4i16, SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv4f32, MVT::nxv4i32, SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv4f32, MVT::nxv4i8,
+ SVE_EXT_COST + SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv4f32, MVT::nxv4i16, SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv4f32, MVT::nxv4i32, SVE_FCVT_COST},
+
// Complex: to v8f32
{ISD::SINT_TO_FP, MVT::v8f32, MVT::v8i8, 10},
{ISD::SINT_TO_FP, MVT::v8f32, MVT::v8i16, 4},
{ISD::UINT_TO_FP, MVT::v8f32, MVT::v8i8, 10},
{ISD::UINT_TO_FP, MVT::v8f32, MVT::v8i16, 4},
+ // SVE: to nxv8f32
+ {ISD::SINT_TO_FP, MVT::nxv8f32, MVT::nxv8i8,
+ SVE_EXT_COST + SVE_UNPACK_ONCE + 2 * SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv8f32, MVT::nxv8i16,
+ SVE_UNPACK_ONCE + 2 * SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv8f32, MVT::nxv8i8,
+ SVE_EXT_COST + SVE_UNPACK_ONCE + 2 * SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv8f32, MVT::nxv8i16,
+ SVE_UNPACK_ONCE + 2 * SVE_FCVT_COST},
+
+ // SVE: to nxv16f32
+ {ISD::SINT_TO_FP, MVT::nxv16f32, MVT::nxv16i8,
+ SVE_UNPACK_TWICE + 4 * SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv16f32, MVT::nxv16i8,
+ SVE_UNPACK_TWICE + 4 * SVE_FCVT_COST},
+
// Complex: to v16f32
{ISD::SINT_TO_FP, MVT::v16f32, MVT::v16i8, 21},
{ISD::UINT_TO_FP, MVT::v16f32, MVT::v16i8, 21},
@@ -2942,10 +3340,46 @@ InstructionCost AArch64TTIImpl::getCastInstrCost(unsigned Opcode, Type *Dst,
{ISD::UINT_TO_FP, MVT::v2f64, MVT::v2i16, 4},
{ISD::UINT_TO_FP, MVT::v2f64, MVT::v2i32, 2},
+ // SVE: to nxv2f64
+ {ISD::SINT_TO_FP, MVT::nxv2f64, MVT::nxv2i8,
+ SVE_EXT_COST + SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv2f64, MVT::nxv2i16, SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv2f64, MVT::nxv2i32, SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv2f64, MVT::nxv2i64, SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv2f64, MVT::nxv2i8,
+ SVE_EXT_COST + SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv2f64, MVT::nxv2i16, SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv2f64, MVT::nxv2i32, SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv2f64, MVT::nxv2i64, SVE_FCVT_COST},
+
// Complex: to v4f64
{ISD::SINT_TO_FP, MVT::v4f64, MVT::v4i32, 4},
{ISD::UINT_TO_FP, MVT::v4f64, MVT::v4i32, 4},
+ // SVE: to nxv4f64
+ {ISD::SINT_TO_FP, MVT::nxv4f64, MVT::nxv4i8,
+ SVE_EXT_COST + SVE_UNPACK_ONCE + 2 * SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv4f64, MVT::nxv4i16,
+ SVE_UNPACK_ONCE + 2 * SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv4f64, MVT::nxv4i32,
+ SVE_UNPACK_ONCE + 2 * SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv4f64, MVT::nxv4i8,
+ SVE_EXT_COST + SVE_UNPACK_ONCE + 2 * SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv4f64, MVT::nxv4i16,
+ SVE_UNPACK_ONCE + 2 * SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv4f64, MVT::nxv4i32,
+ SVE_UNPACK_ONCE + 2 * SVE_FCVT_COST},
+
+ // SVE: to nxv8f64
+ {ISD::SINT_TO_FP, MVT::nxv8f64, MVT::nxv8i8,
+ SVE_EXT_COST + SVE_UNPACK_TWICE + 4 * SVE_FCVT_COST},
+ {ISD::SINT_TO_FP, MVT::nxv8f64, MVT::nxv8i16,
+ SVE_UNPACK_TWICE + 4 * SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv8f64, MVT::nxv8i8,
+ SVE_EXT_COST + SVE_UNPACK_TWICE + 4 * SVE_FCVT_COST},
+ {ISD::UINT_TO_FP, MVT::nxv8f64, MVT::nxv8i16,
+ SVE_UNPACK_TWICE + 4 * SVE_FCVT_COST},
+
// LowerVectorFP_TO_INT
{ISD::FP_TO_SINT, MVT::v2i32, MVT::v2f32, 1},
{ISD::FP_TO_SINT, MVT::v4i32, MVT::v4f32, 1},
@@ -2991,20 +3425,24 @@ InstructionCost AArch64TTIImpl::getCastInstrCost(unsigned Opcode, Type *Dst,
{ISD::FP_TO_SINT, MVT::nxv2i32, MVT::nxv2f64, 1},
{ISD::FP_TO_SINT, MVT::nxv2i16, MVT::nxv2f64, 1},
{ISD::FP_TO_SINT, MVT::nxv2i8, MVT::nxv2f64, 1},
+ {ISD::FP_TO_SINT, MVT::nxv2i1, MVT::nxv2f64, 1},
{ISD::FP_TO_UINT, MVT::nxv2i64, MVT::nxv2f64, 1},
{ISD::FP_TO_UINT, MVT::nxv2i32, MVT::nxv2f64, 1},
{ISD::FP_TO_UINT, MVT::nxv2i16, MVT::nxv2f64, 1},
{ISD::FP_TO_UINT, MVT::nxv2i8, MVT::nxv2f64, 1},
+ {ISD::FP_TO_UINT, MVT::nxv2i1, MVT::nxv2f64, 1},
// Complex, from nxv4f32.
{ISD::FP_TO_SINT, MVT::nxv4i64, MVT::nxv4f32, 4},
{ISD::FP_TO_SINT, MVT::nxv4i32, MVT::nxv4f32, 1},
{ISD::FP_TO_SINT, MVT::nxv4i16, MVT::nxv4f32, 1},
{ISD::FP_TO_SINT, MVT::nxv4i8, MVT::nxv4f32, 1},
+ {ISD::FP_TO_SINT, MVT::nxv4i1, MVT::nxv4f32, 1},
{ISD::FP_TO_UINT, MVT::nxv4i64, MVT::nxv4f32, 4},
{ISD::FP_TO_UINT, MVT::nxv4i32, MVT::nxv4f32, 1},
{ISD::FP_TO_UINT, MVT::nxv4i16, MVT::nxv4f32, 1},
{ISD::FP_TO_UINT, MVT::nxv4i8, MVT::nxv4f32, 1},
+ {ISD::FP_TO_UINT, MVT::nxv4i1, MVT::nxv4f32, 1},
// Complex, from nxv8f64. Illegal -> illegal conversions not required.
{ISD::FP_TO_SINT, MVT::nxv8i16, MVT::nxv8f64, 7},
@@ -3031,10 +3469,12 @@ InstructionCost AArch64TTIImpl::getCastInstrCost(unsigned Opcode, Type *Dst,
{ISD::FP_TO_SINT, MVT::nxv8i32, MVT::nxv8f16, 4},
{ISD::FP_TO_SINT, MVT::nxv8i16, MVT::nxv8f16, 1},
{ISD::FP_TO_SINT, MVT::nxv8i8, MVT::nxv8f16, 1},
+ {ISD::FP_TO_SINT, MVT::nxv8i1, MVT::nxv8f16, 1},
{ISD::FP_TO_UINT, MVT::nxv8i64, MVT::nxv8f16, 10},
{ISD::FP_TO_UINT, MVT::nxv8i32, MVT::nxv8f16, 4},
{ISD::FP_TO_UINT, MVT::nxv8i16, MVT::nxv8f16, 1},
{ISD::FP_TO_UINT, MVT::nxv8i8, MVT::nxv8f16, 1},
+ {ISD::FP_TO_UINT, MVT::nxv8i1, MVT::nxv8f16, 1},
// Complex, from nxv4f16.
{ISD::FP_TO_SINT, MVT::nxv4i64, MVT::nxv4f16, 4},
@@ -3167,6 +3607,20 @@ InstructionCost AArch64TTIImpl::getCastInstrCost(unsigned Opcode, Type *Dst,
FP16Tbl, ISD, DstTy.getSimpleVT(), SrcTy.getSimpleVT()))
return AdjustCost(Entry->Cost);
+ // INT_TO_FP of i64->f32 will scalarize, which is required to avoid
+ // double-rounding issues.
+ if ((ISD == ISD::SINT_TO_FP || ISD == ISD::UINT_TO_FP) &&
+ DstTy.getScalarType() == MVT::f32 && SrcTy.getScalarSizeInBits() > 32 &&
+ isa<FixedVectorType>(Dst) && isa<FixedVectorType>(Src))
+ return AdjustCost(
+ cast<FixedVectorType>(Dst)->getNumElements() *
+ getCastInstrCost(Opcode, Dst->getScalarType(), Src->getScalarType(),
+ CCH, CostKind) +
+ BaseT::getScalarizationOverhead(cast<FixedVectorType>(Src), false, true,
+ CostKind) +
+ BaseT::getScalarizationOverhead(cast<FixedVectorType>(Dst), true, false,
+ CostKind));
+
if ((ISD == ISD::ZERO_EXTEND || ISD == ISD::SIGN_EXTEND) &&
CCH == TTI::CastContextHint::Masked &&
ST->isSVEorStreamingSVEAvailable() &&
@@ -3198,10 +3652,10 @@ InstructionCost AArch64TTIImpl::getCastInstrCost(unsigned Opcode, Type *Dst,
BaseT::getCastInstrCost(Opcode, Dst, Src, CCH, CostKind, I));
}
-InstructionCost AArch64TTIImpl::getExtractWithExtendCost(unsigned Opcode,
- Type *Dst,
- VectorType *VecTy,
- unsigned Index) {
+InstructionCost
+AArch64TTIImpl::getExtractWithExtendCost(unsigned Opcode, Type *Dst,
+ VectorType *VecTy, unsigned Index,
+ TTI::TargetCostKind CostKind) const {
// Make sure we were given a valid extend opcode.
assert((Opcode == Instruction::SExt || Opcode == Instruction::ZExt) &&
@@ -3216,7 +3670,6 @@ InstructionCost AArch64TTIImpl::getExtractWithExtendCost(unsigned Opcode,
// Get the cost for the extract. We compute the cost (if any) for the extend
// below.
- TTI::TargetCostKind CostKind = TTI::TCK_RecipThroughput;
InstructionCost Cost = getVectorInstrCost(Instruction::ExtractElement, VecTy,
CostKind, Index, nullptr, nullptr);
@@ -3261,7 +3714,7 @@ InstructionCost AArch64TTIImpl::getExtractWithExtendCost(unsigned Opcode,
InstructionCost AArch64TTIImpl::getCFInstrCost(unsigned Opcode,
TTI::TargetCostKind CostKind,
- const Instruction *I) {
+ const Instruction *I) const {
if (CostKind != TTI::TCK_RecipThroughput)
return Opcode == Instruction::PHI ? 0 : 1;
assert(CostKind == TTI::TCK_RecipThroughput && "unexpected CostKind");
@@ -3270,9 +3723,9 @@ InstructionCost AArch64TTIImpl::getCFInstrCost(unsigned Opcode,
}
InstructionCost AArch64TTIImpl::getVectorInstrCostHelper(
- unsigned Opcode, Type *Val, unsigned Index, bool HasRealUse,
- const Instruction *I, Value *Scalar,
- ArrayRef<std::tuple<Value *, User *, int>> ScalarUserAndIdx) {
+ unsigned Opcode, Type *Val, TTI::TargetCostKind CostKind, unsigned Index,
+ bool HasRealUse, const Instruction *I, Value *Scalar,
+ ArrayRef<std::tuple<Value *, User *, int>> ScalarUserAndIdx) const {
assert(Val->isVectorTy() && "This must be a vector type");
if (Index != -1U) {
@@ -3304,12 +3757,16 @@ InstructionCost AArch64TTIImpl::getVectorInstrCostHelper(
// and its second operand is a load, then we will generate a LD1, which
// are expensive instructions.
if (I && dyn_cast<LoadInst>(I->getOperand(1)))
- return ST->getVectorInsertExtractBaseCost() + 1;
+ return CostKind == TTI::TCK_CodeSize
+ ? 0
+ : ST->getVectorInsertExtractBaseCost() + 1;
// i1 inserts and extract will include an extra cset or cmp of the vector
// value. Increase the cost by 1 to account.
if (Val->getScalarSizeInBits() == 1)
- return ST->getVectorInsertExtractBaseCost() + 1;
+ return CostKind == TTI::TCK_CodeSize
+ ? 2
+ : ST->getVectorInsertExtractBaseCost() + 1;
// FIXME:
// If the extract-element and insert-element instructions could be
@@ -3380,7 +3837,7 @@ InstructionCost AArch64TTIImpl::getVectorInstrCostHelper(
return false;
for (auto &[S, U, L] : ScalarUserAndIdx) {
for (auto *U : S->users()) {
- if (UserToExtractIdx.find(U) != UserToExtractIdx.end()) {
+ if (UserToExtractIdx.contains(U)) {
auto *FMul = cast<BinaryOperator>(U);
auto *Op0 = FMul->getOperand(0);
auto *Op1 = FMul->getOperand(1);
@@ -3433,51 +3890,54 @@ InstructionCost AArch64TTIImpl::getVectorInstrCostHelper(
return 0;
// All other insert/extracts cost this much.
- return ST->getVectorInsertExtractBaseCost();
+ return CostKind == TTI::TCK_CodeSize ? 1
+ : ST->getVectorInsertExtractBaseCost();
}
InstructionCost AArch64TTIImpl::getVectorInstrCost(unsigned Opcode, Type *Val,
TTI::TargetCostKind CostKind,
- unsigned Index, Value *Op0,
- Value *Op1) {
+ unsigned Index,
+ const Value *Op0,
+ const Value *Op1) const {
bool HasRealUse =
Opcode == Instruction::InsertElement && Op0 && !isa<UndefValue>(Op0);
- return getVectorInstrCostHelper(Opcode, Val, Index, HasRealUse);
+ return getVectorInstrCostHelper(Opcode, Val, CostKind, Index, HasRealUse);
}
InstructionCost AArch64TTIImpl::getVectorInstrCost(
unsigned Opcode, Type *Val, TTI::TargetCostKind CostKind, unsigned Index,
Value *Scalar,
- ArrayRef<std::tuple<Value *, User *, int>> ScalarUserAndIdx) {
- return getVectorInstrCostHelper(Opcode, Val, Index, false, nullptr, Scalar,
- ScalarUserAndIdx);
+ ArrayRef<std::tuple<Value *, User *, int>> ScalarUserAndIdx) const {
+ return getVectorInstrCostHelper(Opcode, Val, CostKind, Index, false, nullptr,
+ Scalar, ScalarUserAndIdx);
}
InstructionCost AArch64TTIImpl::getVectorInstrCost(const Instruction &I,
Type *Val,
TTI::TargetCostKind CostKind,
- unsigned Index) {
- return getVectorInstrCostHelper(I.getOpcode(), Val, Index,
+ unsigned Index) const {
+ return getVectorInstrCostHelper(I.getOpcode(), Val, CostKind, Index,
true /* HasRealUse */, &I);
}
InstructionCost AArch64TTIImpl::getScalarizationOverhead(
VectorType *Ty, const APInt &DemandedElts, bool Insert, bool Extract,
- TTI::TargetCostKind CostKind, ArrayRef<Value *> VL) {
+ TTI::TargetCostKind CostKind, bool ForPoisonSrc,
+ ArrayRef<Value *> VL) const {
if (isa<ScalableVectorType>(Ty))
return InstructionCost::getInvalid();
if (Ty->getElementType()->isFloatingPointTy())
return BaseT::getScalarizationOverhead(Ty, DemandedElts, Insert, Extract,
CostKind);
- return DemandedElts.popcount() * (Insert + Extract) *
- ST->getVectorInsertExtractBaseCost();
+ unsigned VecInstCost =
+ CostKind == TTI::TCK_CodeSize ? 1 : ST->getVectorInsertExtractBaseCost();
+ return DemandedElts.popcount() * (Insert + Extract) * VecInstCost;
}
InstructionCost AArch64TTIImpl::getArithmeticInstrCost(
unsigned Opcode, Type *Ty, TTI::TargetCostKind CostKind,
TTI::OperandValueInfo Op1Info, TTI::OperandValueInfo Op2Info,
- ArrayRef<const Value *> Args,
- const Instruction *CxtI) {
+ ArrayRef<const Value *> Args, const Instruction *CxtI) const {
// The code-generator is currently not able to handle scalable vectors
// of <vscale x 1 x eltty> yet, so return an invalid cost to avoid selecting
@@ -3500,39 +3960,157 @@ InstructionCost AArch64TTIImpl::getArithmeticInstrCost(
default:
return BaseT::getArithmeticInstrCost(Opcode, Ty, CostKind, Op1Info,
Op2Info);
+ case ISD::SREM:
case ISD::SDIV:
- if (Op2Info.isConstant() && Op2Info.isUniform() && Op2Info.isPowerOf2()) {
- // On AArch64, scalar signed division by constants power-of-two are
- // normally expanded to the sequence ADD + CMP + SELECT + SRA.
- // The OperandValue properties many not be same as that of previous
- // operation; conservatively assume OP_None.
- InstructionCost Cost = getArithmeticInstrCost(
- Instruction::Add, Ty, CostKind,
- Op1Info.getNoProps(), Op2Info.getNoProps());
- Cost += getArithmeticInstrCost(Instruction::Sub, Ty, CostKind,
- Op1Info.getNoProps(), Op2Info.getNoProps());
- Cost += getArithmeticInstrCost(
- Instruction::Select, Ty, CostKind,
- Op1Info.getNoProps(), Op2Info.getNoProps());
- Cost += getArithmeticInstrCost(Instruction::AShr, Ty, CostKind,
- Op1Info.getNoProps(), Op2Info.getNoProps());
- return Cost;
+ /*
+ Notes for sdiv/srem specific costs:
+ 1. This only considers the cases where the divisor is constant, uniform and
+ (pow-of-2/non-pow-of-2). Other cases are not important since they either
+ result in some form of (ldr + adrp), corresponding to constant vectors, or
+ scalarization of the division operation.
+ 2. Constant divisors, either negative in whole or partially, don't result in
+ significantly different codegen as compared to positive constant divisors.
+ So, we don't consider negative divisors separately.
+ 3. If the codegen is significantly different with SVE, it has been indicated
+ using comments at appropriate places.
+
+ sdiv specific cases:
+ -----------------------------------------------------------------------
+ codegen | pow-of-2 | Type
+ -----------------------------------------------------------------------
+ add + cmp + csel + asr | Y | i64
+ add + cmp + csel + asr | Y | i32
+ -----------------------------------------------------------------------
+
+ srem specific cases:
+ -----------------------------------------------------------------------
+ codegen | pow-of-2 | Type
+ -----------------------------------------------------------------------
+ negs + and + and + csneg | Y | i64
+ negs + and + and + csneg | Y | i32
+ -----------------------------------------------------------------------
+
+ other sdiv/srem cases:
+ -------------------------------------------------------------------------
+ common codegen | + srem | + sdiv | pow-of-2 | Type
+ -------------------------------------------------------------------------
+ smulh + asr + add + add | - | - | N | i64
+ smull + lsr + add + add | - | - | N | i32
+ usra | and + sub | sshr | Y | <2 x i64>
+ 2 * (scalar code) | - | - | N | <2 x i64>
+ usra | bic + sub | sshr + neg | Y | <4 x i32>
+ smull2 + smull + uzp2 | mls | - | N | <4 x i32>
+ + sshr + usra | | | |
+ -------------------------------------------------------------------------
+ */
+ if (Op2Info.isConstant() && Op2Info.isUniform()) {
+ InstructionCost AddCost =
+ getArithmeticInstrCost(Instruction::Add, Ty, CostKind,
+ Op1Info.getNoProps(), Op2Info.getNoProps());
+ InstructionCost AsrCost =
+ getArithmeticInstrCost(Instruction::AShr, Ty, CostKind,
+ Op1Info.getNoProps(), Op2Info.getNoProps());
+ InstructionCost MulCost =
+ getArithmeticInstrCost(Instruction::Mul, Ty, CostKind,
+ Op1Info.getNoProps(), Op2Info.getNoProps());
+ // add/cmp/csel/csneg should have similar cost while asr/negs/and should
+ // have similar cost.
+ auto VT = TLI->getValueType(DL, Ty);
+ if (VT.isScalarInteger() && VT.getSizeInBits() <= 64) {
+ if (Op2Info.isPowerOf2() || Op2Info.isNegatedPowerOf2()) {
+ // Neg can be folded into the asr instruction.
+ return ISD == ISD::SDIV ? (3 * AddCost + AsrCost)
+ : (3 * AsrCost + AddCost);
+ } else {
+ return MulCost + AsrCost + 2 * AddCost;
+ }
+ } else if (VT.isVector()) {
+ InstructionCost UsraCost = 2 * AsrCost;
+ if (Op2Info.isPowerOf2() || Op2Info.isNegatedPowerOf2()) {
+ // Division with scalable types corresponds to native 'asrd'
+ // instruction when SVE is available.
+ // e.g. %1 = sdiv <vscale x 4 x i32> %a, splat (i32 8)
+
+ // One more for the negation in SDIV
+ InstructionCost Cost =
+ (Op2Info.isNegatedPowerOf2() && ISD == ISD::SDIV) ? AsrCost : 0;
+ if (Ty->isScalableTy() && ST->hasSVE())
+ Cost += 2 * AsrCost;
+ else {
+ Cost +=
+ UsraCost +
+ (ISD == ISD::SDIV
+ ? (LT.second.getScalarType() == MVT::i64 ? 1 : 2) * AsrCost
+ : 2 * AddCost);
+ }
+ return Cost;
+ } else if (LT.second == MVT::v2i64) {
+ return VT.getVectorNumElements() *
+ getArithmeticInstrCost(Opcode, Ty->getScalarType(), CostKind,
+ Op1Info.getNoProps(),
+ Op2Info.getNoProps());
+ } else {
+ // When SVE is available, we get:
+ // smulh + lsr + add/sub + asr + add/sub.
+ if (Ty->isScalableTy() && ST->hasSVE())
+ return MulCost /*smulh cost*/ + 2 * AddCost + 2 * AsrCost;
+ return 2 * MulCost + AddCost /*uzp2 cost*/ + AsrCost + UsraCost;
+ }
+ }
+ }
+ if (Op2Info.isConstant() && !Op2Info.isUniform() &&
+ LT.second.isFixedLengthVector()) {
+ // FIXME: When the constant vector is non-uniform, this may result in
+ // loading the vector from constant pool or in some cases, may also result
+ // in scalarization. For now, we are approximating this with the
+ // scalarization cost.
+ auto ExtractCost = 2 * getVectorInstrCost(Instruction::ExtractElement, Ty,
+ CostKind, -1, nullptr, nullptr);
+ auto InsertCost = getVectorInstrCost(Instruction::InsertElement, Ty,
+ CostKind, -1, nullptr, nullptr);
+ unsigned NElts = cast<FixedVectorType>(Ty)->getNumElements();
+ return ExtractCost + InsertCost +
+ NElts * getArithmeticInstrCost(Opcode, Ty->getScalarType(),
+ CostKind, Op1Info.getNoProps(),
+ Op2Info.getNoProps());
}
[[fallthrough]];
- case ISD::UDIV: {
+ case ISD::UDIV:
+ case ISD::UREM: {
auto VT = TLI->getValueType(DL, Ty);
- if (Op2Info.isConstant() && Op2Info.isUniform()) {
- if (TLI->isOperationLegalOrCustom(ISD::MULHU, VT)) {
- // Vector signed division by constant are expanded to the
- // sequence MULHS + ADD/SUB + SRA + SRL + ADD, and unsigned division
- // to MULHS + SUB + SRL + ADD + SRL.
- InstructionCost MulCost = getArithmeticInstrCost(
- Instruction::Mul, Ty, CostKind, Op1Info.getNoProps(), Op2Info.getNoProps());
- InstructionCost AddCost = getArithmeticInstrCost(
- Instruction::Add, Ty, CostKind, Op1Info.getNoProps(), Op2Info.getNoProps());
- InstructionCost ShrCost = getArithmeticInstrCost(
- Instruction::AShr, Ty, CostKind, Op1Info.getNoProps(), Op2Info.getNoProps());
- return MulCost * 2 + AddCost * 2 + ShrCost * 2 + 1;
+ if (Op2Info.isConstant()) {
+ // If the operand is a power of 2 we can use the shift or and cost.
+ if (ISD == ISD::UDIV && Op2Info.isPowerOf2())
+ return getArithmeticInstrCost(Instruction::LShr, Ty, CostKind,
+ Op1Info.getNoProps(),
+ Op2Info.getNoProps());
+ if (ISD == ISD::UREM && Op2Info.isPowerOf2())
+ return getArithmeticInstrCost(Instruction::And, Ty, CostKind,
+ Op1Info.getNoProps(),
+ Op2Info.getNoProps());
+
+ if (ISD == ISD::UDIV || ISD == ISD::UREM) {
+ // Divides by a constant are expanded to MULHU + SUB + SRL + ADD + SRL.
+ // The MULHU will be expanded to UMULL for the types not listed below,
+ // and will become a pair of UMULL+MULL2 for 128bit vectors.
+ bool HasMULH = VT == MVT::i64 || LT.second == MVT::nxv2i64 ||
+ LT.second == MVT::nxv4i32 || LT.second == MVT::nxv8i16 ||
+ LT.second == MVT::nxv16i8;
+ bool Is128bit = LT.second.is128BitVector();
+
+ InstructionCost MulCost =
+ getArithmeticInstrCost(Instruction::Mul, Ty, CostKind,
+ Op1Info.getNoProps(), Op2Info.getNoProps());
+ InstructionCost AddCost =
+ getArithmeticInstrCost(Instruction::Add, Ty, CostKind,
+ Op1Info.getNoProps(), Op2Info.getNoProps());
+ InstructionCost ShrCost =
+ getArithmeticInstrCost(Instruction::AShr, Ty, CostKind,
+ Op1Info.getNoProps(), Op2Info.getNoProps());
+ InstructionCost DivCost = MulCost * (Is128bit ? 2 : 1) + // UMULL/UMULH
+ (HasMULH ? 0 : ShrCost) + // UMULL shift
+ AddCost * 2 + ShrCost;
+ return DivCost + (ISD == ISD::UREM ? MulCost + AddCost : 0);
}
}
@@ -3544,14 +4122,12 @@ InstructionCost AArch64TTIImpl::getArithmeticInstrCost(
InstructionCost Cost = BaseT::getArithmeticInstrCost(
Opcode, Ty, CostKind, Op1Info, Op2Info);
- if (Ty->isVectorTy()) {
+ if (Ty->isVectorTy() && (ISD == ISD::SDIV || ISD == ISD::UDIV)) {
if (TLI->isOperationLegalOrCustom(ISD, LT.second) && ST->hasSVE()) {
// SDIV/UDIV operations are lowered using SVE, then we can have less
// costs.
- if (isa<FixedVectorType>(Ty) && cast<FixedVectorType>(Ty)
- ->getPrimitiveSizeInBits()
- .getFixedValue() < 128) {
- EVT VT = TLI->getValueType(DL, Ty);
+ if (VT.isSimple() && isa<FixedVectorType>(Ty) &&
+ Ty->getPrimitiveSizeInBits().getFixedValue() < 128) {
static const CostTblEntry DivTbl[]{
{ISD::SDIV, MVT::v2i8, 5}, {ISD::SDIV, MVT::v4i8, 8},
{ISD::SDIV, MVT::v8i8, 8}, {ISD::SDIV, MVT::v2i16, 5},
@@ -3586,10 +4162,10 @@ InstructionCost AArch64TTIImpl::getArithmeticInstrCost(
}
// On AArch64, without SVE, vector divisions are expanded
// into scalar divisions of each pair of elements.
- Cost += getArithmeticInstrCost(Instruction::ExtractElement, Ty,
- CostKind, Op1Info, Op2Info);
- Cost += getArithmeticInstrCost(Instruction::InsertElement, Ty, CostKind,
- Op1Info, Op2Info);
+ Cost += getVectorInstrCost(Instruction::ExtractElement, Ty, CostKind,
+ -1, nullptr, nullptr);
+ Cost += getVectorInstrCost(Instruction::InsertElement, Ty, CostKind, -1,
+ nullptr, nullptr);
}
// TODO: if one of the arguments is scalar, then it's not necessary to
@@ -3675,9 +4251,9 @@ InstructionCost AArch64TTIImpl::getArithmeticInstrCost(
}
}
-InstructionCost AArch64TTIImpl::getAddressComputationCost(Type *Ty,
- ScalarEvolution *SE,
- const SCEV *Ptr) {
+InstructionCost
+AArch64TTIImpl::getAddressComputationCost(Type *Ty, ScalarEvolution *SE,
+ const SCEV *Ptr) const {
// Address computations in vectorized code with non-consecutive addresses will
// likely result in more instructions compared to scalar code where the
// computation can more often be merged into the index mode. The resulting
@@ -3697,15 +4273,10 @@ InstructionCost AArch64TTIImpl::getAddressComputationCost(Type *Ty,
InstructionCost AArch64TTIImpl::getCmpSelInstrCost(
unsigned Opcode, Type *ValTy, Type *CondTy, CmpInst::Predicate VecPred,
TTI::TargetCostKind CostKind, TTI::OperandValueInfo Op1Info,
- TTI::OperandValueInfo Op2Info, const Instruction *I) {
- // TODO: Handle other cost kinds.
- if (CostKind != TTI::TCK_RecipThroughput)
- return BaseT::getCmpSelInstrCost(Opcode, ValTy, CondTy, VecPred, CostKind,
- Op1Info, Op2Info, I);
-
+ TTI::OperandValueInfo Op2Info, const Instruction *I) const {
int ISD = TLI->InstructionOpcodeToISD(Opcode);
// We don't lower some vector selects well that are wider than the register
- // width.
+ // width. TODO: Improve this with different cost kinds.
if (isa<FixedVectorType>(ValTy) && ISD == ISD::SELECT) {
// We would need this many instructions to hide the scalarization happening.
const int AmortizationCost = 20;
@@ -3762,21 +4333,52 @@ InstructionCost AArch64TTIImpl::getCmpSelInstrCost(
}
if (isa<FixedVectorType>(ValTy) && ISD == ISD::SETCC) {
- auto LT = getTypeLegalizationCost(ValTy);
- // Cost v4f16 FCmp without FP16 support via converting to v4f32 and back.
- if (LT.second == MVT::v4f16 && !ST->hasFullFP16())
- return LT.first * 4; // fcvtl + fcvtl + fcmp + xtn
+ Type *ValScalarTy = ValTy->getScalarType();
+ if ((ValScalarTy->isHalfTy() && !ST->hasFullFP16()) ||
+ ValScalarTy->isBFloatTy()) {
+ auto *ValVTy = cast<FixedVectorType>(ValTy);
+
+ // Without dedicated instructions we promote [b]f16 compares to f32.
+ auto *PromotedTy =
+ VectorType::get(Type::getFloatTy(ValTy->getContext()), ValVTy);
+
+ InstructionCost Cost = 0;
+ // Promote operands to float vectors.
+ Cost += 2 * getCastInstrCost(Instruction::FPExt, PromotedTy, ValTy,
+ TTI::CastContextHint::None, CostKind);
+ // Compare float vectors.
+ Cost += getCmpSelInstrCost(Opcode, PromotedTy, CondTy, VecPred, CostKind,
+ Op1Info, Op2Info);
+ // During codegen we'll truncate the vector result from i32 to i16.
+ Cost +=
+ getCastInstrCost(Instruction::Trunc, VectorType::getInteger(ValVTy),
+ VectorType::getInteger(PromotedTy),
+ TTI::CastContextHint::None, CostKind);
+ return Cost;
+ }
}
- // Treat the icmp in icmp(and, 0) as free, as we can make use of ands.
- // FIXME: This can apply to more conditions and add/sub if it can be shown to
- // be profitable.
- if (ValTy->isIntegerTy() && ISD == ISD::SETCC && I &&
- ICmpInst::isEquality(VecPred) &&
+ // Treat the icmp in icmp(and, 0) or icmp(and, -1/1) when it can be folded to
+ // icmp(and, 0) as free, as we can make use of ands, but only if the
+ // comparison is not unsigned. FIXME: Enable for non-throughput cost kinds
+ // providing it will not cause performance regressions.
+ if (CostKind == TTI::TCK_RecipThroughput && ValTy->isIntegerTy() &&
+ ISD == ISD::SETCC && I && !CmpInst::isUnsigned(VecPred) &&
TLI->isTypeLegal(TLI->getValueType(DL, ValTy)) &&
- match(I->getOperand(1), m_Zero()) &&
- match(I->getOperand(0), m_And(m_Value(), m_Value())))
- return 0;
+ match(I->getOperand(0), m_And(m_Value(), m_Value()))) {
+ if (match(I->getOperand(1), m_Zero()))
+ return 0;
+
+ // x >= 1 / x < 1 -> x > 0 / x <= 0
+ if (match(I->getOperand(1), m_One()) &&
+ (VecPred == CmpInst::ICMP_SLT || VecPred == CmpInst::ICMP_SGE))
+ return 0;
+
+ // x <= -1 / x > -1 -> x > 0 / x <= 0
+ if (match(I->getOperand(1), m_AllOnes()) &&
+ (VecPred == CmpInst::ICMP_SLE || VecPred == CmpInst::ICMP_SGT))
+ return 0;
+ }
// The base case handles scalable vectors fine for now, since it treats the
// cost as 1 * legalization cost.
@@ -3810,7 +4412,7 @@ bool AArch64TTIImpl::prefersVectorizedAddressing() const {
InstructionCost
AArch64TTIImpl::getMaskedMemoryOpCost(unsigned Opcode, Type *Src,
Align Alignment, unsigned AddressSpace,
- TTI::TargetCostKind CostKind) {
+ TTI::TargetCostKind CostKind) const {
if (useNeonVector(Src))
return BaseT::getMaskedMemoryOpCost(Opcode, Src, Alignment, AddressSpace,
CostKind);
@@ -3857,7 +4459,7 @@ static unsigned getSVEGatherScatterOverhead(unsigned Opcode,
InstructionCost AArch64TTIImpl::getGatherScatterOpCost(
unsigned Opcode, Type *DataTy, const Value *Ptr, bool VariableMask,
- Align Alignment, TTI::TargetCostKind CostKind, const Instruction *I) {
+ Align Alignment, TTI::TargetCostKind CostKind, const Instruction *I) const {
if (useNeonVector(DataTy) || !isLegalMaskedGatherScatter(DataTy))
return BaseT::getGatherScatterOpCost(Opcode, DataTy, Ptr, VariableMask,
Alignment, CostKind, I);
@@ -3893,11 +4495,11 @@ bool AArch64TTIImpl::useNeonVector(const Type *Ty) const {
}
InstructionCost AArch64TTIImpl::getMemoryOpCost(unsigned Opcode, Type *Ty,
- MaybeAlign Alignment,
+ Align Alignment,
unsigned AddressSpace,
TTI::TargetCostKind CostKind,
TTI::OperandValueInfo OpInfo,
- const Instruction *I) {
+ const Instruction *I) const {
EVT VT = TLI->getValueType(DL, Ty, true);
// Type legalization can't handle structs
if (VT == MVT::Other)
@@ -3928,7 +4530,7 @@ InstructionCost AArch64TTIImpl::getMemoryOpCost(unsigned Opcode, Type *Ty,
return 1;
if (ST->isMisaligned128StoreSlow() && Opcode == Instruction::Store &&
- LT.second.is128BitVector() && (!Alignment || *Alignment < Align(16))) {
+ LT.second.is128BitVector() && Alignment < Align(16)) {
// Unaligned stores are extremely inefficient. We don't split all
// unaligned 128-bit stores because the negative impact that has shown in
// practice on inlined block copy code.
@@ -3955,8 +4557,7 @@ InstructionCost AArch64TTIImpl::getMemoryOpCost(unsigned Opcode, Type *Ty,
EVT EltVT = VT.getVectorElementType();
unsigned EltSize = EltVT.getScalarSizeInBits();
if (!isPowerOf2_32(EltSize) || EltSize < 8 || EltSize > 64 ||
- VT.getVectorNumElements() >= (128 / EltSize) || !Alignment ||
- *Alignment != Align(1))
+ VT.getVectorNumElements() >= (128 / EltSize) || Alignment != Align(1))
return LT.first;
// FIXME: v3i8 lowering currently is very inefficient, due to automatic
// widening to v4i8, which produces suboptimal results.
@@ -3992,13 +4593,20 @@ InstructionCost AArch64TTIImpl::getMemoryOpCost(unsigned Opcode, Type *Ty,
InstructionCost AArch64TTIImpl::getInterleavedMemoryOpCost(
unsigned Opcode, Type *VecTy, unsigned Factor, ArrayRef<unsigned> Indices,
Align Alignment, unsigned AddressSpace, TTI::TargetCostKind CostKind,
- bool UseMaskForCond, bool UseMaskForGaps) {
+ bool UseMaskForCond, bool UseMaskForGaps) const {
assert(Factor >= 2 && "Invalid interleave factor");
auto *VecVTy = cast<VectorType>(VecTy);
if (VecTy->isScalableTy() && !ST->hasSVE())
return InstructionCost::getInvalid();
+ // Scalable VFs will emit vector.[de]interleave intrinsics, and currently we
+ // only have lowering for power-of-2 factors.
+ // TODO: Add lowering for vector.[de]interleave3 intrinsics and support in
+ // InterleavedAccessPass for ld3/st3
+ if (VecTy->isScalableTy() && !isPowerOf2_32(Factor))
+ return InstructionCost::getInvalid();
+
// Vectorization for masked interleaved accesses is only enabled for scalable
// VF.
if (!VecTy->isScalableTy() && (UseMaskForCond || UseMaskForGaps))
@@ -4025,7 +4633,7 @@ InstructionCost AArch64TTIImpl::getInterleavedMemoryOpCost(
}
InstructionCost
-AArch64TTIImpl::getCostOfKeepingLiveOverCall(ArrayRef<Type *> Tys) {
+AArch64TTIImpl::getCostOfKeepingLiveOverCall(ArrayRef<Type *> Tys) const {
InstructionCost Cost = 0;
TTI::TargetCostKind CostKind = TTI::TCK_RecipThroughput;
for (auto *I : Tys) {
@@ -4039,7 +4647,7 @@ AArch64TTIImpl::getCostOfKeepingLiveOverCall(ArrayRef<Type *> Tys) {
return Cost;
}
-unsigned AArch64TTIImpl::getMaxInterleaveFactor(ElementCount VF) {
+unsigned AArch64TTIImpl::getMaxInterleaveFactor(ElementCount VF) const {
return ST->getMaxInterleaveFactor();
}
@@ -4095,39 +4703,100 @@ getFalkorUnrollingPreferences(Loop *L, ScalarEvolution &SE,
}
}
+// This function returns true if the loop:
+// 1. Has a valid cost, and
+// 2. Has a cost within the supplied budget.
+// Otherwise it returns false.
+static bool isLoopSizeWithinBudget(Loop *L, const AArch64TTIImpl &TTI,
+ InstructionCost Budget,
+ unsigned *FinalSize) {
+ // Estimate the size of the loop.
+ InstructionCost LoopCost = 0;
+
+ for (auto *BB : L->getBlocks()) {
+ for (auto &I : *BB) {
+ SmallVector<const Value *, 4> Operands(I.operand_values());
+ InstructionCost Cost =
+ TTI.getInstructionCost(&I, Operands, TTI::TCK_CodeSize);
+ // This can happen with intrinsics that don't currently have a cost model
+ // or for some operations that require SVE.
+ if (!Cost.isValid())
+ return false;
+
+ LoopCost += Cost;
+ if (LoopCost > Budget)
+ return false;
+ }
+ }
+
+ if (FinalSize)
+ *FinalSize = LoopCost.getValue();
+ return true;
+}
+
+static bool shouldUnrollMultiExitLoop(Loop *L, ScalarEvolution &SE,
+ const AArch64TTIImpl &TTI) {
+ // Only consider loops with unknown trip counts for which we can determine
+ // a symbolic expression. Multi-exit loops with small known trip counts will
+ // likely be unrolled anyway.
+ const SCEV *BTC = SE.getSymbolicMaxBackedgeTakenCount(L);
+ if (isa<SCEVConstant>(BTC) || isa<SCEVCouldNotCompute>(BTC))
+ return false;
+
+ // It might not be worth unrolling loops with low max trip counts. Restrict
+ // this to max trip counts > 32 for now.
+ unsigned MaxTC = SE.getSmallConstantMaxTripCount(L);
+ if (MaxTC > 0 && MaxTC <= 32)
+ return false;
+
+ // Make sure the loop size is <= 5.
+ if (!isLoopSizeWithinBudget(L, TTI, 5, nullptr))
+ return false;
+
+ // Small search loops with multiple exits can be highly beneficial to unroll.
+ // We only care about loops with exactly two exiting blocks, although each
+ // block could jump to the same exit block.
+ ArrayRef<BasicBlock *> Blocks = L->getBlocks();
+ if (Blocks.size() != 2)
+ return false;
+
+ if (any_of(Blocks, [](BasicBlock *BB) {
+ return !isa<BranchInst>(BB->getTerminator());
+ }))
+ return false;
+
+ return true;
+}
+
/// For Apple CPUs, we want to runtime-unroll loops to make better use if the
/// OOO engine's wide instruction window and various predictors.
static void
getAppleRuntimeUnrollPreferences(Loop *L, ScalarEvolution &SE,
TargetTransformInfo::UnrollingPreferences &UP,
- AArch64TTIImpl &TTI) {
+ const AArch64TTIImpl &TTI) {
// Limit loops with structure that is highly likely to benefit from runtime
- // unrolling; that is we exclude outer loops, loops with multiple exits and
- // many blocks (i.e. likely with complex control flow). Note that the
- // heuristics here may be overly conservative and we err on the side of
- // avoiding runtime unrolling rather than unroll excessively. They are all
- // subject to further refinement.
- if (!L->isInnermost() || !L->getExitBlock() || L->getNumBlocks() > 8)
+ // unrolling; that is we exclude outer loops and loops with many blocks (i.e.
+ // likely with complex control flow). Note that the heuristics here may be
+ // overly conservative and we err on the side of avoiding runtime unrolling
+ // rather than unroll excessively. They are all subject to further refinement.
+ if (!L->isInnermost() || L->getNumBlocks() > 8)
+ return;
+
+ // Loops with multiple exits are handled by common code.
+ if (!L->getExitBlock())
return;
- const SCEV *BTC = SE.getBackedgeTakenCount(L);
+ const SCEV *BTC = SE.getSymbolicMaxBackedgeTakenCount(L);
if (isa<SCEVConstant>(BTC) || isa<SCEVCouldNotCompute>(BTC) ||
(SE.getSmallConstantMaxTripCount(L) > 0 &&
SE.getSmallConstantMaxTripCount(L) <= 32))
return;
+
if (findStringMetadataForLoop(L, "llvm.loop.isvectorized"))
return;
- int64_t Size = 0;
- for (auto *BB : L->getBlocks()) {
- for (auto &I : *BB) {
- if (!isa<IntrinsicInst>(&I) && isa<CallBase>(&I))
- return;
- SmallVector<const Value *, 4> Operands(I.operand_values());
- Size +=
- *TTI.getInstructionCost(&I, Operands, TTI::TCK_CodeSize).getValue();
- }
- }
+ if (SE.getSymbolicMaxBackedgeTakenCount(L) != SE.getBackedgeTakenCount(L))
+ return;
// Limit to loops with trip counts that are cheap to expand.
UP.SCEVExpansionBudget = 1;
@@ -4136,7 +4805,9 @@ getAppleRuntimeUnrollPreferences(Loop *L, ScalarEvolution &SE,
// dependencies, to expose more parallel memory access streams.
BasicBlock *Header = L->getHeader();
if (Header == L->getLoopLatch()) {
- if (Size > 8)
+ // Estimate the size of the loop.
+ unsigned Size;
+ if (!isLoopSizeWithinBudget(L, TTI, 8, &Size))
return;
SmallPtrSet<Value *, 8> LoadedValues;
@@ -4190,7 +4861,7 @@ getAppleRuntimeUnrollPreferences(Loop *L, ScalarEvolution &SE,
auto *Latch = L->getLoopLatch();
SmallVector<BasicBlock *> Preds(predecessors(Latch));
if (!Term || !Term->isConditional() || Preds.size() == 1 ||
- none_of(Preds, [Header](BasicBlock *Pred) { return Header == Pred; }) ||
+ !llvm::is_contained(Preds, Header) ||
none_of(Preds, [L](BasicBlock *Pred) { return L->contains(Pred); }))
return;
@@ -4216,9 +4887,9 @@ getAppleRuntimeUnrollPreferences(Loop *L, ScalarEvolution &SE,
}
}
-void AArch64TTIImpl::getUnrollingPreferences(Loop *L, ScalarEvolution &SE,
- TTI::UnrollingPreferences &UP,
- OptimizationRemarkEmitter *ORE) {
+void AArch64TTIImpl::getUnrollingPreferences(
+ Loop *L, ScalarEvolution &SE, TTI::UnrollingPreferences &UP,
+ OptimizationRemarkEmitter *ORE) const {
// Enable partial unrolling and runtime unrolling.
BaseT::getUnrollingPreferences(L, SE, UP, ORE);
@@ -4233,6 +4904,24 @@ void AArch64TTIImpl::getUnrollingPreferences(Loop *L, ScalarEvolution &SE,
// Disable partial & runtime unrolling on -Os.
UP.PartialOptSizeThreshold = 0;
+ // No need to unroll auto-vectorized loops
+ if (findStringMetadataForLoop(L, "llvm.loop.isvectorized"))
+ return;
+
+ // Scan the loop: don't unroll loops with calls as this could prevent
+ // inlining.
+ for (auto *BB : L->getBlocks()) {
+ for (auto &I : *BB) {
+ if (isa<CallBase>(I)) {
+ if (isa<CallInst>(I) || isa<InvokeInst>(I))
+ if (const Function *F = cast<CallBase>(I).getCalledFunction())
+ if (!isLoweredToCall(F))
+ continue;
+ return;
+ }
+ }
+ }
+
// Apply subtarget-specific unrolling preferences.
switch (ST->getProcFamily()) {
case AArch64Subtarget::AppleA14:
@@ -4249,30 +4938,24 @@ void AArch64TTIImpl::getUnrollingPreferences(Loop *L, ScalarEvolution &SE,
break;
}
- // Scan the loop: don't unroll loops with calls as this could prevent
- // inlining. Don't unroll vector loops either, as they don't benefit much from
- // unrolling.
- for (auto *BB : L->getBlocks()) {
- for (auto &I : *BB) {
- // Don't unroll vectorised loop.
- if (I.getType()->isVectorTy())
- return;
-
- if (isa<CallInst>(I) || isa<InvokeInst>(I)) {
- if (const Function *F = cast<CallBase>(I).getCalledFunction()) {
- if (!isLoweredToCall(F))
- continue;
- }
- return;
- }
- }
+ // If this is a small, multi-exit loop similar to something like std::find,
+ // then there is typically a performance improvement achieved by unrolling.
+ if (!L->getExitBlock() && shouldUnrollMultiExitLoop(L, SE, *this)) {
+ UP.RuntimeUnrollMultiExit = true;
+ UP.Runtime = true;
+ // Limit unroll count.
+ UP.DefaultUnrollRuntimeCount = 4;
+ // Allow slightly more costly trip-count expansion to catch search loops
+ // with pointer inductions.
+ UP.SCEVExpansionBudget = 5;
+ return;
}
// Enable runtime unrolling for in-order models
// If mcpu is omitted, getProcFamily() returns AArch64Subtarget::Others, so by
// checking for that case, we can ensure that the default behaviour is
// unchanged
- if (ST->getProcFamily() != AArch64Subtarget::Others &&
+ if (ST->getProcFamily() != AArch64Subtarget::Generic &&
!ST->getSchedModel().isOutOfOrder()) {
UP.Runtime = true;
UP.Partial = true;
@@ -4285,12 +4968,13 @@ void AArch64TTIImpl::getUnrollingPreferences(Loop *L, ScalarEvolution &SE,
}
void AArch64TTIImpl::getPeelingPreferences(Loop *L, ScalarEvolution &SE,
- TTI::PeelingPreferences &PP) {
+ TTI::PeelingPreferences &PP) const {
BaseT::getPeelingPreferences(L, SE, PP);
}
Value *AArch64TTIImpl::getOrCreateResultFromMemIntrinsic(IntrinsicInst *Inst,
- Type *ExpectedType) {
+ Type *ExpectedType,
+ bool CanCreate) const {
switch (Inst->getIntrinsicID()) {
default:
return nullptr;
@@ -4299,7 +4983,7 @@ Value *AArch64TTIImpl::getOrCreateResultFromMemIntrinsic(IntrinsicInst *Inst,
case Intrinsic::aarch64_neon_st4: {
// Create a struct type
StructType *ST = dyn_cast<StructType>(ExpectedType);
- if (!ST)
+ if (!CanCreate || !ST)
return nullptr;
unsigned NumElts = Inst->arg_size() - 1;
if (ST->getNumElements() != NumElts)
@@ -4326,7 +5010,7 @@ Value *AArch64TTIImpl::getOrCreateResultFromMemIntrinsic(IntrinsicInst *Inst,
}
bool AArch64TTIImpl::getTgtMemIntrinsic(IntrinsicInst *Inst,
- MemIntrinsicInfo &Info) {
+ MemIntrinsicInfo &Info) const {
switch (Inst->getIntrinsicID()) {
default:
break;
@@ -4371,7 +5055,7 @@ bool AArch64TTIImpl::getTgtMemIntrinsic(IntrinsicInst *Inst,
/// sext instructions that sign extended the same initial value. A getelementptr
/// is considered as "complex" if it has more than 2 operands.
bool AArch64TTIImpl::shouldConsiderAddressTypePromotion(
- const Instruction &I, bool &AllowPromotionWithoutCommonHeader) {
+ const Instruction &I, bool &AllowPromotionWithoutCommonHeader) const {
bool Considerable = false;
AllowPromotionWithoutCommonHeader = false;
if (!isa<SExtInst>(&I))
@@ -4419,8 +5103,7 @@ bool AArch64TTIImpl::isLegalToVectorizeReduction(
case RecurKind::FMin:
case RecurKind::FMax:
case RecurKind::FMulAdd:
- case RecurKind::IAnyOf:
- case RecurKind::FAnyOf:
+ case RecurKind::AnyOf:
return true;
default:
return false;
@@ -4430,7 +5113,7 @@ bool AArch64TTIImpl::isLegalToVectorizeReduction(
InstructionCost
AArch64TTIImpl::getMinMaxReductionCost(Intrinsic::ID IID, VectorType *Ty,
FastMathFlags FMF,
- TTI::TargetCostKind CostKind) {
+ TTI::TargetCostKind CostKind) const {
// The code-generator is currently not able to handle scalable vectors
// of <vscale x 1 x eltty> yet, so return an invalid cost to avoid selecting
// it. This change will be removed when code-generation for these types is
@@ -4455,7 +5138,7 @@ AArch64TTIImpl::getMinMaxReductionCost(Intrinsic::ID IID, VectorType *Ty,
}
InstructionCost AArch64TTIImpl::getArithmeticReductionCostSVE(
- unsigned Opcode, VectorType *ValTy, TTI::TargetCostKind CostKind) {
+ unsigned Opcode, VectorType *ValTy, TTI::TargetCostKind CostKind) const {
std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(ValTy);
InstructionCost LegalizationCost = 0;
if (LT.first > 1) {
@@ -4482,7 +5165,7 @@ InstructionCost AArch64TTIImpl::getArithmeticReductionCostSVE(
InstructionCost
AArch64TTIImpl::getArithmeticReductionCost(unsigned Opcode, VectorType *ValTy,
std::optional<FastMathFlags> FMF,
- TTI::TargetCostKind CostKind) {
+ TTI::TargetCostKind CostKind) const {
// The code-generator is currently not able to handle scalable vectors
// of <vscale x 1 x eltty> yet, so return an invalid cost to avoid selecting
// it. This change will be removed when code-generation for these types is
@@ -4531,6 +5214,7 @@ AArch64TTIImpl::getArithmeticReductionCost(unsigned Opcode, VectorType *ValTy,
{ISD::ADD, MVT::v16i8, 2},
{ISD::ADD, MVT::v4i16, 2},
{ISD::ADD, MVT::v8i16, 2},
+ {ISD::ADD, MVT::v2i32, 2},
{ISD::ADD, MVT::v4i32, 2},
{ISD::ADD, MVT::v2i64, 2},
{ISD::OR, MVT::v8i8, 15},
@@ -4609,7 +5293,57 @@ AArch64TTIImpl::getArithmeticReductionCost(unsigned Opcode, VectorType *ValTy,
return BaseT::getArithmeticReductionCost(Opcode, ValTy, FMF, CostKind);
}
-InstructionCost AArch64TTIImpl::getSpliceCost(VectorType *Tp, int Index) {
+InstructionCost AArch64TTIImpl::getExtendedReductionCost(
+ unsigned Opcode, bool IsUnsigned, Type *ResTy, VectorType *VecTy,
+ std::optional<FastMathFlags> FMF, TTI::TargetCostKind CostKind) const {
+ EVT VecVT = TLI->getValueType(DL, VecTy);
+ EVT ResVT = TLI->getValueType(DL, ResTy);
+
+ if (Opcode == Instruction::Add && VecVT.isSimple() && ResVT.isSimple() &&
+ VecVT.getSizeInBits() >= 64) {
+ std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(VecTy);
+
+ // The legal cases are:
+ // UADDLV 8/16/32->32
+ // UADDLP 32->64
+ unsigned RevVTSize = ResVT.getSizeInBits();
+ if (((LT.second == MVT::v8i8 || LT.second == MVT::v16i8) &&
+ RevVTSize <= 32) ||
+ ((LT.second == MVT::v4i16 || LT.second == MVT::v8i16) &&
+ RevVTSize <= 32) ||
+ ((LT.second == MVT::v2i32 || LT.second == MVT::v4i32) &&
+ RevVTSize <= 64))
+ return (LT.first - 1) * 2 + 2;
+ }
+
+ return BaseT::getExtendedReductionCost(Opcode, IsUnsigned, ResTy, VecTy, FMF,
+ CostKind);
+}
+
+InstructionCost
+AArch64TTIImpl::getMulAccReductionCost(bool IsUnsigned, Type *ResTy,
+ VectorType *VecTy,
+ TTI::TargetCostKind CostKind) const {
+ EVT VecVT = TLI->getValueType(DL, VecTy);
+ EVT ResVT = TLI->getValueType(DL, ResTy);
+
+ if (ST->hasDotProd() && VecVT.isSimple() && ResVT.isSimple()) {
+ std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(VecTy);
+
+ // The legal cases with dotprod are
+ // UDOT 8->32
+ // Which requires an additional uaddv to sum the i32 values.
+ if ((LT.second == MVT::v8i8 || LT.second == MVT::v16i8) &&
+ ResVT == MVT::i32)
+ return LT.first + 2;
+ }
+
+ return BaseT::getMulAccReductionCost(IsUnsigned, ResTy, VecTy, CostKind);
+}
+
+InstructionCost
+AArch64TTIImpl::getSpliceCost(VectorType *Tp, int Index,
+ TTI::TargetCostKind CostKind) const {
static const CostTblEntry ShuffleTbl[] = {
{ TTI::SK_Splice, MVT::nxv16i8, 1 },
{ TTI::SK_Splice, MVT::nxv8i16, 1 },
@@ -4635,7 +5369,6 @@ InstructionCost AArch64TTIImpl::getSpliceCost(VectorType *Tp, int Index) {
std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(Tp);
Type *LegalVTy = EVT(LT.second).getTypeForEVT(Tp->getContext());
- TTI::TargetCostKind CostKind = TTI::TCK_RecipThroughput;
EVT PromotedVT = LT.second.getScalarType() == MVT::i1
? TLI->getPromotedVTForPredicate(EVT(LT.second))
: LT.second;
@@ -4668,27 +5401,54 @@ InstructionCost AArch64TTIImpl::getSpliceCost(VectorType *Tp, int Index) {
InstructionCost AArch64TTIImpl::getPartialReductionCost(
unsigned Opcode, Type *InputTypeA, Type *InputTypeB, Type *AccumType,
ElementCount VF, TTI::PartialReductionExtendKind OpAExtend,
- TTI::PartialReductionExtendKind OpBExtend,
- std::optional<unsigned> BinOp) const {
+ TTI::PartialReductionExtendKind OpBExtend, std::optional<unsigned> BinOp,
+ TTI::TargetCostKind CostKind) const {
InstructionCost Invalid = InstructionCost::getInvalid();
InstructionCost Cost(TTI::TCC_Basic);
- if (Opcode != Instruction::Add)
+ if (CostKind != TTI::TCK_RecipThroughput)
+ return Invalid;
+
+ // Sub opcodes currently only occur in chained cases.
+ // Independent partial reduction subtractions are still costed as an add
+ if ((Opcode != Instruction::Add && Opcode != Instruction::Sub) ||
+ OpAExtend == TTI::PR_None)
return Invalid;
- if (InputTypeA != InputTypeB)
+ // We only support multiply binary operations for now, and for muls we
+ // require the types being extended to be the same.
+ // NOTE: For muls AArch64 supports lowering mixed extensions to a usdot but
+ // only if the i8mm or sve/streaming features are available.
+ if (BinOp && (*BinOp != Instruction::Mul || InputTypeA != InputTypeB ||
+ OpBExtend == TTI::PR_None ||
+ (OpAExtend != OpBExtend && !ST->hasMatMulInt8() &&
+ !ST->isSVEorStreamingSVEAvailable())))
return Invalid;
+ assert((BinOp || (OpBExtend == TTI::PR_None && !InputTypeB)) &&
+ "Unexpected values for OpBExtend or InputTypeB");
EVT InputEVT = EVT::getEVT(InputTypeA);
EVT AccumEVT = EVT::getEVT(AccumType);
- if (VF.isScalable() && !ST->isSVEorStreamingSVEAvailable())
- return Invalid;
- if (VF.isFixed() && (!ST->isNeonAvailable() || !ST->hasDotProd()))
+ unsigned VFMinValue = VF.getKnownMinValue();
+
+ if (VF.isScalable()) {
+ if (!ST->isSVEorStreamingSVEAvailable())
+ return Invalid;
+
+ // Don't accept a partial reduction if the scaled accumulator is vscale x 1,
+ // since we can't lower that type.
+ unsigned Scale =
+ AccumEVT.getScalarSizeInBits() / InputEVT.getScalarSizeInBits();
+ if (VFMinValue == Scale)
+ return Invalid;
+ }
+ if (VF.isFixed() &&
+ (!ST->isNeonAvailable() || !ST->hasDotProd() || AccumEVT == MVT::i64))
return Invalid;
if (InputEVT == MVT::i8) {
- switch (VF.getKnownMinValue()) {
+ switch (VFMinValue) {
default:
return Invalid;
case 8:
@@ -4707,36 +5467,33 @@ InstructionCost AArch64TTIImpl::getPartialReductionCost(
} else if (InputEVT == MVT::i16) {
// FIXME: Allow i32 accumulator but increase cost, as we would extend
// it to i64.
- if (VF.getKnownMinValue() != 8 || AccumEVT != MVT::i64)
+ if (VFMinValue != 8 || AccumEVT != MVT::i64)
return Invalid;
} else
return Invalid;
- // AArch64 supports lowering mixed extensions to a usdot but only if the
- // i8mm or sve/streaming features are available.
- if (OpAExtend == TTI::PR_None || OpBExtend == TTI::PR_None ||
- (OpAExtend != OpBExtend && !ST->hasMatMulInt8() &&
- !ST->isSVEorStreamingSVEAvailable()))
- return Invalid;
-
- if (!BinOp || *BinOp != Instruction::Mul)
- return Invalid;
-
return Cost;
}
-InstructionCost AArch64TTIImpl::getShuffleCost(
- TTI::ShuffleKind Kind, VectorType *Tp, ArrayRef<int> Mask,
- TTI::TargetCostKind CostKind, int Index, VectorType *SubTp,
- ArrayRef<const Value *> Args, const Instruction *CxtI) {
- std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(Tp);
+InstructionCost
+AArch64TTIImpl::getShuffleCost(TTI::ShuffleKind Kind, VectorType *DstTy,
+ VectorType *SrcTy, ArrayRef<int> Mask,
+ TTI::TargetCostKind CostKind, int Index,
+ VectorType *SubTp, ArrayRef<const Value *> Args,
+ const Instruction *CxtI) const {
+ assert((Mask.empty() || DstTy->isScalableTy() ||
+ Mask.size() == DstTy->getElementCount().getKnownMinValue()) &&
+ "Expected the Mask to match the return size if given");
+ assert(SrcTy->getScalarType() == DstTy->getScalarType() &&
+ "Expected the same scalar types");
+ std::pair<InstructionCost, MVT> LT = getTypeLegalizationCost(SrcTy);
// If we have a Mask, and the LT is being legalized somehow, split the Mask
// into smaller vectors and sum the cost of each shuffle.
- if (!Mask.empty() && isa<FixedVectorType>(Tp) && LT.second.isVector() &&
- Tp->getScalarSizeInBits() == LT.second.getScalarSizeInBits() &&
+ if (!Mask.empty() && isa<FixedVectorType>(SrcTy) && LT.second.isVector() &&
+ LT.second.getScalarSizeInBits() * Mask.size() > 128 &&
+ SrcTy->getScalarSizeInBits() == LT.second.getScalarSizeInBits() &&
Mask.size() > LT.second.getVectorNumElements() && !Index && !SubTp) {
-
// Check for LD3/LD4 instructions, which are represented in llvm IR as
// deinterleaving-shuffle(load). The shuffle cost could potentially be free,
// but we model it with a cost of LT.first so that LD3/LD4 have a higher
@@ -4752,22 +5509,24 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
// cost than just the store.
if (CxtI && CxtI->hasOneUse() && isa<StoreInst>(*CxtI->user_begin()) &&
(ShuffleVectorInst::isInterleaveMask(
- Mask, 4, Tp->getElementCount().getKnownMinValue() * 2) ||
+ Mask, 4, SrcTy->getElementCount().getKnownMinValue() * 2) ||
ShuffleVectorInst::isInterleaveMask(
- Mask, 3, Tp->getElementCount().getKnownMinValue() * 2)))
+ Mask, 3, SrcTy->getElementCount().getKnownMinValue() * 2)))
return LT.first;
unsigned TpNumElts = Mask.size();
unsigned LTNumElts = LT.second.getVectorNumElements();
unsigned NumVecs = (TpNumElts + LTNumElts - 1) / LTNumElts;
- VectorType *NTp =
- VectorType::get(Tp->getScalarType(), LT.second.getVectorElementCount());
+ VectorType *NTp = VectorType::get(SrcTy->getScalarType(),
+ LT.second.getVectorElementCount());
InstructionCost Cost;
+ std::map<std::tuple<unsigned, unsigned, SmallVector<int>>, InstructionCost>
+ PreviousCosts;
for (unsigned N = 0; N < NumVecs; N++) {
SmallVector<int> NMask;
// Split the existing mask into chunks of size LTNumElts. Track the source
// sub-vectors to ensure the result has at most 2 inputs.
- unsigned Source1, Source2;
+ unsigned Source1 = -1U, Source2 = -1U;
unsigned NumSources = 0;
for (unsigned E = 0; E < LTNumElts; E++) {
int MaskElt = (N * LTNumElts + E < TpNumElts) ? Mask[N * LTNumElts + E]
@@ -4799,20 +5558,32 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
else
NMask.push_back(MaskElt % LTNumElts);
}
+ // Check if we have already generated this sub-shuffle, which means we
+ // will have already generated the output. For example a <16 x i32> splat
+ // will be the same sub-splat 4 times, which only needs to be generated
+ // once and reused.
+ auto Result =
+ PreviousCosts.insert({std::make_tuple(Source1, Source2, NMask), 0});
+ // Check if it was already in the map (already costed).
+ if (!Result.second)
+ continue;
// If the sub-mask has at most 2 input sub-vectors then re-cost it using
// getShuffleCost. If not then cost it using the worst case as the number
// of element moves into a new vector.
- if (NumSources <= 2)
- Cost += getShuffleCost(NumSources <= 1 ? TTI::SK_PermuteSingleSrc
+ InstructionCost NCost =
+ NumSources <= 2
+ ? getShuffleCost(NumSources <= 1 ? TTI::SK_PermuteSingleSrc
: TTI::SK_PermuteTwoSrc,
- NTp, NMask, CostKind, 0, nullptr, Args, CxtI);
- else
- Cost += LTNumElts;
+ NTp, NTp, NMask, CostKind, 0, nullptr, Args,
+ CxtI)
+ : LTNumElts;
+ Result.first->second = NCost;
+ Cost += NCost;
}
return Cost;
}
- Kind = improveShuffleKindFromMask(Kind, Mask, Tp, Index, SubTp);
+ Kind = improveShuffleKindFromMask(Kind, Mask, SrcTy, Index, SubTp);
bool IsExtractSubvector = Kind == TTI::SK_ExtractSubvector;
// A subvector extract can be implemented with an ext (or trivial extract, if
// from lane 0). This currently only handles low or high extracts to prevent
@@ -4828,6 +5599,35 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
}
Kind = TTI::SK_PermuteSingleSrc;
}
+ // FIXME: This was added to keep the costs equal when adding DstTys. Update
+ // the code to handle length-changing shuffles.
+ if (Kind == TTI::SK_InsertSubvector) {
+ LT = getTypeLegalizationCost(DstTy);
+ SrcTy = DstTy;
+ }
+
+ // Segmented shuffle matching.
+ if (Kind == TTI::SK_PermuteSingleSrc && isa<FixedVectorType>(SrcTy) &&
+ !Mask.empty() && SrcTy->getPrimitiveSizeInBits().isNonZero() &&
+ SrcTy->getPrimitiveSizeInBits().isKnownMultipleOf(
+ AArch64::SVEBitsPerBlock)) {
+
+ FixedVectorType *VTy = cast<FixedVectorType>(SrcTy);
+ unsigned Segments =
+ VTy->getPrimitiveSizeInBits() / AArch64::SVEBitsPerBlock;
+ unsigned SegmentElts = VTy->getNumElements() / Segments;
+
+ // dupq zd.t, zn.t[idx]
+ if ((ST->hasSVE2p1() || ST->hasSME2p1()) &&
+ ST->isSVEorStreamingSVEAvailable() &&
+ isDUPQMask(Mask, Segments, SegmentElts))
+ return LT.first;
+
+ // mov zd.q, vn
+ if (ST->isSVEorStreamingSVEAvailable() &&
+ isDUPFirstSegmentMask(Mask, Segments, SegmentElts))
+ return LT.first;
+ }
// Check for broadcast loads, which are supported by the LD1R instruction.
// In terms of code-size, the shuffle vector is free when a load + dup get
@@ -4839,15 +5639,17 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
if (CostKind == TTI::TCK_CodeSize && Kind == TTI::SK_Broadcast) {
bool IsLoad = !Args.empty() && isa<LoadInst>(Args[0]);
if (IsLoad && LT.second.isVector() &&
- isLegalBroadcastLoad(Tp->getElementType(),
+ isLegalBroadcastLoad(SrcTy->getElementType(),
LT.second.getVectorElementCount()))
return 0;
}
// If we have 4 elements for the shuffle and a Mask, get the cost straight
// from the perfect shuffle tables.
- if (Mask.size() == 4 && Tp->getElementCount() == ElementCount::getFixed(4) &&
- (Tp->getScalarSizeInBits() == 16 || Tp->getScalarSizeInBits() == 32) &&
+ if (Mask.size() == 4 &&
+ SrcTy->getElementCount() == ElementCount::getFixed(4) &&
+ (SrcTy->getScalarSizeInBits() == 16 ||
+ SrcTy->getScalarSizeInBits() == 32) &&
all_of(Mask, [](int E) { return E < 8; }))
return getPerfectShuffleCost(Mask);
@@ -4867,6 +5669,12 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
(Kind == TTI::SK_PermuteTwoSrc || Kind == TTI::SK_PermuteSingleSrc) &&
(isZIPMask(Mask, LT.second.getVectorNumElements(), Unused) ||
isUZPMask(Mask, LT.second.getVectorNumElements(), Unused) ||
+ isREVMask(Mask, LT.second.getScalarSizeInBits(),
+ LT.second.getVectorNumElements(), 16) ||
+ isREVMask(Mask, LT.second.getScalarSizeInBits(),
+ LT.second.getVectorNumElements(), 32) ||
+ isREVMask(Mask, LT.second.getScalarSizeInBits(),
+ LT.second.getVectorNumElements(), 64) ||
// Check for non-zero lane splats
all_of(drop_begin(Mask),
[&Mask](int M) { return M < 0 || M == Mask[0]; })))
@@ -4886,6 +5694,8 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
{TTI::SK_Broadcast, MVT::v2i64, 1},
{TTI::SK_Broadcast, MVT::v4f16, 1},
{TTI::SK_Broadcast, MVT::v8f16, 1},
+ {TTI::SK_Broadcast, MVT::v4bf16, 1},
+ {TTI::SK_Broadcast, MVT::v8bf16, 1},
{TTI::SK_Broadcast, MVT::v2f32, 1},
{TTI::SK_Broadcast, MVT::v4f32, 1},
{TTI::SK_Broadcast, MVT::v2f64, 1},
@@ -4900,6 +5710,8 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
{TTI::SK_Transpose, MVT::v2i64, 1},
{TTI::SK_Transpose, MVT::v4f16, 1},
{TTI::SK_Transpose, MVT::v8f16, 1},
+ {TTI::SK_Transpose, MVT::v4bf16, 1},
+ {TTI::SK_Transpose, MVT::v8bf16, 1},
{TTI::SK_Transpose, MVT::v2f32, 1},
{TTI::SK_Transpose, MVT::v4f32, 1},
{TTI::SK_Transpose, MVT::v2f64, 1},
@@ -4934,9 +5746,11 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
{TTI::SK_Reverse, MVT::v4f32, 2}, // REV64; EXT
{TTI::SK_Reverse, MVT::v2f64, 1}, // EXT
{TTI::SK_Reverse, MVT::v8f16, 2}, // REV64; EXT
+ {TTI::SK_Reverse, MVT::v8bf16, 2}, // REV64; EXT
{TTI::SK_Reverse, MVT::v8i16, 2}, // REV64; EXT
{TTI::SK_Reverse, MVT::v16i8, 2}, // REV64; EXT
{TTI::SK_Reverse, MVT::v4f16, 1}, // REV64
+ {TTI::SK_Reverse, MVT::v4bf16, 1}, // REV64
{TTI::SK_Reverse, MVT::v4i16, 1}, // REV64
{TTI::SK_Reverse, MVT::v8i8, 1}, // REV64
// Splice can all be lowered as `ext`.
@@ -4950,8 +5764,8 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
{TTI::SK_Splice, MVT::v8bf16, 1},
{TTI::SK_Splice, MVT::v8i16, 1},
{TTI::SK_Splice, MVT::v16i8, 1},
- {TTI::SK_Splice, MVT::v4bf16, 1},
{TTI::SK_Splice, MVT::v4f16, 1},
+ {TTI::SK_Splice, MVT::v4bf16, 1},
{TTI::SK_Splice, MVT::v4i16, 1},
{TTI::SK_Splice, MVT::v8i8, 1},
// Broadcast shuffle kinds for scalable vectors
@@ -4995,8 +5809,8 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
return LT.first * Entry->Cost;
}
- if (Kind == TTI::SK_Splice && isa<ScalableVectorType>(Tp))
- return getSpliceCost(Tp, Index);
+ if (Kind == TTI::SK_Splice && isa<ScalableVectorType>(SrcTy))
+ return getSpliceCost(SrcTy, Index, CostKind);
// Inserting a subvector can often be done with either a D, S or H register
// move, so long as the inserted vector is "aligned".
@@ -5014,8 +5828,8 @@ InstructionCost AArch64TTIImpl::getShuffleCost(
// Restore optimal kind.
if (IsExtractSubvector)
Kind = TTI::SK_ExtractSubvector;
- return BaseT::getShuffleCost(Kind, Tp, Mask, CostKind, Index, SubTp, Args,
- CxtI);
+ return BaseT::getShuffleCost(Kind, DstTy, SrcTy, Mask, CostKind, Index, SubTp,
+ Args, CxtI);
}
static bool containsDecreasingPointers(Loop *TheLoop,
@@ -5048,7 +5862,7 @@ unsigned AArch64TTIImpl::getEpilogueVectorizationMinVF() const {
return ST->getEpilogueVectorizationMinVF();
}
-bool AArch64TTIImpl::preferPredicateOverEpilogue(TailFoldingInfo *TFI) {
+bool AArch64TTIImpl::preferPredicateOverEpilogue(TailFoldingInfo *TFI) const {
if (!ST->hasSVE())
return false;
@@ -5109,10 +5923,11 @@ AArch64TTIImpl::getScalingFactorCost(Type *Ty, GlobalValue *BaseGV,
// Scale represents reg2 * scale, thus account for 1 if
// it is not equal to 0 or 1.
return AM.Scale != 0 && AM.Scale != 1;
- return -1;
+ return InstructionCost::getInvalid();
}
-bool AArch64TTIImpl::shouldTreatInstructionLikeSelect(const Instruction *I) {
+bool AArch64TTIImpl::shouldTreatInstructionLikeSelect(
+ const Instruction *I) const {
if (EnableOrLikeSelectOpt) {
// For the binary operators (e.g. or) we need to be more careful than
// selects, here we only transform them if they are already at a natural
@@ -5130,8 +5945,9 @@ bool AArch64TTIImpl::shouldTreatInstructionLikeSelect(const Instruction *I) {
return BaseT::shouldTreatInstructionLikeSelect(I);
}
-bool AArch64TTIImpl::isLSRCostLess(const TargetTransformInfo::LSRCost &C1,
- const TargetTransformInfo::LSRCost &C2) {
+bool AArch64TTIImpl::isLSRCostLess(
+ const TargetTransformInfo::LSRCost &C1,
+ const TargetTransformInfo::LSRCost &C2) const {
// AArch64 specific here is adding the number of instructions to the
// comparison (though not as the first consideration, as some targets do)
// along with changing the priority of the base additions.
@@ -5179,7 +5995,7 @@ static bool areExtractShuffleVectors(Value *Op1, Value *Op2,
!match(Op2, m_Shuffle(m_Value(S2Op1), m_Undef(), m_Mask(M2))))
return false;
- // If we allow splats, set S1Op1/S2Op1 to nullptr for the relavant arg so that
+ // If we allow splats, set S1Op1/S2Op1 to nullptr for the relevant arg so that
// it is not checked as an extract below.
if (AllowSplat && isSplatShuffle(Op1))
S1Op1 = nullptr;
@@ -5583,7 +6399,6 @@ bool AArch64TTIImpl::isProfitableToSinkOperands(
// the backend to generate a umull.
unsigned Bitwidth = I->getType()->getScalarSizeInBits();
APInt UpperMask = APInt::getHighBitsSet(Bitwidth, Bitwidth / 2);
- const DataLayout &DL = I->getDataLayout();
if (!MaskedValueIsZero(OperandInstr, UpperMask, DL))
continue;
NumZExts++;