1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
|
//===- SPIRVBuiltinHelper.h - Helpers for managing calls to builtins ------===//
//
// The LLVM/SPIR-V Translator
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
// Copyright (c) 2022 The Khronos Group Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal with the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimers.
// Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimers in the documentation
// and/or other materials provided with the distribution.
// Neither the names of The Khronos Group, nor the names of its
// contributors may be used to endorse or promote products derived from this
// Software without specific prior written permission.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH
// THE SOFTWARE.
//
//===----------------------------------------------------------------------===//
//
// This file implements helper functions for adding calls to OpenCL or SPIR-V
// builtin functions, or for rewriting calls to one into calls to the other.
//
//===----------------------------------------------------------------------===//
#ifndef SPIRVBUILTINHELPER_H
#define SPIRVBUILTINHELPER_H
#include "LLVMSPIRVLib.h"
#include "libSPIRV/SPIRVOpCode.h"
#include "libSPIRV/SPIRVType.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/IR/Attributes.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/TypedPointerType.h"
namespace SPIRV {
enum class ManglingRules { None, OpenCL, SPIRV };
namespace detail {
/// This is a helper for triggering the static_assert in mapArg.
template <typename> constexpr bool LegalFnType = false;
} // namespace detail
/// A helper class for changing OpenCL builtin function calls to SPIR-V function
/// calls, or vice versa. Most of the functions will return a reference to the
/// current instance, allowing calls to be chained together, for example:
/// mutateCallInst(CI, NewFuncName)
/// .removeArg(3)
/// .appendArg(translateScope());
///
/// Only when the destuctor of this object is called will the original CallInst
/// be destroyed and replaced with the new CallInst be created.
class BuiltinCallMutator {
// Original call instruction
llvm::CallInst *CI;
// New unmangled function name
std::string FuncName;
// Return type mutator. This needs to be saved, because we can't call it until
// the new instruction is created.
std::function<llvm::Value *(llvm::IRBuilder<> &, llvm::CallInst *)> MutateRet;
typedef decltype(MutateRet) MutateRetFuncTy;
// The attribute list for the new called function.
llvm::AttributeList Attrs;
// The attribute list for the new call instruction.
llvm::AttributeList CallAttrs;
// The return type for the new call instruction.
llvm::Type *ReturnTy;
// The arguments for the new call instruction.
llvm::SmallVector<llvm::Value *, 8> Args;
// The pointer element types for the new call instruction.
llvm::SmallVector<llvm::Type *, 8> PointerTypes;
// The mangler rules to use for the new call instruction.
ManglingRules Rules;
friend class BuiltinCallHelper;
BuiltinCallMutator(
llvm::CallInst *CI, std::string FuncName, ManglingRules Rules,
std::function<std::string(llvm::StringRef)> NameMapFn = nullptr);
// This does the actual work of creating of the new call, and will return the
// new instruction.
llvm::Value *doConversion();
public:
~BuiltinCallMutator() {
if (CI)
doConversion();
}
BuiltinCallMutator(const BuiltinCallMutator &) = delete;
BuiltinCallMutator &operator=(const BuiltinCallMutator &) = delete;
BuiltinCallMutator &operator=(BuiltinCallMutator &&) = delete;
BuiltinCallMutator(BuiltinCallMutator &&);
/// The builder used to generate IR for this call.
llvm::IRBuilder<> Builder;
/// Return the resulting new instruction. It is not possible to use any
/// method on this object after calling this function.
llvm::Value *getMutated() { return doConversion(); }
/// Return the number of arguments currently specified for the new call.
unsigned arg_size() const { return Args.size(); }
/// Get the corresponding argument for the new call.
llvm::Value *getArg(unsigned Index) const { return Args[Index]; }
llvm::Type *getType(unsigned Index) const { return PointerTypes[Index]; }
/// Return the pointer element type of the corresponding index, or nullptr if
/// it is not a pointer.
llvm::Type *getPointerElementType(unsigned Index) const {
if (auto *TPT = llvm::dyn_cast<llvm::TypedPointerType>(PointerTypes[Index]))
return TPT->getElementType();
return nullptr;
}
/// A pair representing both the LLVM value of an argument and its
/// corresponding pointer element type. This type can be constructed from
/// implicit conversion from an LLVM value object (but only if it is not of
/// pointer type), or by the appropriate std::pair type.
struct ValueTypePair : public std::pair<llvm::Value *, llvm::Type *> {
ValueTypePair(llvm::Value *V) : pair(V, V->getType()) {
assert(!V->getType()->isPointerTy() &&
"Must specify a pointer element type if value is a pointer.");
}
ValueTypePair(std::pair<llvm::Value *, llvm::Type *> P) : pair(P) {}
ValueTypePair() = delete;
using pair::pair;
};
/// Use the following arguments as the arguments of the new call, replacing
/// any previous arguments. This version may not be used if any argument is of
/// pointer type.
BuiltinCallMutator &setArgs(llvm::ArrayRef<llvm::Value *> Args);
/// This will replace the return type of the call with a different return
/// type. The second argument is a function that will be called with an
/// IRBuilder parameter and the newly generated function, and will return the
/// value to replace all uses of the original call instruction with. Example
/// usage:
///
/// BuiltinCallMutator Mutator = /* ... */;
/// Mutator.changeReturnType(Int16Ty, [](IRBuilder<> &IRB, CallInst *CI) {
/// return IRB.CreateZExt(CI, Int16Ty);
/// });
BuiltinCallMutator &changeReturnType(llvm::Type *ReturnTy,
MutateRetFuncTy MutateFunc);
/// Insert an argument before the given index.
BuiltinCallMutator &insertArg(unsigned Index, ValueTypePair Arg);
/// Add an argument to the end of the argument list.
BuiltinCallMutator &appendArg(ValueTypePair Arg) {
return insertArg(Args.size(), Arg);
}
/// Replace the argument at the given index with a new value.
BuiltinCallMutator &replaceArg(unsigned Index, ValueTypePair Arg);
/// Remove the argument at the given index.
BuiltinCallMutator &removeArg(unsigned Index);
/// Remove all arguments in a range.
BuiltinCallMutator &removeArgs(unsigned Start, unsigned Len) {
for (unsigned I = 0; I < Len; I++)
removeArg(Start);
return *this;
}
/// Move the argument from the given index to the new index.
BuiltinCallMutator &moveArg(unsigned FromIndex, unsigned ToIndex) {
if (FromIndex == ToIndex)
return *this;
ValueTypePair Pair(Args[FromIndex], getType(FromIndex));
removeArg(FromIndex);
insertArg(ToIndex, Pair);
return *this;
}
/// Use a callback function or lambda to convert an argument to a new value.
/// The expected return type of the lambda is anything that is convertible
/// to ValueTypePair, which could be a single Value* (but only if it is not
/// pointer-typed), or a std::pair<Value *, Type *>. The possible signatures
/// of the function parameter are as follows:
/// ValueTypePair func(IRBuilder<> &Builder, Value *, Type *);
/// ValueTypePair func(IRBuilder<> &Builder, Value *);
/// ValueTypePair func(Value *, Type *);
/// ValueTypePair func(Value *);
///
/// When present, the IRBuilder parameter corresponds to a builder that is set
/// to insert immediately before the new call instruction. The Value parameter
/// corresponds to the argument to be mutated. The Type parameter, when
/// present, will be either a TypedPointerType representing the "true" type of
/// the value, or the argument's type otherwise.
template <typename FnType>
BuiltinCallMutator &mapArg(unsigned Index, FnType Func) {
using namespace llvm;
using std::is_invocable;
IRBuilder<> Builder(CI);
Value *V = Args[Index];
[[maybe_unused]] Type *T = getType(Index);
// Dispatch the function call as appropriate, based on the types that the
// function may be called with.
if constexpr (is_invocable<FnType, IRBuilder<> &, Value *, Type *>::value)
replaceArg(Index, Func(Builder, V, T));
else if constexpr (is_invocable<FnType, IRBuilder<> &, Value *>::value)
replaceArg(Index, Func(Builder, V));
else if constexpr (is_invocable<FnType, Value *, Type *>::value)
replaceArg(Index, Func(V, T));
else if constexpr (is_invocable<FnType, Value *>::value)
replaceArg(Index, Func(V));
else {
// We need a helper value that is always false, but is dependent on the
// template parameter to prevent this static_assert from firing when one
// of the if constexprs above fires.
static_assert(detail::LegalFnType<FnType>,
"mapArg lambda signature is not satisfied");
}
return *this;
}
/// Map all arguments according to the given function, as if mapArg(i, Func)
/// had been called for every argument i.
template <typename FnType> BuiltinCallMutator &mapArgs(FnType Func) {
for (unsigned I = 0, E = Args.size(); I < E; I++)
mapArg(I, Func);
return *this;
}
};
/// A helper class for generating calls to SPIR-V builtins with appropriate name
/// mangling rules. It is expected that transformation passes inherit from this
/// class.
class BuiltinCallHelper {
ManglingRules Rules;
std::function<std::string(llvm::StringRef)> NameMapFn;
protected:
llvm::Module *M = nullptr;
bool UseTargetTypes = false;
public:
/// Initialize details about how to mangle and demangle builtins correctly.
/// The Rules argument selects which name mangler to use for mangling.
/// The NameMapFn function will map type names during demangling; it defaults
/// to the identity function.
explicit BuiltinCallHelper(
ManglingRules Rules,
std::function<std::string(llvm::StringRef)> NameMapFn = nullptr)
: Rules(Rules), NameMapFn(std::move(NameMapFn)) {}
/// Initialize the module that will be operated on. This method must be called
/// before future methods.
void initialize(llvm::Module &M);
/// Return a mutator that will replace the given call instruction with a call
/// to the given function name. The function name will have its name mangled
/// in accordance with the argument types provided to the mutator.
BuiltinCallMutator mutateCallInst(llvm::CallInst *CI, std::string FuncName);
/// Return a mutator that will replace the given call instruction with a call
/// to the given SPIR-V opcode (whose name is used in the lookup map of
/// getSPIRVFuncName).
BuiltinCallMutator mutateCallInst(llvm::CallInst *CI, spv::Op Opcode);
/// Create a call to a SPIR-V builtin function (specified via opcode).
/// The return type and argument types may be TypedPointerType, if the actual
/// LLVM type is a pointer type.
llvm::Value *addSPIRVCall(llvm::IRBuilder<> &Builder, spv::Op Opcode,
llvm::Type *ReturnTy,
llvm::ArrayRef<llvm::Value *> Args,
llvm::ArrayRef<llvm::Type *> ArgTys,
const llvm::Twine &Name = "");
/// Create a call to a SPIR-V builtin function, returning a value and type
/// pair suitable for use in BuiltinCallMutator::replaceArg and similar
/// functions.
BuiltinCallMutator::ValueTypePair
addSPIRVCallPair(llvm::IRBuilder<> &Builder, spv::Op Opcode,
llvm::Type *ReturnTy, llvm::ArrayRef<llvm::Value *> Args,
llvm::ArrayRef<llvm::Type *> ArgTys,
const llvm::Twine &Name = "") {
llvm::Value *V =
addSPIRVCall(Builder, Opcode, ReturnTy, Args, ArgTys, Name);
return BuiltinCallMutator::ValueTypePair(V, ReturnTy);
}
/// Adapt the various SPIR-V image types, for example changing a "spirv.Image"
/// type into a "spirv.SampledImage" type with identical parameters.
///
/// The input type is expected to be a TypedPointerType to either a
/// "spirv.*" or "opencl.*" struct type. In the case of "opencl.*" struct
/// types, it will first convert it into the corresponding "spirv.Image"
/// struct type.
///
/// If the image type does not match OldImageKind, this method will abort.
llvm::Type *adjustImageType(llvm::Type *T, llvm::StringRef OldImageKind,
llvm::StringRef NewImageKind);
/// Create a new type representing a SPIR-V opaque type that takes no
/// parameters (such as sampler types).
///
/// If UseRealType is false, a typed pointer type may be returned; if it is
/// true, a pointer type will be used instead.
llvm::Type *getSPIRVType(spv::Op TypeOpcode, bool UseRealType = false);
/// Create a new type representing a SPIR-V opaque type that takes only an
/// access qualifier (such as pipe types).
///
/// If UseRealType is false, a typed pointer type may be returned; if it is
/// true, a pointer type will be used instead.
llvm::Type *getSPIRVType(spv::Op TypeOpcode, spv::AccessQualifier Access,
bool UseRealType = false);
/// Create a new type representing a SPIR-V opaque type that is an image type
/// of some kind.
///
/// If UseRealType is false, a typed pointer type may be returned; if it is
/// true, a pointer type will be used instead.
llvm::Type *getSPIRVType(spv::Op TypeOpcode, llvm::Type *InnerType,
SPIRVTypeImageDescriptor Desc,
std::optional<spv::AccessQualifier> Access,
bool UseRealType = false);
/// Create a new type representing a SPIR-V opaque type that takes arbitrary
/// parameters.
///
/// If UseRealType is false, a typed pointer type may be returned; if it is
/// true, a pointer type will be used instead.
llvm::Type *getSPIRVType(spv::Op TypeOpcode, llvm::StringRef InnerTypeName,
llvm::ArrayRef<unsigned> Parameters,
bool UseRealType = false);
private:
llvm::SmallVector<llvm::Type *, 4> CachedParameterTypes;
llvm::Function *CachedFunc = nullptr;
public:
BuiltinCallMutator::ValueTypePair getCallValue(llvm::CallInst *CI,
unsigned ArgNo);
llvm::Type *getCallValueType(llvm::CallInst *CI, unsigned ArgNo) {
return getCallValue(CI, ArgNo).second;
}
};
} // namespace SPIRV
#endif // SPIRVBUILTINHELPER_H
|