Skip to content

Commit 8800a3c

Browse files
committed
[LV] Add initial support for vectorizing literal struct return values
This patch adds initial support for vectorizing literal struct return values. Currently, this is limited to the case where the struct is homogeneous (all elements have the same type) and not packed. The intended use case for this is vectorizing intrinsics such as: ``` declare { float, float } @llvm.sincos.f32(float %x) ``` Mapping them to structure-returning library calls such as: ``` declare { <4 x float>, <4 x i32> } @Sleef_sincosf4_u10advsimd(<4 x float>) ``` It could also be possible to vectorize the intrinsic (without a libcall) and then later lower the intrinsic to a library call. This may be desired if the only library calls available take output pointers rather than return multiple values. Implementing this required two main changes: 1. Supporting widening `extractvalue` 2. Adding support for "wide" types (in LV and parts of the cost model) The first change is relatively straightforward, the second is larger as it requires changing assumptions that types are always scalars or vectors. In this patch, a "wide" type is defined as a vector, or a struct literal where all elements are vectors (of the same element count). To help with the second change some helpers for wide types have been added (that work similarly to existing vector helpers). These have been used along the paths needed to support vectorizing calls, however, I expect there are many places that still only expect vector types.
1 parent 8712140 commit 8800a3c

File tree

14 files changed

+496
-75
lines changed

14 files changed

+496
-75
lines changed

llvm/include/llvm/Analysis/VectorUtils.h

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "llvm/Analysis/LoopAccessAnalysis.h"
1919
#include "llvm/IR/Module.h"
2020
#include "llvm/IR/VFABIDemangler.h"
21+
#include "llvm/IR/VectorUtils.h"
2122
#include "llvm/Support/CheckedArithmetic.h"
2223

