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 (void)emitDecoration(resultID, spirv::Decoration::SpecId, {val}); 74 } 75 76 specConstIDMap[op.sym_name()] = resultID; 77 return processName(resultID, op.sym_name()); 78 } 79 return failure(); 80 } 81 82 LogicalResult 83 Serializer::processSpecConstantCompositeOp(spirv::SpecConstantCompositeOp op) { 84 uint32_t typeID = 0; 85 if (failed(processType(op.getLoc(), op.type(), typeID))) { 86 return failure(); 87 } 88 89 auto resultID = getNextID(); 90 91 SmallVector<uint32_t, 8> operands; 92 operands.push_back(typeID); 93 operands.push_back(resultID); 94 95 auto constituents = op.constituents(); 96 97 for (auto index : llvm::seq<uint32_t>(0, constituents.size())) { 98 auto constituent = constituents[index].dyn_cast<FlatSymbolRefAttr>(); 99 100 auto constituentName = constituent.getValue(); 101 auto constituentID = getSpecConstID(constituentName); 102 103 if (!constituentID) { 104 return op.emitError("unknown result <id> for specialization constant ") 105 << constituentName; 106 } 107 108 operands.push_back(constituentID); 109 } 110 111 (void)encodeInstructionInto(typesGlobalValues, 112 spirv::Opcode::OpSpecConstantComposite, operands); 113 specConstIDMap[op.sym_name()] = resultID; 114 115 return processName(resultID, op.sym_name()); 116 } 117 118 LogicalResult 119 Serializer::processSpecConstantOperationOp(spirv::SpecConstantOperationOp op) { 120 uint32_t typeID = 0; 121 if (failed(processType(op.getLoc(), op.getType(), typeID))) { 122 return failure(); 123 } 124 125 auto resultID = getNextID(); 126 127 SmallVector<uint32_t, 8> operands; 128 operands.push_back(typeID); 129 operands.push_back(resultID); 130 131 Block &block = op.getRegion().getBlocks().front(); 132 Operation &enclosedOp = block.getOperations().front(); 133 134 std::string enclosedOpName; 135 llvm::raw_string_ostream rss(enclosedOpName); 136 rss << "Op" << enclosedOp.getName().stripDialect(); 137 auto enclosedOpcode = spirv::symbolizeOpcode(rss.str()); 138 139 if (!enclosedOpcode) { 140 op.emitError("Couldn't find op code for op ") 141 << enclosedOp.getName().getStringRef(); 142 return failure(); 143 } 144 145 operands.push_back(static_cast<uint32_t>(enclosedOpcode.getValue())); 146 147 // Append operands to the enclosed op to the list of operands. 148 for (Value operand : enclosedOp.getOperands()) { 149 uint32_t id = getValueID(operand); 150 assert(id && "use before def!"); 151 operands.push_back(id); 152 } 153 154 (void)encodeInstructionInto(typesGlobalValues, 155 spirv::Opcode::OpSpecConstantOp, operands); 156 valueIDMap[op.getResult()] = resultID; 157 158 return success(); 159 } 160 161 LogicalResult Serializer::processUndefOp(spirv::UndefOp op) { 162 auto undefType = op.getType(); 163 auto &id = undefValIDMap[undefType]; 164 if (!id) { 165 id = getNextID(); 166 uint32_t typeID = 0; 167 if (failed(processType(op.getLoc(), undefType, typeID)) || 168 failed(encodeInstructionInto(typesGlobalValues, spirv::Opcode::OpUndef, 169 {typeID, id}))) { 170 return failure(); 171 } 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 (void)processType(op.getLoc(), op.getType(), fnTypeID); 184 185 // Add the function definition. 186 SmallVector<uint32_t, 4> operands; 187 uint32_t resTypeID = 0; 188 auto resultTypes = op.getType().getResults(); 189 if (resultTypes.size() > 1) { 190 return op.emitError("cannot serialize function with multiple return types"); 191 } 192 if (failed(processType(op.getLoc(), 193 (resultTypes.empty() ? getVoidType() : resultTypes[0]), 194 resTypeID))) { 195 return failure(); 196 } 197 operands.push_back(resTypeID); 198 auto funcID = getOrCreateFunctionID(op.getName()); 199 operands.push_back(funcID); 200 operands.push_back(static_cast<uint32_t>(op.function_control())); 201 operands.push_back(fnTypeID); 202 (void)encodeInstructionInto(functionHeader, spirv::Opcode::OpFunction, 203 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 (void)encodeInstructionInto(functionHeader, 219 spirv::Opcode::OpFunctionParameter, 220 {argTypeID, argValueID}); 221 } 222 223 // Process the body. 224 if (op.isExternal()) { 225 return op.emitError("external function is unhandled"); 226 } 227 228 // Some instructions (e.g., OpVariable) in a function must be in the first 229 // block in the function. These instructions will be put in functionHeader. 230 // Thus, we put the label in functionHeader first, and omit it from the first 231 // block. 232 (void)encodeInstructionInto(functionHeader, spirv::Opcode::OpLabel, 233 {getOrCreateBlockID(&op.front())}); 234 (void)processBlock(&op.front(), /*omitLabel=*/true); 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 (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 if (failed(encodeInstructionInto(functionBody, spirv::Opcode::OpFunctionEnd, 257 {}))) { 258 return failure(); 259 } 260 261 functions.append(functionHeader.begin(), functionHeader.end()); 262 functions.append(functionBody.begin(), functionBody.end()); 263 functionHeader.clear(); 264 functionBody.clear(); 265 266 return success(); 267 } 268 269 LogicalResult Serializer::processVariableOp(spirv::VariableOp op) { 270 SmallVector<uint32_t, 4> operands; 271 SmallVector<StringRef, 2> elidedAttrs; 272 uint32_t resultID = 0; 273 uint32_t resultTypeID = 0; 274 if (failed(processType(op.getLoc(), op.getType(), resultTypeID))) { 275 return failure(); 276 } 277 operands.push_back(resultTypeID); 278 resultID = getNextID(); 279 valueIDMap[op.getResult()] = resultID; 280 operands.push_back(resultID); 281 auto attr = op->getAttr(spirv::attributeName<spirv::StorageClass>()); 282 if (attr) { 283 operands.push_back(static_cast<uint32_t>( 284 attr.cast<IntegerAttr>().getValue().getZExtValue())); 285 } 286 elidedAttrs.push_back(spirv::attributeName<spirv::StorageClass>()); 287 for (auto arg : op.getODSOperands(0)) { 288 auto argID = getValueID(arg); 289 if (!argID) { 290 return emitError(op.getLoc(), "operand 0 has a use before def"); 291 } 292 operands.push_back(argID); 293 } 294 (void)emitDebugLine(functionHeader, op.getLoc()); 295 (void)encodeInstructionInto(functionHeader, spirv::Opcode::OpVariable, 296 operands); 297 for (auto attr : op->getAttrs()) { 298 if (llvm::any_of(elidedAttrs, [&](StringRef elided) { 299 return attr.getName() == elided; 300 })) { 301 continue; 302 } 303 if (failed(processDecoration(op.getLoc(), resultID, attr))) { 304 return failure(); 305 } 306 } 307 return success(); 308 } 309 310 LogicalResult 311 Serializer::processGlobalVariableOp(spirv::GlobalVariableOp varOp) { 312 // Get TypeID. 313 uint32_t resultTypeID = 0; 314 SmallVector<StringRef, 4> elidedAttrs; 315 if (failed(processType(varOp.getLoc(), varOp.type(), resultTypeID))) { 316 return failure(); 317 } 318 319 elidedAttrs.push_back("type"); 320 SmallVector<uint32_t, 4> operands; 321 operands.push_back(resultTypeID); 322 auto resultID = getNextID(); 323 324 // Encode the name. 325 auto varName = varOp.sym_name(); 326 elidedAttrs.push_back(SymbolTable::getSymbolAttrName()); 327 if (failed(processName(resultID, varName))) { 328 return failure(); 329 } 330 globalVarIDMap[varName] = resultID; 331 operands.push_back(resultID); 332 333 // Encode StorageClass. 334 operands.push_back(static_cast<uint32_t>(varOp.storageClass())); 335 336 // Encode initialization. 337 if (auto initializer = varOp.initializer()) { 338 auto initializerID = getVariableID(initializer.getValue()); 339 if (!initializerID) { 340 return emitError(varOp.getLoc(), 341 "invalid usage of undefined variable as initializer"); 342 } 343 operands.push_back(initializerID); 344 elidedAttrs.push_back("initializer"); 345 } 346 347 (void)emitDebugLine(typesGlobalValues, varOp.getLoc()); 348 if (failed(encodeInstructionInto(typesGlobalValues, spirv::Opcode::OpVariable, 349 operands))) { 350 elidedAttrs.push_back("initializer"); 351 return failure(); 352 } 353 354 // Encode decorations. 355 for (auto attr : varOp->getAttrs()) { 356 if (llvm::any_of(elidedAttrs, [&](StringRef elided) { 357 return attr.getName() == elided; 358 })) { 359 continue; 360 } 361 if (failed(processDecoration(varOp.getLoc(), resultID, attr))) { 362 return failure(); 363 } 364 } 365 return success(); 366 } 367 368 LogicalResult Serializer::processSelectionOp(spirv::SelectionOp selectionOp) { 369 // Assign <id>s to all blocks so that branches inside the SelectionOp can 370 // resolve properly. 371 auto &body = selectionOp.body(); 372 for (Block &block : body) 373 getOrCreateBlockID(&block); 374 375 auto *headerBlock = selectionOp.getHeaderBlock(); 376 auto *mergeBlock = selectionOp.getMergeBlock(); 377 auto mergeID = getBlockID(mergeBlock); 378 auto loc = selectionOp.getLoc(); 379 380 // Emit the selection header block, which dominates all other blocks, first. 381 // We need to emit an OpSelectionMerge instruction before the selection header 382 // block's terminator. 383 auto emitSelectionMerge = [&]() { 384 (void)emitDebugLine(functionBody, loc); 385 lastProcessedWasMergeInst = true; 386 (void)encodeInstructionInto( 387 functionBody, spirv::Opcode::OpSelectionMerge, 388 {mergeID, static_cast<uint32_t>(selectionOp.selection_control())}); 389 }; 390 // For structured selection, we cannot have blocks in the selection construct 391 // branching to the selection header block. Entering the selection (and 392 // reaching the selection header) must be from the block containing the 393 // spv.mlir.selection op. If there are ops ahead of the spv.mlir.selection op 394 // in the block, we can "merge" them into the selection header. So here we 395 // don't need to emit a separate block; just continue with the existing block. 396 if (failed(processBlock(headerBlock, /*omitLabel=*/true, emitSelectionMerge))) 397 return failure(); 398 399 // Process all blocks with a depth-first visitor starting from the header 400 // block. The selection header block and merge block are skipped by this 401 // visitor. 402 if (failed(visitInPrettyBlockOrder( 403 headerBlock, [&](Block *block) { return processBlock(block); }, 404 /*skipHeader=*/true, /*skipBlocks=*/{mergeBlock}))) 405 return failure(); 406 407 // There is nothing to do for the merge block in the selection, which just 408 // contains a spv.mlir.merge op, itself. But we need to have an OpLabel 409 // instruction to start a new SPIR-V block for ops following this SelectionOp. 410 // The block should use the <id> for the merge block. 411 return encodeInstructionInto(functionBody, spirv::Opcode::OpLabel, {mergeID}); 412 } 413 414 LogicalResult Serializer::processLoopOp(spirv::LoopOp loopOp) { 415 // Assign <id>s to all blocks so that branches inside the LoopOp can resolve 416 // properly. We don't need to assign for the entry block, which is just for 417 // satisfying MLIR region's structural requirement. 418 auto &body = loopOp.body(); 419 for (Block &block : 420 llvm::make_range(std::next(body.begin(), 1), body.end())) { 421 getOrCreateBlockID(&block); 422 } 423 auto *headerBlock = loopOp.getHeaderBlock(); 424 auto *continueBlock = loopOp.getContinueBlock(); 425 auto *mergeBlock = loopOp.getMergeBlock(); 426 auto headerID = getBlockID(headerBlock); 427 auto continueID = getBlockID(continueBlock); 428 auto mergeID = getBlockID(mergeBlock); 429 auto loc = loopOp.getLoc(); 430 431 // This LoopOp is in some MLIR block with preceding and following ops. In the 432 // binary format, it should reside in separate SPIR-V blocks from its 433 // preceding and following ops. So we need to emit unconditional branches to 434 // jump to this LoopOp's SPIR-V blocks and jumping back to the normal flow 435 // afterwards. 436 (void)encodeInstructionInto(functionBody, spirv::Opcode::OpBranch, 437 {headerID}); 438 439 // LoopOp's entry block is just there for satisfying MLIR's structural 440 // requirements so we omit it and start serialization from the loop header 441 // block. 442 443 // Emit the loop header block, which dominates all other blocks, first. We 444 // need to emit an OpLoopMerge instruction before the loop header block's 445 // terminator. 446 auto emitLoopMerge = [&]() { 447 (void)emitDebugLine(functionBody, loc); 448 lastProcessedWasMergeInst = true; 449 (void)encodeInstructionInto( 450 functionBody, spirv::Opcode::OpLoopMerge, 451 {mergeID, continueID, static_cast<uint32_t>(loopOp.loop_control())}); 452 }; 453 if (failed(processBlock(headerBlock, /*omitLabel=*/false, emitLoopMerge))) 454 return failure(); 455 456 // Process all blocks with a depth-first visitor starting from the header 457 // block. The loop header block, loop continue block, and loop merge block are 458 // skipped by this visitor and handled later in this function. 459 if (failed(visitInPrettyBlockOrder( 460 headerBlock, [&](Block *block) { return processBlock(block); }, 461 /*skipHeader=*/true, /*skipBlocks=*/{continueBlock, mergeBlock}))) 462 return failure(); 463 464 // We have handled all other blocks. Now get to the loop continue block. 465 if (failed(processBlock(continueBlock))) 466 return failure(); 467 468 // There is nothing to do for the merge block in the loop, which just contains 469 // a spv.mlir.merge op, itself. But we need to have an OpLabel instruction to 470 // start a new SPIR-V block for ops following this LoopOp. The block should 471 // use the <id> for the merge block. 472 return encodeInstructionInto(functionBody, spirv::Opcode::OpLabel, {mergeID}); 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 (void)emitDebugLine(functionBody, condBranchOp.getLoc()); 488 return encodeInstructionInto(functionBody, spirv::Opcode::OpBranchConditional, 489 arguments); 490 } 491 492 LogicalResult Serializer::processBranchOp(spirv::BranchOp branchOp) { 493 (void)emitDebugLine(functionBody, branchOp.getLoc()); 494 return encodeInstructionInto(functionBody, spirv::Opcode::OpBranch, 495 {getOrCreateBlockID(branchOp.getTarget())}); 496 } 497 498 LogicalResult Serializer::processAddressOfOp(spirv::AddressOfOp addressOfOp) { 499 auto varName = addressOfOp.variable(); 500 auto variableID = getVariableID(varName); 501 if (!variableID) { 502 return addressOfOp.emitError("unknown result <id> for variable ") 503 << varName; 504 } 505 valueIDMap[addressOfOp.pointer()] = variableID; 506 return success(); 507 } 508 509 LogicalResult 510 Serializer::processReferenceOfOp(spirv::ReferenceOfOp referenceOfOp) { 511 auto constName = referenceOfOp.spec_const(); 512 auto constID = getSpecConstID(constName); 513 if (!constID) { 514 return referenceOfOp.emitError( 515 "unknown result <id> for specialization constant ") 516 << constName; 517 } 518 valueIDMap[referenceOfOp.reference()] = constID; 519 return success(); 520 } 521 522 template <> 523 LogicalResult 524 Serializer::processOp<spirv::EntryPointOp>(spirv::EntryPointOp op) { 525 SmallVector<uint32_t, 4> operands; 526 // Add the ExecutionModel. 527 operands.push_back(static_cast<uint32_t>(op.execution_model())); 528 // Add the function <id>. 529 auto funcID = getFunctionID(op.fn()); 530 if (!funcID) { 531 return op.emitError("missing <id> for function ") 532 << op.fn() 533 << "; function needs to be defined before spv.EntryPoint is " 534 "serialized"; 535 } 536 operands.push_back(funcID); 537 // Add the name of the function. 538 (void)spirv::encodeStringLiteralInto(operands, op.fn()); 539 540 // Add the interface values. 541 if (auto interface = op.interface()) { 542 for (auto var : interface.getValue()) { 543 auto id = getVariableID(var.cast<FlatSymbolRefAttr>().getValue()); 544 if (!id) { 545 return op.emitError("referencing undefined global variable." 546 "spv.EntryPoint is at the end of spv.module. All " 547 "referenced variables should already be defined"); 548 } 549 operands.push_back(id); 550 } 551 } 552 return encodeInstructionInto(entryPoints, spirv::Opcode::OpEntryPoint, 553 operands); 554 } 555 556 template <> 557 LogicalResult 558 Serializer::processOp<spirv::ControlBarrierOp>(spirv::ControlBarrierOp op) { 559 StringRef argNames[] = {"execution_scope", "memory_scope", 560 "memory_semantics"}; 561 SmallVector<uint32_t, 3> operands; 562 563 for (auto argName : argNames) { 564 auto argIntAttr = op->getAttrOfType<IntegerAttr>(argName); 565 auto operand = prepareConstantInt(op.getLoc(), argIntAttr); 566 if (!operand) { 567 return failure(); 568 } 569 operands.push_back(operand); 570 } 571 572 return encodeInstructionInto(functionBody, spirv::Opcode::OpControlBarrier, 573 operands); 574 } 575 576 template <> 577 LogicalResult 578 Serializer::processOp<spirv::ExecutionModeOp>(spirv::ExecutionModeOp op) { 579 SmallVector<uint32_t, 4> operands; 580 // Add the function <id>. 581 auto funcID = getFunctionID(op.fn()); 582 if (!funcID) { 583 return op.emitError("missing <id> for function ") 584 << op.fn() 585 << "; function needs to be serialized before ExecutionModeOp is " 586 "serialized"; 587 } 588 operands.push_back(funcID); 589 // Add the ExecutionMode. 590 operands.push_back(static_cast<uint32_t>(op.execution_mode())); 591 592 // Serialize values if any. 593 auto values = op.values(); 594 if (values) { 595 for (auto &intVal : values.getValue()) { 596 operands.push_back(static_cast<uint32_t>( 597 intVal.cast<IntegerAttr>().getValue().getZExtValue())); 598 } 599 } 600 return encodeInstructionInto(executionModes, spirv::Opcode::OpExecutionMode, 601 operands); 602 } 603 604 template <> 605 LogicalResult 606 Serializer::processOp<spirv::MemoryBarrierOp>(spirv::MemoryBarrierOp op) { 607 StringRef argNames[] = {"memory_scope", "memory_semantics"}; 608 SmallVector<uint32_t, 2> operands; 609 610 for (auto argName : argNames) { 611 auto argIntAttr = op->getAttrOfType<IntegerAttr>(argName); 612 auto operand = prepareConstantInt(op.getLoc(), argIntAttr); 613 if (!operand) { 614 return failure(); 615 } 616 operands.push_back(operand); 617 } 618 619 return encodeInstructionInto(functionBody, spirv::Opcode::OpMemoryBarrier, 620 operands); 621 } 622 623 template <> 624 LogicalResult 625 Serializer::processOp<spirv::FunctionCallOp>(spirv::FunctionCallOp op) { 626 auto funcName = op.callee(); 627 uint32_t resTypeID = 0; 628 629 Type resultTy = op.getNumResults() ? *op.result_type_begin() : getVoidType(); 630 if (failed(processType(op.getLoc(), resultTy, resTypeID))) 631 return failure(); 632 633 auto funcID = getOrCreateFunctionID(funcName); 634 auto funcCallID = getNextID(); 635 SmallVector<uint32_t, 8> operands{resTypeID, funcCallID, funcID}; 636 637 for (auto value : op.arguments()) { 638 auto valueID = getValueID(value); 639 assert(valueID && "cannot find a value for spv.FunctionCall"); 640 operands.push_back(valueID); 641 } 642 643 if (!resultTy.isa<NoneType>()) 644 valueIDMap[op.getResult(0)] = funcCallID; 645 646 return encodeInstructionInto(functionBody, spirv::Opcode::OpFunctionCall, 647 operands); 648 } 649 650 template <> 651 LogicalResult 652 Serializer::processOp<spirv::CopyMemoryOp>(spirv::CopyMemoryOp op) { 653 SmallVector<uint32_t, 4> operands; 654 SmallVector<StringRef, 2> elidedAttrs; 655 656 for (Value operand : op->getOperands()) { 657 auto id = getValueID(operand); 658 assert(id && "use before def!"); 659 operands.push_back(id); 660 } 661 662 if (auto attr = op->getAttr("memory_access")) { 663 operands.push_back(static_cast<uint32_t>( 664 attr.cast<IntegerAttr>().getValue().getZExtValue())); 665 } 666 667 elidedAttrs.push_back("memory_access"); 668 669 if (auto attr = op->getAttr("alignment")) { 670 operands.push_back(static_cast<uint32_t>( 671 attr.cast<IntegerAttr>().getValue().getZExtValue())); 672 } 673 674 elidedAttrs.push_back("alignment"); 675 676 if (auto attr = op->getAttr("source_memory_access")) { 677 operands.push_back(static_cast<uint32_t>( 678 attr.cast<IntegerAttr>().getValue().getZExtValue())); 679 } 680 681 elidedAttrs.push_back("source_memory_access"); 682 683 if (auto attr = op->getAttr("source_alignment")) { 684 operands.push_back(static_cast<uint32_t>( 685 attr.cast<IntegerAttr>().getValue().getZExtValue())); 686 } 687 688 elidedAttrs.push_back("source_alignment"); 689 (void)emitDebugLine(functionBody, op.getLoc()); 690 (void)encodeInstructionInto(functionBody, spirv::Opcode::OpCopyMemory, 691 operands); 692 693 return success(); 694 } 695 696 // Pull in auto-generated Serializer::dispatchToAutogenSerialization() and 697 // various Serializer::processOp<...>() specializations. 698 #define GET_SERIALIZATION_FNS 699 #include "mlir/Dialect/SPIRV/IR/SPIRVSerialization.inc" 700 701 } // namespace spirv 702 } // namespace mlir 703