1 //===- SerializeOps.cpp - MLIR SPIR-V Serialization (Ops) -----------------===// 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 defines the serialization methods for MLIR SPIR-V module ops. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "Serializer.h" 14 15 #include "mlir/Dialect/SPIRV/IR/SPIRVAttributes.h" 16 #include "mlir/IR/RegionGraphTraits.h" 17 #include "mlir/Support/LogicalResult.h" 18 #include "mlir/Target/SPIRV/SPIRVBinaryUtils.h" 19 #include "llvm/ADT/DepthFirstIterator.h" 20 #include "llvm/Support/Debug.h" 21 22 #define DEBUG_TYPE "spirv-serialization" 23 24 using namespace mlir; 25 26 /// A pre-order depth-first visitor function for processing basic blocks. 27 /// 28 /// Visits the basic blocks starting from the given `headerBlock` in pre-order 29 /// depth-first manner and calls `blockHandler` on each block. Skips handling 30 /// blocks in the `skipBlocks` list. If `skipHeader` is true, `blockHandler` 31 /// will not be invoked in `headerBlock` but still handles all `headerBlock`'s 32 /// successors. 33 /// 34 /// SPIR-V spec "2.16.1. Universal Validation Rules" requires that "the order 35 /// of blocks in a function must satisfy the rule that blocks appear before 36 /// all blocks they dominate." This can be achieved by a pre-order CFG 37 /// traversal algorithm. To make the serialization output more logical and 38 /// readable to human, we perform depth-first CFG traversal and delay the 39 /// serialization of the merge block and the continue block, if exists, until 40 /// after all other blocks have been processed. 41 static LogicalResult 42 visitInPrettyBlockOrder(Block *headerBlock, 43 function_ref<LogicalResult(Block *)> blockHandler, 44 bool skipHeader = false, BlockRange skipBlocks = {}) { 45 llvm::df_iterator_default_set<Block *, 4> doneBlocks; 46 doneBlocks.insert(skipBlocks.begin(), skipBlocks.end()); 47 48 for (Block *block : llvm::depth_first_ext(headerBlock, doneBlocks)) { 49 if (skipHeader && block == headerBlock) 50 continue; 51 if (failed(blockHandler(block))) 52 return failure(); 53 } 54 return success(); 55 } 56 57 namespace mlir { 58 namespace spirv { 59 LogicalResult Serializer::processConstantOp(spirv::ConstantOp op) { 60 if (auto resultID = prepareConstant(op.getLoc(), op.getType(), op.value())) { 61 valueIDMap[op.getResult()] = resultID; 62 return success(); 63 } 64 return failure(); 65 } 66 67 LogicalResult Serializer::processSpecConstantOp(spirv::SpecConstantOp op) { 68 if (auto resultID = prepareConstantScalar(op.getLoc(), op.default_value(), 69 /*isSpec=*/true)) { 70 // Emit the OpDecorate instruction for SpecId. 71 if (auto specID = op->getAttrOfType<IntegerAttr>("spec_id")) { 72 auto val = static_cast<uint32_t>(specID.getInt()); 73 if (failed(emitDecoration(resultID, spirv::Decoration::SpecId, {val}))) 74 return failure(); 75 } 76 77 specConstIDMap[op.sym_name()] = resultID; 78 return processName(resultID, op.sym_name()); 79 } 80 return failure(); 81 } 82 83 LogicalResult 84 Serializer::processSpecConstantCompositeOp(spirv::SpecConstantCompositeOp op) { 85 uint32_t typeID = 0; 86 if (failed(processType(op.getLoc(), op.type(), typeID))) { 87 return failure(); 88 } 89 90 auto resultID = getNextID(); 91 92 SmallVector<uint32_t, 8> operands; 93 operands.push_back(typeID); 94 operands.push_back(resultID); 95 96 auto constituents = op.constituents(); 97 98 for (auto index : llvm::seq<uint32_t>(0, constituents.size())) { 99 auto constituent = constituents[index].dyn_cast<FlatSymbolRefAttr>(); 100 101 auto constituentName = constituent.getValue(); 102 auto constituentID = getSpecConstID(constituentName); 103 104 if (!constituentID) { 105 return op.emitError("unknown result <id> for specialization constant ") 106 << constituentName; 107 } 108 109 operands.push_back(constituentID); 110 } 111 112 encodeInstructionInto(typesGlobalValues, 113 spirv::Opcode::OpSpecConstantComposite, operands); 114 specConstIDMap[op.sym_name()] = resultID; 115 116 return processName(resultID, op.sym_name()); 117 } 118 119 LogicalResult 120 Serializer::processSpecConstantOperationOp(spirv::SpecConstantOperationOp op) { 121 uint32_t typeID = 0; 122 if (failed(processType(op.getLoc(), op.getType(), typeID))) { 123 return failure(); 124 } 125 126 auto resultID = getNextID(); 127 128 SmallVector<uint32_t, 8> operands; 129 operands.push_back(typeID); 130 operands.push_back(resultID); 131 132 Block &block = op.getRegion().getBlocks().front(); 133 Operation &enclosedOp = block.getOperations().front(); 134 135 std::string enclosedOpName; 136 llvm::raw_string_ostream rss(enclosedOpName); 137 rss << "Op" << enclosedOp.getName().stripDialect(); 138 auto enclosedOpcode = spirv::symbolizeOpcode(rss.str()); 139 140 if (!enclosedOpcode) { 141 op.emitError("Couldn't find op code for op ") 142 << enclosedOp.getName().getStringRef(); 143 return failure(); 144 } 145 146 operands.push_back(static_cast<uint32_t>(enclosedOpcode.getValue())); 147 148 // Append operands to the enclosed op to the list of operands. 149 for (Value operand : enclosedOp.getOperands()) { 150 uint32_t id = getValueID(operand); 151 assert(id && "use before def!"); 152 operands.push_back(id); 153 } 154 155 encodeInstructionInto(typesGlobalValues, spirv::Opcode::OpSpecConstantOp, 156 operands); 157 valueIDMap[op.getResult()] = resultID; 158 159 return success(); 160 } 161 162 LogicalResult Serializer::processUndefOp(spirv::UndefOp op) { 163 auto undefType = op.getType(); 164 auto &id = undefValIDMap[undefType]; 165 if (!id) { 166 id = getNextID(); 167 uint32_t typeID = 0; 168 if (failed(processType(op.getLoc(), undefType, typeID))) 169 return failure(); 170 encodeInstructionInto(typesGlobalValues, spirv::Opcode::OpUndef, 171 {typeID, id}); 172 } 173 valueIDMap[op.getResult()] = id; 174 return success(); 175 } 176 177 LogicalResult Serializer::processFuncOp(spirv::FuncOp op) { 178 LLVM_DEBUG(llvm::dbgs() << "-- start function '" << op.getName() << "' --\n"); 179 assert(functionHeader.empty() && functionBody.empty()); 180 181 uint32_t fnTypeID = 0; 182 // Generate type of the function. 183 if (failed(processType(op.getLoc(), op.getType(), fnTypeID))) 184 return failure(); 185 186 // Add the function definition. 187 SmallVector<uint32_t, 4> operands; 188 uint32_t resTypeID = 0; 189 auto resultTypes = op.getType().getResults(); 190 if (resultTypes.size() > 1) { 191 return op.emitError("cannot serialize function with multiple return types"); 192 } 193 if (failed(processType(op.getLoc(), 194 (resultTypes.empty() ? getVoidType() : resultTypes[0]), 195 resTypeID))) { 196 return failure(); 197 } 198 operands.push_back(resTypeID); 199 auto funcID = getOrCreateFunctionID(op.getName()); 200 operands.push_back(funcID); 201 operands.push_back(static_cast<uint32_t>(op.function_control())); 202 operands.push_back(fnTypeID); 203 encodeInstructionInto(functionHeader, spirv::Opcode::OpFunction, operands); 204 205 // Add function name. 206 if (failed(processName(funcID, op.getName()))) { 207 return failure(); 208 } 209 210 // Declare the parameters. 211 for (auto arg : op.getArguments()) { 212 uint32_t argTypeID = 0; 213 if (failed(processType(op.getLoc(), arg.getType(), argTypeID))) { 214 return failure(); 215 } 216 auto argValueID = getNextID(); 217 valueIDMap[arg] = argValueID; 218 encodeInstructionInto(functionHeader, spirv::Opcode::OpFunctionParameter, 219 {argTypeID, argValueID}); 220 } 221 222 // Process the body. 223 if (op.isExternal()) { 224 return op.emitError("external function is unhandled"); 225 } 226 227 // Some instructions (e.g., OpVariable) in a function must be in the first 228 // block in the function. These instructions will be put in functionHeader. 229 // Thus, we put the label in functionHeader first, and omit it from the first 230 // block. 231 encodeInstructionInto(functionHeader, spirv::Opcode::OpLabel, 232 {getOrCreateBlockID(&op.front())}); 233 if (failed(processBlock(&op.front(), /*omitLabel=*/true))) 234 return failure(); 235 if (failed(visitInPrettyBlockOrder( 236 &op.front(), [&](Block *block) { return processBlock(block); }, 237 /*skipHeader=*/true))) { 238 return failure(); 239 } 240 241 // There might be OpPhi instructions who have value references needing to fix. 242 for (const auto &deferredValue : deferredPhiValues) { 243 Value value = deferredValue.first; 244 uint32_t id = getValueID(value); 245 LLVM_DEBUG(llvm::dbgs() << "[phi] fix reference of value " << value 246 << " to id = " << id << '\n'); 247 assert(id && "OpPhi references undefined value!"); 248 for (size_t offset : deferredValue.second) 249 functionBody[offset] = id; 250 } 251 deferredPhiValues.clear(); 252 253 LLVM_DEBUG(llvm::dbgs() << "-- completed function '" << op.getName() 254 << "' --\n"); 255 // Insert OpFunctionEnd. 256 encodeInstructionInto(functionBody, spirv::Opcode::OpFunctionEnd, {}); 257 258 functions.append(functionHeader.begin(), functionHeader.end()); 259 functions.append(functionBody.begin(), functionBody.end()); 260 functionHeader.clear(); 261 functionBody.clear(); 262 263 return success(); 264 } 265 266 LogicalResult Serializer::processVariableOp(spirv::VariableOp op) { 267 SmallVector<uint32_t, 4> operands; 268 SmallVector<StringRef, 2> elidedAttrs; 269 uint32_t resultID = 0; 270 uint32_t resultTypeID = 0; 271 if (failed(processType(op.getLoc(), op.getType(), resultTypeID))) { 272 return failure(); 273 } 274 operands.push_back(resultTypeID); 275 resultID = getNextID(); 276 valueIDMap[op.getResult()] = resultID; 277 operands.push_back(resultID); 278 auto attr = op->getAttr(spirv::attributeName<spirv::StorageClass>()); 279 if (attr) { 280 operands.push_back(static_cast<uint32_t>( 281 attr.cast<IntegerAttr>().getValue().getZExtValue())); 282 } 283 elidedAttrs.push_back(spirv::attributeName<spirv::StorageClass>()); 284 for (auto arg : op.getODSOperands(0)) { 285 auto argID = getValueID(arg); 286 if (!argID) { 287 return emitError(op.getLoc(), "operand 0 has a use before def"); 288 } 289 operands.push_back(argID); 290 } 291 if (failed(emitDebugLine(functionHeader, op.getLoc()))) 292 return failure(); 293 encodeInstructionInto(functionHeader, spirv::Opcode::OpVariable, operands); 294 for (auto attr : op->getAttrs()) { 295 if (llvm::any_of(elidedAttrs, [&](StringRef elided) { 296 return attr.getName() == elided; 297 })) { 298 continue; 299 } 300 if (failed(processDecoration(op.getLoc(), resultID, attr))) { 301 return failure(); 302 } 303 } 304 return success(); 305 } 306 307 LogicalResult 308 Serializer::processGlobalVariableOp(spirv::GlobalVariableOp varOp) { 309 // Get TypeID. 310 uint32_t resultTypeID = 0; 311 SmallVector<StringRef, 4> elidedAttrs; 312 if (failed(processType(varOp.getLoc(), varOp.type(), resultTypeID))) { 313 return failure(); 314 } 315 316 elidedAttrs.push_back("type"); 317 SmallVector<uint32_t, 4> operands; 318 operands.push_back(resultTypeID); 319 auto resultID = getNextID(); 320 321 // Encode the name. 322 auto varName = varOp.sym_name(); 323 elidedAttrs.push_back(SymbolTable::getSymbolAttrName()); 324 if (failed(processName(resultID, varName))) { 325 return failure(); 326 } 327 globalVarIDMap[varName] = resultID; 328 operands.push_back(resultID); 329 330 // Encode StorageClass. 331 operands.push_back(static_cast<uint32_t>(varOp.storageClass())); 332 333 // Encode initialization. 334 if (auto initializer = varOp.initializer()) { 335 auto initializerID = getVariableID(initializer.getValue()); 336 if (!initializerID) { 337 return emitError(varOp.getLoc(), 338 "invalid usage of undefined variable as initializer"); 339 } 340 operands.push_back(initializerID); 341 elidedAttrs.push_back("initializer"); 342 } 343 344 if (failed(emitDebugLine(typesGlobalValues, varOp.getLoc()))) 345 return failure(); 346 encodeInstructionInto(typesGlobalValues, spirv::Opcode::OpVariable, operands); 347 elidedAttrs.push_back("initializer"); 348 349 // Encode decorations. 350 for (auto attr : varOp->getAttrs()) { 351 if (llvm::any_of(elidedAttrs, [&](StringRef elided) { 352 return attr.getName() == elided; 353 })) { 354 continue; 355 } 356 if (failed(processDecoration(varOp.getLoc(), resultID, attr))) { 357 return failure(); 358 } 359 } 360 return success(); 361 } 362 363 LogicalResult Serializer::processSelectionOp(spirv::SelectionOp selectionOp) { 364 // Assign <id>s to all blocks so that branches inside the SelectionOp can 365 // resolve properly. 366 auto &body = selectionOp.body(); 367 for (Block &block : body) 368 getOrCreateBlockID(&block); 369 370 auto *headerBlock = selectionOp.getHeaderBlock(); 371 auto *mergeBlock = selectionOp.getMergeBlock(); 372 auto mergeID = getBlockID(mergeBlock); 373 auto loc = selectionOp.getLoc(); 374 375 // Emit the selection header block, which dominates all other blocks, first. 376 // We need to emit an OpSelectionMerge instruction before the selection header 377 // block's terminator. 378 auto emitSelectionMerge = [&]() { 379 if (failed(emitDebugLine(functionBody, loc))) 380 return failure(); 381 lastProcessedWasMergeInst = true; 382 encodeInstructionInto( 383 functionBody, spirv::Opcode::OpSelectionMerge, 384 {mergeID, static_cast<uint32_t>(selectionOp.selection_control())}); 385 return success(); 386 }; 387 // For structured selection, we cannot have blocks in the selection construct 388 // branching to the selection header block. Entering the selection (and 389 // reaching the selection header) must be from the block containing the 390 // spv.mlir.selection op. If there are ops ahead of the spv.mlir.selection op 391 // in the block, we can "merge" them into the selection header. So here we 392 // don't need to emit a separate block; just continue with the existing block. 393 if (failed(processBlock(headerBlock, /*omitLabel=*/true, emitSelectionMerge))) 394 return failure(); 395 396 // Process all blocks with a depth-first visitor starting from the header 397 // block. The selection header block and merge block are skipped by this 398 // visitor. 399 if (failed(visitInPrettyBlockOrder( 400 headerBlock, [&](Block *block) { return processBlock(block); }, 401 /*skipHeader=*/true, /*skipBlocks=*/{mergeBlock}))) 402 return failure(); 403 404 // There is nothing to do for the merge block in the selection, which just 405 // contains a spv.mlir.merge op, itself. But we need to have an OpLabel 406 // instruction to start a new SPIR-V block for ops following this SelectionOp. 407 // The block should use the <id> for the merge block. 408 encodeInstructionInto(functionBody, spirv::Opcode::OpLabel, {mergeID}); 409 return success(); 410 } 411 412 LogicalResult Serializer::processLoopOp(spirv::LoopOp loopOp) { 413 // Assign <id>s to all blocks so that branches inside the LoopOp can resolve 414 // properly. We don't need to assign for the entry block, which is just for 415 // satisfying MLIR region's structural requirement. 416 auto &body = loopOp.body(); 417 for (Block &block : 418 llvm::make_range(std::next(body.begin(), 1), body.end())) { 419 getOrCreateBlockID(&block); 420 } 421 auto *headerBlock = loopOp.getHeaderBlock(); 422 auto *continueBlock = loopOp.getContinueBlock(); 423 auto *mergeBlock = loopOp.getMergeBlock(); 424 auto headerID = getBlockID(headerBlock); 425 auto continueID = getBlockID(continueBlock); 426 auto mergeID = getBlockID(mergeBlock); 427 auto loc = loopOp.getLoc(); 428 429 // This LoopOp is in some MLIR block with preceding and following ops. In the 430 // binary format, it should reside in separate SPIR-V blocks from its 431 // preceding and following ops. So we need to emit unconditional branches to 432 // jump to this LoopOp's SPIR-V blocks and jumping back to the normal flow 433 // afterwards. 434 encodeInstructionInto(functionBody, spirv::Opcode::OpBranch, {headerID}); 435 436 // LoopOp's entry block is just there for satisfying MLIR's structural 437 // requirements so we omit it and start serialization from the loop header 438 // block. 439 440 // Emit the loop header block, which dominates all other blocks, first. We 441 // need to emit an OpLoopMerge instruction before the loop header block's 442 // terminator. 443 auto emitLoopMerge = [&]() { 444 if (failed(emitDebugLine(functionBody, loc))) 445 return failure(); 446 lastProcessedWasMergeInst = true; 447 encodeInstructionInto( 448 functionBody, spirv::Opcode::OpLoopMerge, 449 {mergeID, continueID, static_cast<uint32_t>(loopOp.loop_control())}); 450 return success(); 451 }; 452 if (failed(processBlock(headerBlock, /*omitLabel=*/false, emitLoopMerge))) 453 return failure(); 454 455 // Process all blocks with a depth-first visitor starting from the header 456 // block. The loop header block, loop continue block, and loop merge block are 457 // skipped by this visitor and handled later in this function. 458 if (failed(visitInPrettyBlockOrder( 459 headerBlock, [&](Block *block) { return processBlock(block); }, 460 /*skipHeader=*/true, /*skipBlocks=*/{continueBlock, mergeBlock}))) 461 return failure(); 462 463 // We have handled all other blocks. Now get to the loop continue block. 464 if (failed(processBlock(continueBlock))) 465 return failure(); 466 467 // There is nothing to do for the merge block in the loop, which just contains 468 // a spv.mlir.merge op, itself. But we need to have an OpLabel instruction to 469 // start a new SPIR-V block for ops following this LoopOp. The block should 470 // use the <id> for the merge block. 471 encodeInstructionInto(functionBody, spirv::Opcode::OpLabel, {mergeID}); 472 return success(); 473 } 474 475 LogicalResult Serializer::processBranchConditionalOp( 476 spirv::BranchConditionalOp condBranchOp) { 477 auto conditionID = getValueID(condBranchOp.condition()); 478 auto trueLabelID = getOrCreateBlockID(condBranchOp.getTrueBlock()); 479 auto falseLabelID = getOrCreateBlockID(condBranchOp.getFalseBlock()); 480 SmallVector<uint32_t, 5> arguments{conditionID, trueLabelID, falseLabelID}; 481 482 if (auto weights = condBranchOp.branch_weights()) { 483 for (auto val : weights->getValue()) 484 arguments.push_back(val.cast<IntegerAttr>().getInt()); 485 } 486 487 if (failed(emitDebugLine(functionBody, condBranchOp.getLoc()))) 488 return failure(); 489 encodeInstructionInto(functionBody, spirv::Opcode::OpBranchConditional, 490 arguments); 491 return success(); 492 } 493 494 LogicalResult Serializer::processBranchOp(spirv::BranchOp branchOp) { 495 if (failed(emitDebugLine(functionBody, branchOp.getLoc()))) 496 return failure(); 497 encodeInstructionInto(functionBody, spirv::Opcode::OpBranch, 498 {getOrCreateBlockID(branchOp.getTarget())}); 499 return success(); 500 } 501 502 LogicalResult Serializer::processAddressOfOp(spirv::AddressOfOp addressOfOp) { 503 auto varName = addressOfOp.variable(); 504 auto variableID = getVariableID(varName); 505 if (!variableID) { 506 return addressOfOp.emitError("unknown result <id> for variable ") 507 << varName; 508 } 509 valueIDMap[addressOfOp.pointer()] = variableID; 510 return success(); 511 } 512 513 LogicalResult 514 Serializer::processReferenceOfOp(spirv::ReferenceOfOp referenceOfOp) { 515 auto constName = referenceOfOp.spec_const(); 516 auto constID = getSpecConstID(constName); 517 if (!constID) { 518 return referenceOfOp.emitError( 519 "unknown result <id> for specialization constant ") 520 << constName; 521 } 522 valueIDMap[referenceOfOp.reference()] = constID; 523 return success(); 524 } 525 526 template <> 527 LogicalResult 528 Serializer::processOp<spirv::EntryPointOp>(spirv::EntryPointOp op) { 529 SmallVector<uint32_t, 4> operands; 530 // Add the ExecutionModel. 531 operands.push_back(static_cast<uint32_t>(op.execution_model())); 532 // Add the function <id>. 533 auto funcID = getFunctionID(op.fn()); 534 if (!funcID) { 535 return op.emitError("missing <id> for function ") 536 << op.fn() 537 << "; function needs to be defined before spv.EntryPoint is " 538 "serialized"; 539 } 540 operands.push_back(funcID); 541 // Add the name of the function. 542 spirv::encodeStringLiteralInto(operands, op.fn()); 543 544 // Add the interface values. 545 if (auto interface = op.interface()) { 546 for (auto var : interface.getValue()) { 547 auto id = getVariableID(var.cast<FlatSymbolRefAttr>().getValue()); 548 if (!id) { 549 return op.emitError("referencing undefined global variable." 550 "spv.EntryPoint is at the end of spv.module. All " 551 "referenced variables should already be defined"); 552 } 553 operands.push_back(id); 554 } 555 } 556 encodeInstructionInto(entryPoints, spirv::Opcode::OpEntryPoint, operands); 557 return success(); 558 } 559 560 template <> 561 LogicalResult 562 Serializer::processOp<spirv::ControlBarrierOp>(spirv::ControlBarrierOp op) { 563 StringRef argNames[] = {"execution_scope", "memory_scope", 564 "memory_semantics"}; 565 SmallVector<uint32_t, 3> operands; 566 567 for (auto argName : argNames) { 568 auto argIntAttr = op->getAttrOfType<IntegerAttr>(argName); 569 auto operand = prepareConstantInt(op.getLoc(), argIntAttr); 570 if (!operand) { 571 return failure(); 572 } 573 operands.push_back(operand); 574 } 575 576 encodeInstructionInto(functionBody, spirv::Opcode::OpControlBarrier, 577 operands); 578 return success(); 579 } 580 581 template <> 582 LogicalResult 583 Serializer::processOp<spirv::ExecutionModeOp>(spirv::ExecutionModeOp op) { 584 SmallVector<uint32_t, 4> operands; 585 // Add the function <id>. 586 auto funcID = getFunctionID(op.fn()); 587 if (!funcID) { 588 return op.emitError("missing <id> for function ") 589 << op.fn() 590 << "; function needs to be serialized before ExecutionModeOp is " 591 "serialized"; 592 } 593 operands.push_back(funcID); 594 // Add the ExecutionMode. 595 operands.push_back(static_cast<uint32_t>(op.execution_mode())); 596 597 // Serialize values if any. 598 auto values = op.values(); 599 if (values) { 600 for (auto &intVal : values.getValue()) { 601 operands.push_back(static_cast<uint32_t>( 602 intVal.cast<IntegerAttr>().getValue().getZExtValue())); 603 } 604 } 605 encodeInstructionInto(executionModes, spirv::Opcode::OpExecutionMode, 606 operands); 607 return success(); 608 } 609 610 template <> 611 LogicalResult 612 Serializer::processOp<spirv::MemoryBarrierOp>(spirv::MemoryBarrierOp op) { 613 StringRef argNames[] = {"memory_scope", "memory_semantics"}; 614 SmallVector<uint32_t, 2> operands; 615 616 for (auto argName : argNames) { 617 auto argIntAttr = op->getAttrOfType<IntegerAttr>(argName); 618 auto operand = prepareConstantInt(op.getLoc(), argIntAttr); 619 if (!operand) { 620 return failure(); 621 } 622 operands.push_back(operand); 623 } 624 625 encodeInstructionInto(functionBody, spirv::Opcode::OpMemoryBarrier, operands); 626 return success(); 627 } 628 629 template <> 630 LogicalResult 631 Serializer::processOp<spirv::FunctionCallOp>(spirv::FunctionCallOp op) { 632 auto funcName = op.callee(); 633 uint32_t resTypeID = 0; 634 635 Type resultTy = op.getNumResults() ? *op.result_type_begin() : getVoidType(); 636 if (failed(processType(op.getLoc(), resultTy, resTypeID))) 637 return failure(); 638 639 auto funcID = getOrCreateFunctionID(funcName); 640 auto funcCallID = getNextID(); 641 SmallVector<uint32_t, 8> operands{resTypeID, funcCallID, funcID}; 642 643 for (auto value : op.arguments()) { 644 auto valueID = getValueID(value); 645 assert(valueID && "cannot find a value for spv.FunctionCall"); 646 operands.push_back(valueID); 647 } 648 649 if (!resultTy.isa<NoneType>()) 650 valueIDMap[op.getResult(0)] = funcCallID; 651 652 encodeInstructionInto(functionBody, spirv::Opcode::OpFunctionCall, operands); 653 return success(); 654 } 655 656 template <> 657 LogicalResult 658 Serializer::processOp<spirv::CopyMemoryOp>(spirv::CopyMemoryOp op) { 659 SmallVector<uint32_t, 4> operands; 660 SmallVector<StringRef, 2> elidedAttrs; 661 662 for (Value operand : op->getOperands()) { 663 auto id = getValueID(operand); 664 assert(id && "use before def!"); 665 operands.push_back(id); 666 } 667 668 if (auto attr = op->getAttr("memory_access")) { 669 operands.push_back(static_cast<uint32_t>( 670 attr.cast<IntegerAttr>().getValue().getZExtValue())); 671 } 672 673 elidedAttrs.push_back("memory_access"); 674 675 if (auto attr = op->getAttr("alignment")) { 676 operands.push_back(static_cast<uint32_t>( 677 attr.cast<IntegerAttr>().getValue().getZExtValue())); 678 } 679 680 elidedAttrs.push_back("alignment"); 681 682 if (auto attr = op->getAttr("source_memory_access")) { 683 operands.push_back(static_cast<uint32_t>( 684 attr.cast<IntegerAttr>().getValue().getZExtValue())); 685 } 686 687 elidedAttrs.push_back("source_memory_access"); 688 689 if (auto attr = op->getAttr("source_alignment")) { 690 operands.push_back(static_cast<uint32_t>( 691 attr.cast<IntegerAttr>().getValue().getZExtValue())); 692 } 693 694 elidedAttrs.push_back("source_alignment"); 695 if (failed(emitDebugLine(functionBody, op.getLoc()))) 696 return failure(); 697 encodeInstructionInto(functionBody, spirv::Opcode::OpCopyMemory, operands); 698 699 return success(); 700 } 701 702 // Pull in auto-generated Serializer::dispatchToAutogenSerialization() and 703 // various Serializer::processOp<...>() specializations. 704 #define GET_SERIALIZATION_FNS 705 #include "mlir/Dialect/SPIRV/IR/SPIRVSerialization.inc" 706 707 } // namespace spirv 708 } // namespace mlir 709