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 SingleDimLaunchConfigConversion<gpu::SubgroupIdOp, 377 spirv::BuiltIn::SubgroupId>, 378 SingleDimLaunchConfigConversion<gpu::NumSubgroupsOp, 379 spirv::BuiltIn::NumSubgroups>, 380 SingleDimLaunchConfigConversion<gpu::SubgroupSizeOp, 381 spirv::BuiltIn::SubgroupSize>, 382 WorkGroupSizeConversion>(typeConverter, patterns.getContext()); 383 } 384