MLIR  21.0.0git
MPIToLLVM.cpp
Go to the documentation of this file.
1 //===- MPIToLLVM.cpp - MPI to LLVM dialect conversion ---------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 //
10 // Copyright (C) by Argonne National Laboratory
11 // See COPYRIGHT in top-level directory
12 // of MPICH source repository.
13 //
14 
18 #include "mlir/Dialect/DLTI/DLTI.h"
22 #include <memory>
23 
24 using namespace mlir;
25 
26 namespace {
27 
28 template <typename Op, typename... Args>
29 static Op getOrDefineGlobal(ModuleOp &moduleOp, const Location loc,
30  ConversionPatternRewriter &rewriter, StringRef name,
31  Args &&...args) {
32  Op ret;
33  if (!(ret = moduleOp.lookupSymbol<Op>(name))) {
34  ConversionPatternRewriter::InsertionGuard guard(rewriter);
35  rewriter.setInsertionPointToStart(moduleOp.getBody());
36  ret = rewriter.template create<Op>(loc, std::forward<Args>(args)...);
37  }
38  return ret;
39 }
40 
41 static LLVM::LLVMFuncOp getOrDefineFunction(ModuleOp &moduleOp,
42  const Location loc,
43  ConversionPatternRewriter &rewriter,
44  StringRef name,
45  LLVM::LLVMFunctionType type) {
46  return getOrDefineGlobal<LLVM::LLVMFuncOp>(
47  moduleOp, loc, rewriter, name, name, type, LLVM::Linkage::External);
48 }
49 
50 /// When lowering the mpi dialect to functions calls certain details
51 /// differ between various MPI implementations. This class will provide
52 /// these in a generic way, depending on the MPI implementation that got
53 /// selected by the DLTI attribute on the module.
54 class MPIImplTraits {
55  ModuleOp &moduleOp;
56 
57 public:
58  /// Instantiate a new MPIImplTraits object according to the DLTI attribute
59  /// on the given module. Default to MPICH if no attribute is present or
60  /// the value is unknown.
61  static std::unique_ptr<MPIImplTraits> get(ModuleOp &moduleOp);
62 
63  explicit MPIImplTraits(ModuleOp &moduleOp) : moduleOp(moduleOp) {}
64 
65  virtual ~MPIImplTraits() = default;
66 
67  ModuleOp &getModuleOp() { return moduleOp; }
68 
69  /// Gets or creates MPI_COMM_WORLD as a Value.
70  virtual Value getCommWorld(const Location loc,
71  ConversionPatternRewriter &rewriter) = 0;
72 
73  /// Get the MPI_STATUS_IGNORE value (typically a pointer type).
74  virtual intptr_t getStatusIgnore() = 0;
75 
76  /// Gets or creates an MPI datatype as a value which corresponds to the given
77  /// type.
78  virtual Value getDataType(const Location loc,
79  ConversionPatternRewriter &rewriter, Type type) = 0;
80 };
81 
82 //===----------------------------------------------------------------------===//
83 // Implementation details for MPICH ABI compatible MPI implementations
84 //===----------------------------------------------------------------------===//
85 
86 class MPICHImplTraits : public MPIImplTraits {
87  static constexpr int MPI_FLOAT = 0x4c00040a;
88  static constexpr int MPI_DOUBLE = 0x4c00080b;
89  static constexpr int MPI_INT8_T = 0x4c000137;
90  static constexpr int MPI_INT16_T = 0x4c000238;
91  static constexpr int MPI_INT32_T = 0x4c000439;
92  static constexpr int MPI_INT64_T = 0x4c00083a;
93  static constexpr int MPI_UINT8_T = 0x4c00013b;
94  static constexpr int MPI_UINT16_T = 0x4c00023c;
95  static constexpr int MPI_UINT32_T = 0x4c00043d;
96  static constexpr int MPI_UINT64_T = 0x4c00083e;
97 
98 public:
99  using MPIImplTraits::MPIImplTraits;
100 
101  ~MPICHImplTraits() override = default;
102 
103  Value getCommWorld(const Location loc,
104  ConversionPatternRewriter &rewriter) override {
105  static constexpr int MPI_COMM_WORLD = 0x44000000;
106  return rewriter.create<LLVM::ConstantOp>(loc, rewriter.getI32Type(),
107  MPI_COMM_WORLD);
108  }
109 
110  intptr_t getStatusIgnore() override { return 1; }
111 
112  Value getDataType(const Location loc, ConversionPatternRewriter &rewriter,
113  Type type) override {
114  int32_t mtype = 0;
115  if (type.isF32())
116  mtype = MPI_FLOAT;
117  else if (type.isF64())
118  mtype = MPI_DOUBLE;
119  else if (type.isInteger(64) && !type.isUnsignedInteger())
120  mtype = MPI_INT64_T;
121  else if (type.isInteger(64))
122  mtype = MPI_UINT64_T;
123  else if (type.isInteger(32) && !type.isUnsignedInteger())
124  mtype = MPI_INT32_T;
125  else if (type.isInteger(32))
126  mtype = MPI_UINT32_T;
127  else if (type.isInteger(16) && !type.isUnsignedInteger())
128  mtype = MPI_INT16_T;
129  else if (type.isInteger(16))
130  mtype = MPI_UINT16_T;
131  else if (type.isInteger(8) && !type.isUnsignedInteger())
132  mtype = MPI_INT8_T;
133  else if (type.isInteger(8))
134  mtype = MPI_UINT8_T;
135  else
136  assert(false && "unsupported type");
137  return rewriter.create<LLVM::ConstantOp>(loc, rewriter.getI32Type(), mtype);
138  }
139 };
140 
141 //===----------------------------------------------------------------------===//
142 // Implementation details for OpenMPI
143 //===----------------------------------------------------------------------===//
144 class OMPIImplTraits : public MPIImplTraits {
145  LLVM::GlobalOp getOrDefineExternalStruct(const Location loc,
146  ConversionPatternRewriter &rewriter,
147  StringRef name,
148  LLVM::LLVMStructType type) {
149 
150  return getOrDefineGlobal<LLVM::GlobalOp>(
151  getModuleOp(), loc, rewriter, name, type, /*isConstant=*/false,
152  LLVM::Linkage::External, name,
153  /*value=*/Attribute(), /*alignment=*/0, 0);
154  }
155 
156 public:
157  using MPIImplTraits::MPIImplTraits;
158 
159  ~OMPIImplTraits() override = default;
160 
161  Value getCommWorld(const Location loc,
162  ConversionPatternRewriter &rewriter) override {
163  auto context = rewriter.getContext();
164  // get external opaque struct pointer type
165  auto commStructT =
166  LLVM::LLVMStructType::getOpaque("ompi_communicator_t", context);
167  StringRef name = "ompi_mpi_comm_world";
168 
169  // make sure global op definition exists
170  getOrDefineExternalStruct(loc, rewriter, name, commStructT);
171 
172  // get address of symbol
173  return rewriter.create<LLVM::AddressOfOp>(
174  loc, LLVM::LLVMPointerType::get(context),
175  SymbolRefAttr::get(context, name));
176  }
177 
178  intptr_t getStatusIgnore() override { return 0; }
179 
180  Value getDataType(const Location loc, ConversionPatternRewriter &rewriter,
181  Type type) override {
182  StringRef mtype;
183  if (type.isF32())
184  mtype = "ompi_mpi_float";
185  else if (type.isF64())
186  mtype = "ompi_mpi_double";
187  else if (type.isInteger(64) && !type.isUnsignedInteger())
188  mtype = "ompi_mpi_int64_t";
189  else if (type.isInteger(64))
190  mtype = "ompi_mpi_uint64_t";
191  else if (type.isInteger(32) && !type.isUnsignedInteger())
192  mtype = "ompi_mpi_int32_t";
193  else if (type.isInteger(32))
194  mtype = "ompi_mpi_uint32_t";
195  else if (type.isInteger(16) && !type.isUnsignedInteger())
196  mtype = "ompi_mpi_int16_t";
197  else if (type.isInteger(16))
198  mtype = "ompi_mpi_uint16_t";
199  else if (type.isInteger(8) && !type.isUnsignedInteger())
200  mtype = "ompi_mpi_int8_t";
201  else if (type.isInteger(8))
202  mtype = "ompi_mpi_uint8_t";
203  else
204  assert(false && "unsupported type");
205 
206  auto context = rewriter.getContext();
207  // get external opaque struct pointer type
208  auto commStructT =
209  LLVM::LLVMStructType::getOpaque("ompi_predefined_datatype_t", context);
210  // make sure global op definition exists
211  getOrDefineExternalStruct(loc, rewriter, mtype, commStructT);
212  // get address of symbol
213  return rewriter.create<LLVM::AddressOfOp>(
214  loc, LLVM::LLVMPointerType::get(context),
215  SymbolRefAttr::get(context, mtype));
216  }
217 };
218 
219 std::unique_ptr<MPIImplTraits> MPIImplTraits::get(ModuleOp &moduleOp) {
220  auto attr = dlti::query(*&moduleOp, {"MPI:Implementation"}, true);
221  if (failed(attr))
222  return std::make_unique<MPICHImplTraits>(moduleOp);
223  auto strAttr = dyn_cast<StringAttr>(attr.value());
224  if (strAttr && strAttr.getValue() == "OpenMPI")
225  return std::make_unique<OMPIImplTraits>(moduleOp);
226  if (!strAttr || strAttr.getValue() != "MPICH")
227  moduleOp.emitWarning() << "Unknown \"MPI:Implementation\" value in DLTI ("
228  << strAttr.getValue() << "), defaulting to MPICH";
229  return std::make_unique<MPICHImplTraits>(moduleOp);
230 }
231 
232 //===----------------------------------------------------------------------===//
233 // InitOpLowering
234 //===----------------------------------------------------------------------===//
235 
236 struct InitOpLowering : public ConvertOpToLLVMPattern<mpi::InitOp> {
238 
239  LogicalResult
240  matchAndRewrite(mpi::InitOp op, OpAdaptor adaptor,
241  ConversionPatternRewriter &rewriter) const override {
242  Location loc = op.getLoc();
243 
244  // ptrType `!llvm.ptr`
245  Type ptrType = LLVM::LLVMPointerType::get(rewriter.getContext());
246 
247  // instantiate nullptr `%nullptr = llvm.mlir.zero : !llvm.ptr`
248  auto nullPtrOp = rewriter.create<LLVM::ZeroOp>(loc, ptrType);
249  Value llvmnull = nullPtrOp.getRes();
250 
251  // grab a reference to the global module op:
252  auto moduleOp = op->getParentOfType<ModuleOp>();
253 
254  // LLVM Function type representing `i32 MPI_Init(ptr, ptr)`
255  auto initFuncType =
256  LLVM::LLVMFunctionType::get(rewriter.getI32Type(), {ptrType, ptrType});
257  // get or create function declaration:
258  LLVM::LLVMFuncOp initDecl =
259  getOrDefineFunction(moduleOp, loc, rewriter, "MPI_Init", initFuncType);
260 
261  // replace init with function call
262  rewriter.replaceOpWithNewOp<LLVM::CallOp>(op, initDecl,
263  ValueRange{llvmnull, llvmnull});
264 
265  return success();
266  }
267 };
268 
269 //===----------------------------------------------------------------------===//
270 // FinalizeOpLowering
271 //===----------------------------------------------------------------------===//
272 
273 struct FinalizeOpLowering : public ConvertOpToLLVMPattern<mpi::FinalizeOp> {
275 
276  LogicalResult
277  matchAndRewrite(mpi::FinalizeOp op, OpAdaptor adaptor,
278  ConversionPatternRewriter &rewriter) const override {
279  // get loc
280  Location loc = op.getLoc();
281 
282  // grab a reference to the global module op:
283  auto moduleOp = op->getParentOfType<ModuleOp>();
284 
285  // LLVM Function type representing `i32 MPI_Finalize()`
286  auto initFuncType = LLVM::LLVMFunctionType::get(rewriter.getI32Type(), {});
287  // get or create function declaration:
288  LLVM::LLVMFuncOp initDecl = getOrDefineFunction(
289  moduleOp, loc, rewriter, "MPI_Finalize", initFuncType);
290 
291  // replace init with function call
292  rewriter.replaceOpWithNewOp<LLVM::CallOp>(op, initDecl, ValueRange{});
293 
294  return success();
295  }
296 };
297 
298 //===----------------------------------------------------------------------===//
299 // CommRankOpLowering
300 //===----------------------------------------------------------------------===//
301 
302 struct CommRankOpLowering : public ConvertOpToLLVMPattern<mpi::CommRankOp> {
304 
305  LogicalResult
306  matchAndRewrite(mpi::CommRankOp op, OpAdaptor adaptor,
307  ConversionPatternRewriter &rewriter) const override {
308  // get some helper vars
309  Location loc = op.getLoc();
310  MLIRContext *context = rewriter.getContext();
311  Type i32 = rewriter.getI32Type();
312 
313  // ptrType `!llvm.ptr`
314  Type ptrType = LLVM::LLVMPointerType::get(context);
315 
316  // grab a reference to the global module op:
317  auto moduleOp = op->getParentOfType<ModuleOp>();
318 
319  auto mpiTraits = MPIImplTraits::get(moduleOp);
320  // get MPI_COMM_WORLD
321  Value commWorld = mpiTraits->getCommWorld(loc, rewriter);
322 
323  // LLVM Function type representing `i32 MPI_Comm_rank(ptr, ptr)`
324  auto rankFuncType =
325  LLVM::LLVMFunctionType::get(i32, {commWorld.getType(), ptrType});
326  // get or create function declaration:
327  LLVM::LLVMFuncOp initDecl = getOrDefineFunction(
328  moduleOp, loc, rewriter, "MPI_Comm_rank", rankFuncType);
329 
330  // replace init with function call
331  auto one = rewriter.create<LLVM::ConstantOp>(loc, i32, 1);
332  auto rankptr = rewriter.create<LLVM::AllocaOp>(loc, ptrType, i32, one);
333  auto callOp = rewriter.create<LLVM::CallOp>(
334  loc, initDecl, ValueRange{commWorld, rankptr.getRes()});
335 
336  // load the rank into a register
337  auto loadedRank =
338  rewriter.create<LLVM::LoadOp>(loc, i32, rankptr.getResult());
339 
340  // if retval is checked, replace uses of retval with the results from the
341  // call op
342  SmallVector<Value> replacements;
343  if (op.getRetval())
344  replacements.push_back(callOp.getResult());
345 
346  // replace all uses, then erase op
347  replacements.push_back(loadedRank.getRes());
348  rewriter.replaceOp(op, replacements);
349 
350  return success();
351  }
352 };
353 
354 //===----------------------------------------------------------------------===//
355 // SendOpLowering
356 //===----------------------------------------------------------------------===//
357 
358 struct SendOpLowering : public ConvertOpToLLVMPattern<mpi::SendOp> {
360 
361  LogicalResult
362  matchAndRewrite(mpi::SendOp op, OpAdaptor adaptor,
363  ConversionPatternRewriter &rewriter) const override {
364  // get some helper vars
365  Location loc = op.getLoc();
366  MLIRContext *context = rewriter.getContext();
367  Type i32 = rewriter.getI32Type();
368  Type i64 = rewriter.getI64Type();
369  Value memRef = adaptor.getRef();
370  Type elemType = op.getRef().getType().getElementType();
371 
372  // ptrType `!llvm.ptr`
373  Type ptrType = LLVM::LLVMPointerType::get(context);
374 
375  // grab a reference to the global module op:
376  auto moduleOp = op->getParentOfType<ModuleOp>();
377 
378  // get MPI_COMM_WORLD, dataType and pointer
379  Value dataPtr =
380  rewriter.create<LLVM::ExtractValueOp>(loc, ptrType, memRef, 1);
381  Value offset = rewriter.create<LLVM::ExtractValueOp>(loc, i64, memRef, 2);
382  dataPtr =
383  rewriter.create<LLVM::GEPOp>(loc, ptrType, elemType, dataPtr, offset);
384  Value size = rewriter.create<LLVM::ExtractValueOp>(loc, memRef,
385  ArrayRef<int64_t>{3, 0});
386  size = rewriter.create<LLVM::TruncOp>(loc, i32, size);
387  auto mpiTraits = MPIImplTraits::get(moduleOp);
388  Value dataType = mpiTraits->getDataType(loc, rewriter, elemType);
389  Value commWorld = mpiTraits->getCommWorld(loc, rewriter);
390 
391  // LLVM Function type representing `i32 MPI_send(data, count, datatype, dst,
392  // tag, comm)`
393  auto funcType = LLVM::LLVMFunctionType::get(
394  i32, {ptrType, i32, dataType.getType(), i32, i32, commWorld.getType()});
395  // get or create function declaration:
396  LLVM::LLVMFuncOp funcDecl =
397  getOrDefineFunction(moduleOp, loc, rewriter, "MPI_Send", funcType);
398 
399  // replace op with function call
400  auto funcCall = rewriter.create<LLVM::CallOp>(
401  loc, funcDecl,
402  ValueRange{dataPtr, size, dataType, adaptor.getDest(), adaptor.getTag(),
403  commWorld});
404  if (op.getRetval())
405  rewriter.replaceOp(op, funcCall.getResult());
406  else
407  rewriter.eraseOp(op);
408 
409  return success();
410  }
411 };
412 
413 //===----------------------------------------------------------------------===//
414 // RecvOpLowering
415 //===----------------------------------------------------------------------===//
416 
417 struct RecvOpLowering : public ConvertOpToLLVMPattern<mpi::RecvOp> {
419 
420  LogicalResult
421  matchAndRewrite(mpi::RecvOp op, OpAdaptor adaptor,
422  ConversionPatternRewriter &rewriter) const override {
423  // get some helper vars
424  Location loc = op.getLoc();
425  MLIRContext *context = rewriter.getContext();
426  Type i32 = rewriter.getI32Type();
427  Type i64 = rewriter.getI64Type();
428  Value memRef = adaptor.getRef();
429  Type elemType = op.getRef().getType().getElementType();
430 
431  // ptrType `!llvm.ptr`
432  Type ptrType = LLVM::LLVMPointerType::get(context);
433 
434  // grab a reference to the global module op:
435  auto moduleOp = op->getParentOfType<ModuleOp>();
436 
437  // get MPI_COMM_WORLD, dataType, status_ignore and pointer
438  Value dataPtr =
439  rewriter.create<LLVM::ExtractValueOp>(loc, ptrType, memRef, 1);
440  Value offset = rewriter.create<LLVM::ExtractValueOp>(loc, i64, memRef, 2);
441  dataPtr =
442  rewriter.create<LLVM::GEPOp>(loc, ptrType, elemType, dataPtr, offset);
443  Value size = rewriter.create<LLVM::ExtractValueOp>(loc, memRef,
444  ArrayRef<int64_t>{3, 0});
445  size = rewriter.create<LLVM::TruncOp>(loc, i32, size);
446  auto mpiTraits = MPIImplTraits::get(moduleOp);
447  Value dataType = mpiTraits->getDataType(loc, rewriter, elemType);
448  Value commWorld = mpiTraits->getCommWorld(loc, rewriter);
449  Value statusIgnore = rewriter.create<LLVM::ConstantOp>(
450  loc, i64, mpiTraits->getStatusIgnore());
451  statusIgnore =
452  rewriter.create<LLVM::IntToPtrOp>(loc, ptrType, statusIgnore);
453 
454  // LLVM Function type representing `i32 MPI_Recv(data, count, datatype, dst,
455  // tag, comm)`
456  auto funcType =
457  LLVM::LLVMFunctionType::get(i32, {ptrType, i32, dataType.getType(), i32,
458  i32, commWorld.getType(), ptrType});
459  // get or create function declaration:
460  LLVM::LLVMFuncOp funcDecl =
461  getOrDefineFunction(moduleOp, loc, rewriter, "MPI_Recv", funcType);
462 
463  // replace op with function call
464  auto funcCall = rewriter.create<LLVM::CallOp>(
465  loc, funcDecl,
466  ValueRange{dataPtr, size, dataType, adaptor.getSource(),
467  adaptor.getTag(), commWorld, statusIgnore});
468  if (op.getRetval())
469  rewriter.replaceOp(op, funcCall.getResult());
470  else
471  rewriter.eraseOp(op);
472 
473  return success();
474  }
475 };
476 
477 //===----------------------------------------------------------------------===//
478 // ConvertToLLVMPatternInterface implementation
479 //===----------------------------------------------------------------------===//
480 
481 /// Implement the interface to convert Func to LLVM.
482 struct FuncToLLVMDialectInterface : public ConvertToLLVMPatternInterface {
484  /// Hook for derived dialect interface to provide conversion patterns
485  /// and mark dialect legal for the conversion target.
486  void populateConvertToLLVMConversionPatterns(
487  ConversionTarget &target, LLVMTypeConverter &typeConverter,
488  RewritePatternSet &patterns) const final {
490  }
491 };
492 } // namespace
493 
494 //===----------------------------------------------------------------------===//
495 // Pattern Population
496 //===----------------------------------------------------------------------===//
497 
500  patterns.add<CommRankOpLowering, FinalizeOpLowering, InitOpLowering,
501  SendOpLowering, RecvOpLowering>(converter);
502 }
503 
505  registry.addExtension(+[](MLIRContext *ctx, mpi::MPIDialect *dialect) {
506  dialect->addInterfaces<FuncToLLVMDialectInterface>();
507  });
508 }
Attributes are known-constant values of operations.
Definition: Attributes.h:25
IntegerType getI64Type()
Definition: Builders.cpp:65
IntegerType getI32Type()
Definition: Builders.cpp:63
MLIRContext * getContext() const
Definition: Builders.h:56
This class implements a pattern rewriter for use with ConversionPatterns.
void replaceOp(Operation *op, ValueRange newValues) override
Replace the given operation with the new values.
void eraseOp(Operation *op) override
PatternRewriter hook for erasing a dead operation.
This class describes a specific conversion target.
Utility class for operation conversions targeting the LLVM dialect that match exactly one source oper...
Definition: Pattern.h:148
ConvertOpToLLVMPattern(const LLVMTypeConverter &typeConverter, PatternBenefit benefit=1)
Definition: Pattern.h:160
Base class for dialect interfaces providing translation to LLVM IR.
ConvertToLLVMPatternInterface(Dialect *dialect)
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
bool addExtension(TypeID extensionID, std::unique_ptr< DialectExtensionBase > extension)
Add the given extension to the registry.
Conversion from types to the LLVM IR dialect.
Definition: TypeConverter.h:35
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition: Location.h:66
MLIRContext is the top-level object for a collection of MLIR operations.
Definition: MLIRContext.h:60
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition: Builders.h:429
Operation * create(const OperationState &state)
Creates an operation given the fields represented as an OperationState.
Definition: Builders.cpp:453
This provides public APIs that all operations should have.
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
Definition: PatternMatch.h:554
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition: Types.h:74
bool isF64() const
Definition: Types.cpp:41
bool isF32() const
Definition: Types.cpp:40
bool isUnsignedInteger() const
Return true if this is an unsigned integer type (with the specified width).
Definition: Types.cpp:88
bool isInteger() const
Return true if this is an integer type (with the specified width).
Definition: Types.cpp:56
This class provides an abstraction over the different types of ranges over Values.
Definition: ValueRange.h:381
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition: Value.h:96
Type getType() const
Return the type of this value.
Definition: Value.h:129
NestedPattern Op(FilterFunctionType filter=defaultFilterFunction)
FailureOr< Attribute > query(Operation *op, ArrayRef< DataLayoutEntryKey > keys, bool emitError=false)
Perform a DLTI-query at op, recursively querying each key of keys on query interface-implementing att...
Definition: DLTI.cpp:527
void populateMPIToLLVMConversionPatterns(LLVMTypeConverter &converter, RewritePatternSet &patterns)
Definition: MPIToLLVM.cpp:498
void registerConvertMPIToLLVMInterface(DialectRegistry &registry)
Definition: MPIToLLVM.cpp:504
Include the generated interface declarations.
const FrozenRewritePatternSet & patterns
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
LLVM::LLVMFuncOp getOrDefineFunction(gpu::GPUModuleOp moduleOp, Location loc, OpBuilder &b, StringRef name, LLVM::LLVMFunctionType type)
Find or create an external function declaration in the given module.