1 //===- GPUToSPIRV.cpp - GPU to SPIR-V Patterns ----------------------------===//
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 patterns to convert GPU dialect to SPIR-V dialect.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "mlir/Conversion/GPUToSPIRV/GPUToSPIRV.h"
14 #include "mlir/Dialect/GPU/GPUDialect.h"
15 #include "mlir/Dialect/SPIRV/IR/SPIRVDialect.h"
16 #include "mlir/Dialect/SPIRV/IR/SPIRVEnums.h"
17 #include "mlir/Dialect/SPIRV/IR/SPIRVOps.h"
18 #include "mlir/Dialect/SPIRV/IR/TargetAndABI.h"
19 #include "mlir/Dialect/SPIRV/Transforms/SPIRVConversion.h"
20 #include "mlir/IR/BuiltinOps.h"
21 #include "mlir/Transforms/DialectConversion.h"
22 
23 using namespace mlir;
24 
25 static constexpr const char kSPIRVModule[] = "__spv__";
26 
27 namespace {
28 /// Pattern lowering GPU block/thread size/id to loading SPIR-V invocation
29 /// builtin variables.
30 template <typename SourceOp, spirv::BuiltIn builtin>
31 class LaunchConfigConversion : public OpConversionPattern<SourceOp> {
32 public:
33   using OpConversionPattern<SourceOp>::OpConversionPattern;
34 
35   LogicalResult
36   matchAndRewrite(SourceOp op, typename SourceOp::Adaptor adaptor,
37                   ConversionPatternRewriter &rewriter) const override;
38 };
39 
40 /// Pattern lowering subgroup size/id to loading SPIR-V invocation
41 /// builtin variables.
42 template <typename SourceOp, spirv::BuiltIn builtin>
43 class SingleDimLaunchConfigConversion : public OpConversionPattern<SourceOp> {
44 public:
45   using OpConversionPattern<SourceOp>::OpConversionPattern;
46 
47   LogicalResult
48   matchAndRewrite(SourceOp op, typename SourceOp::Adaptor adaptor,
49                   ConversionPatternRewriter &rewriter) const override;
50 };
51 
52 /// This is separate because in Vulkan workgroup size is exposed to shaders via
53 /// a constant with WorkgroupSize decoration. So here we cannot generate a
54 /// builtin variable; instead the information in the `spv.entry_point_abi`
55 /// attribute on the surrounding FuncOp is used to replace the gpu::BlockDimOp.
56 class WorkGroupSizeConversion : public OpConversionPattern<gpu::BlockDimOp> {
57 public:
58   WorkGroupSizeConversion(TypeConverter &typeConverter, MLIRContext *context)
59       : OpConversionPattern(typeConverter, context, /*benefit*/ 10) {}
60 
61   LogicalResult
62   matchAndRewrite(gpu::BlockDimOp op, OpAdaptor adaptor,
63                   ConversionPatternRewriter &rewriter) const override;
64 };
65 
66 /// Pattern to convert a kernel function in GPU dialect within a spv.module.
67 class GPUFuncOpConversion final : public OpConversionPattern<gpu::GPUFuncOp> {
68 public:
69   using OpConversionPattern<gpu::GPUFuncOp>::OpConversionPattern;
70 
71   LogicalResult
72   matchAndRewrite(gpu::GPUFuncOp funcOp, OpAdaptor adaptor,
73                   ConversionPatternRewriter &rewriter) const override;
74 
75 private:
76   SmallVector<int32_t, 3> workGroupSizeAsInt32;
77 };
78 
79 /// Pattern to convert a gpu.module to a spv.module.
80 class GPUModuleConversion final : public OpConversionPattern<gpu::GPUModuleOp> {
81 public:
82   using OpConversionPattern<gpu::GPUModuleOp>::OpConversionPattern;
83 
84   LogicalResult
85   matchAndRewrite(gpu::GPUModuleOp moduleOp, OpAdaptor adaptor,
86                   ConversionPatternRewriter &rewriter) const override;
87 };
88 
89 class GPUModuleEndConversion final
90     : public OpConversionPattern<gpu::ModuleEndOp> {
91 public:
92   using OpConversionPattern::OpConversionPattern;
93 
94   LogicalResult
95   matchAndRewrite(gpu::ModuleEndOp endOp, OpAdaptor adaptor,
96                   ConversionPatternRewriter &rewriter) const override {
97     rewriter.eraseOp(endOp);
98     return success();
99   }
100 };
101 
102 /// Pattern to convert a gpu.return into a SPIR-V return.
103 // TODO: This can go to DRR when GPU return has operands.
104 class GPUReturnOpConversion final : public OpConversionPattern<gpu::ReturnOp> {
105 public:
106   using OpConversionPattern<gpu::ReturnOp>::OpConversionPattern;
107 
108   LogicalResult
109   matchAndRewrite(gpu::ReturnOp returnOp, OpAdaptor adaptor,
110                   ConversionPatternRewriter &rewriter) const override;
111 };
112 
113 /// Pattern to convert a gpu.barrier op into a spv.ControlBarrier op.
114 class GPUBarrierConversion final : public OpConversionPattern<gpu::BarrierOp> {
115 public:
116   using OpConversionPattern::OpConversionPattern;
117 
118   LogicalResult
119   matchAndRewrite(gpu::BarrierOp barrierOp, OpAdaptor adaptor,
120                   ConversionPatternRewriter &rewriter) const override;
121 };
122 
123 } // namespace
124 
125 //===----------------------------------------------------------------------===//
126 // Builtins.
127 //===----------------------------------------------------------------------===//
128 
129 template <typename SourceOp, spirv::BuiltIn builtin>
130 LogicalResult LaunchConfigConversion<SourceOp, builtin>::matchAndRewrite(
131     SourceOp op, typename SourceOp::Adaptor adaptor,
132     ConversionPatternRewriter &rewriter) const {
133   auto *typeConverter = this->template getTypeConverter<SPIRVTypeConverter>();
134   auto indexType = typeConverter->getIndexType();
135 
136   // SPIR-V invocation builtin variables are a vector of type <3xi32>
137   auto spirvBuiltin =
138       spirv::getBuiltinVariableValue(op, builtin, indexType, rewriter);
139   rewriter.replaceOpWithNewOp<spirv::CompositeExtractOp>(
140       op, indexType, spirvBuiltin,
141       rewriter.getI32ArrayAttr({static_cast<int32_t>(op.dimension())}));
142   return success();
143 }
144 
145 template <typename SourceOp, spirv::BuiltIn builtin>
146 LogicalResult
147 SingleDimLaunchConfigConversion<SourceOp, builtin>::matchAndRewrite(
148     SourceOp op, typename SourceOp::Adaptor adaptor,
149     ConversionPatternRewriter &rewriter) const {
150   auto *typeConverter = this->template getTypeConverter<SPIRVTypeConverter>();
151   auto indexType = typeConverter->getIndexType();
152 
153   auto spirvBuiltin =
154       spirv::getBuiltinVariableValue(op, builtin, indexType, rewriter);
155   rewriter.replaceOp(op, spirvBuiltin);
156   return success();
157 }
158 
159 LogicalResult WorkGroupSizeConversion::matchAndRewrite(
160     gpu::BlockDimOp op, OpAdaptor adaptor,
161     ConversionPatternRewriter &rewriter) const {
162   auto workGroupSizeAttr = spirv::lookupLocalWorkGroupSize(op);
163   if (!workGroupSizeAttr)
164     return failure();
165 
166   auto val = workGroupSizeAttr
167                  .getValues<int32_t>()[static_cast<int32_t>(op.dimension())];
168   auto convertedType =
169       getTypeConverter()->convertType(op.getResult().getType());
170   if (!convertedType)
171     return failure();
172   rewriter.replaceOpWithNewOp<spirv::ConstantOp>(
173       op, convertedType, IntegerAttr::get(convertedType, val));
174   return success();
175 }
176 
177 //===----------------------------------------------------------------------===//
178 // GPUFuncOp
179 //===----------------------------------------------------------------------===//
180 
181 // Legalizes a GPU function as an entry SPIR-V function.
182 static spirv::FuncOp
183 lowerAsEntryFunction(gpu::GPUFuncOp funcOp, TypeConverter &typeConverter,
184                      ConversionPatternRewriter &rewriter,
185                      spirv::EntryPointABIAttr entryPointInfo,
186                      ArrayRef<spirv::InterfaceVarABIAttr> argABIInfo) {
187   auto fnType = funcOp.getType();
188   if (fnType.getNumResults()) {
189     funcOp.emitError("SPIR-V lowering only supports entry functions"
190                      "with no return values right now");
191     return nullptr;
192   }
193   if (!argABIInfo.empty() && fnType.getNumInputs() != argABIInfo.size()) {
194     funcOp.emitError(
195         "lowering as entry functions requires ABI info for all arguments "
196         "or none of them");
197     return nullptr;
198   }
199   // Update the signature to valid SPIR-V types and add the ABI
200   // attributes. These will be "materialized" by using the
201   // LowerABIAttributesPass.
202   TypeConverter::SignatureConversion signatureConverter(fnType.getNumInputs());
203   {
204     for (const auto &argType : enumerate(funcOp.getType().getInputs())) {
205       auto convertedType = typeConverter.convertType(argType.value());
206       signatureConverter.addInputs(argType.index(), convertedType);
207     }
208   }
209   auto newFuncOp = rewriter.create<spirv::FuncOp>(
210       funcOp.getLoc(), funcOp.getName(),
211       rewriter.getFunctionType(signatureConverter.getConvertedTypes(),
212                                llvm::None));
213   for (const auto &namedAttr : funcOp->getAttrs()) {
214     if (namedAttr.getName() == FunctionOpInterface::getTypeAttrName() ||
215         namedAttr.getName() == SymbolTable::getSymbolAttrName())
216       continue;
217     newFuncOp->setAttr(namedAttr.getName(), namedAttr.getValue());
218   }
219 
220   rewriter.inlineRegionBefore(funcOp.getBody(), newFuncOp.getBody(),
221                               newFuncOp.end());
222   if (failed(rewriter.convertRegionTypes(&newFuncOp.getBody(), typeConverter,
223                                          &signatureConverter)))
224     return nullptr;
225   rewriter.eraseOp(funcOp);
226 
227   // Set the attributes for argument and the function.
228   StringRef argABIAttrName = spirv::getInterfaceVarABIAttrName();
229   for (auto argIndex : llvm::seq<unsigned>(0, argABIInfo.size())) {
230     newFuncOp.setArgAttr(argIndex, argABIAttrName, argABIInfo[argIndex]);
231   }
232   newFuncOp->setAttr(spirv::getEntryPointABIAttrName(), entryPointInfo);
233 
234   return newFuncOp;
235 }
236 
237 /// Populates `argABI` with spv.interface_var_abi attributes for lowering
238 /// gpu.func to spv.func if no arguments have the attributes set
239 /// already. Returns failure if any argument has the ABI attribute set already.
240 static LogicalResult
241 getDefaultABIAttrs(MLIRContext *context, gpu::GPUFuncOp funcOp,
242                    SmallVectorImpl<spirv::InterfaceVarABIAttr> &argABI) {
243   spirv::TargetEnvAttr targetEnv = spirv::lookupTargetEnvOrDefault(funcOp);
244   if (!spirv::needsInterfaceVarABIAttrs(targetEnv))
245     return success();
246 
247   for (auto argIndex : llvm::seq<unsigned>(0, funcOp.getNumArguments())) {
248     if (funcOp.getArgAttrOfType<spirv::InterfaceVarABIAttr>(
249             argIndex, spirv::getInterfaceVarABIAttrName()))
250       return failure();
251     // Vulkan's interface variable requirements needs scalars to be wrapped in a
252     // struct. The struct held in storage buffer.
253     Optional<spirv::StorageClass> sc;
254     if (funcOp.getArgument(argIndex).getType().isIntOrIndexOrFloat())
255       sc = spirv::StorageClass::StorageBuffer;
256     argABI.push_back(spirv::getInterfaceVarABIAttr(0, argIndex, sc, context));
257   }
258   return success();
259 }
260 
261 LogicalResult GPUFuncOpConversion::matchAndRewrite(
262     gpu::GPUFuncOp funcOp, OpAdaptor adaptor,
263     ConversionPatternRewriter &rewriter) const {
264   if (!gpu::GPUDialect::isKernel(funcOp))
265     return failure();
266 
267   SmallVector<spirv::InterfaceVarABIAttr, 4> argABI;
268   if (failed(getDefaultABIAttrs(rewriter.getContext(), funcOp, argABI))) {
269     argABI.clear();
270     for (auto argIndex : llvm::seq<unsigned>(0, funcOp.getNumArguments())) {
271       // If the ABI is already specified, use it.
272       auto abiAttr = funcOp.getArgAttrOfType<spirv::InterfaceVarABIAttr>(
273           argIndex, spirv::getInterfaceVarABIAttrName());
274       if (!abiAttr) {
275         funcOp.emitRemark(
276             "match failure: missing 'spv.interface_var_abi' attribute at "
277             "argument ")
278             << argIndex;
279         return failure();
280       }
281       argABI.push_back(abiAttr);
282     }
283   }
284 
285   auto entryPointAttr = spirv::lookupEntryPointABI(funcOp);
286   if (!entryPointAttr) {
287     funcOp.emitRemark("match failure: missing 'spv.entry_point_abi' attribute");
288     return failure();
289   }
290   spirv::FuncOp newFuncOp = lowerAsEntryFunction(
291       funcOp, *getTypeConverter(), rewriter, entryPointAttr, argABI);
292   if (!newFuncOp)
293     return failure();
294   newFuncOp->removeAttr(
295       rewriter.getStringAttr(gpu::GPUDialect::getKernelFuncAttrName()));
296   return success();
297 }
298 
299 //===----------------------------------------------------------------------===//
300 // ModuleOp with gpu.module.
301 //===----------------------------------------------------------------------===//
302 
303 LogicalResult GPUModuleConversion::matchAndRewrite(
304     gpu::GPUModuleOp moduleOp, OpAdaptor adaptor,
305     ConversionPatternRewriter &rewriter) const {
306   spirv::TargetEnvAttr targetEnv = spirv::lookupTargetEnvOrDefault(moduleOp);
307   spirv::AddressingModel addressingModel = spirv::getAddressingModel(targetEnv);
308   FailureOr<spirv::MemoryModel> memoryModel = spirv::getMemoryModel(targetEnv);
309   if (failed(memoryModel))
310     return moduleOp.emitRemark("match failure: could not selected memory model "
311                                "based on 'spv.target_env'");
312 
313   // Add a keyword to the module name to avoid symbolic conflict.
314   std::string spvModuleName = (kSPIRVModule + moduleOp.getName()).str();
315   auto spvModule = rewriter.create<spirv::ModuleOp>(
316       moduleOp.getLoc(), addressingModel, memoryModel.getValue(), llvm::None,
317       StringRef(spvModuleName));
318 
319   // Move the region from the module op into the SPIR-V module.
320   Region &spvModuleRegion = spvModule.getRegion();
321   rewriter.inlineRegionBefore(moduleOp.body(), spvModuleRegion,
322                               spvModuleRegion.begin());
323   // The spv.module build method adds a block. Remove that.
324   rewriter.eraseBlock(&spvModuleRegion.back());
325   rewriter.eraseOp(moduleOp);
326   return success();
327 }
328 
329 //===----------------------------------------------------------------------===//
330 // GPU return inside kernel functions to SPIR-V return.
331 //===----------------------------------------------------------------------===//
332 
333 LogicalResult GPUReturnOpConversion::matchAndRewrite(
334     gpu::ReturnOp returnOp, OpAdaptor adaptor,
335     ConversionPatternRewriter &rewriter) const {
336   if (!adaptor.getOperands().empty())
337     return failure();
338 
339   rewriter.replaceOpWithNewOp<spirv::ReturnOp>(returnOp);
340   return success();
341 }
342 
343 //===----------------------------------------------------------------------===//
344 // Barrier.
345 //===----------------------------------------------------------------------===//
346 
347 LogicalResult GPUBarrierConversion::matchAndRewrite(
348     gpu::BarrierOp barrierOp, OpAdaptor adaptor,
349     ConversionPatternRewriter &rewriter) const {
350   MLIRContext *context = getContext();
351   // Both execution and memory scope should be workgroup.
352   auto scope = spirv::ScopeAttr::get(context, spirv::Scope::Workgroup);
353   // Require acquire and release memory semantics for workgroup memory.
354   auto memorySemantics = spirv::MemorySemanticsAttr::get(
355       context, spirv::MemorySemantics::WorkgroupMemory |
356                    spirv::MemorySemantics::AcquireRelease);
357   rewriter.replaceOpWithNewOp<spirv::ControlBarrierOp>(barrierOp, scope, scope,
358                                                        memorySemantics);
359   return success();
360 }
361 
362 //===----------------------------------------------------------------------===//
363 // GPU To SPIRV Patterns.
364 //===----------------------------------------------------------------------===//
365 
366 void mlir::populateGPUToSPIRVPatterns(SPIRVTypeConverter &typeConverter,
367                                       RewritePatternSet &patterns) {
368   patterns.add<
369       GPUBarrierConversion, GPUFuncOpConversion, GPUModuleConversion,
370       GPUModuleEndConversion, GPUReturnOpConversion,
371       LaunchConfigConversion<gpu::BlockIdOp, spirv::BuiltIn::WorkgroupId>,
372       LaunchConfigConversion<gpu::GridDimOp, spirv::BuiltIn::NumWorkgroups>,
373       LaunchConfigConversion<gpu::BlockDimOp, spirv::BuiltIn::WorkgroupSize>,
374       LaunchConfigConversion<gpu::ThreadIdOp,
375                              spirv::BuiltIn::LocalInvocationId>,
376       LaunchConfigConversion<gpu::GlobalIdOp,
377                              spirv::BuiltIn::GlobalInvocationId>,
378       SingleDimLaunchConfigConversion<gpu::SubgroupIdOp,
379                                       spirv::BuiltIn::SubgroupId>,
380       SingleDimLaunchConfigConversion<gpu::NumSubgroupsOp,
381                                       spirv::BuiltIn::NumSubgroups>,
382       SingleDimLaunchConfigConversion<gpu::SubgroupSizeOp,
383                                       spirv::BuiltIn::SubgroupSize>,
384       WorkGroupSizeConversion>(typeConverter, patterns.getContext());
385 }
386