| // Copyright 2020-2021 The Clspv Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "llvm/ADT/ArrayRef.h" |
| #include "llvm/ADT/DenseMap.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/IR/IRBuilder.h" |
| #include "llvm/IR/InstIterator.h" |
| #include "llvm/IR/InstVisitor.h" |
| #include "llvm/IR/Instructions.h" |
| #include "llvm/IR/Intrinsics.h" |
| #include "llvm/IR/Module.h" |
| #include "llvm/IR/ValueHandle.h" |
| #include "llvm/Pass.h" |
| #include "llvm/Support/Debug.h" |
| #include "llvm/Transforms/Utils/Cloning.h" |
| #include "llvm/Transforms/Utils/Local.h" |
| |
| #include "BuiltinsEnum.h" |
| #include "Constants.h" |
| #include "clspv/Passes.h" |
| |
| #include "BitcastUtils.h" |
| #include "Builtins.h" |
| #include "LongVectorLoweringPass.h" |
| |
| #include <array> |
| #include <functional> |
| #include <map> |
| |
| using namespace llvm; |
| |
| #define DEBUG_TYPE "LongVectorLowering" |
| |
| namespace { |
| |
| using PartitionCallback = std::function<void(Instruction *)>; |
| |
| Type *getPaddingArray(LLVMContext &Ctx, uint64_t Size) { |
| if (Size % sizeof(uint32_t) == 0) { |
| return ArrayType::get(Type::getInt32Ty(Ctx), Size / sizeof(uint32_t)); |
| } else if (Size % sizeof(uint16_t) == 0) { |
| return ArrayType::get(Type::getInt16Ty(Ctx), Size / sizeof(uint16_t)); |
| } else { |
| return ArrayType::get(Type::getInt8Ty(Ctx), Size / sizeof(uint8_t)); |
| } |
| } |
| |
| /// Partition the @p Instructions based on their liveness. |
| void partitionInstructions(ArrayRef<WeakTrackingVH> Instructions, |
| PartitionCallback OnDead, |
| PartitionCallback OnAlive) { |
| for (auto OldValueHandle : Instructions) { |
| // Handle situations when the weak handle is no longer valid. |
| if (!OldValueHandle.pointsToAliveValue()) { |
| continue; // Nothing else to do for this handle. |
| } |
| |
| auto *OldInstruction = cast<Instruction>(OldValueHandle); |
| bool Dead = OldInstruction->use_empty(); |
| if (Dead) { |
| OnDead(OldInstruction); |
| } else { |
| OnAlive(OldInstruction); |
| } |
| } |
| } |
| |
| /// Get the scalar overload for the given LLVM @p Intrinsic. |
| Function *getIntrinsicScalarVersion(Function &Intrinsic) { |
| auto id = Intrinsic.getIntrinsicID(); |
| assert(id != Intrinsic::not_intrinsic); |
| |
| switch (id) { |
| default: |
| #ifndef NDEBUG |
| dbgs() << "Intrinsic " << Intrinsic.getName() << " is not yet supported"; |
| #endif |
| llvm_unreachable("Missing support for intrinsic."); |
| break; |
| |
| case Intrinsic::abs: |
| case Intrinsic::canonicalize: |
| case Intrinsic::ceil: |
| case Intrinsic::copysign: |
| case Intrinsic::cos: |
| case Intrinsic::ctlz: |
| case Intrinsic::cttz: |
| case Intrinsic::exp: |
| case Intrinsic::fabs: |
| case Intrinsic::floor: |
| case Intrinsic::fma: |
| case Intrinsic::fmuladd: |
| case Intrinsic::fshl: |
| case Intrinsic::fshr: |
| case Intrinsic::log: |
| case Intrinsic::smax: |
| case Intrinsic::umax: |
| case Intrinsic::smin: |
| case Intrinsic::umin: |
| case Intrinsic::minnum: |
| case Intrinsic::maxnum: |
| case Intrinsic::minimumnum: |
| case Intrinsic::maximumnum: |
| case Intrinsic::pow: |
| case Intrinsic::rint: |
| case Intrinsic::sin: |
| case Intrinsic::sqrt: |
| case Intrinsic::trunc: |
| case Intrinsic::sadd_sat: |
| case Intrinsic::uadd_sat: |
| case Intrinsic::ssub_sat: |
| case Intrinsic::usub_sat: { |
| SmallVector<Type *, 16> ParamTys; |
| bool Success = Intrinsic::isSignatureValid(&Intrinsic, ParamTys); |
| assert(Success); |
| (void)Success; |
| |
| // Map vectors to scalars. |
| for (auto *&Param : ParamTys) { |
| // TODO Need support for other types, like pointers. Need test case. |
| assert(Param->isVectorTy()); |
| Param = Param->getScalarType(); |
| } |
| |
| return Intrinsic::getOrInsertDeclaration(Intrinsic.getParent(), id, |
| ParamTys); |
| break; |
| } |
| } |
| } |
| |
| std::string |
| getMangledScalarName(const clspv::Builtins::FunctionInfo &VectorInfo) { |
| // Copy the informations about the vector version. |
| // Return type is not important for mangling. |
| // Only update arguments to make them scalars. |
| clspv::Builtins::FunctionInfo ScalarInfo = VectorInfo; |
| for (size_t i = 0; i < ScalarInfo.getParameterCount(); ++i) { |
| ScalarInfo.getParameter(i).vector_size = 0; |
| } |
| return clspv::Builtins::GetMangledFunctionName(ScalarInfo); |
| } |
| |
| std::string getSpirvCompliantName(const clspv::Builtins::FunctionInfo &IInfo) { |
| // Copy the informations about the vector version. |
| // Return type is not important for mangling. |
| // Only update arguments to have spirv compatible vectors. |
| clspv::Builtins::FunctionInfo Info = IInfo; |
| for (size_t i = 0; i < Info.getParameterCount(); ++i) { |
| if (Info.getParameter(i).vector_size > (int)clspv::SPIRVMaxVectorSize()) { |
| Info.getParameter(i).vector_size = clspv::SPIRVMaxVectorSize(); |
| } |
| } |
| return clspv::Builtins::GetMangledFunctionName(Info); |
| } |
| |
| Type *getScalarPointerType(Function &Builtin) { |
| const auto &Info = clspv::Builtins::Lookup(&Builtin); |
| switch (Info.getType()) { |
| case clspv::Builtins::kSincos: |
| case clspv::Builtins::kModf: |
| case clspv::Builtins::kFract: |
| return Builtin.getReturnType()->getScalarType(); |
| case clspv::Builtins::kFrexp: |
| case clspv::Builtins::kRemquo: |
| case clspv::Builtins::kLgammaR: |
| return Type::getInt32Ty(Builtin.getParent()->getContext()); |
| case clspv::Builtins::kVloadHalf: |
| case clspv::Builtins::kVloadaHalf: |
| case clspv::Builtins::kVstoreHalf: |
| case clspv::Builtins::kVstoreaHalf: |
| return Type::getHalfTy(Builtin.getParent()->getContext()); |
| case clspv::Builtins::kVstore: |
| return Builtin.getArg(0)->getType()->getScalarType(); |
| case clspv::Builtins::kVload: |
| return Builtin.getReturnType()->getScalarType(); |
| default: |
| // What about llvm intrinsics (e.g. memcpy) or other OpenCL builtins? |
| return nullptr; |
| } |
| } |
| |
| /// Get the scalar overload for the given OpenCL builtin function @p Builtin. |
| Function *getBIFScalarVersion(Function &Builtin) { |
| assert(!Builtin.isIntrinsic()); |
| const auto &Info = clspv::Builtins::Lookup(&Builtin); |
| assert(Info.getType() != clspv::Builtins::kBuiltinNone); |
| |
| FunctionType *FunctionTy = nullptr; |
| switch (Info.getType()) { |
| default: { |
| #ifndef NDEBUG |
| dbgs() << "BIF " << Builtin.getName() << " is not yet supported\n"; |
| #endif |
| llvm_unreachable("BIF not handled yet."); |
| } |
| |
| // TODO Add support for other builtins by providing testcases and listing the |
| // builtins here. |
| case clspv::Builtins::kAbs: |
| case clspv::Builtins::kAcosh: |
| case clspv::Builtins::kAcos: |
| case clspv::Builtins::kAcospi: |
| case clspv::Builtins::kAsin: |
| case clspv::Builtins::kAsinh: |
| case clspv::Builtins::kAsinpi: |
| case clspv::Builtins::kAtan2: |
| case clspv::Builtins::kAtan2pi: |
| case clspv::Builtins::kAtan: |
| case clspv::Builtins::kAtanh: |
| case clspv::Builtins::kAtanpi: |
| case clspv::Builtins::kCeil: |
| case clspv::Builtins::kClamp: |
| case clspv::Builtins::kClspvFract: |
| case clspv::Builtins::kCos: |
| case clspv::Builtins::kCosh: |
| case clspv::Builtins::kCospi: |
| case clspv::Builtins::kDegrees: |
| case clspv::Builtins::kExp: |
| case clspv::Builtins::kExp2: |
| case clspv::Builtins::kExpm1: |
| case clspv::Builtins::kFabs: |
| case clspv::Builtins::kFloor: |
| case clspv::Builtins::kFma: |
| case clspv::Builtins::kFmax: |
| case clspv::Builtins::kFmin: |
| case clspv::Builtins::kFract: |
| case clspv::Builtins::kFrexp: |
| case clspv::Builtins::kHalfCos: |
| case clspv::Builtins::kHalfExp: |
| case clspv::Builtins::kHalfExp2: |
| case clspv::Builtins::kHalfLog: |
| case clspv::Builtins::kHalfLog2: |
| case clspv::Builtins::kHalfPowr: |
| case clspv::Builtins::kHalfRsqrt: |
| case clspv::Builtins::kHalfSin: |
| case clspv::Builtins::kHalfSqrt: |
| case clspv::Builtins::kHalfTan: |
| case clspv::Builtins::kLdexp: |
| case clspv::Builtins::kLog: |
| case clspv::Builtins::kLog2: |
| case clspv::Builtins::kMad: |
| case clspv::Builtins::kMax: |
| case clspv::Builtins::kMin: |
| case clspv::Builtins::kMix: |
| case clspv::Builtins::kNativeExp: |
| case clspv::Builtins::kPopcount: |
| case clspv::Builtins::kPow: |
| case clspv::Builtins::kPowr: |
| case clspv::Builtins::kRadians: |
| case clspv::Builtins::kRint: |
| case clspv::Builtins::kRsqrt: |
| case clspv::Builtins::kSign: |
| case clspv::Builtins::kSin: |
| case clspv::Builtins::kSinh: |
| case clspv::Builtins::kSinpi: |
| case clspv::Builtins::kSmoothstep: |
| case clspv::Builtins::kSpirvOp: |
| case clspv::Builtins::kStep: |
| case clspv::Builtins::kSqrt: |
| case clspv::Builtins::kSubGroupNonUniformBroadcast: |
| case clspv::Builtins::kTan: |
| case clspv::Builtins::kTanh: |
| case clspv::Builtins::kTrunc: { |
| // Scalarise all the input/output types. Here we intentionally do not rely |
| // on getEquivalentType because we want the scalar overload. |
| SmallVector<Type *, 16> ScalarParamTys; |
| for (auto &Param : Builtin.args()) { |
| auto *ParamTy = Param.getType(); |
| |
| Type *ScalarParamTy = nullptr; |
| if (ParamTy->isPointerTy()) { |
| ScalarParamTy = ParamTy; |
| } else { |
| assert((ParamTy->isVectorTy() || ParamTy->isFloatingPointTy() || |
| ParamTy->isIntegerTy()) && |
| "Unsupported kind of parameter type."); |
| ScalarParamTy = ParamTy->getScalarType(); |
| } |
| |
| assert(ScalarParamTy); |
| ScalarParamTys.push_back(ScalarParamTy); |
| } |
| |
| Type *ReturnTy; |
| if (!Builtin.getReturnType()->isVectorTy()) { |
| SmallVector<Type *, 16> RetTys; |
| assert(Builtin.getReturnType()->isStructTy()); |
| StructType *RetTy = cast<StructType>(Builtin.getReturnType()); |
| for (unsigned int eachRetTy = 0; |
| eachRetTy < RetTy->getStructNumElements(); eachRetTy++) { |
| assert(RetTy->getStructElementType(eachRetTy)->isVectorTy()); |
| RetTys.push_back( |
| RetTy->getStructElementType(eachRetTy)->getScalarType()); |
| } |
| ReturnTy = StructType::create(RetTys); |
| } else { |
| ReturnTy = Builtin.getReturnType()->getScalarType(); |
| } |
| |
| FunctionTy = |
| FunctionType::get(ReturnTy, ScalarParamTys, Builtin.isVarArg()); |
| break; |
| } |
| } |
| |
| // Handle signedness of parameters by using clspv::Builtins API. |
| std::string ScalarName = getMangledScalarName(Info); |
| |
| // Get the scalar version, which might not already exist in the module. |
| auto *M = Builtin.getParent(); |
| auto *ScalarFn = M->getFunction(ScalarName); |
| |
| if (ScalarFn == nullptr) { |
| ScalarFn = Function::Create(FunctionTy, Builtin.getLinkage(), ScalarName); |
| ScalarFn->setCallingConv(Builtin.getCallingConv()); |
| ScalarFn->copyAttributesFrom(&Builtin); |
| |
| M->getFunctionList().push_front(ScalarFn); |
| } |
| |
| assert(Builtin.getCallingConv() == ScalarFn->getCallingConv()); |
| |
| return ScalarFn; |
| } |
| |
| /// Convert the given value @p V to a value of the given @p EquivalentTy. |
| /// |
| /// @return @p V when @p V's type is @p newType. |
| /// @return an equivalent pointer when both @p V and @p newType are pointers. |
| /// @return an equivalent vector when @p V is an aggregate. |
| /// @return an equivalent aggregate when @p V is a vector. |
| Value *convertEquivalentValue(IRBuilder<> &B, Value *V, Type *EquivalentTy, |
| const DataLayout *DL) { |
| if (V->getType() == EquivalentTy) { |
| return V; |
| } |
| |
| if (EquivalentTy->isPointerTy()) { |
| assert(V->getType()->isPointerTy()); |
| return B.CreateBitCast(V, EquivalentTy); |
| } |
| |
| assert(EquivalentTy->isVectorTy() || EquivalentTy->isArrayTy() || |
| EquivalentTy->isStructTy()); |
| |
| Value *NewValue = PoisonValue::get(EquivalentTy); |
| |
| if (EquivalentTy->isStructTy()) { |
| StructType *StructTy = dyn_cast<StructType>(EquivalentTy); |
| StructType *VStructTy = dyn_cast<StructType>(V->getType()); |
| unsigned Arity = StructTy->getStructNumElements(); |
| // We use convertEquivalentValue to convert in both ways (vector to array |
| // and array to vector). Thus, the structure with added padding elements |
| // might not be the one we expect. |
| bool inverse = false; |
| if (Arity > VStructTy->getStructNumElements()) { |
| inverse = true; |
| Arity = VStructTy->getStructNumElements(); |
| } |
| if (Arity == 0) |
| return nullptr; |
| for (unsigned i = 0; i < Arity; ++i) { |
| unsigned idx_struct = i; |
| unsigned idx_V = |
| DL->getStructLayout(VStructTy)->getElementContainingOffset( |
| DL->getStructLayout(StructTy)->getElementOffset(i)); |
| if (inverse) { |
| idx_struct = DL->getStructLayout(StructTy)->getElementContainingOffset( |
| DL->getStructLayout(VStructTy)->getElementOffset(i)); |
| idx_V = i; |
| } |
| Type *ElementType = StructTy->getContainedType(idx_struct); |
| Value *Element = B.CreateExtractValue(V, {idx_V}); |
| Value *NewElement = convertEquivalentValue(B, Element, ElementType, DL); |
| NewValue = B.CreateInsertValue(NewValue, NewElement, {idx_struct}); |
| } |
| } else if (EquivalentTy->isVectorTy()) { |
| assert(V->getType()->isArrayTy()); |
| |
| unsigned Arity = V->getType()->getArrayNumElements(); |
| for (unsigned i = 0; i < Arity; ++i) { |
| Value *Scalar = B.CreateExtractValue(V, i); |
| NewValue = B.CreateInsertElement(NewValue, Scalar, i); |
| } |
| } else { |
| assert(EquivalentTy->isArrayTy()); |
| assert(V->getType()->isVectorTy()); |
| |
| unsigned Arity = EquivalentTy->getArrayNumElements(); |
| for (unsigned i = 0; i < Arity; ++i) { |
| Value *Scalar = B.CreateExtractElement(V, i); |
| NewValue = B.CreateInsertValue(NewValue, Scalar, i); |
| } |
| } |
| |
| return NewValue; |
| } |
| |
| using ScalarOperationFactory = |
| std::function<Value *(IRBuilder<> & /* B */, ArrayRef<Value *> /* Args */)>; |
| |
| /// Scalarise the vector instruction @p I element-wise by invoking the operation |
| /// @p ScalarOperation. |
| Value *convertVectorOperation(Instruction &I, Type *EquivalentReturnTy, |
| ArrayRef<Value *> EquivalentArgs, |
| ScalarOperationFactory ScalarOperation, |
| Type *pointer_scalar_ty = nullptr) { |
| assert(EquivalentReturnTy != nullptr); |
| |
| unsigned Arity; |
| if (!EquivalentReturnTy->isArrayTy()) { |
| assert(EquivalentReturnTy->isStructTy()); |
| assert(EquivalentReturnTy->getStructNumElements() != 0); |
| StructType *RetTy = cast<StructType>(EquivalentReturnTy); |
| Arity = UINT_MAX; |
| for (unsigned int eachRetTy = 0; eachRetTy < RetTy->getStructNumElements(); |
| eachRetTy++) { |
| assert(RetTy->getStructElementType(eachRetTy)->isArrayTy()); |
| assert(Arity == UINT_MAX || |
| Arity == |
| RetTy->getStructElementType(eachRetTy)->getArrayNumElements()); |
| Arity = RetTy->getStructElementType(eachRetTy)->getArrayNumElements(); |
| } |
| } else { |
| Arity = EquivalentReturnTy->getArrayNumElements(); |
| } |
| |
| Value *ReturnValue = PoisonValue::get(EquivalentReturnTy); |
| |
| auto &C = I.getContext(); |
| auto *IntTy = IntegerType::get(C, 32); |
| auto *Zero = ConstantInt::get(IntTy, 0); |
| |
| // Invoke the scalar operation once for each vector element. |
| IRBuilder<> B(&I); |
| for (unsigned i = 0; i < Arity; ++i) { |
| SmallVector<Value *, 16> Args; |
| Args.resize(EquivalentArgs.size()); |
| |
| for (unsigned j = 0; j < Args.size(); ++j) { |
| auto *ArgTy = EquivalentArgs[j]->getType(); |
| if (ArgTy->isPointerTy()) { |
| assert(pointer_scalar_ty && "Missing pointer scalar type"); |
| Args[j] = B.CreateInBoundsGEP(ArrayType::get(pointer_scalar_ty, Arity), |
| EquivalentArgs[j], |
| {Zero, ConstantInt::get(IntTy, i)}); |
| } else if (ArgTy->isArrayTy()) { |
| Args[j] = B.CreateExtractValue(EquivalentArgs[j], i); |
| } else { |
| assert((ArgTy->isFloatingPointTy() || ArgTy->isIntegerTy()) && |
| "Unsupported kind of parameter type."); |
| Args[j] = EquivalentArgs[j]; |
| } |
| } |
| |
| Value *Scalar = ScalarOperation(B, Args); |
| |
| if (isa<Instruction>(Scalar)) { |
| cast<Instruction>(Scalar)->copyIRFlags(&I); |
| } |
| |
| if (!EquivalentReturnTy->isArrayTy()) { |
| StructType *RetTy = cast<StructType>(EquivalentReturnTy); |
| for (unsigned int eachRetTy = 0; |
| eachRetTy < RetTy->getStructNumElements(); eachRetTy++) { |
| auto Val = B.CreateExtractValue(Scalar, eachRetTy); |
| ReturnValue = B.CreateInsertValue(ReturnValue, Val, {eachRetTy, i}); |
| } |
| } else { |
| ReturnValue = B.CreateInsertValue(ReturnValue, Scalar, i); |
| } |
| } |
| |
| return ReturnValue; |
| } |
| |
| /// Map the arguments of the wrapper function (which are either not long-vectors |
| /// or aggregates of scalars) to the original arguments of the user-defined |
| /// function (which can be long-vectors). Handle pointers as well. |
| SmallVector<Value *, 16> mapWrapperArgsToWrappeeArgs(IRBuilder<> &B, |
| Function &Wrappee, |
| Function &Wrapper, |
| const DataLayout *DL) { |
| SmallVector<Value *, 16> Args; |
| |
| std::size_t ArgumentCount = Wrapper.arg_size(); |
| Args.reserve(ArgumentCount); |
| |
| for (std::size_t i = 0; i < ArgumentCount; ++i) { |
| auto *Arg = Wrappee.getArg(i); |
| auto *NewArg = Wrapper.getArg(i); |
| NewArg->takeName(Arg); |
| auto *OldArgTy = Wrappee.getFunctionType()->getParamType(i); |
| auto *EquivalentArg = convertEquivalentValue(B, NewArg, OldArgTy, DL); |
| Args.push_back(EquivalentArg); |
| } |
| |
| return Args; |
| } |
| |
| /// Create a new, equivalent function with no long-vector types. |
| /// |
| /// This is achieved by creating a new function (the "wrapper") which inlines |
| /// the given function (the "wrappee"). Only the parameters and return types are |
| /// mapped. The function body still needs to be lowered. |
| Function *createFunctionWithMappedTypes(Function &F, |
| FunctionType *EquivalentFunctionTy, |
| const DataLayout *DL) { |
| assert(!F.isVarArg() && "varargs not supported"); |
| |
| auto *Wrapper = Function::Create(EquivalentFunctionTy, F.getLinkage()); |
| Wrapper->takeName(&F); |
| Wrapper->setCallingConv(F.getCallingConv()); |
| Wrapper->copyAttributesFrom(&F); |
| Wrapper->copyMetadata(&F, /* offset */ 0); |
| |
| BasicBlock::Create(F.getContext(), "", Wrapper); |
| IRBuilder<> B(&Wrapper->getEntryBlock()); |
| |
| // Fill in the body of the wrapper function. |
| auto WrappeeArgs = mapWrapperArgsToWrappeeArgs(B, F, *Wrapper, DL); |
| CallInst *Call = B.CreateCall(&F, WrappeeArgs); |
| if (Call->getType()->isVoidTy()) { |
| B.CreateRetVoid(); |
| } else { |
| auto *EquivalentReturnTy = EquivalentFunctionTy->getReturnType(); |
| Value *ReturnValue = |
| convertEquivalentValue(B, Call, EquivalentReturnTy, DL); |
| B.CreateRet(ReturnValue); |
| } |
| |
| // Ensure wrapper has a parent or InlineFunction will crash. |
| F.getParent()->getFunctionList().push_front(Wrapper); |
| |
| // Inline the original function. |
| InlineFunctionInfo Info; |
| auto Result = InlineFunction(*Call, Info); |
| if (!Result.isSuccess()) { |
| LLVM_DEBUG(dbgs() << "Failed to inline " << F.getName() << '\n'); |
| LLVM_DEBUG(dbgs() << "Reason: " << Result.getFailureReason() << '\n'); |
| llvm_unreachable("Unexpected failure when inlining function."); |
| } |
| |
| // Inlining a function can introduce constant expression that we could not |
| // handle afterwards. |
| BitcastUtils::RemoveCstExprFromFunction(Wrapper); |
| |
| return Wrapper; |
| } |
| |
| FixedVectorType *getSpirvCompliantVectorType(FixedVectorType *VectorTy) { |
| while (VectorTy->getNumElements() > clspv::SPIRVMaxVectorSize()) { |
| VectorTy = FixedVectorType::getHalfElementsVectorType(VectorTy); |
| } |
| return VectorTy; |
| } |
| |
| using ReduceOperationFactory = |
| std::function<Value *(IRBuilder<> &, Value *, Value *)>; |
| |
| /* |
| * Convert Any/All Operation on long vectors by using Any/All operators on |
| * smaller sub-vectors. |
| * Then reduce the severals results with Or/And operator. |
| */ |
| Value *convertOpAnyOrAllOperation(CallInst &VectorCall, |
| ArrayRef<Value *> EquivalentArgs, |
| ReduceOperationFactory Reduce) { |
| assert(EquivalentArgs.size() == 2); |
| auto *VectorOperand = VectorCall.getOperand(1); |
| auto *VectorTy = VectorOperand->getType(); |
| assert(VectorTy->isVectorTy()); |
| FixedVectorType *FixedVectorTy = dyn_cast<FixedVectorType>(VectorTy); |
| FixedVectorType *DstType = getSpirvCompliantVectorType(FixedVectorTy); |
| |
| Function *OpAnyOrAllInitialFunction = VectorCall.getCalledFunction(); |
| |
| std::string OpAnyOrAllFunctionName = |
| getSpirvCompliantName(clspv::Builtins::Lookup(OpAnyOrAllInitialFunction)); |
| |
| auto *M = OpAnyOrAllInitialFunction->getParent(); |
| Function *OpAnyOrAllFunction = M->getFunction(OpAnyOrAllFunctionName); |
| |
| /* Create the function if it does not exist in the module */ |
| if (OpAnyOrAllFunction == nullptr) { |
| SmallVector<Type *, 2> ParamTys; |
| ParamTys.push_back(VectorCall.getOperand(0)->getType()); |
| ParamTys.push_back(DstType); |
| |
| OpAnyOrAllFunction = Function::Create( |
| FunctionType::get(VectorCall.getFunctionType()->getReturnType(), |
| ParamTys, false), |
| OpAnyOrAllInitialFunction->getLinkage(), OpAnyOrAllFunctionName); |
| |
| OpAnyOrAllFunction->setCallingConv( |
| OpAnyOrAllInitialFunction->getCallingConv()); |
| OpAnyOrAllFunction->copyAttributesFrom(OpAnyOrAllInitialFunction); |
| |
| M->getFunctionList().push_front(OpAnyOrAllFunction); |
| } |
| |
| IRBuilder<> B(&VectorCall); |
| Value *ReturnValue = nullptr; |
| Value *Vector = PoisonValue::get(DstType); |
| unsigned int InitNumElements = FixedVectorTy->getNumElements(); |
| unsigned int DstNumElements = DstType->getNumElements(); |
| // for each sub-vector calls |
| for (unsigned eachCall = 0; eachCall < InitNumElements / DstNumElements; |
| eachCall++) { |
| |
| // recreate the sub-vector |
| for (unsigned eachVecElement = 0; eachVecElement < DstNumElements; |
| eachVecElement++) { |
| auto *Val = B.CreateExtractValue( |
| EquivalentArgs[1], eachVecElement + eachCall * DstNumElements); |
| Vector = B.CreateInsertElement(Vector, Val, B.getInt64(eachVecElement)); |
| } |
| |
| SmallVector<Value *, 2> Args; |
| Args.push_back(EquivalentArgs[0]); |
| Args.push_back(Vector); |
| |
| CallInst *Call = B.CreateCall(OpAnyOrAllFunction, Args); |
| Call->copyIRFlags(&VectorCall); |
| Call->copyMetadata(VectorCall); |
| Call->setCallingConv(VectorCall.getCallingConv()); |
| |
| if (eachCall == 0) { |
| ReturnValue = Call; |
| } else { |
| ReturnValue = Reduce(B, ReturnValue, Call); |
| } |
| } |
| |
| assert(ReturnValue != nullptr); |
| |
| return ReturnValue; |
| } |
| |
| } // namespace |
| |
| PreservedAnalyses clspv::LongVectorLoweringPass::run(Module &M, |
| ModuleAnalysisManager &) { |
| PreservedAnalyses PA; |
| DL = &M.getDataLayout(); |
| runOnGlobals(M); |
| |
| for (auto &F : M.functions()) { |
| BitcastUtils::RemoveCstExprFromFunction(&F); |
| runOnFunction(F); |
| } |
| |
| cleanDeadFunctions(); |
| |
| cleanDeadGlobals(); |
| |
| return PA; |
| } |
| |
| Value *clspv::LongVectorLoweringPass::visit(Value *V) { |
| // Already handled? |
| auto it = ValueMap.find(V); |
| if (it != ValueMap.end()) { |
| return it->second; |
| } |
| |
| if (V->getType()->isPointerTy()) { |
| auto where = GlobalVariableMap.find(dyn_cast_or_null<GlobalVariable>(V)); |
| if (where != GlobalVariableMap.end()) { |
| return where->second; |
| } |
| } |
| |
| if (isa<Argument>(V)) { |
| assert(getEquivalentType(V->getType()) == nullptr && |
| "Argument not handled when visiting function."); |
| return nullptr; |
| } |
| |
| assert(isa<User>(V) && "Kind of llvm::Value not yet supported."); |
| if (!handlingRequired(*cast<User>(V))) { |
| return nullptr; |
| } |
| |
| if (auto *I = dyn_cast<Instruction>(V)) { |
| // Dispatch to the appropriate method using InstVisitor. |
| return visit(I); |
| } |
| |
| if (auto *C = dyn_cast<Constant>(V)) { |
| return visitConstant(*C); |
| } |
| |
| #ifndef NDEBUG |
| dbgs() << "Value not handled: " << *V << '\n'; |
| #endif |
| llvm_unreachable("Kind of value not handled yet."); |
| } |
| |
| Value *clspv::LongVectorLoweringPass::visitConstant(Constant &Cst) { |
| if (auto *GV = dyn_cast<GlobalVariable>(&Cst)) { |
| auto *EquivalentGV = GlobalVariableMap[GV]; |
| assert(EquivalentGV && |
| "Global variable should have been already processed."); |
| return EquivalentGV; |
| } |
| |
| auto *EquivalentTy = getEquivalentType(Cst.getType()); |
| assert(EquivalentTy && "Nothing to lower."); |
| |
| if (Cst.isNullValue()) { |
| return Constant::getNullValue(EquivalentTy); |
| } |
| |
| if (isa<PoisonValue>(Cst)) { |
| return PoisonValue::get(EquivalentTy); |
| } |
| if (isa<UndefValue>(Cst)) { |
| return UndefValue::get(EquivalentTy); |
| } |
| |
| if (auto *Vector = dyn_cast<ConstantDataVector>(&Cst)) { |
| assert(isa<ArrayType>(EquivalentTy)); |
| |
| SmallVector<Constant *, 16> Scalars; |
| for (unsigned i = 0; i < Vector->getNumElements(); ++i) { |
| Scalars.push_back(Vector->getElementAsConstant(i)); |
| } |
| |
| return ConstantArray::get(cast<ArrayType>(EquivalentTy), Scalars); |
| } |
| |
| if (auto *Vector = dyn_cast<ConstantVector>(&Cst)) { |
| assert(isa<ArrayType>(EquivalentTy)); |
| |
| SmallVector<Constant *, 16> Scalars; |
| for (unsigned i = 0; i < Vector->getNumOperands(); ++i) { |
| Scalars.push_back(dyn_cast<Constant>(visitOrSelf(Vector->getOperand(i)))); |
| } |
| |
| return ConstantArray::get(cast<ArrayType>(EquivalentTy), Scalars); |
| } |
| |
| if (auto *CFP = dyn_cast<ConstantFP>(&Cst)) { |
| assert(isa<ArrayType>(EquivalentTy)); |
| // This occurs for splats of float constants. They have vector type. |
| auto *vecTy = cast<VectorType>(Cst.getType()); |
| auto num_elems = vecTy->getElementCount().getFixedValue(); |
| Type *scalarTy = vecTy->getElementType(); |
| const auto floatVal = CFP->getValueAPF(); |
| |
| SmallVector<Constant *, 16> Scalars; |
| for (decltype(num_elems) i = 0; i < num_elems; ++i) { |
| Scalars.push_back(ConstantFP::get(scalarTy, floatVal)); |
| } |
| |
| return ConstantArray::get(cast<ArrayType>(EquivalentTy), Scalars); |
| } |
| |
| // TODO(#874): this pass needs updated to handle constantexpr more robustly. |
| if (auto *CE = dyn_cast<ConstantExpr>(&Cst)) { |
| switch (CE->getOpcode()) { |
| case Instruction::GetElementPtr: { |
| auto *GEP = cast<GEPOperator>(CE); |
| auto *EquivalentSourceTy = getEquivalentType(GEP->getSourceElementType()); |
| Constant *EquivalentPointer = cast<Constant>(GEP->getPointerOperand()); |
| SmallVector<Value *, 4> Indices(GEP->idx_begin(), GEP->idx_end()); |
| |
| auto *EquivalentGEP = ConstantExpr::getGetElementPtr( |
| EquivalentSourceTy, EquivalentPointer, Indices, GEP->isInBounds(), |
| GEP->getInRange()); |
| |
| return EquivalentGEP; |
| } |
| |
| default: |
| #ifndef NDEBUG |
| dbgs() << "Constant Expression not handled: " << *CE << '\n'; |
| dbgs() << "Constant Expression Opcode: " << CE->getOpcodeName() << '\n'; |
| #endif |
| llvm_unreachable("Unsupported kind of ConstantExpr"); |
| } |
| } |
| |
| #ifndef NDEBUG |
| dbgs() << "Constant not handled: " << Cst << '\n'; |
| #endif |
| llvm_unreachable("Unsupported kind of constant"); |
| } |
| |
| Value *clspv::LongVectorLoweringPass::visitNAryOperator(Instruction &I) { |
| SmallVector<Value *, 16> EquivalentArgs; |
| for (auto &Operand : I.operands()) { |
| Value *EquivalentOperand = visit(Operand.get()); |
| assert(EquivalentOperand && "operand not lowered"); |
| EquivalentArgs.push_back(EquivalentOperand); |
| } |
| Type *EquivalentReturnTy = getEquivalentType(I.getType()); |
| assert(EquivalentReturnTy && "return type not lowered"); |
| |
| auto ScalarFactory = [Opcode = I.getOpcode()](auto &B, auto Args) { |
| return B.CreateNAryOp(Opcode, Args); |
| }; |
| |
| Value *V = convertVectorOperation(I, EquivalentReturnTy, EquivalentArgs, |
| ScalarFactory); |
| registerReplacement(I, *V); |
| return V; |
| } |
| |
| Value *clspv::LongVectorLoweringPass::visitInstruction(Instruction &I) { |
| #ifndef NDEBUG |
| dbgs() << "Instruction not handled: " << I << '\n'; |
| #endif |
| llvm_unreachable("Missing support for instruction"); |
| } |
| |
| Value *clspv::LongVectorLoweringPass::visitAllocaInst(AllocaInst &I) { |
| auto *EquivalentTy = getEquivalentType(I.getAllocatedType()); |
| assert(EquivalentTy && "type not lowered"); |
| |
| Value *ArraySize = I.getArraySize(); |
| assert(visit(ArraySize) == nullptr && "TODO Need test case"); |
| |
| IRBuilder<> B(&I); |
| unsigned AS = I.getType()->getAddressSpace(); |
| auto *V = B.CreateAlloca(EquivalentTy, AS, ArraySize); |
| V->setAlignment(I.getAlign()); |
| registerReplacement(I, *V); |
| return V; |
| } |
| |
| Value *clspv::LongVectorLoweringPass::visitBinaryOperator(BinaryOperator &I) { |
| return visitNAryOperator(I); |
| } |
| |
| Value *clspv::LongVectorLoweringPass::visitCallInst(CallInst &I) { |
| SmallVector<Value *, 16> EquivalentArgs; |
| for (auto &ArgUse : I.args()) { |
| Value *Arg = ArgUse.get(); |
| Value *EquivalentArg = visitOrSelf(Arg); |
| EquivalentArgs.push_back(EquivalentArg); |
| } |
| |
| auto *ReturnTy = I.getType(); |
| auto *EquivalentReturnTy = getEquivalentTypeOrSelf(ReturnTy); |
| |
| #ifndef NDEBUG |
| bool NeedHandling = false; |
| NeedHandling |= (EquivalentReturnTy != ReturnTy); |
| NeedHandling |= |
| !std::equal(I.arg_begin(), I.arg_end(), std::begin(EquivalentArgs), |
| [](auto const &ArgUse, Value *EquivalentArg) { |
| return ArgUse.get() == EquivalentArg; |
| }); |
| assert(NeedHandling && "Expected something to lower for this call."); |
| #endif |
| |
| Function *F = I.getCalledFunction(); |
| assert(F && "Only function calls are supported."); |
| |
| const auto &Info = clspv::Builtins::Lookup(F); |
| bool SpirvOpBuiltin = (Info.getType() == clspv::Builtins::kSpirvOp); |
| bool OpenCLBuiltin = (Info.getType() != clspv::Builtins::kBuiltinNone); |
| bool Builtin = (OpenCLBuiltin || F->isIntrinsic()); |
| |
| Value *V = nullptr; |
| if (Builtin && F->isDeclaration() && !SpirvOpBuiltin) { |
| V = convertAllBuiltinCall(I, EquivalentReturnTy, EquivalentArgs); |
| } else if (SpirvOpBuiltin && F->isDeclaration()) { |
| V = convertSpirvOpBuiltinCall(I, EquivalentReturnTy, EquivalentArgs); |
| } else { |
| V = convertUserDefinedFunctionCall(I, EquivalentArgs); |
| } |
| |
| registerReplacement(I, *V); |
| return V; |
| } |
| |
| Value *clspv::LongVectorLoweringPass::visitCastInst(CastInst &I) { |
| auto *OriginalValue = I.getOperand(0); |
| auto *EquivalentValue = visitOrSelf(OriginalValue); |
| auto *OriginalDestTy = I.getDestTy(); |
| auto *EquivalentDestTy = getEquivalentTypeOrSelf(OriginalDestTy); |
| |
| // We expect something to lower, or this function shouldn't have been called. |
| assert(((OriginalValue != EquivalentValue) || |
| (OriginalDestTy != EquivalentDestTy)) && |
| "nothing to lower"); |
| |
| Value *V = nullptr; |
| switch (I.getOpcode()) { |
| case Instruction::BitCast: { |
| if (OriginalDestTy->isPointerTy()) { |
| // Bitcast over pointers are lowered to one bitcast. |
| assert(EquivalentDestTy->isPointerTy()); |
| IRBuilder<> B(&I); |
| V = B.CreateBitCast(EquivalentValue, EquivalentDestTy, I.getName()); |
| } else { |
| IRBuilder<> B(&I); |
| SmallVector<Value *, 8> Values; |
| Values.push_back(EquivalentValue); |
| if (EquivalentValue->getType()->isArrayTy()) { |
| BitcastUtils::ExtractFromArray(B, Values); |
| } |
| BitcastUtils::ConvertInto(EquivalentDestTy, B, Values); |
| V = Values[0]; |
| } |
| break; |
| } |
| case Instruction::Trunc: |
| case Instruction::ZExt: |
| case Instruction::SExt: |
| case Instruction::FPToUI: |
| case Instruction::FPToSI: |
| case Instruction::UIToFP: |
| case Instruction::SIToFP: |
| case Instruction::FPTrunc: |
| case Instruction::FPExt: { |
| // Scalarise the cast. |
| // |
| // Because all the elements of EquivalentDestTy have the same type, we can |
| // simply pick the first. |
| assert(EquivalentDestTy->isArrayTy()); |
| Type *ScalarTy = EquivalentDestTy->getArrayElementType(); |
| auto ScalarFactory = [&I, ScalarTy](auto &B, auto Args) { |
| assert(Args.size() == 1); |
| return B.CreateCast(I.getOpcode(), Args[0], ScalarTy, I.getName()); |
| }; |
| V = convertVectorOperation(I, EquivalentDestTy, EquivalentValue, |
| ScalarFactory); |
| break; |
| } |
| case Instruction::PtrToInt: { |
| IRBuilder<> B(&I); |
| V = B.CreatePtrToInt(EquivalentValue, EquivalentDestTy, I.getName()); |
| break; |
| } |
| case Instruction::PtrToAddr: { |
| IRBuilder<> B(&I); |
| V = B.CreatePtrToAddr(EquivalentValue, I.getName()); |
| break; |
| } |
| case Instruction::IntToPtr: { |
| IRBuilder<> B(&I); |
| V = B.CreateIntToPtr(EquivalentValue, EquivalentDestTy, I.getName()); |
| break; |
| } |
| |
| default: |
| llvm_unreachable("Cast unsupported."); |
| break; |
| } |
| |
| assert(V); |
| registerReplacement(I, *V); |
| return V; |
| } |
| |
| Value *clspv::LongVectorLoweringPass::visitCmpInst(CmpInst &I) { |
| auto *EquivalentType = getEquivalentType(I.getType()); |
| assert(EquivalentType && "type not lowered"); |
| |
| std::array<Value *, 2> EquivalentArgs{{ |
| visit(I.getOperand(0)), |
| visit(I.getOperand(1)), |
| }}; |
| assert(EquivalentArgs[0] && EquivalentArgs[1] && "argument(s) not lowered"); |
| |
| Value *V = convertVectorOperation( |
| I, EquivalentType, EquivalentArgs, |
| [Int = I.isIntPredicate(), P = I.getPredicate()](auto &B, auto Args) { |
| assert(Args.size() == 2); |
| if (Int) { |
| return B.CreateICmp(P, Args[0], Args[1]); |
| } else { |
| return B.CreateFCmp(P, Args[0], Args[1]); |
| } |
| }); |
| |
| registerReplacement(I, *V); |
| return V; |
| } |
| |
| Value * |
| clspv::LongVectorLoweringPass::visitExtractElementInst(ExtractElementInst &I) { |
| Value *EquivalentValue = visit(I.getOperand(0)); |
| assert(EquivalentValue && "value not lowered"); |
| ConstantInt *CI = dyn_cast<ConstantInt>(I.getOperand(1)); |
| IRBuilder<> B(&I); |
| if (CI) { |
| unsigned Index = CI->getZExtValue(); |
| |
| auto *V = B.CreateExtractValue(EquivalentValue, Index); |
| registerReplacement(I, *V); |
| return V; |
| } |
| |
| // Extract array pointer |
| Value *PtrOperand = nullptr; |
| if (auto *Load = dyn_cast<LoadInst>(EquivalentValue)) { |
| PtrOperand = Load->getPointerOperand(); |
| } else { |
| IRBuilder<> BFront(&I.getFunction()->front().front()); |
| |
| PtrOperand = BFront.CreateAlloca(EquivalentValue->getType()); |
| B.CreateStore(EquivalentValue, PtrOperand); |
| } |
| |
| auto *GEP = B.CreateInBoundsGEP(EquivalentValue->getType(), PtrOperand, |
| {B.getInt32(0), I.getOperand(1)}); |
| auto *Load = |
| B.CreateLoad(EquivalentValue->getType()->getArrayElementType(), GEP); |
| registerReplacement(I, *Load); |
| return Load; |
| } |
| |
| void clspv::LongVectorLoweringPass::reworkIndices( |
| SmallVector<unsigned, 4> &Indices, Type *Ty) { |
| auto EqTy = getEquivalentType(Ty); |
| if (!EqTy) |
| return; |
| SmallVector<unsigned, 4> Idxs(Indices); |
| SmallVector<uint64_t, 4> Indices_64b; |
| Indices.clear(); |
| for (auto Idx : Idxs) { |
| Indices.push_back(Idx); |
| Indices_64b.push_back((uint64_t)Idx); |
| auto IndexedTy = GetElementPtrInst::getIndexedType(Ty, Indices_64b); |
| if (getEquivalentType(IndexedTy)) { |
| auto id = Indices.pop_back_val(); |
| Indices_64b.pop_back(); |
| if (auto STy = dyn_cast<StructType>( |
| GetElementPtrInst::getIndexedType(Ty, Indices_64b))) { |
| auto off = DL->getStructLayout(STy)->getElementOffset(id); |
| auto newId = |
| DL->getStructLayout(dyn_cast<StructType>(getEquivalentType(STy))) |
| ->getElementContainingOffset(off); |
| Indices.push_back(newId); |
| Indices_64b.push_back((uint64_t)newId); |
| } else { |
| Indices.push_back(id); |
| Indices_64b.push_back((uint64_t)id); |
| } |
| } |
| } |
| } |
| |
| Value * |
| clspv::LongVectorLoweringPass::visitExtractValueInst(ExtractValueInst &I) { |
| Value *EquivalentValue = visit(I.getOperand(0)); |
| if (!EquivalentValue) |
| return nullptr; |
| |
| SmallVector<unsigned, 4> Indices(I.indices()); |
| reworkIndices(Indices, I.getOperand(0)->getType()); |
| |
| IRBuilder<> B(&I); |
| Value *V = B.CreateExtractValue(EquivalentValue, Indices); |
| registerReplacement(I, *V); |
| return V; |
| } |
| |
| void clspv::LongVectorLoweringPass::reworkIndices( |
| SmallVector<Value *, 4> &Indices, Type *Ty) { |
| auto EqTy = getEquivalentType(Ty); |
| if (!EqTy) |
| return; |
| assert(Indices.size() > 0); |
| SmallVector<Value *, 4> Idxs(Indices); |
| Indices.clear(); |
| Indices.push_back(Idxs[0]); |
| for (unsigned i = 1; i < Idxs.size(); i++) { |
| Indices.push_back(Idxs[i]); |
| // Get original indices up to ith element for below: |
| auto CumulativeOldIdxs = ArrayRef<Value *>(Idxs.begin(), Idxs.begin() + i); |
| auto IndexedTy = GetElementPtrInst::getIndexedType(Ty, CumulativeOldIdxs); |
| if (getEquivalentType(IndexedTy)) { |
| auto Idx = Indices.pop_back_val(); |
| if (auto STy = dyn_cast<StructType>( |
| GetElementPtrInst::getIndexedType(Ty, CumulativeOldIdxs))) { |
| auto Cst = dyn_cast<ConstantInt>(Idx); |
| if (!Cst) { |
| llvm_unreachable("unexpected index for gep on struct type"); |
| } |
| auto id = Cst->getZExtValue(); |
| auto off = DL->getStructLayout(STy)->getElementOffset(id); |
| auto newId = |
| DL->getStructLayout(dyn_cast<StructType>(getEquivalentType(STy))) |
| ->getElementContainingOffset(off); |
| Indices.push_back(ConstantInt::get(Idx->getType(), newId)); |
| } else { |
| Indices.push_back(Idx); |
| } |
| } |
| } |
| } |
| |
| Value * |
| clspv::LongVectorLoweringPass::visitGetElementPtrInst(GetElementPtrInst &I) { |
| auto *EquivalentPointer = I.getPointerOperand(); |
| auto *Type = getEquivalentType(I.getSourceElementType()); |
| if (!Type) { |
| return nullptr; |
| } else { |
| // For an opaque pointer check if the pass rewrote the pointer already and |
| // use the value if it did. This occurs with global variables |
| auto *tmp = visit(I.getPointerOperand()); |
| if (tmp) { |
| EquivalentPointer = tmp; |
| } |
| } |
| |
| IRBuilder<> B(&I); |
| SmallVector<Value *, 4> Indices(I.indices()); |
| reworkIndices(Indices, I.getSourceElementType()); |
| |
| Value *V; |
| if (I.isInBounds()) { |
| V = B.CreateInBoundsGEP(Type, EquivalentPointer, Indices); |
| } else { |
| V = B.CreateGEP(Type, EquivalentPointer, Indices); |
| } |
| registerReplacement(I, *V); |
| return V; |
| } |
| |
| Value * |
| clspv::LongVectorLoweringPass::visitInsertElementInst(InsertElementInst &I) { |
| Value *EquivalentValue = visit(I.getOperand(0)); |
| assert(EquivalentValue && "value not lowered"); |
| |
| Value *ScalarElement = I.getOperand(1); |
| assert(ScalarElement->getType()->isIntegerTy() || |
| ScalarElement->getType()->isFloatingPointTy()); |
| |
| ConstantInt *CI = dyn_cast<ConstantInt>(I.getOperand(2)); |
| |
| IRBuilder<> B(&I); |
| if (CI) { |
| unsigned Index = CI->getZExtValue(); |
| |
| auto *V = B.CreateInsertValue(EquivalentValue, ScalarElement, {Index}); |
| registerReplacement(I, *V); |
| return V; |
| } |
| |
| // Extract array pointer |
| Value *PtrOperand = nullptr; |
| if (auto *Load = dyn_cast<LoadInst>(EquivalentValue)) { |
| PtrOperand = Load->getPointerOperand(); |
| } else { |
| IRBuilder<> BFront(&I.getFunction()->front().front()); |
| |
| PtrOperand = BFront.CreateAlloca(EquivalentValue->getType()); |
| B.CreateStore(EquivalentValue, PtrOperand); |
| } |
| |
| auto *GEP = B.CreateInBoundsGEP(EquivalentValue->getType(), PtrOperand, |
| {B.getInt32(0), I.getOperand(2)}); |
| B.CreateStore(ScalarElement, GEP); |
| registerReplacement(I, *EquivalentValue); |
| return EquivalentValue; |
| } |
| |
| Value *clspv::LongVectorLoweringPass::visitInsertValueInst(InsertValueInst &I) { |
| Value *EquivalentAggregate = visitOrSelf(I.getOperand(0)); |
| Value *EquivalentInsertValue = visitOrSelf(I.getOperand(1)); |
| |
| if (EquivalentAggregate == I.getOperand(0) && |
| EquivalentInsertValue == I.getOperand(1)) // Nothing lowered |
| return nullptr; |
| |
| SmallVector<unsigned, 4> Idxs(I.indices()); |
| reworkIndices(Idxs, I.getOperand(0)->getType()); |
| |
| IRBuilder<> B(&I); |
| Value *V = |
| B.CreateInsertValue(EquivalentAggregate, EquivalentInsertValue, Idxs); |
| registerReplacement(I, *V); |
| |
| return V; |
| } |
| |
| Value *clspv::LongVectorLoweringPass::visitLoadInst(LoadInst &I) { |
| Type *EquivalentTy = getEquivalentType(I.getType()); |
| assert(EquivalentTy && "type not lowered"); |
| auto *EquivalentPointer = I.getPointerOperand(); |
| // For an opaque pointer check if the pass rewrote the pointer already and |
| // use the value if it did. This occurs with global variables |
| auto *tmp = visit(I.getPointerOperand()); |
| if (tmp) { |
| EquivalentPointer = tmp; |
| } |
| |
| IRBuilder<> B(&I); |
| auto *V = B.CreateAlignedLoad(EquivalentTy, EquivalentPointer, I.getAlign(), |
| I.isVolatile()); |
| registerReplacement(I, *V); |
| return V; |
| } |
| |
| Value *clspv::LongVectorLoweringPass::visitPHINode(PHINode &I) { |
| llvm_unreachable("PHI should be handled elsewhere"); |
| } |
| |
| Value *clspv::LongVectorLoweringPass::visitSelectInst(SelectInst &I) { |
| auto *EquivalentCondition = visitOrSelf(I.getCondition()); |
| auto *EquivalentTrueValue = visitOrSelf(I.getTrueValue()); |
| auto *EquivalentFalseValue = visitOrSelf(I.getFalseValue()); |
| |
| assert(((EquivalentCondition != I.getCondition()) || |
| (EquivalentTrueValue != I.getTrueValue()) || |
| (EquivalentFalseValue != I.getFalseValue())) && |
| "nothing to lower"); |
| |
| auto *EquivalentReturnTy = EquivalentTrueValue->getType(); |
| assert(EquivalentFalseValue->getType() == EquivalentReturnTy); |
| |
| // We have two cases to handle here: |
| // - when the condition is a scalar to select one of the two long-vector |
| // alternatives. In this case, we would ideally create a single select |
| // instruction. However, the SPIR-V producer does not yet handle aggregate |
| // selections correctly. Therefore, we scalarise the selection when |
| // vectors/aggregates are involved. |
| // - when the condition is a long-vector, too. In this case, we do an |
| // element-wise select and construct an aggregate for the result. |
| Value *V = nullptr; |
| if (EquivalentCondition->getType()->isSingleValueType()) { |
| assert(EquivalentTrueValue->getType()->isAggregateType()); |
| assert(EquivalentFalseValue->getType()->isAggregateType()); |
| |
| std::array<Value *, 2> EquivalentArgs{{ |
| EquivalentTrueValue, |
| EquivalentFalseValue, |
| }}; |
| auto ScalarFactory = [EquivalentCondition](auto &B, auto Args) { |
| assert(Args.size() == 2); |
| return B.CreateSelect(EquivalentCondition, Args[0], Args[1]); |
| }; |
| |
| V = convertVectorOperation(I, EquivalentReturnTy, EquivalentArgs, |
| ScalarFactory); |
| } else { |
| assert(EquivalentCondition->getType()->isAggregateType()); |
| |
| std::array<Value *, 3> EquivalentArgs{{ |
| EquivalentCondition, |
| EquivalentTrueValue, |
| EquivalentFalseValue, |
| }}; |
| |
| auto ScalarFactory = [](auto &B, auto Args) { |
| assert(Args.size() == 3); |
| return B.CreateSelect(Args[0], Args[1], Args[2]); |
| }; |
| V = convertVectorOperation(I, EquivalentReturnTy, EquivalentArgs, |
| ScalarFactory); |
| } |
| |
| assert(V); |
| registerReplacement(I, *V); |
| return V; |
| } |
| |
| Value * |
| clspv::LongVectorLoweringPass::visitShuffleVectorInst(ShuffleVectorInst &I) { |
| assert(isa<FixedVectorType>(I.getType()) && |
| "shufflevector on scalable vectors is not supported."); |
| |
| auto *EquivalentLHS = visitOrSelf(I.getOperand(0)); |
| auto *EquivalentRHS = visitOrSelf(I.getOperand(1)); |
| auto *EquivalentType = getEquivalentTypeOrSelf(I.getType()); |
| |
| assert(((EquivalentLHS != I.getOperand(0)) || |
| (EquivalentRHS != I.getOperand(1)) || |
| (EquivalentType != I.getType())) && |
| "nothing to lower"); |
| |
| IRBuilder<> B(&I); |
| |
| // The arguments (LHS and RHS) could be either short-vector or long-vector |
| // types. The latter are already lowered to an aggregate type. |
| // |
| // Extract the scalar at the given index using the appropriate method. |
| auto getScalar = [&B](Value *Vector, unsigned Index) { |
| if (Vector->getType()->isVectorTy()) { |
| return B.CreateExtractElement(Vector, Index); |
| } else { |
| assert(Vector->getType()->isArrayTy()); |
| return B.CreateExtractValue(Vector, Index); |
| } |
| }; |
| |
| // The resulting value could be a short or a long vector as well. |
| auto setScalar = [&B](Value *Vector, Value *Scalar, unsigned Index) { |
| if (Vector->getType()->isVectorTy()) { |
| return B.CreateInsertElement(Vector, Scalar, Index); |
| } else { |
| assert(Vector->getType()->isArrayTy()); |
| return B.CreateInsertValue(Vector, Scalar, Index); |
| } |
| }; |
| |
| unsigned Arity = I.getShuffleMask().size(); |
| auto *ScalarTy = I.getType()->getElementType(); |
| |
| auto *LHSTy = cast<VectorType>(I.getOperand(0)->getType()); |
| assert(!LHSTy->getElementCount().isScalable() && "broken assumption"); |
| unsigned LHSArity = LHSTy->getElementCount().getFixedValue(); |
| |
| // Construct the equivalent shuffled vector, as an array or a vector. |
| Value *V = PoisonValue::get(EquivalentType); |
| for (unsigned i = 0; i < Arity; ++i) { |
| int Mask = I.getMaskValue(i); |
| assert(-1 <= Mask && "Unexpected mask value."); |
| |
| Value *Scalar = nullptr; |
| if (Mask == -1) { |
| Scalar = PoisonValue::get(ScalarTy); |
| } else if (static_cast<unsigned>(Mask) < LHSArity) { |
| Scalar = getScalar(EquivalentLHS, Mask); |
| } else { |
| Scalar = getScalar(EquivalentRHS, Mask - LHSArity); |
| } |
| |
| V = setScalar(V, Scalar, i); |
| } |
| |
| registerReplacement(I, *V); |
| return V; |
| } |
| |
| Value *clspv::LongVectorLoweringPass::visitStoreInst(StoreInst &I) { |
| Value *EquivalentValue = visit(I.getValueOperand()); |
| assert(EquivalentValue && "value not lowered"); |
| Value *EquivalentPointer = I.getPointerOperand(); |
| // For an opaque pointer check if the pass rewrote the pointer already and |
| // use the value if it did. This occurs with global variables |
| auto *tmp = visit(I.getPointerOperand()); |
| if (tmp) { |
| EquivalentPointer = tmp; |
| } |
| |
| IRBuilder<> B(&I); |
| auto *V = B.CreateAlignedStore(EquivalentValue, EquivalentPointer, |
| I.getAlign(), I.isVolatile()); |
| registerReplacement(I, *V); |
| return V; |
| } |
| |
| Value *clspv::LongVectorLoweringPass::visitUnaryOperator(UnaryOperator &I) { |
| return visitNAryOperator(I); |
| } |
| |
| bool clspv::LongVectorLoweringPass::handlingRequired(User &U) { |
| if (getEquivalentType(U.getType()) != nullptr) { |
| return true; |
| } |
| |
| for (auto &Operand : U.operands()) { |
| auto *OperandTy = Operand.get()->getType(); |
| if (getEquivalentType(OperandTy) != nullptr) { |
| return true; |
| } |
| } |
| |
| // With opaque pointers, some users require special examination. |
| if (auto *alloca = dyn_cast<AllocaInst>(&U)) { |
| if (getEquivalentType(alloca->getAllocatedType()) != nullptr) |
| return true; |
| } else if (auto *gep = dyn_cast<GetElementPtrInst>(&U)) { |
| if (getEquivalentType(gep->getSourceElementType()) != nullptr) |
| return true; |
| } else if (auto *ptr = dyn_cast<PtrToIntInst>(&U)) { |
| if (visit(ptr->getPointerOperand()) != nullptr) |
| return true; |
| } else if (auto *ptr = dyn_cast<PtrToAddrInst>(&U)) { |
| if (visit(ptr->getPointerOperand()) != nullptr) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void clspv::LongVectorLoweringPass::registerReplacement(Value &U, Value &V) { |
| LLVM_DEBUG(dbgs() << "Replacement for " << U << ": " << V << '\n'); |
| assert(ValueMap.count(&U) == 0 && "Value already registered"); |
| ValueMap.insert({&U, &V}); |
| |
| if (U.getType() == V.getType()) { |
| LLVM_DEBUG(dbgs() << "\tAnd replace its usages.\n"); |
| U.replaceAllUsesWith(&V); |
| } |
| |
| if (U.hasName()) { |
| V.takeName(&U); |
| } |
| |
| auto *I = dyn_cast<Instruction>(&U); |
| auto *J = dyn_cast<Instruction>(&V); |
| if (I && J) { |
| J->copyMetadata(*I); |
| } |
| } |
| |
| Type *clspv::LongVectorLoweringPass::getEquivalentType(Type *Ty) { |
| auto it = TypeMap.find(Ty); |
| if (it != TypeMap.end()) { |
| return it->second; |
| } |
| |
| // Recursive implementation, taking advantage of the cache. |
| auto *EquivalentTy = getEquivalentTypeImpl(Ty); |
| TypeMap.insert({Ty, EquivalentTy}); |
| |
| if (EquivalentTy) { |
| LLVM_DEBUG(dbgs() << "Generating equivalent type for " << *Ty << ": " |
| << *EquivalentTy << '\n'); |
| } |
| |
| return EquivalentTy; |
| } |
| |
| Type *clspv::LongVectorLoweringPass::getEquivalentTypeImpl(Type *Ty) { |
| if (Ty->isIntegerTy() || Ty->isFloatingPointTy() || Ty->isVoidTy() || |
| Ty->isLabelTy() || Ty->isMetadataTy() || Ty->isPointerTy() || |
| Ty->isTargetExtTy()) { |
| // No lowering required. |
| return nullptr; |
| } |
| |
| if (auto *VectorTy = dyn_cast<VectorType>(Ty)) { |
| unsigned VecWidth = VectorTy->getElementCount().getKnownMinValue(); |
| // Larger vector types can be produced by optimizations like InstCombine, |
| // these will be handled in our UndoInstCombine pass. |
| bool RequireLowering = (VecWidth >= 8 && VecWidth <= 16); |
| |
| if (RequireLowering) { |
| assert(!VectorTy->getElementCount().isScalable() && |
| "Unsupported scalable vector"); |
| |
| // This assumes that the element type of the vector is a primitive scalar. |
| // That is, no vectors of pointers for example. |
| Type *ScalarTy = VectorTy->getElementType(); |
| assert((ScalarTy->isFloatingPointTy() || ScalarTy->isIntegerTy()) && |
| "Unsupported scalar type"); |
| |
| return ArrayType::get(ScalarTy, VecWidth); |
| } |
| |
| return nullptr; |
| } |
| |
| if (auto *ArrayTy = dyn_cast<ArrayType>(Ty)) { |
| if (auto *ElementTy = getEquivalentType(ArrayTy->getElementType())) { |
| return ArrayType::get(ElementTy, ArrayTy->getNumElements()); |
| } |
| |
| return nullptr; |
| } |
| |
| if (auto *StructTy = dyn_cast<StructType>(Ty)) { |
| unsigned Arity = StructTy->getStructNumElements(); |
| if (Arity == 0) |
| return nullptr; |
| LLVMContext &Ctx = StructTy->getContainedType(0)->getContext(); |
| SmallVector<Type *, 16> Types; |
| bool RequiredLowering = false; |
| bool Packed = StructTy->isPacked(); |
| for (unsigned i = 0; i < Arity; ++i) { |
| Type *CTy = StructTy->getContainedType(i); |
| auto *EquivalentTy = getEquivalentType(CTy); |
| if (EquivalentTy != nullptr) { |
| Types.push_back(EquivalentTy); |
| RequiredLowering = true; |
| |
| auto InitialOff = DL->getStructLayout(StructTy)->getElementOffset(i); |
| auto NewOff = DL->getStructLayout(StructType::get(Ctx, Types, Packed)) |
| ->getElementOffset(Types.size() - 1); |
| if (InitialOff != NewOff) { |
| Types.pop_back(); |
| Types.push_back(getPaddingArray(Ctx, InitialOff - NewOff)); |
| Types.push_back(EquivalentTy); |
| } |
| } else { |
| Types.push_back(CTy); |
| } |
| } |
| |
| if (RequiredLowering) { |
| auto InitialSize = DL->getTypeAllocSize(StructTy); |
| auto NewSize = DL->getTypeAllocSize(StructType::get(Ctx, Types, Packed)); |
| if (InitialSize != NewSize) { |
| Types.push_back(getPaddingArray(Ctx, InitialSize - NewSize)); |
| } |
| return StructType::get(Ctx, Types, Packed); |
| } else { |
| return nullptr; |
| } |
| } |
| |
| if (auto *FunctionTy = dyn_cast<FunctionType>(Ty)) { |
| assert(!FunctionTy->isVarArg() && "VarArgs not supported"); |
| |
| bool RequireLowering = false; |
| |
| // Convert parameter types. |
| SmallVector<Type *, 16> EquivalentParamTys; |
| EquivalentParamTys.reserve(FunctionTy->getNumParams()); |
| for (auto *ParamTy : FunctionTy->params()) { |
| auto *EquivalentParamTy = getEquivalentTypeOrSelf(ParamTy); |
| EquivalentParamTys.push_back(EquivalentParamTy); |
| RequireLowering |= (EquivalentParamTy != ParamTy); |
| } |
| |
| // Convert return type. |
| auto *ReturnTy = FunctionTy->getReturnType(); |
| auto *EquivalentReturnTy = getEquivalentTypeOrSelf(ReturnTy); |
| RequireLowering |= (EquivalentReturnTy != ReturnTy); |
| |
| if (RequireLowering) { |
| return FunctionType::get(EquivalentReturnTy, EquivalentParamTys, |
| FunctionTy->isVarArg()); |
| } else { |
| return nullptr; |
| } |
| } |
| |
| #ifndef NDEBUG |
| dbgs() << "Unsupported type: " << *Ty << '\n'; |
| #endif |
| llvm_unreachable("Unsupported kind of Type."); |
| } |
| |
| bool clspv::LongVectorLoweringPass::runOnGlobals(Module &M) { |
| assert(GlobalVariableMap.empty()); |
| |
| // Iterate over the globals, generate equivalent ones when needed. Insert the |
| // new globals before the existing one in the module's list to avoid visiting |
| // it again. |
| for (auto &GV : M.globals()) { |
| if (auto *EquivalentTy = getEquivalentType(GV.getValueType())) { |
| Constant *EquivalentInitializer = nullptr; |
| if (GV.hasInitializer()) { |
| auto *Initializer = GV.getInitializer(); |
| EquivalentInitializer = cast<Constant>(visitConstant(*Initializer)); |
| } |
| |
| auto *EquivalentGV = new GlobalVariable( |
| M, EquivalentTy, GV.isConstant(), GV.getLinkage(), |
| EquivalentInitializer, "", |
| /* insert before: */ &GV, GV.getThreadLocalMode(), |
| GV.getAddressSpace(), GV.isExternallyInitialized()); |
| EquivalentGV->takeName(&GV); |
| EquivalentGV->setAlignment(GV.getAlign()); |
| EquivalentGV->copyMetadata(&GV, /* offset: */ 0); |
| EquivalentGV->copyAttributesFrom(&GV); |
| |
| LLVM_DEBUG(dbgs() << "Mapping global variable:\n\toriginal: " << GV |
| << "\n\toriginal type: " << *(GV.getValueType()) |
| << "\n\treplacement: " << *EquivalentGV |
| << "\n\treplacement type: " << *EquivalentTy << "\n"); |
| |
| GlobalVariableMap.insert({&GV, EquivalentGV}); |
| } |
| } |
| |
| bool Modified = !GlobalVariableMap.empty(); |
| return Modified; |
| } |
| |
| bool clspv::LongVectorLoweringPass::runOnFunction(Function &F) { |
| LLVM_DEBUG(dbgs() << "Processing " << F.getName() << '\n'); |
| |
| // Skip declarations. |
| if (F.isDeclaration()) { |
| return false; |
| } |
| |
| // Lower the function parameters and return type if needed. |
| // It is possible the function was already partially processed when visiting a |
| // call site. If this is the case, a wrapper function has been created for it. |
| // However, its instructions haven't been visited yet. |
| Function *FunctionToVisit = convertUserDefinedFunction(F); |
| if (FunctionToVisit == nullptr) { |
| // The parameters don't rely on long vectors, but maybe some instructions in |
| // the function body do. |
| FunctionToVisit = &F; |
| } |
| |
| bool Modified = (FunctionToVisit != &F); |
| // First, replace PHINodes that need modified with placeholders. |
| for (Instruction &I : instructions(FunctionToVisit)) { |
| if (auto *phi = dyn_cast<PHINode>(&I)) { |
| auto *equivalent_ty = getEquivalentType(phi->getType()); |
| if (equivalent_ty && equivalent_ty != phi->getType()) { |
| IRBuilder<> b(phi); |
| auto *new_phi = b.CreatePHI(equivalent_ty, phi->getNumIncomingValues()); |
| registerReplacement(*phi, *new_phi); |
| Modified = true; |
| } |
| } |
| } |
| for (Instruction &I : instructions(FunctionToVisit)) { |
| // Use the Value overload of visit to ensure cache is used. |
| Modified |= (visit(static_cast<Value *>(&I)) != nullptr); |
| } |
| // Finally, update placeholder PHINodes with correct incoming values. |
| for (Instruction &I : instructions(FunctionToVisit)) { |
| if (auto *phi = dyn_cast<PHINode>(&I)) { |
| auto *equivalent_ty = getEquivalentType(phi->getType()); |
| if (equivalent_ty && equivalent_ty != phi->getType()) { |
| auto *replacement = cast<PHINode>(ValueMap[phi]); |
| const auto num_incoming = phi->getNumIncomingValues(); |
| for (unsigned i = 0; i < num_incoming; ++i) { |
| auto *block = phi->getIncomingBlock(0); |
| auto *val = visitOrSelf(phi->getIncomingValue(0)); |
| replacement->addIncoming(val, block); |
| phi->removeIncomingValue(block, false); |
| } |
| } |
| } |
| } |
| |
| cleanDeadInstructions(); |
| |
| LLVM_DEBUG(dbgs() << "Final version for " << F.getName() << '\n'); |
| LLVM_DEBUG(dbgs() << *FunctionToVisit << '\n'); |
| |
| return Modified; |
| } |
| |
| Value *clspv::LongVectorLoweringPass::convertBuiltinCall( |
| CallInst &VectorCall, Type *EquivalentReturnTy, |
| ArrayRef<Value *> EquivalentArgs) { |
| Function *VectorFunction = VectorCall.getCalledFunction(); |
| assert(VectorFunction); |
| |
| // Use and update the FunctionMap cache. |
| Function *ScalarFunction = FunctionMap[VectorFunction]; |
| if (ScalarFunction == nullptr) { |
| // Handle both OpenCL builtin functions, available as simple declarations, |
| // and LLVM intrinsics. |
| auto getter = VectorFunction->isIntrinsic() ? getIntrinsicScalarVersion |
| : getBIFScalarVersion; |
| ScalarFunction = getter(*VectorFunction); |
| FunctionMap[VectorFunction] = ScalarFunction; |
| } |
| assert(ScalarFunction); |
| |
| auto ScalarFactory = [ScalarFunction, &VectorCall](auto &B, auto Args) { |
| CallInst *ScalarCall = B.CreateCall(ScalarFunction, Args); |
| ScalarCall->copyIRFlags(&VectorCall); |
| ScalarCall->copyMetadata(VectorCall); |
| ScalarCall->setCallingConv(VectorCall.getCallingConv()); |
| return ScalarCall; |
| }; |
| |
| auto *ScalarPointerDataTy = getScalarPointerType(*VectorFunction); |
| return convertVectorOperation(VectorCall, EquivalentReturnTy, EquivalentArgs, |
| ScalarFactory, ScalarPointerDataTy); |
| } |
| |
| Value *clspv::LongVectorLoweringPass::convertAllBuiltinCall( |
| CallInst &CI, Type *EquivalentReturnTy, ArrayRef<Value *> EquivalentArgs) { |
| Function *Builtin = CI.getCalledFunction(); |
| assert(Builtin); |
| const auto &Info = clspv::Builtins::Lookup(Builtin); |
| |
| switch (Info.getType()) { |
| default: |
| return convertBuiltinCall(CI, EquivalentReturnTy, EquivalentArgs); |
| case clspv::Builtins::kShuffle: { |
| auto Src = EquivalentArgs[0]; |
| auto Mask = EquivalentArgs[1]; |
| return convertBuiltinShuffle2(CI, EquivalentReturnTy, Src, Src, Mask); |
| } |
| case clspv::Builtins::kShuffle2: { |
| auto SrcA = EquivalentArgs[0]; |
| auto SrcB = EquivalentArgs[1]; |
| auto Mask = EquivalentArgs[2]; |
| return convertBuiltinShuffle2(CI, EquivalentReturnTy, SrcA, SrcB, Mask); |
| } |
| } |
| } |
| |
| Value *clspv::LongVectorLoweringPass::convertBuiltinShuffle2( |
| CallInst &CI, Type *EquivalentReturnTy, Value *SrcA, Value *SrcB, |
| Value *Mask) { |
| auto MaskTy = Mask->getType(); |
| unsigned MaskArity; |
| unsigned MaskElementSizeInBits; |
| if (MaskTy->isVectorTy()) { |
| MaskArity = cast<FixedVectorType>(MaskTy)->getNumElements(); |
| MaskElementSizeInBits = |
| cast<FixedVectorType>(MaskTy)->getScalarSizeInBits(); |
| } else if (MaskTy->isArrayTy()) { |
| MaskArity = cast<ArrayType>(MaskTy)->getArrayNumElements(); |
| MaskElementSizeInBits = |
| cast<ArrayType>(MaskTy)->getElementType()->getScalarSizeInBits(); |
| } else { |
| llvm_unreachable("unexpected Type for Mask in Shuffle"); |
| } |
| |
| IRBuilder<> B(&CI); |
| IRBuilder<> BFront(&CI.getFunction()->front().front()); |
| |
| bool isShuffle2 = SrcA != SrcB; |
| |
| assert(SrcA->getType() == SrcB->getType()); |
| auto SrcTy = SrcA->getType(); |
| Type *ScalarTy; |
| unsigned NumElements; |
| if (SrcTy->isArrayTy()) { |
| auto SrcArrayTy = cast<ArrayType>(SrcTy); |
| ScalarTy = SrcArrayTy->getElementType(); |
| |
| // Because we cannot ExtractValue at a variable index from an array, we need |
| // to copy it to something where we will be able to load from a variable |
| // index |
| auto *alloca = BFront.CreateAlloca(SrcTy); |
| auto SrcArity = cast<ArrayType>(SrcTy)->getArrayNumElements(); |
| for (uint64_t i = 0; i < SrcArity; i++) { |
| auto Val = B.CreateExtractValue(SrcA, i); |
| auto Gep = B.CreateGEP(alloca->getAllocatedType(), alloca, |
| {B.getInt32(0), B.getInt32(i)}); |
| B.CreateStore(Val, Gep); |
| } |
| SrcA = alloca; |
| |
| if (isShuffle2) { |
| // Because we cannot ExtractValue at a variable index from an array, we |
| // need to copy it to something where we will be able to load from a |
| // variable index |
| alloca = BFront.CreateAlloca(SrcTy); |
| for (uint64_t i = 0; i < SrcArity; i++) { |
| auto Val = B.CreateExtractValue(SrcB, i); |
| auto Gep = B.CreateGEP(alloca->getAllocatedType(), alloca, |
| {B.getInt32(0), B.getInt32(i)}); |
| B.CreateStore(Val, Gep); |
| } |
| SrcB = alloca; |
| } |
| NumElements = SrcTy->getArrayNumElements(); |
| } else { |
| assert(SrcTy->isVectorTy()); |
| ScalarTy = SrcTy->getScalarType(); |
| NumElements = cast<FixedVectorType>(SrcTy)->getNumElements(); |
| } |
| |
| auto getScalar = [&B](Value *Vector, unsigned Index) { |
| if (Vector->getType()->isVectorTy()) { |
| return B.CreateExtractElement(Vector, Index); |
| } else { |
| assert(Vector->getType()->isArrayTy()); |
| return B.CreateExtractValue(Vector, Index); |
| } |
| }; |
| |
| auto getScalarWithIdValue = [&B, &ScalarTy](Value *Vector, Value *Index) { |
| if (Vector->getType()->isVectorTy()) { |
| return B.CreateExtractElement(Vector, Index); |
| } else { |
| assert(isa<AllocaInst>(Vector)); |
| auto gep = B.CreateGEP(cast<AllocaInst>(Vector)->getAllocatedType(), |
| Vector, {B.getInt32(0), Index}); |
| return (Value *)B.CreateLoad(ScalarTy, gep); |
| } |
| }; |
| |
| auto setScalar = [&B](Value *Vector, Value *Scalar, unsigned Index) { |
| if (Vector->getType()->isVectorTy()) { |
| return B.CreateInsertElement(Vector, Scalar, Index); |
| } else { |
| assert(Vector->getType()->isArrayTy()); |
| return B.CreateInsertValue(Vector, Scalar, Index); |
| } |
| }; |
| |
| Value *Res = PoisonValue::get(EquivalentReturnTy); |
| for (unsigned i = 0; i < MaskArity; ++i) { |
| |
| Value *NumElementsVal = B.getIntN(MaskElementSizeInBits, NumElements); |
| Value *Maski = getScalar(Mask, i); |
| Value *Maskimod = B.CreateURem(Maski, NumElementsVal); |
| |
| Value *ScalarA = getScalarWithIdValue(SrcA, Maskimod); |
| Value *Scalar; |
| if (isShuffle2) { |
| Value *ScalarB = getScalarWithIdValue(SrcB, Maskimod); |
| |
| Value *NumElementsValTimes2 = |
| B.getIntN(MaskElementSizeInBits, NumElements * 2); |
| Value *Maskimod2 = B.CreateURem(Maski, NumElementsValTimes2); |
| Value *Cmp = B.CreateCmp(CmpInst::ICMP_SGE, Maskimod2, NumElementsVal); |
| Scalar = B.CreateSelect(Cmp, ScalarB, ScalarA); |
| } else { |
| Scalar = ScalarA; |
| } |
| |
| Res = setScalar(Res, Scalar, i); |
| } |
| return Res; |
| } |
| |
| Value *clspv::LongVectorLoweringPass::convertSpirvOpBuiltinCall( |
| CallInst &VectorCall, Type *EquivalentReturnTy, |
| ArrayRef<Value *> EquivalentArgs) { |
| if (auto *SpirvIdValue = dyn_cast<ConstantInt>(VectorCall.getOperand(0))) { |
| switch (SpirvIdValue->getZExtValue()) { |
| case 154: { // OpAny |
| auto ReduceFactory = [](auto &Builder, auto A, auto B) { |
| return Builder.CreateOr(A, B); |
| }; |
| return convertOpAnyOrAllOperation(VectorCall, EquivalentArgs, |
| ReduceFactory); |
| } |
| case 155: { // OpAll |
| auto ReduceFactory = [](auto &Builder, auto A, auto B) { |
| return Builder.CreateAnd(A, B); |
| }; |
| return convertOpAnyOrAllOperation(VectorCall, EquivalentArgs, |
| ReduceFactory); |
| } |
| default: |
| break; |
| } |
| } |
| return convertBuiltinCall(VectorCall, EquivalentReturnTy, EquivalentArgs); |
| } |
| |
| Function * |
| clspv::LongVectorLoweringPass::convertUserDefinedFunction(Function &F) { |
| auto it = FunctionMap.find(&F); |
| if (it != FunctionMap.end()) { |
| return it->second; |
| } |
| |
| LLVM_DEBUG(dbgs() << "Handling of user defined function:\n"); |
| LLVM_DEBUG(dbgs() << F << '\n'); |
| |
| auto *FunctionTy = F.getFunctionType(); |
| auto *EquivalentFunctionTy = |
| cast_or_null<FunctionType>(getEquivalentType(FunctionTy)); |
| |
| // If no work is needed, mark it as so for future reference and bail out. |
| if (EquivalentFunctionTy == nullptr) { |
| LLVM_DEBUG(dbgs() << "No need of wrapper function\n"); |
| FunctionMap.insert({&F, nullptr}); |
| return nullptr; |
| } |
| |
| Function *EquivalentFunction = |
| createFunctionWithMappedTypes(F, EquivalentFunctionTy, DL); |
| |
| LLVM_DEBUG(dbgs() << "Wrapper function:\n" << *EquivalentFunction << "\n"); |
| |
| // The body of the new function is intentionally not visited right now because |
| // we could be currently visiting a call instruction. Instead, it is being |
| // visited in runOnFunction. This is to ensure the state of the lowering pass |
| // remains valid. |
| FunctionMap.insert({&F, EquivalentFunction}); |
| return EquivalentFunction; |
| } |
| |
| CallInst *clspv::LongVectorLoweringPass::convertUserDefinedFunctionCall( |
| CallInst &Call, ArrayRef<Value *> EquivalentArgs) { |
| Function *Callee = Call.getCalledFunction(); |
| assert(Callee); |
| |
| Function *EquivalentFunction = convertUserDefinedFunction(*Callee); |
| assert(EquivalentFunction); |
| |
| IRBuilder<> B(&Call); |
| CallInst *NewCall = B.CreateCall(EquivalentFunction, EquivalentArgs); |
| |
| NewCall->copyIRFlags(&Call); |
| NewCall->copyMetadata(Call); |
| NewCall->setCallingConv(Call.getCallingConv()); |
| |
| return NewCall; |
| } |
| |
| void clspv::LongVectorLoweringPass::cleanDeadInstructions() { |
| // Collect all instructions that have been replaced by another one, and remove |
| // them from the function. To address dependencies, use a fixed-point |
| // algorithm: |
| // 1. Collect the instructions that have been replaced. |
| // 2. Collect among these instructions the ones which have no uses and remove |
| // them. |
| // 3. Repeat step 2 until no progress is made. |
| |
| // Select instructions that were replaced by another one. |
| // Ignore constants as they are not owned by the module and therefore don't |
| // need to be removed. |
| using WeakInstructions = SmallVector<WeakTrackingVH, 32>; |
| WeakInstructions OldInstructions; |
| for (const auto &Mapping : ValueMap) { |
| if (Mapping.getSecond() != nullptr) { |
| if (auto *OldInstruction = dyn_cast<Instruction>(Mapping.getFirst())) { |
| OldInstructions.push_back(OldInstruction); |
| } else { |
| assert(isa<Constant>(Mapping.getFirst()) && |
| "Only Instruction and Constant are expected in ValueMap"); |
| } |
| } |
| } |
| |
| // Erase any mapping, as they won't be valid anymore. |
| ValueMap.clear(); |
| |
| for (bool Progress = true; Progress;) { |
| std::size_t PreviousSize = OldInstructions.size(); |
| |
| // Identify instructions that are actually dead and can be removed using |
| // RecursivelyDeleteTriviallyDeadInstructions. |
| // Use a third buffer to capture the instructions that are still alive to |
| // avoid mutating OldInstructions while iterating over it. |
| WeakInstructions NextBatch; |
| WeakInstructions TriviallyDeads; |
| partitionInstructions( |
| OldInstructions, |
| [&TriviallyDeads](Instruction *DeadInstruction) { |
| // Additionally, manually remove from the parent instructions with |
| // possible side-effect, generally speaking, such as call or alloca |
| // instructions. Those are not trivially dead. |
| if (isInstructionTriviallyDead(DeadInstruction)) { |
| TriviallyDeads.push_back(DeadInstruction); |
| } else { |
| DeadInstruction->eraseFromParent(); |
| } |
| }, |
| [&NextBatch](Instruction *AliveInstruction) { |
| NextBatch.push_back(AliveInstruction); |
| }); |
| |
| RecursivelyDeleteTriviallyDeadInstructions(TriviallyDeads); |
| |
| // Update OldInstructions for the next iteration of the fixed-point. |
| OldInstructions = std::move(NextBatch); |
| Progress = (OldInstructions.size() < PreviousSize); |
| } |
| |
| #ifndef NDEBUG |
| if (!OldInstructions.empty()) { |
| dbgs() << "These values were expected to be removed:\n"; |
| for (auto ValueHandle : OldInstructions) { |
| dbgs() << '\t' << *ValueHandle << '\n'; |
| } |
| llvm_unreachable("Not all supposedly-dead instruction were removed!"); |
| } |
| #endif |
| } |
| |
| void clspv::LongVectorLoweringPass::cleanDeadFunctions() { |
| // Take into account dependencies between functions when removing them. |
| // First collect all dead functions. |
| using Functions = SmallVector<Function *, 32>; |
| Functions DeadFunctions; |
| for (const auto &Mapping : FunctionMap) { |
| if (Mapping.getSecond() != nullptr) { |
| Function *F = Mapping.getFirst(); |
| DeadFunctions.push_back(F); |
| } |
| } |
| |
| // Erase any mapping, as they won't be valid anymore. |
| FunctionMap.clear(); |
| |
| for (bool Progress = true; Progress;) { |
| std::size_t PreviousSize = DeadFunctions.size(); |
| |
| Functions NextBatch; |
| for (auto *F : DeadFunctions) { |
| bool Dead = F->use_empty(); |
| if (Dead) { |
| LLVM_DEBUG(dbgs() << "Removing " << F->getName() |
| << " from the module.\n"); |
| F->eraseFromParent(); |
| Progress = true; |
| } else { |
| NextBatch.push_back(F); |
| } |
| } |
| |
| DeadFunctions = std::move(NextBatch); |
| Progress = (DeadFunctions.size() < PreviousSize); |
| } |
| |
| assert(DeadFunctions.empty() && |
| "Not all supposedly-dead functions were removed!"); |
| } |
| |
| void clspv::LongVectorLoweringPass::cleanDeadGlobals() { |
| for (auto const &Mapping : GlobalVariableMap) { |
| auto *GV = Mapping.first; |
| GV->removeDeadConstantUsers(); |
| if (GV->getNumUses() == 0) { |
| GV->eraseFromParent(); |
| } |
| } |
| } |