2324
namespace llvm {
@@ -127,18 +128,8 @@ namespace Intrinsic {
127128
typedef unsigned ID;
128129
}
129130

130-
/// A helper function for converting Scalar types to vector types. If
131-
/// the incoming type is void, we return void. If the EC represents a
132-
/// scalar, we return the scalar type.
133-
inline Type *ToVectorTy(Type *Scalar, ElementCount EC) {
134-
if (Scalar->isVoidTy() || Scalar->isMetadataTy() || EC.isScalar())
135-
return Scalar;
136-
return VectorType::get(Scalar, EC);
137-
}
138-
139-
inline Type *ToVectorTy(Type *Scalar, unsigned VF) {
140-
return ToVectorTy(Scalar, ElementCount::getFixed(VF));
141-
}
131+
/// Returns true if `Ty` can be widened by the loop vectorizer.
132+
bool canWidenType(Type *Ty);
142133

143134
/// Identify if the intrinsic is trivially vectorizable.
144135
/// This method returns true if the intrinsic's argument types are all scalars

llvm/include/llvm/CodeGen/BasicTTIImpl.h

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,8 +1564,8 @@ class BasicTTIImplBase : public TargetTransformInfoImplCRTPBase<T> {
15641564
Type *RetTy = ICA.getReturnType();
15651565

15661566
ElementCount RetVF =
1567-
(RetTy->isVectorTy() ? cast<VectorType>(RetTy)->getElementCount()
1568-
: ElementCount::getFixed(1));
1567+
isWideTy(RetTy) ? getWideTypeVF(RetTy) : ElementCount::getFixed(1);
1568+
15691569
const IntrinsicInst *I = ICA.getInst();
15701570
const SmallVectorImpl<const Value *> &Args = ICA.getArgs();
15711571
FastMathFlags FMF = ICA.getFlags();
@@ -1886,10 +1886,13 @@ class BasicTTIImplBase : public TargetTransformInfoImplCRTPBase<T> {
18861886
InstructionCost ScalarizationCost = InstructionCost::getInvalid();
18871887
if (RetVF.isVector() && !RetVF.isScalable()) {
18881888
ScalarizationCost = 0;
1889-
if (!RetTy->isVoidTy())
1890-
ScalarizationCost += getScalarizationOverhead(
1891-
cast<VectorType>(RetTy),
1892-
/*Insert*/ true, /*Extract*/ false, CostKind);
1889+
if (!RetTy->isVoidTy()) {
1890+
for (Type *VectorTy : getContainedTypes(RetTy)) {
1891+
ScalarizationCost += getScalarizationOverhead(
1892+
cast<VectorType>(VectorTy),
1893+
/*Insert*/ true, /*Extract*/ false, CostKind);
1894+
}
1895+
}
18931896
ScalarizationCost +=
18941897
getOperandsScalarizationOverhead(Args, ICA.getArgTypes(), CostKind);
18951898
}
@@ -2480,27 +2483,32 @@ class BasicTTIImplBase : public TargetTransformInfoImplCRTPBase<T> {
24802483
// Else, assume that we need to scalarize this intrinsic. For math builtins
24812484
// this will emit a costly libcall, adding call overhead and spills. Make it
24822485
// very expensive.
2483-
if (auto *RetVTy = dyn_cast<VectorType>(RetTy)) {
2486+
if (isWideTy(RetTy)) {
2487+
const SmallVector<Type *, 2> RetVTys = getContainedTypes(RetTy);
2488+
24842489
// Scalable vectors cannot be scalarized, so return Invalid.
2485-
if (isa<ScalableVectorType>(RetTy) || any_of(Tys, [](const Type *Ty) {
2486-
return isa<ScalableVectorType>(Ty);
2487-
}))
2490+
if (any_of(concat<Type *const>(RetVTys, Tys),
2491+
[](Type *Ty) { return isa<ScalableVectorType>(Ty); }))
24882492
return InstructionCost::getInvalid();
24892493

2490-
InstructionCost ScalarizationCost =
2491-
SkipScalarizationCost
2492-
? ScalarizationCostPassed
2493-
: getScalarizationOverhead(RetVTy, /*Insert*/ true,
2494-
/*Extract*/ false, CostKind);
2494+
InstructionCost ScalarizationCost = ScalarizationCostPassed;
2495+
if (!SkipScalarizationCost) {
2496+
ScalarizationCost = 0;
2497+
for (Type *RetVTy : RetVTys) {
2498+
ScalarizationCost += getScalarizationOverhead(
2499+
cast<VectorType>(RetVTy), /*Insert*/ true,
2500+
/*Extract*/ false, CostKind);
2501+
}
2502+
}
24952503

2496-
unsigned ScalarCalls = cast<FixedVectorType>(RetVTy)->getNumElements();
2504+
unsigned ScalarCalls = getWideTypeVF(RetTy).getFixedValue();
24972505
SmallVector<Type *, 4> ScalarTys;
24982506
for (Type *Ty : Tys) {
24992507
if (Ty->isVectorTy())
25002508
Ty = Ty->getScalarType();
25012509
ScalarTys.push_back(Ty);
25022510
}
2503-
IntrinsicCostAttributes Attrs(IID, RetTy->getScalarType(), ScalarTys, FMF);
2511+
IntrinsicCostAttributes Attrs(IID, ToNarrowTy(RetTy), ScalarTys, FMF);
25042512
InstructionCost ScalarCost =
25052513
thisT()->getIntrinsicInstrCost(Attrs, CostKind);
25062514
for (Type *Ty : Tys) {

llvm/include/llvm/IR/VectorUtils.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//===----------- VectorUtils.h - Vector type utility functions -*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "llvm/ADT/SmallVector.h"
10+
#include "llvm/IR/DerivedTypes.h"
11+
12+
namespace llvm {
13+
14+
/// A helper function for converting Scalar types to vector types. If
15+
/// the incoming type is void, we return void. If the EC represents a
16+
/// scalar, we return the scalar type.
17+
inline Type *ToVectorTy(Type *Scalar, ElementCount EC) {
18+
if (Scalar->isVoidTy() || Scalar->isMetadataTy() || EC.isScalar())
19+
return Scalar;
20+
return VectorType::get(Scalar, EC);
21+
}
22+
23+
inline Type *ToVectorTy(Type *Scalar, unsigned VF) {
24+
return ToVectorTy(Scalar, ElementCount::getFixed(VF));
25+
}
26+
27+
/// A helper for converting to wider (vector) types. For scalar types, this is
28+
/// equivalent to calling `ToVectorTy`. For struct types, this returns a new
29+
/// struct where each element type has been widened to a vector type. Note: Only
30+
/// unpacked literal struct types are supported.
31+
Type *ToWideTy(Type *Ty, ElementCount EC);
32+
33+
/// A helper for converting wide types to narrow (non-vector) types. For vector
34+
/// types, this is equivalent to calling .getScalarType(). For struct types,
35+
/// this returns a new struct where each element type has been converted to a
36+
/// scalar type. Note: Only unpacked literal struct types are supported.
37+
Type *ToNarrowTy(Type *Ty);
38+
39+
/// Returns the types contained in `Ty`. For struct types, it returns the
40+
/// elements, all other types are returned directly.
41+
SmallVector<Type *, 2> getContainedTypes(Type *Ty);
42+
43+
/// Returns true if `Ty` is a vector type or a struct of vector types where all
44+
/// vector types share the same VF.
45+
bool isWideTy(Type *Ty);
46+
47+
/// Returns the vectorization factor for a widened type.
48+
inline ElementCount getWideTypeVF(Type *Ty) {
49+
assert(isWideTy(Ty) && "expected widened type!");
50+
return cast<VectorType>(getContainedTypes(Ty).front())->getElementCount();
51+
}
52+
53+
} // namespace llvm

llvm/lib/Analysis/VectorUtils.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,20 @@ static cl::opt<unsigned> MaxInterleaveGroupFactor(
3939
cl::desc("Maximum factor for an interleaved access group (default = 8)"),
4040
cl::init(8));
4141

42+
/// Returns true if `Ty` can be widened by the loop vectorizer.
43+
bool llvm::canWidenType(Type *Ty) {
44+
Type *ElTy = Ty;
45+
// For now, only allow widening non-packed literal structs where all
46+
// element types are the same. This simplifies the cost model and
47+
// conversion between scalar and wide types.
48+
if (auto *StructTy = dyn_cast<StructType>(Ty);
49+
StructTy && !StructTy->isPacked() && StructTy->isLiteral() &&
50+
StructTy->containsHomogeneousTypes()) {
51+
ElTy = StructTy->elements().front();
52+
}
53+
return VectorType::isValidElementType(ElTy);
54+
}
55+
4256
/// Return true if all of the intrinsic's arguments and return type are scalars
4357
/// for the scalar form of the intrinsic, and vectors for the vector form of the
4458
/// intrinsic (except operands that are marked as always being scalar by

llvm/lib/IR/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ add_llvm_component_library(LLVMCore
7373
Value.cpp
7474
ValueSymbolTable.cpp
7575
VectorBuilder.cpp
76+
VectorUtils.cpp
7677
Verifier.cpp
7778
VFABIDemangler.cpp
7879
RuntimeLibcalls.cpp

llvm/lib/IR/VFABIDemangler.cpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "llvm/ADT/SmallString.h"
1212
#include "llvm/ADT/StringSwitch.h"
1313
#include "llvm/IR/Module.h"
14+
#include "llvm/IR/VectorUtils.h"
1415
#include "llvm/Support/Debug.h"
1516
#include "llvm/Support/raw_ostream.h"
1617
#include <limits>
@@ -346,12 +347,15 @@ getScalableECFromSignature(const FunctionType *Signature, const VFISAKind ISA,
346347
// Also check the return type if not void.
347348
Type *RetTy = Signature->getReturnType();
348349
if (!RetTy->isVoidTy()) {
349-
std::optional<ElementCount> ReturnEC = getElementCountForTy(ISA, RetTy);
350-
// If we have an unknown scalar element type we can't find a reasonable VF.
351-
if (!ReturnEC)
352-
return std::nullopt;
353-
if (ElementCount::isKnownLT(*ReturnEC, MinEC))
354-
MinEC = *ReturnEC;
350+
for (Type *RetTy : getContainedTypes(RetTy)) {
351+
std::optional<ElementCount> ReturnEC = getElementCountForTy(ISA, RetTy);
352+
// If we have an unknown scalar element type we can't find a reasonable
353+
// VF.
354+
if (!ReturnEC)
355+
return std::nullopt;
356+
if (ElementCount::isKnownLT(*ReturnEC, MinEC))
357+
MinEC = *ReturnEC;
358+
}
355359
}
356360

357361
// The SVE Vector function call ABI bases the VF on the widest element types
@@ -566,7 +570,7 @@ FunctionType *VFABI::createFunctionType(const VFInfo &Info,
566570

567571
auto *RetTy = ScalarFTy->getReturnType();
568572
if (!RetTy->isVoidTy())
569-
RetTy = VectorType::get(RetTy, VF);
573+
RetTy = ToWideTy(RetTy, VF);
570574
return FunctionType::get(RetTy, VecTypes, false);
571575
}
572576

llvm/lib/IR/VectorUtils.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//===----------- VectorUtils.cpp - Vector type utility functions ----------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "llvm/IR/VectorUtils.h"
10+
#include "llvm/ADT/SmallVectorExtras.h"
11+
12+
using namespace llvm;
13+
14+
/// A helper for converting to wider (vector) types. For scalar types, this is
15+
/// equivalent to calling `ToVectorTy`. For struct types, this returns a new
16+
/// struct where each element type has been widened to a vector type. Note: Only
17+
/// unpacked literal struct types are supported.
18+
Type *llvm::ToWideTy(Type *Ty, ElementCount EC) {
19+
if (EC.isScalar())
20+
return Ty;
21+
auto *StructTy = dyn_cast<StructType>(Ty);
22+
if (!StructTy)
23+
return ToVectorTy(Ty, EC);
24+
assert(StructTy->isLiteral() && !StructTy->isPacked() &&
25+
"expected unpacked struct literal");
26+
return StructType::get(
27+
Ty->getContext(),
28+
map_to_vector(StructTy->elements(), [&](Type *ElTy) -> Type * {
29+
return VectorType::get(ElTy, EC);
30+
}));
31+
}
32+
33+
/// A helper for converting wide types to narrow (non-vector) types. For vector
34+
/// types, this is equivalent to calling .getScalarType(). For struct types,
35+
/// this returns a new struct where each element type has been converted to a
36+
/// scalar type. Note: Only unpacked literal struct types are supported.
37+
Type *llvm::ToNarrowTy(Type *Ty) {
38+
auto *StructTy = dyn_cast<StructType>(Ty);
39+
if (!StructTy)
40+
return Ty->getScalarType();
41+
assert(StructTy->isLiteral() && !StructTy->isPacked() &&
42+
"expected unpacked struct literal");
43+
return StructType::get(
44+
Ty->getContext(),
45+
map_to_vector(StructTy->elements(), [](Type *ElTy) -> Type * {
46+
return ElTy->getScalarType();
47+
}));
48+
}
49+
50+
/// Returns the types contained in `Ty`. For struct types, it returns the
51+
/// elements, all other types are returned directly.
52+
SmallVector<Type *, 2> llvm::getContainedTypes(Type *Ty) {
53+
auto *StructTy = dyn_cast<StructType>(Ty);
54+
if (StructTy)
55+
return to_vector<2>(StructTy->elements());
56+
return {Ty};
57+
}
58+
59+
/// Returns true if `Ty` is a vector type or a struct of vector types where all
60+
/// vector types share the same VF.
61+
bool llvm::isWideTy(Type *Ty) {
62+
auto ContainedTys = getContainedTypes(Ty);
63+
if (ContainedTys.empty() || !ContainedTys.front()->isVectorTy())
64+
return false;
65+
ElementCount VF = cast<VectorType>(ContainedTys.front())->getElementCount();
66+
return all_of(ContainedTys, [&](Type *Ty) {
67+
return Ty->isVectorTy() && cast<VectorType>(Ty)->getElementCount() == VF;
68+
});
69+
}

llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -949,8 +949,8 @@ bool LoopVectorizationLegality::canVectorizeInstrs() {
949949
// Check that the instruction return type is vectorizable.
950950
// We can't vectorize casts from vector type to scalar type.
951951
// Also, we can't vectorize extractelement instructions.
952-
if ((!VectorType::isValidElementType(I.getType()) &&
953-
!I.getType()->isVoidTy()) ||
952+
Type *InstTy = I.getType();
953+
if (!(InstTy->isVoidTy() || canWidenType(InstTy)) ||
954954
(isa<CastInst>(I) &&
955955
!VectorType::isValidElementType(I.getOperand(0)->getType())) ||
956956
isa<ExtractElementInst>(I)) {

0 commit comments

Comments
 (0)