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
visitInPrettyBlockOrder(Block * headerBlock,function_ref<LogicalResult (Block *)> blockHandler,bool skipHeader=false,BlockRange skipBlocks={})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 {
processConstantOp(spirv::ConstantOp op)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
processSpecConstantOp(spirv::SpecConstantOp op)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
processSpecConstantCompositeOp(spirv::SpecConstantCompositeOp op)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
processSpecConstantOperationOp(spirv::SpecConstantOperationOp op)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
processUndefOp(spirv::UndefOp op)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
processFuncOp(spirv::FuncOp op)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
processVariableOp(spirv::VariableOp op)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,
299 [&](StringRef elided) { return attr.first == elided; })) {
300 continue;
301 }
302 if (failed(processDecoration(op.getLoc(), resultID, attr))) {
303 return failure();
304 }
305 }
306 return success();
307 }
308
309 LogicalResult
processGlobalVariableOp(spirv::GlobalVariableOp varOp)310 Serializer::processGlobalVariableOp(spirv::GlobalVariableOp varOp) {
311 // Get TypeID.
312 uint32_t resultTypeID = 0;
313 SmallVector<StringRef, 4> elidedAttrs;
314 if (failed(processType(varOp.getLoc(), varOp.type(), resultTypeID))) {
315 return failure();
316 }
317
318 if (isInterfaceStructPtrType(varOp.type())) {
319 auto structType = varOp.type()
320 .cast<spirv::PointerType>()
321 .getPointeeType()
322 .cast<spirv::StructType>();
323 if (failed(
324 emitDecoration(getTypeID(structType), spirv::Decoration::Block))) {
325 return varOp.emitError("cannot decorate ")
326 << structType << " with Block decoration";
327 }
328 }
329
330 elidedAttrs.push_back("type");
331 SmallVector<uint32_t, 4> operands;
332 operands.push_back(resultTypeID);
333 auto resultID = getNextID();
334
335 // Encode the name.
336 auto varName = varOp.sym_name();
337 elidedAttrs.push_back(SymbolTable::getSymbolAttrName());
338 if (failed(processName(resultID, varName))) {
339 return failure();
340 }
341 globalVarIDMap[varName] = resultID;
342 operands.push_back(resultID);
343
344 // Encode StorageClass.
345 operands.push_back(static_cast<uint32_t>(varOp.storageClass()));
346
347 // Encode initialization.
348 if (auto initializer = varOp.initializer()) {
349 auto initializerID = getVariableID(initializer.getValue());
350 if (!initializerID) {
351 return emitError(varOp.getLoc(),
352 "invalid usage of undefined variable as initializer");
353 }
354 operands.push_back(initializerID);
355 elidedAttrs.push_back("initializer");
356 }
357
358 (void)emitDebugLine(typesGlobalValues, varOp.getLoc());
359 if (failed(encodeInstructionInto(typesGlobalValues, spirv::Opcode::OpVariable,
360 operands))) {
361 elidedAttrs.push_back("initializer");
362 return failure();
363 }
364
365 // Encode decorations.
366 for (auto attr : varOp->getAttrs()) {
367 if (llvm::any_of(elidedAttrs,
368 [&](StringRef elided) { return attr.first == elided; })) {
369 continue;
370 }
371 if (failed(processDecoration(varOp.getLoc(), resultID, attr))) {
372 return failure();
373 }
374 }
375 return success();
376 }
377
processSelectionOp(spirv::SelectionOp selectionOp)378 LogicalResult Serializer::processSelectionOp(spirv::SelectionOp selectionOp) {
379 // Assign <id>s to all blocks so that branches inside the SelectionOp can
380 // resolve properly.
381 auto &body = selectionOp.body();
382 for (Block &block : body)
383 getOrCreateBlockID(&block);
384
385 auto *headerBlock = selectionOp.getHeaderBlock();
386 auto *mergeBlock = selectionOp.getMergeBlock();
387 auto mergeID = getBlockID(mergeBlock);
388 auto loc = selectionOp.getLoc();
389
390 // Emit the selection header block, which dominates all other blocks, first.
391 // We need to emit an OpSelectionMerge instruction before the selection header
392 // block's terminator.
393 auto emitSelectionMerge = [&]() {
394 (void)emitDebugLine(functionBody, loc);
395 lastProcessedWasMergeInst = true;
396 (void)encodeInstructionInto(
397 functionBody, spirv::Opcode::OpSelectionMerge,
398 {mergeID, static_cast<uint32_t>(selectionOp.selection_control())});
399 };
400 // For structured selection, we cannot have blocks in the selection construct
401 // branching to the selection header block. Entering the selection (and
402 // reaching the selection header) must be from the block containing the
403 // spv.mlir.selection op. If there are ops ahead of the spv.mlir.selection op
404 // in the block, we can "merge" them into the selection header. So here we
405 // don't need to emit a separate block; just continue with the existing block.
406 if (failed(processBlock(headerBlock, /*omitLabel=*/true, emitSelectionMerge)))
407 return failure();
408
409 // Process all blocks with a depth-first visitor starting from the header
410 // block. The selection header block and merge block are skipped by this
411 // visitor.
412 if (failed(visitInPrettyBlockOrder(
413 headerBlock, [&](Block *block) { return processBlock(block); },
414 /*skipHeader=*/true, /*skipBlocks=*/{mergeBlock})))
415 return failure();
416
417 // There is nothing to do for the merge block in the selection, which just
418 // contains a spv.mlir.merge op, itself. But we need to have an OpLabel
419 // instruction to start a new SPIR-V block for ops following this SelectionOp.
420 // The block should use the <id> for the merge block.
421 return encodeInstructionInto(functionBody, spirv::Opcode::OpLabel, {mergeID});
422 }
423
processLoopOp(spirv::LoopOp loopOp)424 LogicalResult Serializer::processLoopOp(spirv::LoopOp loopOp) {
425 // Assign <id>s to all blocks so that branches inside the LoopOp can resolve
426 // properly. We don't need to assign for the entry block, which is just for
427 // satisfying MLIR region's structural requirement.
428 auto &body = loopOp.body();
429 for (Block &block :
430 llvm::make_range(std::next(body.begin(), 1), body.end())) {
431 getOrCreateBlockID(&block);
432 }
433 auto *headerBlock = loopOp.getHeaderBlock();
434 auto *continueBlock = loopOp.getContinueBlock();
435 auto *mergeBlock = loopOp.getMergeBlock();
436 auto headerID = getBlockID(headerBlock);
437 auto continueID = getBlockID(continueBlock);
438 auto mergeID = getBlockID(mergeBlock);
439 auto loc = loopOp.getLoc();
440
441 // This LoopOp is in some MLIR block with preceding and following ops. In the
442 // binary format, it should reside in separate SPIR-V blocks from its
443 // preceding and following ops. So we need to emit unconditional branches to
444 // jump to this LoopOp's SPIR-V blocks and jumping back to the normal flow
445 // afterwards.
446 (void)encodeInstructionInto(functionBody, spirv::Opcode::OpBranch,
447 {headerID});
448
449 // LoopOp's entry block is just there for satisfying MLIR's structural
450 // requirements so we omit it and start serialization from the loop header
451 // block.
452
453 // Emit the loop header block, which dominates all other blocks, first. We
454 // need to emit an OpLoopMerge instruction before the loop header block's
455 // terminator.
456 auto emitLoopMerge = [&]() {
457 (void)emitDebugLine(functionBody, loc);
458 lastProcessedWasMergeInst = true;
459 (void)encodeInstructionInto(
460 functionBody, spirv::Opcode::OpLoopMerge,
461 {mergeID, continueID, static_cast<uint32_t>(loopOp.loop_control())});
462 };
463 if (failed(processBlock(headerBlock, /*omitLabel=*/false, emitLoopMerge)))
464 return failure();
465
466 // Process all blocks with a depth-first visitor starting from the header
467 // block. The loop header block, loop continue block, and loop merge block are
468 // skipped by this visitor and handled later in this function.
469 if (failed(visitInPrettyBlockOrder(
470 headerBlock, [&](Block *block) { return processBlock(block); },
471 /*skipHeader=*/true, /*skipBlocks=*/{continueBlock, mergeBlock})))
472 return failure();
473
474 // We have handled all other blocks. Now get to the loop continue block.
475 if (failed(processBlock(continueBlock)))
476 return failure();
477
478 // There is nothing to do for the merge block in the loop, which just contains
479 // a spv.mlir.merge op, itself. But we need to have an OpLabel instruction to
480 // start a new SPIR-V block for ops following this LoopOp. The block should
481 // use the <id> for the merge block.
482 return encodeInstructionInto(functionBody, spirv::Opcode::OpLabel, {mergeID});
483 }
484
processBranchConditionalOp(spirv::BranchConditionalOp condBranchOp)485 LogicalResult Serializer::processBranchConditionalOp(
486 spirv::BranchConditionalOp condBranchOp) {
487 auto conditionID = getValueID(condBranchOp.condition());
488 auto trueLabelID = getOrCreateBlockID(condBranchOp.getTrueBlock());
489 auto falseLabelID = getOrCreateBlockID(condBranchOp.getFalseBlock());
490 SmallVector<uint32_t, 5> arguments{conditionID, trueLabelID, falseLabelID};
491
492 if (auto weights = condBranchOp.branch_weights()) {
493 for (auto val : weights->getValue())
494 arguments.push_back(val.cast<IntegerAttr>().getInt());
495 }
496
497 (void)emitDebugLine(functionBody, condBranchOp.getLoc());
498 return encodeInstructionInto(functionBody, spirv::Opcode::OpBranchConditional,
499 arguments);
500 }
501
processBranchOp(spirv::BranchOp branchOp)502 LogicalResult Serializer::processBranchOp(spirv::BranchOp branchOp) {
503 (void)emitDebugLine(functionBody, branchOp.getLoc());
504 return encodeInstructionInto(functionBody, spirv::Opcode::OpBranch,
505 {getOrCreateBlockID(branchOp.getTarget())});
506 }
507
processAddressOfOp(spirv::AddressOfOp addressOfOp)508 LogicalResult Serializer::processAddressOfOp(spirv::AddressOfOp addressOfOp) {
509 auto varName = addressOfOp.variable();
510 auto variableID = getVariableID(varName);
511 if (!variableID) {
512 return addressOfOp.emitError("unknown result <id> for variable ")
513 << varName;
514 }
515 valueIDMap[addressOfOp.pointer()] = variableID;
516 return success();
517 }
518
519 LogicalResult
processReferenceOfOp(spirv::ReferenceOfOp referenceOfOp)520 Serializer::processReferenceOfOp(spirv::ReferenceOfOp referenceOfOp) {
521 auto constName = referenceOfOp.spec_const();
522 auto constID = getSpecConstID(constName);
523 if (!constID) {
524 return referenceOfOp.emitError(
525 "unknown result <id> for specialization constant ")
526 << constName;
527 }
528 valueIDMap[referenceOfOp.reference()] = constID;
529 return success();
530 }
531
532 template <>
533 LogicalResult
processOp(spirv::EntryPointOp op)534 Serializer::processOp<spirv::EntryPointOp>(spirv::EntryPointOp op) {
535 SmallVector<uint32_t, 4> operands;
536 // Add the ExecutionModel.
537 operands.push_back(static_cast<uint32_t>(op.execution_model()));
538 // Add the function <id>.
539 auto funcID = getFunctionID(op.fn());
540 if (!funcID) {
541 return op.emitError("missing <id> for function ")
542 << op.fn()
543 << "; function needs to be defined before spv.EntryPoint is "
544 "serialized";
545 }
546 operands.push_back(funcID);
547 // Add the name of the function.
548 (void)spirv::encodeStringLiteralInto(operands, op.fn());
549
550 // Add the interface values.
551 if (auto interface = op.interface()) {
552 for (auto var : interface.getValue()) {
553 auto id = getVariableID(var.cast<FlatSymbolRefAttr>().getValue());
554 if (!id) {
555 return op.emitError("referencing undefined global variable."
556 "spv.EntryPoint is at the end of spv.module. All "
557 "referenced variables should already be defined");
558 }
559 operands.push_back(id);
560 }
561 }
562 return encodeInstructionInto(entryPoints, spirv::Opcode::OpEntryPoint,
563 operands);
564 }
565
566 template <>
567 LogicalResult
processOp(spirv::ControlBarrierOp op)568 Serializer::processOp<spirv::ControlBarrierOp>(spirv::ControlBarrierOp op) {
569 StringRef argNames[] = {"execution_scope", "memory_scope",
570 "memory_semantics"};
571 SmallVector<uint32_t, 3> operands;
572
573 for (auto argName : argNames) {
574 auto argIntAttr = op->getAttrOfType<IntegerAttr>(argName);
575 auto operand = prepareConstantInt(op.getLoc(), argIntAttr);
576 if (!operand) {
577 return failure();
578 }
579 operands.push_back(operand);
580 }
581
582 return encodeInstructionInto(functionBody, spirv::Opcode::OpControlBarrier,
583 operands);
584 }
585
586 template <>
587 LogicalResult
processOp(spirv::ExecutionModeOp op)588 Serializer::processOp<spirv::ExecutionModeOp>(spirv::ExecutionModeOp op) {
589 SmallVector<uint32_t, 4> operands;
590 // Add the function <id>.
591 auto funcID = getFunctionID(op.fn());
592 if (!funcID) {
593 return op.emitError("missing <id> for function ")
594 << op.fn()
595 << "; function needs to be serialized before ExecutionModeOp is "
596 "serialized";
597 }
598 operands.push_back(funcID);
599 // Add the ExecutionMode.
600 operands.push_back(static_cast<uint32_t>(op.execution_mode()));
601
602 // Serialize values if any.
603 auto values = op.values();
604 if (values) {
605 for (auto &intVal : values.getValue()) {
606 operands.push_back(static_cast<uint32_t>(
607 intVal.cast<IntegerAttr>().getValue().getZExtValue()));
608 }
609 }
610 return encodeInstructionInto(executionModes, spirv::Opcode::OpExecutionMode,
611 operands);
612 }
613
614 template <>
615 LogicalResult
processOp(spirv::MemoryBarrierOp op)616 Serializer::processOp<spirv::MemoryBarrierOp>(spirv::MemoryBarrierOp op) {
617 StringRef argNames[] = {"memory_scope", "memory_semantics"};
618 SmallVector<uint32_t, 2> operands;
619
620 for (auto argName : argNames) {
621 auto argIntAttr = op->getAttrOfType<IntegerAttr>(argName);
622 auto operand = prepareConstantInt(op.getLoc(), argIntAttr);
623 if (!operand) {
624 return failure();
625 }
626 operands.push_back(operand);
627 }
628
629 return encodeInstructionInto(functionBody, spirv::Opcode::OpMemoryBarrier,
630 operands);
631 }
632
633 template <>
634 LogicalResult
processOp(spirv::FunctionCallOp op)635 Serializer::processOp<spirv::FunctionCallOp>(spirv::FunctionCallOp op) {
636 auto funcName = op.callee();
637 uint32_t resTypeID = 0;
638
639 Type resultTy = op.getNumResults() ? *op.result_type_begin() : getVoidType();
640 if (failed(processType(op.getLoc(), resultTy, resTypeID)))
641 return failure();
642
643 auto funcID = getOrCreateFunctionID(funcName);
644 auto funcCallID = getNextID();
645 SmallVector<uint32_t, 8> operands{resTypeID, funcCallID, funcID};
646
647 for (auto value : op.arguments()) {
648 auto valueID = getValueID(value);
649 assert(valueID && "cannot find a value for spv.FunctionCall");
650 operands.push_back(valueID);
651 }
652
653 if (!resultTy.isa<NoneType>())
654 valueIDMap[op.getResult(0)] = funcCallID;
655
656 return encodeInstructionInto(functionBody, spirv::Opcode::OpFunctionCall,
657 operands);
658 }
659
660 template <>
661 LogicalResult
processOp(spirv::CopyMemoryOp op)662 Serializer::processOp<spirv::CopyMemoryOp>(spirv::CopyMemoryOp op) {
663 SmallVector<uint32_t, 4> operands;
664 SmallVector<StringRef, 2> elidedAttrs;
665
666 for (Value operand : op->getOperands()) {
667 auto id = getValueID(operand);
668 assert(id && "use before def!");
669 operands.push_back(id);
670 }
671
672 if (auto attr = op->getAttr("memory_access")) {
673 operands.push_back(static_cast<uint32_t>(
674 attr.cast<IntegerAttr>().getValue().getZExtValue()));
675 }
676
677 elidedAttrs.push_back("memory_access");
678
679 if (auto attr = op->getAttr("alignment")) {
680 operands.push_back(static_cast<uint32_t>(
681 attr.cast<IntegerAttr>().getValue().getZExtValue()));
682 }
683
684 elidedAttrs.push_back("alignment");
685
686 if (auto attr = op->getAttr("source_memory_access")) {
687 operands.push_back(static_cast<uint32_t>(
688 attr.cast<IntegerAttr>().getValue().getZExtValue()));
689 }
690
691 elidedAttrs.push_back("source_memory_access");
692
693 if (auto attr = op->getAttr("source_alignment")) {
694 operands.push_back(static_cast<uint32_t>(
695 attr.cast<IntegerAttr>().getValue().getZExtValue()));
696 }
697
698 elidedAttrs.push_back("source_alignment");
699 (void)emitDebugLine(functionBody, op.getLoc());
700 (void)encodeInstructionInto(functionBody, spirv::Opcode::OpCopyMemory,
701 operands);
702
703 return success();
704 }
705
706 // Pull in auto-generated Serializer::dispatchToAutogenSerialization() and
707 // various Serializer::processOp<...>() specializations.
708 #define GET_SERIALIZATION_FNS
709 #include "mlir/Dialect/SPIRV/IR/SPIRVSerialization.inc"
710
711 } // namespace spirv
712 } // namespace mlir
713