1 //===- GPUToSPIRV.cpp - MLIR SPIR-V lowering passes -----------------------===//
2 //
3 // Copyright 2019 The MLIR Authors.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 //   http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 // =============================================================================
17 //
18 // This file implements a pass to convert a kernel function in the GPU Dialect
19 // into a spv.module operation
20 //
21 //===----------------------------------------------------------------------===//
22 #include "mlir/Conversion/StandardToSPIRV/ConvertStandardToSPIRV.h"
23 #include "mlir/Dialect/GPU/GPUDialect.h"
24 #include "mlir/Dialect/SPIRV/SPIRVDialect.h"
25 #include "mlir/Dialect/SPIRV/SPIRVOps.h"
26 #include "mlir/Pass/Pass.h"
27 
28 using namespace mlir;
29 
30 namespace {
31 
32 /// Pattern lowering GPU block/thread size/id to loading SPIR-V invocation
33 /// builin variables.
34 template <typename OpTy, spirv::BuiltIn builtin>
35 class LaunchConfigConversion : public SPIRVOpLowering<OpTy> {
36 public:
37   using SPIRVOpLowering<OpTy>::SPIRVOpLowering;
38 
39   PatternMatchResult
40   matchAndRewrite(Operation *op, ArrayRef<Value *> operands,
41                   ConversionPatternRewriter &rewriter) const override;
42 };
43 
44 /// Pattern to convert a kernel function in GPU dialect (a FuncOp with the
45 /// attribute gpu.kernel) within a spv.module.
46 class KernelFnConversion final : public SPIRVOpLowering<FuncOp> {
47 public:
48   using SPIRVOpLowering<FuncOp>::SPIRVOpLowering;
49 
50   PatternMatchResult
51   matchAndRewrite(Operation *op, ArrayRef<Value *> operands,
52                   ConversionPatternRewriter &rewriter) const override;
53 };
54 } // namespace
55 
56 template <typename OpTy, spirv::BuiltIn builtin>
57 PatternMatchResult LaunchConfigConversion<OpTy, builtin>::matchAndRewrite(
58     Operation *op, ArrayRef<Value *> operands,
59     ConversionPatternRewriter &rewriter) const {
60   auto dimAttr = op->getAttrOfType<StringAttr>("dimension");
61   if (!dimAttr) {
62     return this->matchFailure();
63   }
64   int32_t index = 0;
65   if (dimAttr.getValue() == "x") {
66     index = 0;
67   } else if (dimAttr.getValue() == "y") {
68     index = 1;
69   } else if (dimAttr.getValue() == "z") {
70     index = 2;
71   } else {
72     return this->matchFailure();
73   }
74 
75   // SPIR-V invocation builtin variables are a vector of type <3xi32>
76   auto spirvBuiltin = this->loadFromBuiltinVariable(op, builtin, rewriter);
77   rewriter.replaceOpWithNewOp<spirv::CompositeExtractOp>(
78       op, rewriter.getIntegerType(32), spirvBuiltin,
79       rewriter.getI32ArrayAttr({index}));
80   return this->matchSuccess();
81 }
82 
83 PatternMatchResult
84 KernelFnConversion::matchAndRewrite(Operation *op, ArrayRef<Value *> operands,
85                                     ConversionPatternRewriter &rewriter) const {
86   auto funcOp = cast<FuncOp>(op);
87   FuncOp newFuncOp;
88   if (!gpu::GPUDialect::isKernel(funcOp)) {
89     return succeeded(lowerFunction(funcOp, &typeConverter, rewriter, newFuncOp))
90                ? matchSuccess()
91                : matchFailure();
92   }
93 
94   if (failed(
95           lowerAsEntryFunction(funcOp, &typeConverter, rewriter, newFuncOp))) {
96     return matchFailure();
97   }
98   return matchSuccess();
99 }
100 
101 namespace {
102 /// Pass to lower GPU Dialect to SPIR-V. The pass only converts those functions
103 /// that have the "gpu.kernel" attribute, i.e. those functions that are
104 /// referenced in gpu::LaunchKernelOp operations. For each such function
105 ///
106 /// 1) Create a spirv::ModuleOp, and clone the function into spirv::ModuleOp
107 /// (the original function is still needed by the gpu::LaunchKernelOp, so cannot
108 /// replace it).
109 ///
110 /// 2) Lower the body of the spirv::ModuleOp.
111 class GPUToSPIRVPass : public ModulePass<GPUToSPIRVPass> {
112   void runOnModule() override;
113 };
114 } // namespace
115 
116 void GPUToSPIRVPass::runOnModule() {
117   auto context = &getContext();
118   auto module = getModule();
119 
120   SmallVector<Operation *, 4> spirvModules;
121   module.walk([&module, &spirvModules](FuncOp funcOp) {
122     if (gpu::GPUDialect::isKernel(funcOp)) {
123       OpBuilder builder(module.getBodyRegion());
124       // Create a new spirv::ModuleOp for this function, and clone the
125       // function into it.
126       // TODO : Generalize this to account for different extensions,
127       // capabilities, extended_instruction_sets, other addressing models
128       // and memory models.
129       auto spvModule = builder.create<spirv::ModuleOp>(
130           funcOp.getLoc(),
131           builder.getI32IntegerAttr(
132               static_cast<int32_t>(spirv::AddressingModel::Logical)),
133           builder.getI32IntegerAttr(
134               static_cast<int32_t>(spirv::MemoryModel::GLSL450)));
135       OpBuilder moduleBuilder(spvModule.getOperation()->getRegion(0));
136       moduleBuilder.clone(*funcOp.getOperation());
137       spirvModules.push_back(spvModule);
138     }
139   });
140 
141   /// Dialect conversion to lower the functions with the spirv::ModuleOps.
142   SPIRVBasicTypeConverter basicTypeConverter;
143   SPIRVTypeConverter typeConverter(&basicTypeConverter);
144   OwningRewritePatternList patterns;
145   patterns.insert<
146       KernelFnConversion,
147       LaunchConfigConversion<gpu::BlockDim, spirv::BuiltIn::WorkgroupSize>,
148       LaunchConfigConversion<gpu::BlockId, spirv::BuiltIn::WorkgroupId>,
149       LaunchConfigConversion<gpu::GridDim, spirv::BuiltIn::NumWorkgroups>,
150       LaunchConfigConversion<gpu::ThreadId, spirv::BuiltIn::LocalInvocationId>>(
151       context, typeConverter);
152   populateStandardToSPIRVPatterns(context, patterns);
153 
154   ConversionTarget target(*context);
155   target.addLegalDialect<spirv::SPIRVDialect>();
156   target.addDynamicallyLegalOp<FuncOp>([&](FuncOp Op) {
157     return basicTypeConverter.isSignatureLegal(Op.getType());
158   });
159 
160   if (failed(applyFullConversion(spirvModules, target, patterns,
161                                  &typeConverter))) {
162     return signalPassFailure();
163   }
164 
165   // After the SPIR-V modules have been generated, some finalization is needed
166   // for the entry functions. For example, adding spv.EntryPoint op,
167   // spv.ExecutionMode op, etc.
168   for (auto *spvModule : spirvModules) {
169     for (auto op :
170          cast<spirv::ModuleOp>(spvModule).getBlock().getOps<FuncOp>()) {
171       if (gpu::GPUDialect::isKernel(op)) {
172         OpBuilder builder(op.getContext());
173         builder.setInsertionPointAfter(op);
174         if (failed(finalizeEntryFunction(op, builder))) {
175           return signalPassFailure();
176         }
177         op.getOperation()->removeAttr(Identifier::get(
178             gpu::GPUDialect::getKernelFuncAttrName(), op.getContext()));
179       }
180     }
181   }
182 }
183 
184 OpPassBase<ModuleOp> *createGPUToSPIRVPass() { return new GPUToSPIRVPass(); }
185 
186 static PassRegistration<GPUToSPIRVPass>
187     pass("convert-gpu-to-spirv", "Convert GPU dialect to SPIR-V dialect");
188