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