1 //===- GPUDialect.cpp - MLIR Dialect for GPU Kernels implementation -------===// 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 the GPU kernel-related dialect and its operations. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "mlir/Dialect/GPU/GPUDialect.h" 14 #include "mlir/Dialect/LLVMIR/LLVMDialect.h" 15 #include "mlir/Dialect/StandardOps/Ops.h" 16 #include "mlir/IR/Builders.h" 17 #include "mlir/IR/Function.h" 18 #include "mlir/IR/FunctionImplementation.h" 19 #include "mlir/IR/Module.h" 20 #include "mlir/IR/OpImplementation.h" 21 #include "mlir/IR/PatternMatch.h" 22 #include "mlir/IR/StandardTypes.h" 23 24 using namespace mlir; 25 using namespace mlir::gpu; 26 27 //===----------------------------------------------------------------------===// 28 // GPUDialect 29 //===----------------------------------------------------------------------===// 30 31 StringRef GPUDialect::getDialectName() { return "gpu"; } 32 33 bool GPUDialect::isKernel(Operation *op) { 34 UnitAttr isKernelAttr = op->getAttrOfType<UnitAttr>(getKernelFuncAttrName()); 35 return static_cast<bool>(isKernelAttr); 36 } 37 38 GPUDialect::GPUDialect(MLIRContext *context) 39 : Dialect(getDialectName(), context) { 40 addOperations< 41 #define GET_OP_LIST 42 #include "mlir/Dialect/GPU/GPUOps.cpp.inc" 43 >(); 44 } 45 46 LogicalResult GPUDialect::verifyOperationAttribute(Operation *op, 47 NamedAttribute attr) { 48 if (!attr.second.isa<UnitAttr>() || 49 !attr.first.is(getContainerModuleAttrName())) 50 return success(); 51 52 auto module = dyn_cast<ModuleOp>(op); 53 if (!module) 54 return op->emitError("expected '") 55 << getContainerModuleAttrName() << "' attribute to be attached to '" 56 << ModuleOp::getOperationName() << '\''; 57 58 auto walkResult = module.walk([&module](LaunchFuncOp launchOp) -> WalkResult { 59 // Ignore launches that are nested more or less deep than functions in the 60 // module we are currently checking. 61 if (!launchOp.getParentOp() || 62 launchOp.getParentOp()->getParentOp() != module) 63 return success(); 64 65 // Ignore launch ops with missing attributes here. The errors will be 66 // reported by the verifiers of those ops. 67 if (!launchOp.getAttrOfType<StringAttr>( 68 LaunchFuncOp::getKernelAttrName()) || 69 !launchOp.getAttrOfType<SymbolRefAttr>( 70 LaunchFuncOp::getKernelModuleAttrName())) 71 return success(); 72 73 // Check that `launch_func` refers to a well-formed GPU kernel module. 74 StringRef kernelModuleName = launchOp.getKernelModuleName(); 75 auto kernelModule = module.lookupSymbol<GPUModuleOp>(kernelModuleName); 76 if (!kernelModule) 77 return launchOp.emitOpError() 78 << "kernel module '" << kernelModuleName << "' is undefined"; 79 80 // Check that `launch_func` refers to a well-formed kernel function. 81 StringRef kernelName = launchOp.kernel(); 82 Operation *kernelFunc = kernelModule.lookupSymbol(kernelName); 83 auto kernelGPUFunction = dyn_cast_or_null<gpu::GPUFuncOp>(kernelFunc); 84 auto kernelLLVMFunction = dyn_cast_or_null<LLVM::LLVMFuncOp>(kernelFunc); 85 if (!kernelGPUFunction && !kernelLLVMFunction) 86 return launchOp.emitOpError("kernel function '") 87 << kernelName << "' is undefined"; 88 if (!kernelFunc->getAttrOfType<mlir::UnitAttr>( 89 GPUDialect::getKernelFuncAttrName())) 90 return launchOp.emitOpError("kernel function is missing the '") 91 << GPUDialect::getKernelFuncAttrName() << "' attribute"; 92 93 unsigned actualNumArguments = launchOp.getNumKernelOperands(); 94 unsigned expectedNumArguments = kernelLLVMFunction 95 ? kernelLLVMFunction.getNumArguments() 96 : kernelGPUFunction.getNumArguments(); 97 if (expectedNumArguments != actualNumArguments) 98 return launchOp.emitOpError("got ") 99 << actualNumArguments << " kernel operands but expected " 100 << expectedNumArguments; 101 102 // Due to the ordering of the current impl of lowering and LLVMLowering, 103 // type checks need to be temporarily disabled. 104 // TODO(ntv,zinenko,herhut): reactivate checks once "changing gpu.launchFunc 105 // to encode target module" has landed. 106 // auto functionType = kernelFunc.getType(); 107 // for (unsigned i = 0; i < numKernelFuncArgs; ++i) { 108 // if (getKernelOperand(i).getType() != functionType.getInput(i)) { 109 // return emitOpError("type of function argument ") 110 // << i << " does not match"; 111 // } 112 // } 113 114 return success(); 115 }); 116 117 return walkResult.wasInterrupted() ? failure() : success(); 118 } 119 120 template <typename T> static LogicalResult verifyIndexOp(T op) { 121 auto dimension = op.dimension(); 122 if (dimension != "x" && dimension != "y" && dimension != "z") 123 return op.emitError("dimension \"") << dimension << "\" is invalid"; 124 return success(); 125 } 126 127 static LogicalResult verifyAllReduce(gpu::AllReduceOp allReduce) { 128 if (allReduce.body().empty() != allReduce.op().hasValue()) 129 return allReduce.emitError( 130 "expected either an op attribute or a non-empty body"); 131 if (!allReduce.body().empty()) { 132 if (allReduce.body().front().getNumArguments() != 2) 133 return allReduce.emitError("expected two region arguments"); 134 for (auto argument : allReduce.body().front().getArguments()) { 135 if (argument.getType() != allReduce.getType()) 136 return allReduce.emitError("incorrect region argument type"); 137 } 138 unsigned yieldCount = 0; 139 for (Block &block : allReduce.body()) { 140 if (auto yield = dyn_cast<gpu::YieldOp>(block.getTerminator())) { 141 if (yield.getNumOperands() != 1) 142 return allReduce.emitError("expected one gpu.yield operand"); 143 if (yield.getOperand(0).getType() != allReduce.getType()) 144 return allReduce.emitError("incorrect gpu.yield type"); 145 ++yieldCount; 146 } 147 } 148 if (yieldCount == 0) 149 return allReduce.emitError("expected gpu.yield op in region"); 150 } 151 return success(); 152 } 153 154 static LogicalResult verifyShuffleOp(gpu::ShuffleOp shuffleOp) { 155 auto type = shuffleOp.value().getType(); 156 if (shuffleOp.result().getType() != type) { 157 return shuffleOp.emitOpError() 158 << "requires the same type for value operand and result"; 159 } 160 if (!type.isIntOrFloat() || type.getIntOrFloatBitWidth() != 32) { 161 return shuffleOp.emitOpError() 162 << "requires value operand type to be f32 or i32"; 163 } 164 return success(); 165 } 166 167 static void printShuffleOp(OpAsmPrinter &p, ShuffleOp op) { 168 p << ShuffleOp::getOperationName() << ' ' << op.getOperands() << ' ' 169 << op.mode() << " : " << op.value().getType(); 170 } 171 172 static ParseResult parseShuffleOp(OpAsmParser &parser, OperationState &state) { 173 SmallVector<OpAsmParser::OperandType, 3> operandInfo; 174 if (parser.parseOperandList(operandInfo, 3)) 175 return failure(); 176 177 StringRef mode; 178 if (parser.parseKeyword(&mode)) 179 return failure(); 180 state.addAttribute("mode", parser.getBuilder().getStringAttr(mode)); 181 182 Type valueType; 183 Type int32Type = parser.getBuilder().getIntegerType(32); 184 Type int1Type = parser.getBuilder().getI1Type(); 185 if (parser.parseColonType(valueType) || 186 parser.resolveOperands(operandInfo, {valueType, int32Type, int32Type}, 187 parser.getCurrentLocation(), state.operands) || 188 parser.addTypesToList({valueType, int1Type}, state.types)) 189 return failure(); 190 return success(); 191 } 192 193 //===----------------------------------------------------------------------===// 194 // LaunchOp 195 //===----------------------------------------------------------------------===// 196 197 void LaunchOp::build(Builder *builder, OperationState &result, Value gridSizeX, 198 Value gridSizeY, Value gridSizeZ, Value blockSizeX, 199 Value blockSizeY, Value blockSizeZ, ValueRange operands) { 200 // Add grid and block sizes as op operands, followed by the data operands. 201 result.addOperands( 202 {gridSizeX, gridSizeY, gridSizeZ, blockSizeX, blockSizeY, blockSizeZ}); 203 result.addOperands(operands); 204 205 // Create a kernel body region with kNumConfigRegionAttributes + N arguments, 206 // where the first kNumConfigRegionAttributes arguments have `index` type and 207 // the rest have the same types as the data operands. 208 Region *kernelRegion = result.addRegion(); 209 Block *body = new Block(); 210 body->addArguments( 211 std::vector<Type>(kNumConfigRegionAttributes, builder->getIndexType())); 212 body->addArguments(llvm::to_vector<4>(operands.getTypes())); 213 kernelRegion->push_back(body); 214 } 215 216 KernelDim3 LaunchOp::getBlockIds() { 217 assert(!body().getBlocks().empty() && "FuncOp body must not be empty."); 218 auto args = body().getBlocks().front().getArguments(); 219 return KernelDim3{args[0], args[1], args[2]}; 220 } 221 222 KernelDim3 LaunchOp::getThreadIds() { 223 assert(!body().getBlocks().empty() && "FuncOp body must not be empty."); 224 auto args = body().getBlocks().front().getArguments(); 225 return KernelDim3{args[3], args[4], args[5]}; 226 } 227 228 KernelDim3 LaunchOp::getGridSize() { 229 assert(!body().getBlocks().empty() && "FuncOp body must not be empty."); 230 auto args = body().getBlocks().front().getArguments(); 231 return KernelDim3{args[6], args[7], args[8]}; 232 } 233 234 KernelDim3 LaunchOp::getBlockSize() { 235 assert(!body().getBlocks().empty() && "FuncOp body must not be empty."); 236 auto args = body().getBlocks().front().getArguments(); 237 return KernelDim3{args[9], args[10], args[11]}; 238 } 239 240 LaunchOp::operand_range LaunchOp::getKernelOperandValues() { 241 return llvm::drop_begin(getOperands(), kNumConfigOperands); 242 } 243 244 LaunchOp::operand_type_range LaunchOp::getKernelOperandTypes() { 245 return llvm::drop_begin(getOperandTypes(), kNumConfigOperands); 246 } 247 248 KernelDim3 LaunchOp::getGridSizeOperandValues() { 249 return KernelDim3{getOperand(0), getOperand(1), getOperand(2)}; 250 } 251 252 KernelDim3 LaunchOp::getBlockSizeOperandValues() { 253 return KernelDim3{getOperand(3), getOperand(4), getOperand(5)}; 254 } 255 256 iterator_range<Block::args_iterator> LaunchOp::getKernelArguments() { 257 auto args = body().getBlocks().front().getArguments(); 258 return llvm::drop_begin(args, LaunchOp::kNumConfigRegionAttributes); 259 } 260 261 static LogicalResult verify(LaunchOp op) { 262 // Kernel launch takes kNumConfigOperands leading operands for grid/block 263 // sizes and transforms them into kNumConfigRegionAttributes region arguments 264 // for block/thread identifiers and grid/block sizes. 265 if (!op.body().empty()) { 266 Block &entryBlock = op.body().front(); 267 if (entryBlock.getNumArguments() != 268 LaunchOp::kNumConfigOperands + op.getNumOperands()) 269 return op.emitOpError("unexpected number of region arguments"); 270 } 271 272 // Block terminators without successors are expected to exit the kernel region 273 // and must be `gpu.terminator`. 274 for (Block &block : op.body()) { 275 if (block.empty()) 276 continue; 277 if (block.back().getNumSuccessors() != 0) 278 continue; 279 if (!isa<gpu::TerminatorOp>(&block.back())) { 280 return block.back() 281 .emitError() 282 .append("expected '", gpu::TerminatorOp::getOperationName(), 283 "' or a terminator with successors") 284 .attachNote(op.getLoc()) 285 .append("in '", LaunchOp::getOperationName(), "' body region"); 286 } 287 } 288 289 return success(); 290 } 291 292 // Pretty-print the kernel grid/block size assignment as 293 // (%iter-x, %iter-y, %iter-z) in 294 // (%size-x = %ssa-use, %size-y = %ssa-use, %size-z = %ssa-use) 295 // where %size-* and %iter-* will correspond to the body region arguments. 296 static void printSizeAssignment(OpAsmPrinter &p, KernelDim3 size, 297 ValueRange operands, KernelDim3 ids) { 298 p << '(' << ids.x << ", " << ids.y << ", " << ids.z << ") in ("; 299 p << size.x << " = " << operands[0] << ", "; 300 p << size.y << " = " << operands[1] << ", "; 301 p << size.z << " = " << operands[2] << ')'; 302 } 303 304 static void printLaunchOp(OpAsmPrinter &p, LaunchOp op) { 305 ValueRange operands = op.getOperands(); 306 307 // Print the launch configuration. 308 p << LaunchOp::getOperationName() << ' ' << op.getBlocksKeyword(); 309 printSizeAssignment(p, op.getGridSize(), operands.take_front(3), 310 op.getBlockIds()); 311 p << ' ' << op.getThreadsKeyword(); 312 printSizeAssignment(p, op.getBlockSize(), operands.slice(3, 3), 313 op.getThreadIds()); 314 315 // From now on, the first kNumConfigOperands operands corresponding to grid 316 // and block sizes are irrelevant, so we can drop them. 317 operands = operands.drop_front(LaunchOp::kNumConfigOperands); 318 319 // Print the data argument remapping. 320 if (!op.body().empty() && !operands.empty()) { 321 p << ' ' << op.getArgsKeyword() << '('; 322 Block *entryBlock = &op.body().front(); 323 interleaveComma(llvm::seq<int>(0, operands.size()), p, [&](int i) { 324 p << entryBlock->getArgument(LaunchOp::kNumConfigRegionAttributes + i) 325 << " = " << operands[i]; 326 }); 327 p << ") "; 328 } 329 330 // Print the types of data arguments. 331 if (!operands.empty()) 332 p << ": " << operands.getTypes(); 333 334 p.printRegion(op.body(), /*printEntryBlockArgs=*/false); 335 p.printOptionalAttrDict(op.getAttrs()); 336 } 337 338 // Parse the size assignment blocks for blocks and threads. These have the form 339 // (%region_arg, %region_arg, %region_arg) in 340 // (%region_arg = %operand, %region_arg = %operand, %region_arg = %operand) 341 // where %region_arg are percent-identifiers for the region arguments to be 342 // introduced further (SSA defs), and %operand are percent-identifiers for the 343 // SSA value uses. 344 static ParseResult 345 parseSizeAssignment(OpAsmParser &parser, 346 MutableArrayRef<OpAsmParser::OperandType> sizes, 347 MutableArrayRef<OpAsmParser::OperandType> regionSizes, 348 MutableArrayRef<OpAsmParser::OperandType> indices) { 349 assert(indices.size() == 3 && "space for three indices expected"); 350 SmallVector<OpAsmParser::OperandType, 3> args; 351 if (parser.parseRegionArgumentList(args, /*requiredOperandCount=*/3, 352 OpAsmParser::Delimiter::Paren) || 353 parser.parseKeyword("in") || parser.parseLParen()) 354 return failure(); 355 std::move(args.begin(), args.end(), indices.begin()); 356 357 for (int i = 0; i < 3; ++i) { 358 if (i != 0 && parser.parseComma()) 359 return failure(); 360 if (parser.parseRegionArgument(regionSizes[i]) || parser.parseEqual() || 361 parser.parseOperand(sizes[i])) 362 return failure(); 363 } 364 365 return parser.parseRParen(); 366 } 367 368 // Parses a Launch operation. 369 // operation ::= `gpu.launch` `blocks` `(` ssa-id-list `)` `in` ssa-reassignment 370 // `threads` `(` ssa-id-list `)` `in` ssa-reassignment 371 // (`args` ssa-reassignment `:` type-list)? 372 // region attr-dict? 373 // ssa-reassignment ::= `(` ssa-id `=` ssa-use (`,` ssa-id `=` ssa-use)* `)` 374 static ParseResult parseLaunchOp(OpAsmParser &parser, OperationState &result) { 375 // Sizes of the grid and block. 376 SmallVector<OpAsmParser::OperandType, LaunchOp::kNumConfigOperands> sizes( 377 LaunchOp::kNumConfigOperands); 378 MutableArrayRef<OpAsmParser::OperandType> sizesRef(sizes); 379 380 // Actual (data) operands passed to the kernel. 381 SmallVector<OpAsmParser::OperandType, 4> dataOperands; 382 383 // Region arguments to be created. 384 SmallVector<OpAsmParser::OperandType, 16> regionArgs( 385 LaunchOp::kNumConfigRegionAttributes); 386 MutableArrayRef<OpAsmParser::OperandType> regionArgsRef(regionArgs); 387 388 // Parse the size assignment segments: the first segment assigns grid sizes 389 // and defines values for block identifiers; the second segment assigns block 390 // sizes and defines values for thread identifiers. In the region argument 391 // list, identifiers precede sizes, and block-related values precede 392 // thread-related values. 393 if (parser.parseKeyword(LaunchOp::getBlocksKeyword().data()) || 394 parseSizeAssignment(parser, sizesRef.take_front(3), 395 regionArgsRef.slice(6, 3), 396 regionArgsRef.slice(0, 3)) || 397 parser.parseKeyword(LaunchOp::getThreadsKeyword().data()) || 398 parseSizeAssignment(parser, sizesRef.drop_front(3), 399 regionArgsRef.slice(9, 3), 400 regionArgsRef.slice(3, 3)) || 401 parser.resolveOperands(sizes, parser.getBuilder().getIndexType(), 402 result.operands)) 403 return failure(); 404 405 // If kernel argument renaming segment is present, parse it. When present, 406 // the segment should have at least one element. If this segment is present, 407 // so is the trailing type list. Parse it as well and use the parsed types 408 // to resolve the operands passed to the kernel arguments. 409 SmallVector<Type, 4> dataTypes; 410 if (!parser.parseOptionalKeyword(LaunchOp::getArgsKeyword())) { 411 llvm::SMLoc argsLoc = parser.getCurrentLocation(); 412 413 regionArgs.push_back({}); 414 dataOperands.push_back({}); 415 if (parser.parseLParen() || parser.parseRegionArgument(regionArgs.back()) || 416 parser.parseEqual() || parser.parseOperand(dataOperands.back())) 417 return failure(); 418 419 while (!parser.parseOptionalComma()) { 420 regionArgs.push_back({}); 421 dataOperands.push_back({}); 422 if (parser.parseRegionArgument(regionArgs.back()) || 423 parser.parseEqual() || parser.parseOperand(dataOperands.back())) 424 return failure(); 425 } 426 427 if (parser.parseRParen() || parser.parseColonTypeList(dataTypes) || 428 parser.resolveOperands(dataOperands, dataTypes, argsLoc, 429 result.operands)) 430 return failure(); 431 } 432 433 // Introduce the body region and parse it. The region has 434 // kNumConfigRegionAttributes leading arguments that correspond to 435 // block/thread identifiers and grid/block sizes, all of the `index` type. 436 // Follow the actual kernel arguments. 437 Type index = parser.getBuilder().getIndexType(); 438 dataTypes.insert(dataTypes.begin(), LaunchOp::kNumConfigRegionAttributes, 439 index); 440 Region *body = result.addRegion(); 441 return failure(parser.parseRegion(*body, regionArgs, dataTypes) || 442 parser.parseOptionalAttrDict(result.attributes)); 443 } 444 445 void LaunchOp::eraseKernelArgument(unsigned index) { 446 Block &entryBlock = body().front(); 447 assert(index < entryBlock.getNumArguments() - kNumConfigRegionAttributes && 448 "kernel argument index overflow"); 449 entryBlock.eraseArgument(kNumConfigRegionAttributes + index); 450 getOperation()->eraseOperand(kNumConfigOperands + index); 451 } 452 453 namespace { 454 // Clone any known constants passed as operands to the kernel into its body. 455 class PropagateConstantBounds : public OpRewritePattern<LaunchOp> { 456 using OpRewritePattern<LaunchOp>::OpRewritePattern; 457 458 PatternMatchResult matchAndRewrite(LaunchOp launchOp, 459 PatternRewriter &rewriter) const override { 460 rewriter.startRootUpdate(launchOp); 461 PatternRewriter::InsertionGuard guard(rewriter); 462 rewriter.setInsertionPointToStart(&launchOp.body().front()); 463 464 // Traverse operands passed to kernel and check if some of them are known 465 // constants. If so, clone the constant operation inside the kernel region 466 // and use it instead of passing the value from the parent region. Perform 467 // the traversal in the inverse order to simplify index arithmetics when 468 // dropping arguments. 469 auto operands = launchOp.getKernelOperandValues(); 470 auto kernelArgs = launchOp.getKernelArguments(); 471 bool found = false; 472 for (unsigned i = operands.size(); i > 0; --i) { 473 unsigned index = i - 1; 474 Value operand = operands[index]; 475 if (!isa_and_nonnull<ConstantOp>(operand.getDefiningOp())) 476 continue; 477 478 found = true; 479 Value internalConstant = 480 rewriter.clone(*operand.getDefiningOp())->getResult(0); 481 Value kernelArg = *std::next(kernelArgs.begin(), index); 482 kernelArg.replaceAllUsesWith(internalConstant); 483 launchOp.eraseKernelArgument(index); 484 } 485 486 if (!found) { 487 rewriter.cancelRootUpdate(launchOp); 488 return matchFailure(); 489 } 490 491 rewriter.finalizeRootUpdate(launchOp); 492 return matchSuccess(); 493 } 494 }; 495 } // end namespace 496 497 void LaunchOp::getCanonicalizationPatterns(OwningRewritePatternList &results, 498 MLIRContext *context) { 499 results.insert<PropagateConstantBounds>(context); 500 } 501 502 //===----------------------------------------------------------------------===// 503 // LaunchFuncOp 504 //===----------------------------------------------------------------------===// 505 506 void LaunchFuncOp::build(Builder *builder, OperationState &result, 507 GPUFuncOp kernelFunc, Value gridSizeX, Value gridSizeY, 508 Value gridSizeZ, Value blockSizeX, Value blockSizeY, 509 Value blockSizeZ, ValueRange kernelOperands) { 510 // Add grid and block sizes as op operands, followed by the data operands. 511 result.addOperands( 512 {gridSizeX, gridSizeY, gridSizeZ, blockSizeX, blockSizeY, blockSizeZ}); 513 result.addOperands(kernelOperands); 514 result.addAttribute(getKernelAttrName(), 515 builder->getStringAttr(kernelFunc.getName())); 516 auto kernelModule = kernelFunc.getParentOfType<GPUModuleOp>(); 517 result.addAttribute(getKernelModuleAttrName(), 518 builder->getSymbolRefAttr(kernelModule.getName())); 519 } 520 521 void LaunchFuncOp::build(Builder *builder, OperationState &result, 522 GPUFuncOp kernelFunc, KernelDim3 gridSize, 523 KernelDim3 blockSize, ValueRange kernelOperands) { 524 build(builder, result, kernelFunc, gridSize.x, gridSize.y, gridSize.z, 525 blockSize.x, blockSize.y, blockSize.z, kernelOperands); 526 } 527 528 StringRef LaunchFuncOp::kernel() { 529 return getAttrOfType<StringAttr>(getKernelAttrName()).getValue(); 530 } 531 532 unsigned LaunchFuncOp::getNumKernelOperands() { 533 return getNumOperands() - kNumConfigOperands; 534 } 535 536 StringRef LaunchFuncOp::getKernelModuleName() { 537 return getAttrOfType<SymbolRefAttr>(getKernelModuleAttrName()) 538 .getRootReference(); 539 } 540 541 Value LaunchFuncOp::getKernelOperand(unsigned i) { 542 return getOperation()->getOperand(i + kNumConfigOperands); 543 } 544 545 KernelDim3 LaunchFuncOp::getGridSizeOperandValues() { 546 return KernelDim3{getOperand(0), getOperand(1), getOperand(2)}; 547 } 548 549 KernelDim3 LaunchFuncOp::getBlockSizeOperandValues() { 550 return KernelDim3{getOperand(3), getOperand(4), getOperand(5)}; 551 } 552 553 static LogicalResult verify(LaunchFuncOp op) { 554 auto module = op.getParentOfType<ModuleOp>(); 555 if (!module) 556 return op.emitOpError("expected to belong to a module"); 557 558 if (!module.getAttrOfType<UnitAttr>(GPUDialect::getContainerModuleAttrName())) 559 return op.emitOpError( 560 "expected the closest surrounding module to have the '" + 561 GPUDialect::getContainerModuleAttrName() + "' attribute"); 562 563 auto kernelAttr = op.getAttrOfType<StringAttr>(op.getKernelAttrName()); 564 if (!kernelAttr) 565 return op.emitOpError("string attribute '" + op.getKernelAttrName() + 566 "' must be specified"); 567 568 auto kernelModuleAttr = 569 op.getAttrOfType<SymbolRefAttr>(op.getKernelModuleAttrName()); 570 if (!kernelModuleAttr) 571 return op.emitOpError("symbol reference attribute '" + 572 op.getKernelModuleAttrName() + "' must be specified"); 573 574 return success(); 575 } 576 577 //===----------------------------------------------------------------------===// 578 // GPUFuncOp 579 //===----------------------------------------------------------------------===// 580 581 /// Adds a workgroup attribution to "op" of the MemRef type with the given shape 582 /// and element type. 583 Value GPUFuncOp::addWorkgroupAttribution(ArrayRef<int64_t> shape, 584 Type elementType) { 585 unsigned pos = getNumFuncArguments() + getNumWorkgroupAttributions(); 586 Block &bodyBlock = body().front(); 587 Value attribution = bodyBlock.insertArgument( 588 std::next(bodyBlock.args_begin(), pos), 589 MemRefType::get(shape, elementType, /*affineMapComposition=*/{}, 590 GPUDialect::getWorkgroupAddressSpace())); 591 auto numWorkgroupBuffersAttr = 592 getAttrOfType<IntegerAttr>(getNumWorkgroupAttributionsAttrName()); 593 setAttr(getNumWorkgroupAttributionsAttrName(), 594 IntegerAttr::get(numWorkgroupBuffersAttr.getType(), 595 numWorkgroupBuffersAttr.getValue() + 1)); 596 return attribution; 597 } 598 599 void GPUFuncOp::build(Builder *builder, OperationState &result, StringRef name, 600 FunctionType type, ArrayRef<Type> workgroupAttributions, 601 ArrayRef<Type> privateAttributions, 602 ArrayRef<NamedAttribute> attrs) { 603 result.addAttribute(SymbolTable::getSymbolAttrName(), 604 builder->getStringAttr(name)); 605 result.addAttribute(getTypeAttrName(), TypeAttr::get(type)); 606 result.addAttribute(getNumWorkgroupAttributionsAttrName(), 607 builder->getI64IntegerAttr(workgroupAttributions.size())); 608 result.addAttributes(attrs); 609 Region *body = result.addRegion(); 610 Block *entryBlock = new Block; 611 entryBlock->addArguments(type.getInputs()); 612 entryBlock->addArguments(workgroupAttributions); 613 entryBlock->addArguments(privateAttributions); 614 615 body->getBlocks().push_back(entryBlock); 616 } 617 618 /// Parses a GPU function memory attribution. 619 /// 620 /// memory-attribution ::= (`workgroup` `(` ssa-id-and-type-list `)`)? 621 /// (`private` `(` ssa-id-and-type-list `)`)? 622 /// 623 /// Note that this function parses only one of the two similar parts, with the 624 /// keyword provided as argument. 625 static ParseResult 626 parseAttributions(OpAsmParser &parser, StringRef keyword, 627 SmallVectorImpl<OpAsmParser::OperandType> &args, 628 SmallVectorImpl<Type> &argTypes) { 629 // If we could not parse the keyword, just assume empty list and succeed. 630 if (failed(parser.parseOptionalKeyword(keyword))) 631 return success(); 632 633 if (failed(parser.parseLParen())) 634 return failure(); 635 636 // Early exit for an empty list. 637 if (succeeded(parser.parseOptionalRParen())) 638 return success(); 639 640 do { 641 OpAsmParser::OperandType arg; 642 Type type; 643 644 if (parser.parseRegionArgument(arg) || parser.parseColonType(type)) 645 return failure(); 646 647 args.push_back(arg); 648 argTypes.push_back(type); 649 } while (succeeded(parser.parseOptionalComma())); 650 651 return parser.parseRParen(); 652 } 653 654 /// Parses a GPU function. 655 /// 656 /// <operation> ::= `gpu.func` symbol-ref-id `(` argument-list `)` 657 /// (`->` function-result-list)? memory-attribution `kernel`? 658 /// function-attributes? region 659 static ParseResult parseGPUFuncOp(OpAsmParser &parser, OperationState &result) { 660 SmallVector<OpAsmParser::OperandType, 8> entryArgs; 661 SmallVector<SmallVector<NamedAttribute, 2>, 1> argAttrs; 662 SmallVector<SmallVector<NamedAttribute, 2>, 1> resultAttrs; 663 SmallVector<Type, 8> argTypes; 664 SmallVector<Type, 4> resultTypes; 665 bool isVariadic; 666 667 // Parse the function name. 668 StringAttr nameAttr; 669 if (parser.parseSymbolName(nameAttr, ::mlir::SymbolTable::getSymbolAttrName(), 670 result.attributes)) 671 return failure(); 672 673 auto signatureLocation = parser.getCurrentLocation(); 674 if (failed(impl::parseFunctionSignature( 675 parser, /*allowVariadic=*/false, entryArgs, argTypes, argAttrs, 676 isVariadic, resultTypes, resultAttrs))) 677 return failure(); 678 679 if (entryArgs.empty() && !argTypes.empty()) 680 return parser.emitError(signatureLocation) 681 << "gpu.func requires named arguments"; 682 683 // Construct the function type. More types will be added to the region, but 684 // not to the function type. 685 Builder &builder = parser.getBuilder(); 686 auto type = builder.getFunctionType(argTypes, resultTypes); 687 result.addAttribute(GPUFuncOp::getTypeAttrName(), TypeAttr::get(type)); 688 689 // Parse workgroup memory attributions. 690 if (failed(parseAttributions(parser, GPUFuncOp::getWorkgroupKeyword(), 691 entryArgs, argTypes))) 692 return failure(); 693 694 // Store the number of operands we just parsed as the number of workgroup 695 // memory attributions. 696 unsigned numWorkgroupAttrs = argTypes.size() - type.getNumInputs(); 697 result.addAttribute(GPUFuncOp::getNumWorkgroupAttributionsAttrName(), 698 builder.getI64IntegerAttr(numWorkgroupAttrs)); 699 700 // Parse private memory attributions. 701 if (failed(parseAttributions(parser, GPUFuncOp::getPrivateKeyword(), 702 entryArgs, argTypes))) 703 return failure(); 704 705 // Parse the kernel attribute if present. 706 if (succeeded(parser.parseOptionalKeyword(GPUFuncOp::getKernelKeyword()))) 707 result.addAttribute(GPUDialect::getKernelFuncAttrName(), 708 builder.getUnitAttr()); 709 710 // Parse attributes. 711 if (failed(parser.parseOptionalAttrDictWithKeyword(result.attributes))) 712 return failure(); 713 mlir::impl::addArgAndResultAttrs(builder, result, argAttrs, resultAttrs); 714 715 // Parse the region. If no argument names were provided, take all names 716 // (including those of attributions) from the entry block. 717 auto *body = result.addRegion(); 718 return parser.parseRegion(*body, entryArgs, argTypes); 719 } 720 721 static void printAttributions(OpAsmPrinter &p, StringRef keyword, 722 ArrayRef<BlockArgument> values) { 723 if (values.empty()) 724 return; 725 726 p << ' ' << keyword << '('; 727 interleaveComma(values, p, 728 [&p](BlockArgument v) { p << v << " : " << v.getType(); }); 729 p << ')'; 730 } 731 732 /// Prints a GPU Func op. 733 static void printGPUFuncOp(OpAsmPrinter &p, GPUFuncOp op) { 734 p << GPUFuncOp::getOperationName() << ' '; 735 p.printSymbolName(op.getName()); 736 737 FunctionType type = op.getType(); 738 impl::printFunctionSignature(p, op.getOperation(), type.getInputs(), 739 /*isVariadic=*/false, type.getResults()); 740 741 printAttributions(p, op.getWorkgroupKeyword(), op.getWorkgroupAttributions()); 742 printAttributions(p, op.getPrivateKeyword(), op.getPrivateAttributions()); 743 if (op.isKernel()) 744 p << ' ' << op.getKernelKeyword(); 745 746 impl::printFunctionAttributes(p, op.getOperation(), type.getNumInputs(), 747 type.getNumResults(), 748 {op.getNumWorkgroupAttributionsAttrName(), 749 GPUDialect::getKernelFuncAttrName()}); 750 p.printRegion(op.getBody(), /*printEntryBlockArgs=*/false); 751 } 752 753 void GPUFuncOp::setType(FunctionType newType) { 754 auto oldType = getType(); 755 assert(newType.getNumResults() == oldType.getNumResults() && 756 "unimplemented: changes to the number of results"); 757 758 SmallVector<char, 16> nameBuf; 759 for (int i = newType.getNumInputs(), e = oldType.getNumInputs(); i < e; i++) 760 removeAttr(getArgAttrName(i, nameBuf)); 761 762 setAttr(getTypeAttrName(), TypeAttr::get(newType)); 763 } 764 765 /// Hook for FunctionLike verifier. 766 LogicalResult GPUFuncOp::verifyType() { 767 Type type = getTypeAttr().getValue(); 768 if (!type.isa<FunctionType>()) 769 return emitOpError("requires '" + getTypeAttrName() + 770 "' attribute of function type"); 771 772 if (isKernel() && getType().getNumResults() != 0) 773 return emitOpError() << "expected void return type for kernel function"; 774 775 return success(); 776 } 777 778 static LogicalResult verifyAttributions(Operation *op, 779 ArrayRef<BlockArgument> attributions, 780 unsigned memorySpace) { 781 for (Value v : attributions) { 782 auto type = v.getType().dyn_cast<MemRefType>(); 783 if (!type) 784 return op->emitOpError() << "expected memref type in attribution"; 785 786 if (type.getMemorySpace() != memorySpace) { 787 return op->emitOpError() 788 << "expected memory space " << memorySpace << " in attribution"; 789 } 790 } 791 return success(); 792 } 793 794 /// Verifies the body of the function. 795 LogicalResult GPUFuncOp::verifyBody() { 796 unsigned numFuncArguments = getNumArguments(); 797 unsigned numWorkgroupAttributions = getNumWorkgroupAttributions(); 798 unsigned numBlockArguments = front().getNumArguments(); 799 if (numBlockArguments < numFuncArguments + numWorkgroupAttributions) 800 return emitOpError() << "expected at least " 801 << numFuncArguments + numWorkgroupAttributions 802 << " arguments to body region"; 803 804 ArrayRef<Type> funcArgTypes = getType().getInputs(); 805 for (unsigned i = 0; i < numFuncArguments; ++i) { 806 Type blockArgType = front().getArgument(i).getType(); 807 if (funcArgTypes[i] != blockArgType) 808 return emitOpError() << "expected body region argument #" << i 809 << " to be of type " << funcArgTypes[i] << ", got " 810 << blockArgType; 811 } 812 813 if (failed(verifyAttributions(getOperation(), getWorkgroupAttributions(), 814 GPUDialect::getWorkgroupAddressSpace())) || 815 failed(verifyAttributions(getOperation(), getPrivateAttributions(), 816 GPUDialect::getPrivateAddressSpace()))) 817 return failure(); 818 819 return success(); 820 } 821 822 //===----------------------------------------------------------------------===// 823 // ReturnOp 824 //===----------------------------------------------------------------------===// 825 826 static ParseResult parseReturnOp(OpAsmParser &parser, OperationState &result) { 827 llvm::SmallVector<OpAsmParser::OperandType, 4> operands; 828 llvm::SmallVector<Type, 4> types; 829 if (parser.parseOperandList(operands) || 830 parser.parseOptionalColonTypeList(types) || 831 parser.resolveOperands(operands, types, parser.getCurrentLocation(), 832 result.operands)) 833 return failure(); 834 835 return success(); 836 } 837 838 static LogicalResult verify(gpu::ReturnOp returnOp) { 839 GPUFuncOp function = returnOp.getParentOfType<GPUFuncOp>(); 840 841 FunctionType funType = function.getType(); 842 843 if (funType.getNumResults() != returnOp.operands().size()) 844 return returnOp.emitOpError() 845 .append("expected ", funType.getNumResults(), " result operands") 846 .attachNote(function.getLoc()) 847 .append("return type declared here"); 848 849 for (auto pair : llvm::enumerate( 850 llvm::zip(function.getType().getResults(), returnOp.operands()))) { 851 Type type; 852 Value operand; 853 std::tie(type, operand) = pair.value(); 854 if (type != operand.getType()) 855 return returnOp.emitOpError() << "unexpected type `" << operand.getType() 856 << "' for operand #" << pair.index(); 857 } 858 return success(); 859 } 860 861 //===----------------------------------------------------------------------===// 862 // GPUModuleOp 863 //===----------------------------------------------------------------------===// 864 865 void GPUModuleOp::build(Builder *builder, OperationState &result, 866 StringRef name) { 867 ensureTerminator(*result.addRegion(), *builder, result.location); 868 result.attributes.push_back(builder->getNamedAttr( 869 ::mlir::SymbolTable::getSymbolAttrName(), builder->getStringAttr(name))); 870 } 871 872 static ParseResult parseGPUModuleOp(OpAsmParser &parser, 873 OperationState &result) { 874 StringAttr nameAttr; 875 if (parser.parseSymbolName(nameAttr, SymbolTable::getSymbolAttrName(), 876 result.attributes)) 877 return failure(); 878 879 // If module attributes are present, parse them. 880 if (parser.parseOptionalAttrDictWithKeyword(result.attributes)) 881 return failure(); 882 883 // Parse the module body. 884 auto *body = result.addRegion(); 885 if (parser.parseRegion(*body, None, None)) 886 return failure(); 887 888 // Ensure that this module has a valid terminator. 889 GPUModuleOp::ensureTerminator(*body, parser.getBuilder(), result.location); 890 return success(); 891 } 892 893 static void print(OpAsmPrinter &p, GPUModuleOp op) { 894 p << op.getOperationName() << ' '; 895 p.printSymbolName(op.getName()); 896 p.printOptionalAttrDictWithKeyword(op.getAttrs(), 897 {SymbolTable::getSymbolAttrName()}); 898 p.printRegion(op.getOperation()->getRegion(0), /*printEntryBlockArgs=*/false, 899 /*printBlockTerminators=*/false); 900 } 901 902 // Namespace avoids ambiguous ReturnOpOperandAdaptor. 903 namespace mlir { 904 namespace gpu { 905 #define GET_OP_CLASSES 906 #include "mlir/Dialect/GPU/GPUOps.cpp.inc" 907 } // namespace gpu 908 } // namespace mlir 909