MLIR  19.0.0git
ConvertGPULaunchFuncToVulkanLaunchFunc.cpp
Go to the documentation of this file.
1 //===- ConvertGPULaunchFuncToVulkanLaunchFunc.cpp - MLIR conversion pass --===//
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 // This file implements a pass to convert gpu launch function into a vulkan
10 // launch function. Creates a SPIR-V binary shader from the `spirv::ModuleOp`
11 // using `spirv::serialize` function, attaches binary data and entry point name
12 // as an attributes to vulkan launch call op.
13 //
14 //===----------------------------------------------------------------------===//
15 
17 
22 #include "mlir/IR/Attributes.h"
23 #include "mlir/IR/Builders.h"
24 #include "mlir/IR/BuiltinOps.h"
25 #include "mlir/IR/BuiltinTypes.h"
26 #include "mlir/Pass/Pass.h"
28 
29 namespace mlir {
30 #define GEN_PASS_DEF_CONVERTGPULAUNCHFUNCTOVULKANLAUNCHFUNC
31 #include "mlir/Conversion/Passes.h.inc"
32 } // namespace mlir
33 
34 using namespace mlir;
35 
36 static constexpr const char *kSPIRVBlobAttrName = "spirv_blob";
37 static constexpr const char *kSPIRVEntryPointAttrName = "spirv_entry_point";
38 static constexpr const char *kSPIRVElementTypesAttrName = "spirv_element_types";
39 static constexpr const char *kVulkanLaunch = "vulkanLaunch";
40 
41 namespace {
42 
43 /// A pass to convert gpu launch op to vulkan launch call op, by creating a
44 /// SPIR-V binary shader from `spirv::ModuleOp` using `spirv::serialize`
45 /// function and attaching binary data and entry point name as an attributes to
46 /// created vulkan launch call op.
47 class ConvertGpuLaunchFuncToVulkanLaunchFunc
48  : public impl::ConvertGpuLaunchFuncToVulkanLaunchFuncBase<
49  ConvertGpuLaunchFuncToVulkanLaunchFunc> {
50 public:
51  void runOnOperation() override;
52 
53 private:
54  /// Creates a SPIR-V binary shader from the given `module` using
55  /// `spirv::serialize` function.
56  LogicalResult createBinaryShader(ModuleOp module,
57  std::vector<char> &binaryShader);
58 
59  /// Converts the given `launchOp` to vulkan launch call.
60  void convertGpuLaunchFunc(gpu::LaunchFuncOp launchOp);
61 
62  /// Checks where the given type is supported by Vulkan runtime.
63  bool isSupportedType(Type type) {
64  if (auto memRefType = dyn_cast_or_null<MemRefType>(type)) {
65  auto elementType = memRefType.getElementType();
66  return memRefType.hasRank() &&
67  (memRefType.getRank() >= 1 && memRefType.getRank() <= 3) &&
68  (elementType.isIntOrFloat());
69  }
70  return false;
71  }
72 
73  /// Declares the vulkan launch function. Returns an error if the any type of
74  /// operand is unsupported by Vulkan runtime.
75  LogicalResult declareVulkanLaunchFunc(Location loc,
76  gpu::LaunchFuncOp launchOp);
77 
78 private:
79  /// The number of vulkan launch configuration operands, placed at the leading
80  /// positions of the operand list.
81  static constexpr unsigned kVulkanLaunchNumConfigOperands = 3;
82 };
83 
84 } // namespace
85 
86 void ConvertGpuLaunchFuncToVulkanLaunchFunc::runOnOperation() {
87  bool done = false;
88  getOperation().walk([this, &done](gpu::LaunchFuncOp op) {
89  if (done) {
90  op.emitError("should only contain one 'gpu::LaunchFuncOp' op");
91  return signalPassFailure();
92  }
93  done = true;
94  convertGpuLaunchFunc(op);
95  });
96 
97  // Erase `gpu::GPUModuleOp` and `spirv::Module` operations.
98  for (auto gpuModule :
99  llvm::make_early_inc_range(getOperation().getOps<gpu::GPUModuleOp>()))
100  gpuModule.erase();
101 
102  for (auto spirvModule :
103  llvm::make_early_inc_range(getOperation().getOps<spirv::ModuleOp>()))
104  spirvModule.erase();
105 }
106 
107 LogicalResult ConvertGpuLaunchFuncToVulkanLaunchFunc::declareVulkanLaunchFunc(
108  Location loc, gpu::LaunchFuncOp launchOp) {
109  auto builder = OpBuilder::atBlockEnd(getOperation().getBody());
110 
111  // Workgroup size is written into the kernel. So to properly modelling
112  // vulkan launch, we have to skip local workgroup size configuration here.
113  SmallVector<Type, 8> gpuLaunchTypes(launchOp.getOperandTypes());
114  // The first kVulkanLaunchNumConfigOperands of the gpu.launch_func op are the
115  // same as the config operands for the vulkan launch call op.
116  SmallVector<Type, 8> vulkanLaunchTypes(gpuLaunchTypes.begin(),
117  gpuLaunchTypes.begin() +
118  kVulkanLaunchNumConfigOperands);
119  vulkanLaunchTypes.append(gpuLaunchTypes.begin() +
120  gpu::LaunchOp::kNumConfigOperands,
121  gpuLaunchTypes.end());
122 
123  // Check that all operands have supported types except those for the
124  // launch configuration.
125  for (auto type :
126  llvm::drop_begin(vulkanLaunchTypes, kVulkanLaunchNumConfigOperands)) {
127  if (!isSupportedType(type))
128  return launchOp.emitError() << type << " is unsupported to run on Vulkan";
129  }
130 
131  // Declare vulkan launch function.
132  auto funcType = builder.getFunctionType(vulkanLaunchTypes, {});
133  builder.create<func::FuncOp>(loc, kVulkanLaunch, funcType).setPrivate();
134 
135  return success();
136 }
137 
138 LogicalResult ConvertGpuLaunchFuncToVulkanLaunchFunc::createBinaryShader(
139  ModuleOp module, std::vector<char> &binaryShader) {
140  bool done = false;
142  for (auto spirvModule : module.getOps<spirv::ModuleOp>()) {
143  if (done)
144  return spirvModule.emitError("should only contain one 'spirv.module' op");
145  done = true;
146 
147  if (failed(spirv::serialize(spirvModule, binary)))
148  return failure();
149  }
150  binaryShader.resize(binary.size() * sizeof(uint32_t));
151  std::memcpy(binaryShader.data(), reinterpret_cast<char *>(binary.data()),
152  binaryShader.size());
153  return success();
154 }
155 
156 void ConvertGpuLaunchFuncToVulkanLaunchFunc::convertGpuLaunchFunc(
157  gpu::LaunchFuncOp launchOp) {
158  ModuleOp module = getOperation();
159  OpBuilder builder(launchOp);
160  Location loc = launchOp.getLoc();
161 
162  // Serialize `spirv::Module` into binary form.
163  std::vector<char> binary;
164  if (failed(createBinaryShader(module, binary)))
165  return signalPassFailure();
166 
167  // Declare vulkan launch function.
168  if (failed(declareVulkanLaunchFunc(loc, launchOp)))
169  return signalPassFailure();
170 
171  SmallVector<Value, 8> gpuLaunchOperands(launchOp.getOperands());
172  SmallVector<Value, 8> vulkanLaunchOperands(
173  gpuLaunchOperands.begin(),
174  gpuLaunchOperands.begin() + kVulkanLaunchNumConfigOperands);
175  vulkanLaunchOperands.append(gpuLaunchOperands.begin() +
176  gpu::LaunchOp::kNumConfigOperands,
177  gpuLaunchOperands.end());
178 
179  // Create vulkan launch call op.
180  auto vulkanLaunchCallOp = builder.create<func::CallOp>(
181  loc, TypeRange{}, SymbolRefAttr::get(builder.getContext(), kVulkanLaunch),
182  vulkanLaunchOperands);
183 
184  // Set SPIR-V binary shader data as an attribute.
185  vulkanLaunchCallOp->setAttr(
187  builder.getStringAttr(StringRef(binary.data(), binary.size())));
188 
189  // Set entry point name as an attribute.
190  vulkanLaunchCallOp->setAttr(kSPIRVEntryPointAttrName,
191  launchOp.getKernelName());
192 
193  // Add MemRef element types before they're lost when lowering to LLVM.
194  SmallVector<Type> elementTypes;
195  for (Type type : llvm::drop_begin(launchOp.getOperandTypes(),
196  gpu::LaunchOp::kNumConfigOperands)) {
197  // The below cast always succeeds as it has already been verified in
198  // 'declareVulkanLaunchFunc' that these are MemRefs with compatible element
199  // types.
200  elementTypes.push_back(cast<MemRefType>(type).getElementType());
201  }
202  vulkanLaunchCallOp->setAttr(kSPIRVElementTypesAttrName,
203  builder.getTypeArrayAttr(elementTypes));
204 
205  launchOp.erase();
206 }
207 
208 std::unique_ptr<mlir::OperationPass<mlir::ModuleOp>>
210  return std::make_unique<ConvertGpuLaunchFuncToVulkanLaunchFunc>();
211 }
static constexpr const char * kVulkanLaunch
static constexpr const char * kSPIRVElementTypesAttrName
static constexpr const char * kSPIRVEntryPointAttrName
static constexpr const char * kSPIRVBlobAttrName
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition: Location.h:63
This class helps build Operations.
Definition: Builders.h:209
static OpBuilder atBlockEnd(Block *block, Listener *listener=nullptr)
Create a builder and set the insertion point to after the last operation in the block but still insid...
Definition: Builders.h:248
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
Definition: Operation.cpp:268
This class provides an abstraction over the various different ranges of value types.
Definition: TypeRange.h:36
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition: Types.h:74
LogicalResult serialize(ModuleOp module, SmallVectorImpl< uint32_t > &binary, const SerializationOptions &options={})
Serializes the given SPIR-V module and writes to binary.
Include the generated interface declarations.
LogicalResult failure(bool isFailure=true)
Utility function to generate a LogicalResult.
Definition: LogicalResult.h:62
LogicalResult success(bool isSuccess=true)
Utility function to generate a LogicalResult.
Definition: LogicalResult.h:56
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
std::unique_ptr< OperationPass< mlir::ModuleOp > > createConvertGpuLaunchFuncToVulkanLaunchFuncPass()
bool failed(LogicalResult result)
Utility function that returns true if the provided LogicalResult corresponds to a failure value.
Definition: LogicalResult.h:72
This class represents an efficient way to signal success or failure.
Definition: LogicalResult.h:26