CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
623 lines
25 KiB
C++
623 lines
25 KiB
C++
//===-- SPIRVLegalizePointerCast.cpp ----------------------*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// The LLVM IR has multiple legal patterns we cannot lower to Logical SPIR-V.
|
|
// This pass modifies such loads to have an IR we can directly lower to valid
|
|
// logical SPIR-V.
|
|
// OpenCL can avoid this because they rely on ptrcast, which is not supported
|
|
// by logical SPIR-V.
|
|
//
|
|
// This pass relies on the assign_ptr_type intrinsic to deduce the type of the
|
|
// pointed values, must replace all occurences of `ptrcast`. This is why
|
|
// unhandled cases are reported as unreachable: we MUST cover all cases.
|
|
//
|
|
// 1. Loading the first element of an array
|
|
//
|
|
// %array = [10 x i32]
|
|
// %value = load i32, ptr %array
|
|
//
|
|
// LLVM can skip the GEP instruction, and only request loading the first 4
|
|
// bytes. In logical SPIR-V, we need an OpAccessChain to access the first
|
|
// element. This pass will add a getelementptr instruction before the load.
|
|
//
|
|
//
|
|
// 2. Implicit downcast from load
|
|
//
|
|
// %1 = getelementptr <4 x i32>, ptr %vec4, i64 0
|
|
// %2 = load <3 x i32>, ptr %1
|
|
//
|
|
// The pointer in the GEP instruction is only used for offset computations,
|
|
// but it doesn't NEED to match the pointed type. OpAccessChain however
|
|
// requires this. Also, LLVM loads define the bitwidth of the load, not the
|
|
// pointer. In this example, we can guess %vec4 is a vec4 thanks to the GEP
|
|
// instruction basetype, but we only want to load the first 3 elements, hence
|
|
// do a partial load. In logical SPIR-V, this is not legal. What we must do
|
|
// is load the full vector (basetype), extract 3 elements, and recombine them
|
|
// to form a 3-element vector.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "SPIRVLegalizePointerCast.h"
|
|
#include "SPIRV.h"
|
|
#include "SPIRVSubtarget.h"
|
|
#include "SPIRVTargetMachine.h"
|
|
#include "SPIRVUtils.h"
|
|
#include "llvm/IR/IRBuilder.h"
|
|
#include "llvm/IR/IntrinsicInst.h"
|
|
#include "llvm/IR/Intrinsics.h"
|
|
#include "llvm/IR/IntrinsicsSPIRV.h"
|
|
#include "llvm/Transforms/Utils/Cloning.h"
|
|
#include "llvm/Transforms/Utils/LowerMemIntrinsics.h"
|
|
|
|
using namespace llvm;
|
|
|
|
namespace {
|
|
class SPIRVLegalizePointerCastImpl {
|
|
|
|
// Builds the `spv_assign_type` assigning |Ty| to |Value| at the current
|
|
// builder position.
|
|
void buildAssignType(IRBuilder<> &B, Type *Ty, Value *Arg) {
|
|
Value *OfType = PoisonValue::get(Ty);
|
|
CallInst *AssignCI = buildIntrWithMD(Intrinsic::spv_assign_type,
|
|
{Arg->getType()}, OfType, Arg, {}, B);
|
|
GR->addAssignPtrTypeInstr(Arg, AssignCI);
|
|
}
|
|
|
|
static FixedVectorType *makeVectorFromTotalBits(Type *ElemTy,
|
|
TypeSize TotalBits) {
|
|
unsigned ElemBits = ElemTy->getScalarSizeInBits();
|
|
assert(ElemBits && TotalBits % ElemBits == 0 &&
|
|
"TotalBits must be divisible by element bit size");
|
|
return FixedVectorType::get(ElemTy, TotalBits / ElemBits);
|
|
}
|
|
|
|
Value *resizeVectorBitsWithShuffle(IRBuilder<> &B, Value *V,
|
|
FixedVectorType *DstTy) {
|
|
auto *SrcTy = cast<FixedVectorType>(V->getType());
|
|
assert(SrcTy->getElementType() == DstTy->getElementType() &&
|
|
"shuffle resize expects identical element types");
|
|
|
|
const unsigned NumNeeded = DstTy->getNumElements();
|
|
const unsigned NumSource = SrcTy->getNumElements();
|
|
|
|
SmallVector<int> Mask(NumNeeded);
|
|
for (unsigned I = 0; I < NumNeeded; ++I)
|
|
Mask[I] = (I < NumSource) ? static_cast<int>(I) : -1;
|
|
|
|
Value *Resized = B.CreateShuffleVector(V, V, Mask);
|
|
buildAssignType(B, DstTy, Resized);
|
|
return Resized;
|
|
}
|
|
|
|
// Loads parts of the vector of type |SourceType| from the pointer |Source|
|
|
// and create a new vector of type |TargetType|. |TargetType| must be a vector
|
|
// type.
|
|
// Returns the loaded value.
|
|
Value *loadVectorFromVector(IRBuilder<> &B, FixedVectorType *SourceType,
|
|
FixedVectorType *TargetType, Value *Source) {
|
|
LoadInst *NewLoad = B.CreateLoad(SourceType, Source);
|
|
buildAssignType(B, SourceType, NewLoad);
|
|
Value *AssignValue = NewLoad;
|
|
if (TargetType->getElementType() != SourceType->getElementType()) {
|
|
const DataLayout &DL = B.GetInsertBlock()->getModule()->getDataLayout();
|
|
TypeSize TargetTypeSize = DL.getTypeSizeInBits(TargetType);
|
|
TypeSize SourceTypeSize = DL.getTypeSizeInBits(SourceType);
|
|
|
|
Value *BitcastSrcVal = NewLoad;
|
|
FixedVectorType *BitcastSrcTy =
|
|
cast<FixedVectorType>(BitcastSrcVal->getType());
|
|
FixedVectorType *BitcastDstTy = TargetType;
|
|
|
|
if (TargetTypeSize != SourceTypeSize) {
|
|
unsigned TargetElemBits =
|
|
TargetType->getElementType()->getScalarSizeInBits();
|
|
if (SourceTypeSize % TargetElemBits == 0) {
|
|
// No Resize needed. Same total bits as source, but use target element
|
|
// type.
|
|
BitcastDstTy = makeVectorFromTotalBits(TargetType->getElementType(),
|
|
SourceTypeSize);
|
|
} else {
|
|
// Resize source to target total bitwidth using source element type.
|
|
BitcastSrcTy = makeVectorFromTotalBits(SourceType->getElementType(),
|
|
TargetTypeSize);
|
|
BitcastSrcVal = resizeVectorBitsWithShuffle(B, NewLoad, BitcastSrcTy);
|
|
}
|
|
}
|
|
AssignValue =
|
|
B.CreateIntrinsic(Intrinsic::spv_bitcast,
|
|
{BitcastDstTy, BitcastSrcTy}, {BitcastSrcVal});
|
|
buildAssignType(B, BitcastDstTy, AssignValue);
|
|
if (BitcastDstTy == TargetType)
|
|
return AssignValue;
|
|
}
|
|
|
|
assert(TargetType->getNumElements() < SourceType->getNumElements());
|
|
SmallVector<int> Mask(/* Size= */ TargetType->getNumElements());
|
|
for (unsigned I = 0; I < TargetType->getNumElements(); ++I)
|
|
Mask[I] = I;
|
|
Value *Output = B.CreateShuffleVector(AssignValue, AssignValue, Mask);
|
|
buildAssignType(B, TargetType, Output);
|
|
return Output;
|
|
}
|
|
|
|
// Returns true if |FromTy| has a memory layout compatible with loading or
|
|
// storing |ToTy|.
|
|
bool isCompatibleMemoryLayout(Type *ToTy, Type *FromTy) {
|
|
if (ToTy == FromTy)
|
|
return true;
|
|
auto *SVT = dyn_cast<FixedVectorType>(FromTy);
|
|
auto *DVT = dyn_cast<FixedVectorType>(ToTy);
|
|
if (SVT && DVT)
|
|
return true;
|
|
auto *SAT = dyn_cast<ArrayType>(FromTy);
|
|
if (SAT && DVT) {
|
|
if (SAT->getElementType() == DVT->getElementType())
|
|
return true;
|
|
if (auto *MAT = dyn_cast<FixedVectorType>(SAT->getElementType()))
|
|
if (MAT->getElementType() == DVT->getElementType())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Traverses the aggregate type to find the first sub-type that matches
|
|
// the TargetElemType's memory layout, optionally emitting a GEP intrinsic.
|
|
std::optional<std::pair<Value *, Type *>>
|
|
getPointerToFirstCompatibleType(IRBuilder<> &B, Value *BasePtr,
|
|
Type *PointerType, Type *TargetElemType,
|
|
bool IsInBounds) {
|
|
Type *CurrentTy = GR->findDeducedElementType(BasePtr);
|
|
assert(CurrentTy && "Could not deduce aggregate type");
|
|
SmallVector<Value *, 8> Args{/* isInBounds= */ B.getInt1(IsInBounds),
|
|
BasePtr};
|
|
Args.push_back(B.getInt32(0)); // Pointer offset
|
|
|
|
while (!isCompatibleMemoryLayout(TargetElemType, CurrentTy)) {
|
|
if (auto *ST = dyn_cast<StructType>(CurrentTy)) {
|
|
if (ST->getNumElements() == 0)
|
|
return std::nullopt;
|
|
CurrentTy = ST->getTypeAtIndex(0u);
|
|
} else if (auto *AT = dyn_cast<ArrayType>(CurrentTy)) {
|
|
CurrentTy = AT->getElementType();
|
|
} else if (auto *VT = dyn_cast<FixedVectorType>(CurrentTy)) {
|
|
CurrentTy = VT->getElementType();
|
|
} else {
|
|
return std::nullopt;
|
|
}
|
|
Args.push_back(B.getInt32(0));
|
|
}
|
|
|
|
Value *GEP = BasePtr;
|
|
if (Args.size() > 3) {
|
|
std::array<Type *, 2> Types = {PointerType, BasePtr->getType()};
|
|
GEP = B.CreateIntrinsic(Intrinsic::spv_gep, {Types}, {Args});
|
|
GR->buildAssignPtr(B, CurrentTy, GEP);
|
|
}
|
|
|
|
return std::make_pair(GEP, CurrentTy);
|
|
}
|
|
|
|
// Builds a legalized load from a pointer, drilling down through
|
|
// memory layouts to find a compatible type. Load flags will be
|
|
// copied from |BadLoad|, which should be the load being legalized.
|
|
Value *buildLegalizedLoad(IRBuilder<> &B, Type *ElementType, Value *Source,
|
|
LoadInst *BadLoad) {
|
|
auto ResultOpt = getPointerToFirstCompatibleType(
|
|
B, Source, BadLoad->getPointerOperandType(), ElementType, false);
|
|
assert(ResultOpt && "Failed to load from aggregate: "
|
|
"Could not find compatible memory layout.");
|
|
auto [GEP, CurrentTy] = *ResultOpt;
|
|
|
|
auto *SAT = dyn_cast<ArrayType>(CurrentTy);
|
|
auto *SVT = dyn_cast<FixedVectorType>(CurrentTy);
|
|
auto *DVT = dyn_cast<FixedVectorType>(ElementType);
|
|
auto *MAT =
|
|
SAT ? dyn_cast<FixedVectorType>(SAT->getElementType()) : nullptr;
|
|
|
|
if (ElementType == CurrentTy) {
|
|
LoadInst *LI = B.CreateLoad(ElementType, GEP);
|
|
LI->setAlignment(BadLoad->getAlign());
|
|
buildAssignType(B, ElementType, LI);
|
|
return LI;
|
|
}
|
|
if (SVT && DVT)
|
|
return loadVectorFromVector(B, SVT, DVT, GEP);
|
|
if (SAT && DVT && SAT->getElementType() == DVT->getElementType())
|
|
return loadVectorFromArray(B, DVT, GEP);
|
|
if (MAT && DVT && MAT->getElementType() == DVT->getElementType())
|
|
return loadVectorFromMatrixArray(B, DVT, GEP, MAT);
|
|
|
|
llvm_unreachable("Failed to load from aggregate.");
|
|
}
|
|
Value *
|
|
buildVectorFromLoadedElements(IRBuilder<> &B, FixedVectorType *TargetType,
|
|
SmallVector<Value *, 4> &LoadedElements) {
|
|
// <1 x T> shares the SPIR-V type with T, so emitting OpCompositeInsert on
|
|
// a scalar would be invalid. Bridge with spv_bitcast instead.
|
|
if (TargetType->getNumElements() == 1) {
|
|
Value *Scalar = LoadedElements[0];
|
|
Value *NewVector = B.CreateIntrinsic(
|
|
Intrinsic::spv_bitcast, {TargetType, Scalar->getType()}, {Scalar});
|
|
buildAssignType(B, TargetType, NewVector);
|
|
return NewVector;
|
|
}
|
|
|
|
// Build the vector from the loaded elements.
|
|
Value *NewVector = PoisonValue::get(TargetType);
|
|
buildAssignType(B, TargetType, NewVector);
|
|
|
|
for (unsigned I = 0, E = TargetType->getNumElements(); I < E; ++I) {
|
|
Value *Index = B.getInt32(I);
|
|
SmallVector<Type *, 4> Types = {TargetType, TargetType,
|
|
TargetType->getElementType(),
|
|
Index->getType()};
|
|
SmallVector<Value *> Args = {NewVector, LoadedElements[I], Index};
|
|
NewVector = B.CreateIntrinsic(Intrinsic::spv_insertelt, {Types}, {Args});
|
|
buildAssignType(B, TargetType, NewVector);
|
|
}
|
|
return NewVector;
|
|
}
|
|
|
|
// Loads elements from a matrix with an array of vector memory layout and
|
|
// constructs a vector.
|
|
Value *loadVectorFromMatrixArray(IRBuilder<> &B, FixedVectorType *TargetType,
|
|
Value *Source,
|
|
FixedVectorType *ArrElemVecTy) {
|
|
Type *TargetElemTy = TargetType->getElementType();
|
|
unsigned ScalarsPerArrayElement = ArrElemVecTy->getNumElements();
|
|
// Load each element of the array.
|
|
SmallVector<Value *, 4> LoadedElements;
|
|
std::array<Type *, 2> Types = {Source->getType(), Source->getType()};
|
|
for (unsigned I = 0, E = TargetType->getNumElements(); I < E; ++I) {
|
|
unsigned ArrayIndex = I / ScalarsPerArrayElement;
|
|
unsigned ElementIndexInArrayElem = I % ScalarsPerArrayElement;
|
|
// Create a GEP to access the i-th element of the array.
|
|
std::array<Value *, 4> Args = {
|
|
B.getInt1(/*Inbounds=*/false), Source, B.getInt32(0),
|
|
ConstantInt::get(B.getInt32Ty(), ArrayIndex)};
|
|
auto *ElementPtr = B.CreateIntrinsic(Intrinsic::spv_gep, {Types}, {Args});
|
|
GR->buildAssignPtr(B, ArrElemVecTy, ElementPtr);
|
|
Value *LoadVec = B.CreateLoad(ArrElemVecTy, ElementPtr);
|
|
buildAssignType(B, ArrElemVecTy, LoadVec);
|
|
LoadedElements.push_back(makeExtractElement(B, TargetElemTy, LoadVec,
|
|
ElementIndexInArrayElem));
|
|
}
|
|
return buildVectorFromLoadedElements(B, TargetType, LoadedElements);
|
|
}
|
|
// Loads elements from an array and constructs a vector.
|
|
Value *loadVectorFromArray(IRBuilder<> &B, FixedVectorType *TargetType,
|
|
Value *Source) {
|
|
// Load each element of the array.
|
|
SmallVector<Value *, 4> LoadedElements;
|
|
std::array<Type *, 2> Types = {Source->getType(), Source->getType()};
|
|
for (unsigned I = 0, E = TargetType->getNumElements(); I < E; ++I) {
|
|
// Create a GEP to access the i-th element of the array.
|
|
std::array<Value *, 4> Args = {B.getInt1(/*Inbounds=*/false), Source,
|
|
B.getInt32(0),
|
|
ConstantInt::get(B.getInt32Ty(), I)};
|
|
auto *ElementPtr = B.CreateIntrinsic(Intrinsic::spv_gep, {Types}, {Args});
|
|
GR->buildAssignPtr(B, TargetType->getElementType(), ElementPtr);
|
|
|
|
// Load the value from the element pointer.
|
|
Value *Load = B.CreateLoad(TargetType->getElementType(), ElementPtr);
|
|
buildAssignType(B, TargetType->getElementType(), Load);
|
|
LoadedElements.push_back(Load);
|
|
}
|
|
return buildVectorFromLoadedElements(B, TargetType, LoadedElements);
|
|
}
|
|
|
|
// Stores elements from a vector into a matrix (an array of vectors).
|
|
void storeMatrixArrayFromVector(IRBuilder<> &B, Value *SrcVector,
|
|
Value *DstArrayPtr, ArrayType *ArrTy,
|
|
Align Alignment) {
|
|
auto *SrcVecTy = cast<FixedVectorType>(SrcVector->getType());
|
|
auto *ArrElemVecTy = cast<FixedVectorType>(ArrTy->getElementType());
|
|
Type *ElemTy = ArrElemVecTy->getElementType();
|
|
unsigned ScalarsPerArrayElement = ArrElemVecTy->getNumElements();
|
|
unsigned SrcNumElements = SrcVecTy->getNumElements();
|
|
assert(
|
|
SrcNumElements % ScalarsPerArrayElement == 0 &&
|
|
"Source vector size must be a multiple of array element vector size");
|
|
|
|
std::array<Type *, 2> Types = {DstArrayPtr->getType(),
|
|
DstArrayPtr->getType()};
|
|
|
|
for (unsigned I = 0; I < SrcNumElements; I += ScalarsPerArrayElement) {
|
|
unsigned ArrayIndex = I / ScalarsPerArrayElement;
|
|
// Create a GEP to access the array element.
|
|
std::array<Value *, 4> Args = {
|
|
B.getInt1(/*Inbounds=*/false), DstArrayPtr, B.getInt32(0),
|
|
ConstantInt::get(B.getInt32Ty(), ArrayIndex)};
|
|
auto *ElementPtr = B.CreateIntrinsic(Intrinsic::spv_gep, {Types}, {Args});
|
|
GR->buildAssignPtr(B, ArrElemVecTy, ElementPtr);
|
|
|
|
// Extract scalar elements from the source vector for this array slot.
|
|
SmallVector<Value *, 4> Elements;
|
|
for (unsigned J = 0; J < ScalarsPerArrayElement; ++J)
|
|
Elements.push_back(makeExtractElement(B, ElemTy, SrcVector, I + J));
|
|
|
|
// Build a vector from the extracted elements and store it.
|
|
Value *Vec = buildVectorFromLoadedElements(B, ArrElemVecTy, Elements);
|
|
StoreInst *SI = B.CreateStore(Vec, ElementPtr);
|
|
SI->setAlignment(Alignment);
|
|
}
|
|
}
|
|
|
|
// Stores elements from a vector into an array.
|
|
void storeArrayFromVector(IRBuilder<> &B, Value *SrcVector,
|
|
Value *DstArrayPtr, ArrayType *ArrTy,
|
|
Align Alignment) {
|
|
auto *VecTy = cast<FixedVectorType>(SrcVector->getType());
|
|
Type *ElemTy = ArrTy->getElementType();
|
|
|
|
// Ensure the element types of the array and vector are the same.
|
|
assert(VecTy->getElementType() == ElemTy &&
|
|
"Element types of array and vector must be the same.");
|
|
std::array<Type *, 2> Types = {DstArrayPtr->getType(),
|
|
DstArrayPtr->getType()};
|
|
|
|
for (unsigned I = 0, E = VecTy->getNumElements(); I < E; ++I) {
|
|
// Create a GEP to access the i-th element of the array.
|
|
std::array<Value *, 4> Args = {B.getInt1(/*Inbounds=*/false), DstArrayPtr,
|
|
B.getInt32(0),
|
|
ConstantInt::get(B.getInt32Ty(), I)};
|
|
auto *ElementPtr = B.CreateIntrinsic(Intrinsic::spv_gep, {Types}, {Args});
|
|
GR->buildAssignPtr(B, ElemTy, ElementPtr);
|
|
|
|
// Extract the element from the vector and store it.
|
|
Value *Element =
|
|
E == 1 ? SrcVector : makeExtractElement(B, ElemTy, SrcVector, I);
|
|
StoreInst *SI = B.CreateStore(Element, ElementPtr);
|
|
SI->setAlignment(Alignment);
|
|
}
|
|
}
|
|
|
|
// Replaces the load instruction to get rid of the ptrcast used as source
|
|
// operand.
|
|
void transformLoad(IRBuilder<> &B, LoadInst *LI, Value *CastedOperand,
|
|
Value *OriginalOperand) {
|
|
Type *ToTy = GR->findDeducedElementType(CastedOperand);
|
|
B.SetInsertPoint(LI);
|
|
|
|
Value *Output = buildLegalizedLoad(B, ToTy, OriginalOperand, LI);
|
|
|
|
GR->replaceAllUsesWith(LI, Output, /* DeleteOld= */ true);
|
|
DeadInstructions.push_back(LI);
|
|
}
|
|
|
|
// Creates an spv_insertelt instruction (equivalent to llvm's insertelement).
|
|
Value *makeInsertElement(IRBuilder<> &B, Value *Vector, Value *Element,
|
|
unsigned Index) {
|
|
Type *Int32Ty = Type::getInt32Ty(B.getContext());
|
|
SmallVector<Type *, 4> Types = {Vector->getType(), Vector->getType(),
|
|
Element->getType(), Int32Ty};
|
|
SmallVector<Value *> Args = {Vector, Element, B.getInt32(Index)};
|
|
Instruction *NewI =
|
|
B.CreateIntrinsic(Intrinsic::spv_insertelt, {Types}, {Args});
|
|
buildAssignType(B, Vector->getType(), NewI);
|
|
return NewI;
|
|
}
|
|
|
|
// Creates an spv_extractelt instruction (equivalent to llvm's
|
|
// extractelement).
|
|
Value *makeExtractElement(IRBuilder<> &B, Type *ElementType, Value *Vector,
|
|
unsigned Index) {
|
|
Type *Int32Ty = Type::getInt32Ty(B.getContext());
|
|
SmallVector<Type *, 3> Types = {ElementType, Vector->getType(), Int32Ty};
|
|
SmallVector<Value *> Args = {Vector, B.getInt32(Index)};
|
|
Instruction *NewI =
|
|
B.CreateIntrinsic(Intrinsic::spv_extractelt, {Types}, {Args});
|
|
buildAssignType(B, ElementType, NewI);
|
|
return NewI;
|
|
}
|
|
|
|
// Stores the given Src vector operand into the Dst vector, adjusting the size
|
|
// if required.
|
|
Value *storeVectorFromVector(IRBuilder<> &B, Value *Src, Value *Dst,
|
|
Align Alignment) {
|
|
FixedVectorType *SrcType = cast<FixedVectorType>(Src->getType());
|
|
FixedVectorType *DstType =
|
|
cast<FixedVectorType>(GR->findDeducedElementType(Dst));
|
|
auto dstNumElements = DstType->getNumElements();
|
|
auto srcNumElements = SrcType->getNumElements();
|
|
|
|
// if the element type differs, it is a bitcast.
|
|
if (DstType->getElementType() != SrcType->getElementType()) {
|
|
// Support bitcast between vectors of different sizes only if
|
|
// the total bitwidth is the same.
|
|
[[maybe_unused]] auto dstBitWidth =
|
|
DstType->getElementType()->getScalarSizeInBits() * dstNumElements;
|
|
[[maybe_unused]] auto srcBitWidth =
|
|
SrcType->getElementType()->getScalarSizeInBits() * srcNumElements;
|
|
assert(dstBitWidth == srcBitWidth &&
|
|
"Unsupported bitcast between vectors of different sizes.");
|
|
|
|
Src =
|
|
B.CreateIntrinsic(Intrinsic::spv_bitcast, {DstType, SrcType}, {Src});
|
|
buildAssignType(B, DstType, Src);
|
|
SrcType = DstType;
|
|
|
|
StoreInst *SI = B.CreateStore(Src, Dst);
|
|
SI->setAlignment(Alignment);
|
|
return SI;
|
|
}
|
|
|
|
assert(DstType->getNumElements() >= SrcType->getNumElements());
|
|
LoadInst *LI = B.CreateLoad(DstType, Dst);
|
|
LI->setAlignment(Alignment);
|
|
Value *OldValues = LI;
|
|
buildAssignType(B, OldValues->getType(), OldValues);
|
|
Value *NewValues = Src;
|
|
|
|
for (unsigned I = 0; I < SrcType->getNumElements(); ++I) {
|
|
Value *Element =
|
|
makeExtractElement(B, SrcType->getElementType(), NewValues, I);
|
|
OldValues = makeInsertElement(B, OldValues, Element, I);
|
|
}
|
|
|
|
StoreInst *SI = B.CreateStore(OldValues, Dst);
|
|
SI->setAlignment(Alignment);
|
|
return SI;
|
|
}
|
|
|
|
// Builds a legalized store to a pointer, drilling down through
|
|
// memory layouts to find a compatible type.
|
|
void buildLegalizedStore(IRBuilder<> &B, Value *Src, Value *Dst,
|
|
Align Alignment) {
|
|
auto ResultOpt = getPointerToFirstCompatibleType(B, Dst, Dst->getType(),
|
|
Src->getType(), true);
|
|
assert(ResultOpt && "Failed to store to aggregate: "
|
|
"Could not find compatible memory layout.");
|
|
auto [GEP, CurrentTy] = *ResultOpt;
|
|
|
|
auto *DAT = dyn_cast<ArrayType>(CurrentTy);
|
|
auto *DVT = dyn_cast<FixedVectorType>(CurrentTy);
|
|
auto *SVT = dyn_cast<FixedVectorType>(Src->getType());
|
|
auto *DMAT =
|
|
DAT ? dyn_cast<FixedVectorType>(DAT->getElementType()) : nullptr;
|
|
|
|
if (Src->getType() == CurrentTy) {
|
|
StoreInst *SI = B.CreateStore(Src, GEP);
|
|
SI->setAlignment(Alignment);
|
|
return;
|
|
}
|
|
if (DVT && SVT) {
|
|
storeVectorFromVector(B, Src, GEP, Alignment);
|
|
return;
|
|
}
|
|
if (DAT && SVT && SVT->getElementType() == DAT->getElementType()) {
|
|
storeArrayFromVector(B, Src, GEP, DAT, Alignment);
|
|
return;
|
|
}
|
|
if (DMAT && SVT && DMAT->getElementType() == SVT->getElementType()) {
|
|
storeMatrixArrayFromVector(B, Src, GEP, DAT, Alignment);
|
|
return;
|
|
}
|
|
|
|
llvm_unreachable("Failed to store to aggregate.");
|
|
}
|
|
|
|
// Transforms a store instruction (or SPV intrinsic) using a ptrcast as
|
|
// operand into a valid logical SPIR-V store with no ptrcast.
|
|
void transformStore(IRBuilder<> &B, Instruction *BadStore, Value *Src,
|
|
Value *Dst, Align Alignment) {
|
|
B.SetInsertPoint(BadStore);
|
|
buildLegalizedStore(B, Src, Dst, Alignment);
|
|
DeadInstructions.push_back(BadStore);
|
|
}
|
|
|
|
void legalizePointerCast(IntrinsicInst *II) {
|
|
Value *CastedOperand = II;
|
|
Value *OriginalOperand = II->getOperand(0);
|
|
|
|
IRBuilder<> B(II->getContext());
|
|
std::vector<Value *> Users;
|
|
for (Use &U : II->uses())
|
|
Users.push_back(U.getUser());
|
|
|
|
for (Value *User : Users) {
|
|
if (LoadInst *LI = dyn_cast<LoadInst>(User)) {
|
|
transformLoad(B, LI, CastedOperand, OriginalOperand);
|
|
continue;
|
|
}
|
|
|
|
if (StoreInst *SI = dyn_cast<StoreInst>(User)) {
|
|
transformStore(B, SI, SI->getValueOperand(), OriginalOperand,
|
|
SI->getAlign());
|
|
continue;
|
|
}
|
|
|
|
if (IntrinsicInst *Intrin = dyn_cast<IntrinsicInst>(User)) {
|
|
if (Intrin->getIntrinsicID() == Intrinsic::spv_assign_ptr_type) {
|
|
DeadInstructions.push_back(Intrin);
|
|
continue;
|
|
}
|
|
|
|
if (Intrin->getIntrinsicID() == Intrinsic::spv_gep) {
|
|
GR->replaceAllUsesWith(CastedOperand, OriginalOperand,
|
|
/* DeleteOld= */ false);
|
|
continue;
|
|
}
|
|
|
|
if (Intrin->getIntrinsicID() == Intrinsic::spv_store) {
|
|
Align Alignment;
|
|
if (ConstantInt *C = dyn_cast<ConstantInt>(Intrin->getOperand(3)))
|
|
Alignment = Align(C->getZExtValue());
|
|
transformStore(B, Intrin, Intrin->getArgOperand(0), OriginalOperand,
|
|
Alignment);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
llvm_unreachable("Unsupported ptrcast user. Please fix.");
|
|
}
|
|
|
|
DeadInstructions.push_back(II);
|
|
}
|
|
|
|
public:
|
|
SPIRVLegalizePointerCastImpl(const SPIRVTargetMachine &TM) : TM(TM) {}
|
|
|
|
bool run(Function &F) {
|
|
const SPIRVSubtarget &ST = TM.getSubtarget<SPIRVSubtarget>(F);
|
|
GR = ST.getSPIRVGlobalRegistry();
|
|
DeadInstructions.clear();
|
|
|
|
std::vector<IntrinsicInst *> WorkList;
|
|
for (auto &BB : F) {
|
|
for (auto &I : BB) {
|
|
auto *II = dyn_cast<IntrinsicInst>(&I);
|
|
if (II && II->getIntrinsicID() == Intrinsic::spv_ptrcast)
|
|
WorkList.push_back(II);
|
|
}
|
|
}
|
|
|
|
for (IntrinsicInst *II : WorkList)
|
|
legalizePointerCast(II);
|
|
|
|
for (Instruction *I : DeadInstructions)
|
|
I->eraseFromParent();
|
|
|
|
return DeadInstructions.size() != 0;
|
|
}
|
|
|
|
private:
|
|
const SPIRVTargetMachine &TM;
|
|
SPIRVGlobalRegistry *GR = nullptr;
|
|
std::vector<Instruction *> DeadInstructions;
|
|
};
|
|
|
|
class SPIRVLegalizePointerCastLegacy : public FunctionPass {
|
|
public:
|
|
static char ID;
|
|
SPIRVLegalizePointerCastLegacy(const SPIRVTargetMachine &TM)
|
|
: FunctionPass(ID), TM(TM) {}
|
|
|
|
bool runOnFunction(Function &F) override {
|
|
return SPIRVLegalizePointerCastImpl(TM).run(F);
|
|
}
|
|
|
|
private:
|
|
const SPIRVTargetMachine &TM;
|
|
};
|
|
} // namespace
|
|
|
|
PreservedAnalyses SPIRVLegalizePointerCast::run(Function &F,
|
|
FunctionAnalysisManager &AM) {
|
|
return SPIRVLegalizePointerCastImpl(TM).run(F) ? PreservedAnalyses::none()
|
|
: PreservedAnalyses::all();
|
|
}
|
|
|
|
char SPIRVLegalizePointerCastLegacy::ID = 0;
|
|
INITIALIZE_PASS(SPIRVLegalizePointerCastLegacy, "spirv-legalize-pointer-cast",
|
|
"SPIRV legalize pointer cast pass", false, false)
|
|
|
|
FunctionPass *llvm::createSPIRVLegalizePointerCastPass(SPIRVTargetMachine *TM) {
|
|
return new SPIRVLegalizePointerCastLegacy(*TM);
|
|
}
|