MLIR  22.0.0git
Serializer.h
Go to the documentation of this file.
1 //===- Serializer.h - MLIR SPIR-V Serializer ------------------------------===//
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 declares the MLIR SPIR-V module to SPIR-V binary serializer.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #ifndef MLIR_LIB_TARGET_SPIRV_SERIALIZATION_SERIALIZER_H
14 #define MLIR_LIB_TARGET_SPIRV_SERIALIZATION_SERIALIZER_H
15 
17 #include "mlir/IR/Builders.h"
19 #include "llvm/ADT/SetVector.h"
20 #include "llvm/ADT/SmallVector.h"
21 #include "llvm/Support/raw_ostream.h"
22 
23 namespace mlir {
24 namespace spirv {
25 
26 void encodeInstructionInto(SmallVectorImpl<uint32_t> &binary, spirv::Opcode op,
27  ArrayRef<uint32_t> operands);
28 
29 /// A SPIR-V module serializer.
30 ///
31 /// A SPIR-V binary module is a single linear stream of instructions; each
32 /// instruction is composed of 32-bit words with the layout:
33 ///
34 /// | <word-count>|<opcode> | <operand> | <operand> | ... |
35 /// | <------ word -------> | <-- word --> | <-- word --> | ... |
36 ///
37 /// For the first word, the 16 high-order bits are the word count of the
38 /// instruction, the 16 low-order bits are the opcode enumerant. The
39 /// instructions then belong to different sections, which must be laid out in
40 /// the particular order as specified in "2.4 Logical Layout of a Module" of
41 /// the SPIR-V spec.
42 class Serializer {
43 public:
44  /// Creates a serializer for the given SPIR-V `module`.
45  explicit Serializer(spirv::ModuleOp module,
46  const SerializationOptions &options);
47 
48  /// Serializes the remembered SPIR-V module.
49  LogicalResult serialize();
50 
51  /// Collects the final SPIR-V `binary`.
52  void collect(SmallVectorImpl<uint32_t> &binary);
53 
54 #ifndef NDEBUG
55  /// (For debugging) prints each value and its corresponding result <id>.
56  void printValueIDMap(raw_ostream &os);
57 #endif
58 
59 private:
60  // Note that there are two main categories of methods in this class:
61  // * process*() methods are meant to fully serialize a SPIR-V module entity
62  // (header, type, op, etc.). They update internal vectors containing
63  // different binary sections. They are not meant to be called except the
64  // top-level serialization loop.
65  // * prepare*() methods are meant to be helpers that prepare for serializing
66  // certain entity. They may or may not update internal vectors containing
67  // different binary sections. They are meant to be called among themselves
68  // or by other process*() methods for subtasks.
69 
70  //===--------------------------------------------------------------------===//
71  // <id>
72  //===--------------------------------------------------------------------===//
73 
74  // Note that it is illegal to use id <0> in SPIR-V binary module. Various
75  // methods in this class, if using SPIR-V word (uint32_t) as interface,
76  // check or return id <0> to indicate error in processing.
77 
78  /// Consumes the next unused <id>. This method will never return 0.
79  uint32_t getNextID() { return nextID++; }
80 
81  //===--------------------------------------------------------------------===//
82  // Module structure
83  //===--------------------------------------------------------------------===//
84 
85  uint32_t getSpecConstID(StringRef constName) const {
86  return specConstIDMap.lookup(constName);
87  }
88 
89  uint32_t getVariableID(StringRef varName) const {
90  return globalVarIDMap.lookup(varName);
91  }
92 
93  uint32_t getFunctionID(StringRef fnName) const {
94  return funcIDMap.lookup(fnName);
95  }
96 
97  /// Gets the <id> for the function with the given name. Assigns the next
98  /// available <id> if the function haven't been deserialized.
99  uint32_t getOrCreateFunctionID(StringRef fnName);
100 
101  void processCapability();
102 
103  void processDebugInfo();
104 
105  LogicalResult processExtension();
106 
107  void processMemoryModel();
108 
109  LogicalResult processConstantOp(spirv::ConstantOp op);
110 
111  LogicalResult processConstantCompositeReplicateOp(
112  spirv::EXTConstantCompositeReplicateOp op);
113 
114  LogicalResult processSpecConstantOp(spirv::SpecConstantOp op);
115 
116  LogicalResult
117  processSpecConstantCompositeOp(spirv::SpecConstantCompositeOp op);
118 
119  LogicalResult processSpecConstantCompositeReplicateOp(
120  spirv::EXTSpecConstantCompositeReplicateOp op);
121 
122  LogicalResult
123  processSpecConstantOperationOp(spirv::SpecConstantOperationOp op);
124 
125  LogicalResult processGraphConstantARMOp(spirv::GraphConstantARMOp op);
126 
127  /// SPIR-V dialect supports OpUndef using spirv.UndefOp that produces a SSA
128  /// value to use with other operations. The SPIR-V spec recommends that
129  /// OpUndef be generated at module level. The serialization generates an
130  /// OpUndef for each type needed at module level.
131  LogicalResult processUndefOp(spirv::UndefOp op);
132 
133  /// Emit OpName for the given `resultID`.
134  LogicalResult processName(uint32_t resultID, StringRef name);
135 
136  /// Processes a SPIR-V function op.
137  LogicalResult processFuncOp(spirv::FuncOp op);
138  LogicalResult processFuncParameter(spirv::FuncOp op);
139 
140  /// Processes a SPIR-V GraphARM op.
141  LogicalResult processGraphARMOp(spirv::GraphARMOp op);
142 
143  /// Processes a SPIR-V GraphEntryPointARM op.
144  LogicalResult processGraphEntryPointARMOp(spirv::GraphEntryPointARMOp op);
145 
146  /// Processes a SPIR-V GraphOutputsARMOp op.
147  LogicalResult processGraphOutputsARMOp(spirv::GraphOutputsARMOp op);
148 
149  LogicalResult processVariableOp(spirv::VariableOp op);
150 
151  /// Process a SPIR-V GlobalVariableOp
152  LogicalResult processGlobalVariableOp(spirv::GlobalVariableOp varOp);
153 
154  /// Process attributes that translate to decorations on the result <id>
155  LogicalResult processDecorationAttr(Location loc, uint32_t resultID,
156  Decoration decoration, Attribute attr);
157  LogicalResult processDecoration(Location loc, uint32_t resultID,
158  NamedAttribute attr);
159 
160  template <typename DType>
161  LogicalResult processTypeDecoration(Location loc, DType type,
162  uint32_t resultId) {
163  return emitError(loc, "unhandled decoration for type:") << type;
164  }
165 
166  /// Process member decoration
167  LogicalResult processMemberDecoration(
168  uint32_t structID,
169  const spirv::StructType::MemberDecorationInfo &memberDecorationInfo);
170 
171  //===--------------------------------------------------------------------===//
172  // Types
173  //===--------------------------------------------------------------------===//
174 
175  uint32_t getTypeID(Type type) const { return typeIDMap.lookup(type); }
176 
177  Type getVoidType() { return mlirBuilder.getNoneType(); }
178 
179  bool isVoidType(Type type) const { return isa<NoneType>(type); }
180 
181  /// Returns true if the given type is a pointer type to a struct in some
182  /// interface storage class.
183  bool isInterfaceStructPtrType(Type type) const;
184 
185  /// Main dispatch method for serializing a type. The result <id> of the
186  /// serialized type will be returned as `typeID`.
187  LogicalResult processType(Location loc, Type type, uint32_t &typeID);
188  LogicalResult processTypeImpl(Location loc, Type type, uint32_t &typeID,
189  SetVector<StringRef> &serializationCtx);
190 
191  /// Method for preparing basic SPIR-V type serialization. Returns the type's
192  /// opcode and operands for the instruction via `typeEnum` and `operands`.
193  LogicalResult prepareBasicType(Location loc, Type type, uint32_t resultID,
194  spirv::Opcode &typeEnum,
195  SmallVectorImpl<uint32_t> &operands,
196  bool &deferSerialization,
197  SetVector<StringRef> &serializationCtx);
198 
199  LogicalResult prepareFunctionType(Location loc, FunctionType type,
200  spirv::Opcode &typeEnum,
201  SmallVectorImpl<uint32_t> &operands);
202 
203  LogicalResult prepareGraphType(Location loc, GraphType type,
204  spirv::Opcode &typeEnum,
205  SmallVectorImpl<uint32_t> &operands);
206 
207  //===--------------------------------------------------------------------===//
208  // Constant
209  //===--------------------------------------------------------------------===//
210 
211  uint32_t getConstantID(Attribute value) const {
212  return constIDMap.lookup(value);
213  }
214 
215  uint32_t getConstantCompositeReplicateID(
216  std::pair<Attribute, Type> valueTypePair) const {
217  return constCompositeReplicateIDMap.lookup(valueTypePair);
218  }
219 
220  /// Main dispatch method for processing a constant with the given `constType`
221  /// and `valueAttr`. `constType` is needed here because we can interpret the
222  /// `valueAttr` as a different type than the type of `valueAttr` itself; for
223  /// example, ArrayAttr, whose type is NoneType, is used for spirv::ArrayType
224  /// constants.
225  uint32_t prepareConstant(Location loc, Type constType, Attribute valueAttr);
226 
227  /// Prepares array attribute serialization. This method emits corresponding
228  /// OpConstant* and returns the result <id> associated with it. Returns 0 if
229  /// failed.
230  uint32_t prepareArrayConstant(Location loc, Type constType, ArrayAttr attr);
231 
232  /// Prepares bool/int/float DenseElementsAttr serialization. This method
233  /// iterates the DenseElementsAttr to construct the constant array, and
234  /// returns the result <id> associated with it. Returns 0 if failed. Note
235  /// that the size of `index` must match the rank.
236  /// TODO: Consider to enhance splat elements cases. For splat cases,
237  /// we don't need to loop over all elements, especially when the splat value
238  /// is zero. We can use OpConstantNull when the value is zero.
239  uint32_t prepareDenseElementsConstant(Location loc, Type constType,
240  DenseElementsAttr valueAttr, int dim,
242 
243  /// Prepares scalar attribute serialization. This method emits corresponding
244  /// OpConstant* and returns the result <id> associated with it. Returns 0 if
245  /// the attribute is not for a scalar bool/integer/float value. If `isSpec` is
246  /// true, then the constant will be serialized as a specialization constant.
247  uint32_t prepareConstantScalar(Location loc, Attribute valueAttr,
248  bool isSpec = false);
249 
250  uint32_t prepareConstantBool(Location loc, BoolAttr boolAttr,
251  bool isSpec = false);
252 
253  uint32_t prepareConstantInt(Location loc, IntegerAttr intAttr,
254  bool isSpec = false);
255 
256  uint32_t getGraphConstantARMId(Attribute value) const {
257  return graphConstIDMap.lookup(value);
258  }
259 
260  uint32_t prepareGraphConstantId(Location loc, Type graphConstType,
261  IntegerAttr intAttr);
262 
263  uint32_t prepareConstantFp(Location loc, FloatAttr floatAttr,
264  bool isSpec = false);
265 
266  /// Prepares `spirv.EXTConstantCompositeReplicateOp` serialization. This
267  /// method emits OpConstantCompositeReplicateEXT and returns the result <id>
268  /// associated with it.
269  uint32_t prepareConstantCompositeReplicate(Location loc, Type resultType,
270  Attribute valueAttr);
271 
272  //===--------------------------------------------------------------------===//
273  // Control flow
274  //===--------------------------------------------------------------------===//
275 
276  /// Returns the result <id> for the given block.
277  uint32_t getBlockID(Block *block) const { return blockIDMap.lookup(block); }
278 
279  /// Returns the result <id> for the given block. If no <id> has been assigned,
280  /// assigns the next available <id>
281  uint32_t getOrCreateBlockID(Block *block);
282 
283 #ifndef NDEBUG
284  /// (For debugging) prints the block with its result <id>.
285  void printBlock(Block *block, raw_ostream &os);
286 #endif
287 
288  /// Processes the given `block` and emits SPIR-V instructions for all ops
289  /// inside. Does not emit OpLabel for this block if `omitLabel` is true.
290  /// `emitMerge` is a callback that will be invoked before handling the
291  /// terminator op to inject the Op*Merge instruction if this is a SPIR-V
292  /// selection/loop header block.
293  LogicalResult processBlock(Block *block, bool omitLabel = false,
294  function_ref<LogicalResult()> emitMerge = nullptr);
295 
296  /// Emits OpPhi instructions for the given block if it has block arguments.
297  LogicalResult emitPhiForBlockArguments(Block *block);
298 
299  LogicalResult processSelectionOp(spirv::SelectionOp selectionOp);
300 
301  LogicalResult processLoopOp(spirv::LoopOp loopOp);
302 
303  LogicalResult processBranchConditionalOp(spirv::BranchConditionalOp);
304 
305  LogicalResult processBranchOp(spirv::BranchOp branchOp);
306 
307  //===--------------------------------------------------------------------===//
308  // Operations
309  //===--------------------------------------------------------------------===//
310 
311  LogicalResult encodeExtensionInstruction(Operation *op,
312  StringRef extensionSetName,
313  uint32_t opcode,
314  ArrayRef<uint32_t> operands);
315 
316  uint32_t getValueID(Value val) const { return valueIDMap.lookup(val); }
317 
318  LogicalResult processAddressOfOp(spirv::AddressOfOp addressOfOp);
319 
320  LogicalResult processReferenceOfOp(spirv::ReferenceOfOp referenceOfOp);
321 
322  /// Main dispatch method for serializing an operation.
323  LogicalResult processOperation(Operation *op);
324 
325  /// Serializes an operation `op` as core instruction with `opcode` if
326  /// `extInstSet` is empty. Otherwise serializes it as an extended instruction
327  /// with `opcode` from `extInstSet`.
328  /// This method is a generic one for dispatching any SPIR-V ops that has no
329  /// variadic operands and attributes in TableGen definitions.
330  LogicalResult processOpWithoutGrammarAttr(Operation *op, StringRef extInstSet,
331  uint32_t opcode);
332 
333  /// Dispatches to the serialization function for an operation in SPIR-V
334  /// dialect that is a mirror of an instruction in the SPIR-V spec. This is
335  /// auto-generated from ODS. Dispatch is handled for all operations in SPIR-V
336  /// dialect that have hasOpcode == 1.
337  LogicalResult dispatchToAutogenSerialization(Operation *op);
338 
339  /// Serializes an operation in the SPIR-V dialect that is a mirror of an
340  /// instruction in the SPIR-V spec. This is auto generated if hasOpcode == 1
341  /// and autogenSerialization == 1 in ODS.
342  template <typename OpTy>
343  LogicalResult processOp(OpTy op) {
344  return op.emitError("unsupported op serialization");
345  }
346 
347  //===--------------------------------------------------------------------===//
348  // Utilities
349  //===--------------------------------------------------------------------===//
350 
351  /// Emits an OpDecorate instruction to decorate the given `target` with the
352  /// given `decoration`.
353  LogicalResult emitDecoration(uint32_t target, spirv::Decoration decoration,
354  ArrayRef<uint32_t> params = {});
355 
356  /// Emits an OpLine instruction with the given `loc` location information into
357  /// the given `binary` vector.
358  LogicalResult emitDebugLine(SmallVectorImpl<uint32_t> &binary, Location loc);
359 
360 private:
361  /// The SPIR-V module to be serialized.
362  spirv::ModuleOp module;
363 
364  /// An MLIR builder for getting MLIR constructs.
365  mlir::Builder mlirBuilder;
366 
367  /// Serialization options.
368  SerializationOptions options;
369 
370  /// A flag which indicates if the last processed instruction was a merge
371  /// instruction.
372  /// According to SPIR-V spec: "If a branch merge instruction is used, the last
373  /// OpLine in the block must be before its merge instruction".
374  bool lastProcessedWasMergeInst = false;
375 
376  /// The <id> of the OpString instruction, which specifies a file name, for
377  /// use by other debug instructions.
378  uint32_t fileID = 0;
379 
380  /// The next available result <id>.
381  uint32_t nextID = 1;
382 
383  // The following are for different SPIR-V instruction sections. They follow
384  // the logical layout of a SPIR-V module.
385 
386  SmallVector<uint32_t, 4> capabilities;
387  SmallVector<uint32_t, 0> extensions;
388  SmallVector<uint32_t, 0> extendedSets;
389  SmallVector<uint32_t, 3> memoryModel;
390  SmallVector<uint32_t, 0> entryPoints;
391  SmallVector<uint32_t, 4> executionModes;
394  SmallVector<uint32_t, 0> decorations;
395  SmallVector<uint32_t, 0> typesGlobalValues;
396  SmallVector<uint32_t, 0> functions;
398 
399  /// Recursive struct references are serialized as OpTypePointer instructions
400  /// to the recursive struct type. However, the OpTypePointer instruction
401  /// cannot be emitted before the recursive struct's OpTypeStruct.
402  /// RecursiveStructPointerInfo stores the data needed to emit such
403  /// OpTypePointer instructions after forward references to such types.
404  struct RecursiveStructPointerInfo {
405  uint32_t pointerTypeID;
406  spirv::StorageClass storageClass;
407  };
408 
409  // Maps spirv::StructType to its recursive reference member info.
411  recursiveStructInfos;
412 
413  /// `functionHeader` contains all the instructions that must be in the first
414  /// block in the function or graph, and `functionBody` contains the rest.
415  /// After processing FuncOp/GraphARMOp, the encoded instructions of a function
416  /// or graph are appended to `functions` or `graphs` respectively. Examples of
417  /// instructions in `functionHeader` in order:
418  ///
419  /// For a FuncOp:
420  /// OpFunction ...
421  /// OpFunctionParameter ...
422  /// OpFunctionParameter ...
423  /// OpLabel ...
424  /// OpVariable ...
425  /// OpVariable ...
426  ///
427  /// For a GraphARMOp
428  /// OpGraphARM ...
429  /// OpGraphInputARM ...
430  SmallVector<uint32_t, 0> functionHeader;
431  SmallVector<uint32_t, 0> functionBody;
432 
433  /// Map from type used in SPIR-V module to their <id>s.
434  DenseMap<Type, uint32_t> typeIDMap;
435 
436  /// Map from constant values to their <id>s.
438 
439  /// Map from a replicated composite constant's value and type to their <id>s.
440  DenseMap<std::pair<Attribute, Type>, uint32_t> constCompositeReplicateIDMap;
441 
442  /// Map from specialization constant names to their <id>s.
443  llvm::StringMap<uint32_t> specConstIDMap;
444 
445  /// Map from graph constant ID value to their <id>s.
446  DenseMap<Attribute, uint32_t> graphConstIDMap;
447 
448  /// Map from GlobalVariableOps name to <id>s.
449  llvm::StringMap<uint32_t> globalVarIDMap;
450 
451  /// Map from FuncOps name to <id>s.
452  llvm::StringMap<uint32_t> funcIDMap;
453 
454  /// Map from blocks to their <id>s.
455  DenseMap<Block *, uint32_t> blockIDMap;
456 
457  /// Map from the Type to the <id> that represents undef value of that type.
458  DenseMap<Type, uint32_t> undefValIDMap;
459 
460  /// Map from results of normal operations to their <id>s.
461  DenseMap<Value, uint32_t> valueIDMap;
462 
463  /// Map from extended instruction set name to <id>s.
464  llvm::StringMap<uint32_t> extendedInstSetIDMap;
465 
466  /// Map from values used in OpPhi instructions to their offset in the
467  /// `functions` section.
468  ///
469  /// When processing a block with arguments, we need to emit OpPhi
470  /// instructions to record the predecessor block <id>s and the values they
471  /// send to the block in question. But it's not guaranteed all values are
472  /// visited and thus assigned result <id>s. So we need this list to capture
473  /// the offsets into `functions` where a value is used so that we can fix it
474  /// up later after processing all the blocks in a function.
475  ///
476  /// More concretely, say if we are visiting the following blocks:
477  ///
478  /// ```mlir
479  /// ^phi(%arg0: i32):
480  /// ...
481  /// ^parent1:
482  /// ...
483  /// spirv.Branch ^phi(%val0: i32)
484  /// ^parent2:
485  /// ...
486  /// spirv.Branch ^phi(%val1: i32)
487  /// ```
488  ///
489  /// When we are serializing the `^phi` block, we need to emit at the beginning
490  /// of the block OpPhi instructions which has the following parameters:
491  ///
492  /// OpPhi id-for-i32 id-for-%arg0 id-for-%val0 id-for-^parent1
493  /// id-for-%val1 id-for-^parent2
494  ///
495  /// But we don't know the <id> for %val0 and %val1 yet. One way is to visit
496  /// all the blocks twice and use the first visit to assign an <id> to each
497  /// value. But it's paying the overheads just for OpPhi emission. Instead,
498  /// we still visit the blocks once for emission. When we emit the OpPhi
499  /// instructions, we use 0 as a placeholder for the <id>s for %val0 and %val1.
500  /// At the same time, we record their offsets in the emitted binary (which is
501  /// placed inside `functions`) here. And then after emitting all blocks, we
502  /// replace the dummy <id> 0 with the real result <id> by overwriting
503  /// `functions[offset]`.
504  DenseMap<Value, SmallVector<size_t, 1>> deferredPhiValues;
505 };
506 } // namespace spirv
507 } // namespace mlir
508 
509 #endif // MLIR_LIB_TARGET_SPIRV_SERIALIZATION_SERIALIZER_H
Attributes are known-constant values of operations.
Definition: Attributes.h:25
Block represents an ordered list of Operations.
Definition: Block.h:33
Special case of IntegerAttr to represent boolean integers, i.e., signless i1 integers.
This class is a general helper class for creating context-global objects like types,...
Definition: Builders.h:51
NoneType getNoneType()
Definition: Builders.cpp:87
An attribute that represents a reference to a dense vector or tensor object.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition: Location.h:76
NamedAttribute represents a combination of a name and an Attribute value.
Definition: Attributes.h:164
Operation is the basic unit of execution within MLIR.
Definition: Operation.h:88
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition: Types.h:74
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition: Value.h:96
A SPIR-V module serializer.
Definition: Serializer.h:42
void printValueIDMap(raw_ostream &os)
(For debugging) prints each value and its corresponding result <id>.
Definition: Serializer.cpp:161
Serializer(spirv::ModuleOp module, const SerializationOptions &options)
Creates a serializer for the given SPIR-V module.
Definition: Serializer.cpp:104
LogicalResult serialize()
Serializes the remembered SPIR-V module.
Definition: Serializer.cpp:108
void collect(SmallVectorImpl< uint32_t > &binary)
Collects the final SPIR-V binary.
Definition: Serializer.cpp:134
void encodeInstructionInto(SmallVectorImpl< uint32_t > &binary, spirv::Opcode op, ArrayRef< uint32_t > operands)
Encodes an SPIR-V instruction with the given opcode and operands into the given binary vector.
Definition: Serializer.cpp:97
Include the generated interface declarations.
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.