MLIR  18.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.
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  void processExtension();
106 
107  void processMemoryModel();
108 
109  LogicalResult processConstantOp(spirv::ConstantOp op);
110 
111  LogicalResult processSpecConstantOp(spirv::SpecConstantOp op);
112 
114  processSpecConstantCompositeOp(spirv::SpecConstantCompositeOp op);
115 
117  processSpecConstantOperationOp(spirv::SpecConstantOperationOp op);
118 
119  /// SPIR-V dialect supports OpUndef using spirv.UndefOp that produces a SSA
120  /// value to use with other operations. The SPIR-V spec recommends that
121  /// OpUndef be generated at module level. The serialization generates an
122  /// OpUndef for each type needed at module level.
123  LogicalResult processUndefOp(spirv::UndefOp op);
124 
125  /// Emit OpName for the given `resultID`.
126  LogicalResult processName(uint32_t resultID, StringRef name);
127 
128  /// Processes a SPIR-V function op.
129  LogicalResult processFuncOp(spirv::FuncOp op);
130 
131  LogicalResult processVariableOp(spirv::VariableOp op);
132 
133  /// Process a SPIR-V GlobalVariableOp
134  LogicalResult processGlobalVariableOp(spirv::GlobalVariableOp varOp);
135 
136  /// Process attributes that translate to decorations on the result <id>
137  LogicalResult processDecoration(Location loc, uint32_t resultID,
138  NamedAttribute attr);
139 
140  template <typename DType>
141  LogicalResult processTypeDecoration(Location loc, DType type,
142  uint32_t resultId) {
143  return emitError(loc, "unhandled decoration for type:") << type;
144  }
145 
146  /// Process member decoration
147  LogicalResult processMemberDecoration(
148  uint32_t structID,
149  const spirv::StructType::MemberDecorationInfo &memberDecorationInfo);
150 
151  //===--------------------------------------------------------------------===//
152  // Types
153  //===--------------------------------------------------------------------===//
154 
155  uint32_t getTypeID(Type type) const { return typeIDMap.lookup(type); }
156 
157  Type getVoidType() { return mlirBuilder.getNoneType(); }
158 
159  bool isVoidType(Type type) const { return isa<NoneType>(type); }
160 
161  /// Returns true if the given type is a pointer type to a struct in some
162  /// interface storage class.
163  bool isInterfaceStructPtrType(Type type) const;
164 
165  /// Main dispatch method for serializing a type. The result <id> of the
166  /// serialized type will be returned as `typeID`.
167  LogicalResult processType(Location loc, Type type, uint32_t &typeID);
168  LogicalResult processTypeImpl(Location loc, Type type, uint32_t &typeID,
169  SetVector<StringRef> &serializationCtx);
170 
171  /// Method for preparing basic SPIR-V type serialization. Returns the type's
172  /// opcode and operands for the instruction via `typeEnum` and `operands`.
173  LogicalResult prepareBasicType(Location loc, Type type, uint32_t resultID,
174  spirv::Opcode &typeEnum,
175  SmallVectorImpl<uint32_t> &operands,
176  bool &deferSerialization,
177  SetVector<StringRef> &serializationCtx);
178 
179  LogicalResult prepareFunctionType(Location loc, FunctionType type,
180  spirv::Opcode &typeEnum,
181  SmallVectorImpl<uint32_t> &operands);
182 
183  //===--------------------------------------------------------------------===//
184  // Constant
185  //===--------------------------------------------------------------------===//
186 
187  uint32_t getConstantID(Attribute value) const {
188  return constIDMap.lookup(value);
189  }
190 
191  /// Main dispatch method for processing a constant with the given `constType`
192  /// and `valueAttr`. `constType` is needed here because we can interpret the
193  /// `valueAttr` as a different type than the type of `valueAttr` itself; for
194  /// example, ArrayAttr, whose type is NoneType, is used for spirv::ArrayType
195  /// constants.
196  uint32_t prepareConstant(Location loc, Type constType, Attribute valueAttr);
197 
198  /// Prepares array attribute serialization. This method emits corresponding
199  /// OpConstant* and returns the result <id> associated with it. Returns 0 if
200  /// failed.
201  uint32_t prepareArrayConstant(Location loc, Type constType, ArrayAttr attr);
202 
203  /// Prepares bool/int/float DenseElementsAttr serialization. This method
204  /// iterates the DenseElementsAttr to construct the constant array, and
205  /// returns the result <id> associated with it. Returns 0 if failed. Note
206  /// that the size of `index` must match the rank.
207  /// TODO: Consider to enhance splat elements cases. For splat cases,
208  /// we don't need to loop over all elements, especially when the splat value
209  /// is zero. We can use OpConstantNull when the value is zero.
210  uint32_t prepareDenseElementsConstant(Location loc, Type constType,
211  DenseElementsAttr valueAttr, int dim,
213 
214  /// Prepares scalar attribute serialization. This method emits corresponding
215  /// OpConstant* and returns the result <id> associated with it. Returns 0 if
216  /// the attribute is not for a scalar bool/integer/float value. If `isSpec` is
217  /// true, then the constant will be serialized as a specialization constant.
218  uint32_t prepareConstantScalar(Location loc, Attribute valueAttr,
219  bool isSpec = false);
220 
221  uint32_t prepareConstantBool(Location loc, BoolAttr boolAttr,
222  bool isSpec = false);
223 
224  uint32_t prepareConstantInt(Location loc, IntegerAttr intAttr,
225  bool isSpec = false);
226 
227  uint32_t prepareConstantFp(Location loc, FloatAttr floatAttr,
228  bool isSpec = false);
229 
230  //===--------------------------------------------------------------------===//
231  // Control flow
232  //===--------------------------------------------------------------------===//
233 
234  /// Returns the result <id> for the given block.
235  uint32_t getBlockID(Block *block) const { return blockIDMap.lookup(block); }
236 
237  /// Returns the result <id> for the given block. If no <id> has been assigned,
238  /// assigns the next available <id>
239  uint32_t getOrCreateBlockID(Block *block);
240 
241 #ifndef NDEBUG
242  /// (For debugging) prints the block with its result <id>.
243  void printBlock(Block *block, raw_ostream &os);
244 #endif
245 
246  /// Processes the given `block` and emits SPIR-V instructions for all ops
247  /// inside. Does not emit OpLabel for this block if `omitLabel` is true.
248  /// `emitMerge` is a callback that will be invoked before handling the
249  /// terminator op to inject the Op*Merge instruction if this is a SPIR-V
250  /// selection/loop header block.
251  LogicalResult processBlock(Block *block, bool omitLabel = false,
252  function_ref<LogicalResult()> emitMerge = nullptr);
253 
254  /// Emits OpPhi instructions for the given block if it has block arguments.
255  LogicalResult emitPhiForBlockArguments(Block *block);
256 
257  LogicalResult processSelectionOp(spirv::SelectionOp selectionOp);
258 
259  LogicalResult processLoopOp(spirv::LoopOp loopOp);
260 
261  LogicalResult processBranchConditionalOp(spirv::BranchConditionalOp);
262 
263  LogicalResult processBranchOp(spirv::BranchOp branchOp);
264 
265  //===--------------------------------------------------------------------===//
266  // Operations
267  //===--------------------------------------------------------------------===//
268 
269  LogicalResult encodeExtensionInstruction(Operation *op,
270  StringRef extensionSetName,
271  uint32_t opcode,
272  ArrayRef<uint32_t> operands);
273 
274  uint32_t getValueID(Value val) const { return valueIDMap.lookup(val); }
275 
276  LogicalResult processAddressOfOp(spirv::AddressOfOp addressOfOp);
277 
278  LogicalResult processReferenceOfOp(spirv::ReferenceOfOp referenceOfOp);
279 
280  /// Main dispatch method for serializing an operation.
281  LogicalResult processOperation(Operation *op);
282 
283  /// Serializes an operation `op` as core instruction with `opcode` if
284  /// `extInstSet` is empty. Otherwise serializes it as an extended instruction
285  /// with `opcode` from `extInstSet`.
286  /// This method is a generic one for dispatching any SPIR-V ops that has no
287  /// variadic operands and attributes in TableGen definitions.
288  LogicalResult processOpWithoutGrammarAttr(Operation *op, StringRef extInstSet,
289  uint32_t opcode);
290 
291  /// Dispatches to the serialization function for an operation in SPIR-V
292  /// dialect that is a mirror of an instruction in the SPIR-V spec. This is
293  /// auto-generated from ODS. Dispatch is handled for all operations in SPIR-V
294  /// dialect that have hasOpcode == 1.
295  LogicalResult dispatchToAutogenSerialization(Operation *op);
296 
297  /// Serializes an operation in the SPIR-V dialect that is a mirror of an
298  /// instruction in the SPIR-V spec. This is auto generated if hasOpcode == 1
299  /// and autogenSerialization == 1 in ODS.
300  template <typename OpTy>
301  LogicalResult processOp(OpTy op) {
302  return op.emitError("unsupported op serialization");
303  }
304 
305  //===--------------------------------------------------------------------===//
306  // Utilities
307  //===--------------------------------------------------------------------===//
308 
309  /// Emits an OpDecorate instruction to decorate the given `target` with the
310  /// given `decoration`.
311  LogicalResult emitDecoration(uint32_t target, spirv::Decoration decoration,
312  ArrayRef<uint32_t> params = {});
313 
314  /// Emits an OpLine instruction with the given `loc` location information into
315  /// the given `binary` vector.
316  LogicalResult emitDebugLine(SmallVectorImpl<uint32_t> &binary, Location loc);
317 
318 private:
319  /// The SPIR-V module to be serialized.
320  spirv::ModuleOp module;
321 
322  /// An MLIR builder for getting MLIR constructs.
323  mlir::Builder mlirBuilder;
324 
325  /// Serialization options.
326  SerializationOptions options;
327 
328  /// A flag which indicates if the last processed instruction was a merge
329  /// instruction.
330  /// According to SPIR-V spec: "If a branch merge instruction is used, the last
331  /// OpLine in the block must be before its merge instruction".
332  bool lastProcessedWasMergeInst = false;
333 
334  /// The <id> of the OpString instruction, which specifies a file name, for
335  /// use by other debug instructions.
336  uint32_t fileID = 0;
337 
338  /// The next available result <id>.
339  uint32_t nextID = 1;
340 
341  // The following are for different SPIR-V instruction sections. They follow
342  // the logical layout of a SPIR-V module.
343 
344  SmallVector<uint32_t, 4> capabilities;
345  SmallVector<uint32_t, 0> extensions;
346  SmallVector<uint32_t, 0> extendedSets;
347  SmallVector<uint32_t, 3> memoryModel;
348  SmallVector<uint32_t, 0> entryPoints;
349  SmallVector<uint32_t, 4> executionModes;
352  SmallVector<uint32_t, 0> decorations;
353  SmallVector<uint32_t, 0> typesGlobalValues;
354  SmallVector<uint32_t, 0> functions;
355 
356  /// Recursive struct references are serialized as OpTypePointer instructions
357  /// to the recursive struct type. However, the OpTypePointer instruction
358  /// cannot be emitted before the recursive struct's OpTypeStruct.
359  /// RecursiveStructPointerInfo stores the data needed to emit such
360  /// OpTypePointer instructions after forward references to such types.
361  struct RecursiveStructPointerInfo {
362  uint32_t pointerTypeID;
363  spirv::StorageClass storageClass;
364  };
365 
366  // Maps spirv::StructType to its recursive reference member info.
368  recursiveStructInfos;
369 
370  /// `functionHeader` contains all the instructions that must be in the first
371  /// block in the function, and `functionBody` contains the rest. After
372  /// processing FuncOp, the encoded instructions of a function are appended to
373  /// `functions`. An example of instructions in `functionHeader` in order:
374  /// OpFunction ...
375  /// OpFunctionParameter ...
376  /// OpFunctionParameter ...
377  /// OpLabel ...
378  /// OpVariable ...
379  /// OpVariable ...
380  SmallVector<uint32_t, 0> functionHeader;
381  SmallVector<uint32_t, 0> functionBody;
382 
383  /// Map from type used in SPIR-V module to their <id>s.
384  DenseMap<Type, uint32_t> typeIDMap;
385 
386  /// Map from constant values to their <id>s.
388 
389  /// Map from specialization constant names to their <id>s.
390  llvm::StringMap<uint32_t> specConstIDMap;
391 
392  /// Map from GlobalVariableOps name to <id>s.
393  llvm::StringMap<uint32_t> globalVarIDMap;
394 
395  /// Map from FuncOps name to <id>s.
396  llvm::StringMap<uint32_t> funcIDMap;
397 
398  /// Map from blocks to their <id>s.
399  DenseMap<Block *, uint32_t> blockIDMap;
400 
401  /// Map from the Type to the <id> that represents undef value of that type.
402  DenseMap<Type, uint32_t> undefValIDMap;
403 
404  /// Map from results of normal operations to their <id>s.
405  DenseMap<Value, uint32_t> valueIDMap;
406 
407  /// Map from extended instruction set name to <id>s.
408  llvm::StringMap<uint32_t> extendedInstSetIDMap;
409 
410  /// Map from values used in OpPhi instructions to their offset in the
411  /// `functions` section.
412  ///
413  /// When processing a block with arguments, we need to emit OpPhi
414  /// instructions to record the predecessor block <id>s and the values they
415  /// send to the block in question. But it's not guaranteed all values are
416  /// visited and thus assigned result <id>s. So we need this list to capture
417  /// the offsets into `functions` where a value is used so that we can fix it
418  /// up later after processing all the blocks in a function.
419  ///
420  /// More concretely, say if we are visiting the following blocks:
421  ///
422  /// ```mlir
423  /// ^phi(%arg0: i32):
424  /// ...
425  /// ^parent1:
426  /// ...
427  /// spirv.Branch ^phi(%val0: i32)
428  /// ^parent2:
429  /// ...
430  /// spirv.Branch ^phi(%val1: i32)
431  /// ```
432  ///
433  /// When we are serializing the `^phi` block, we need to emit at the beginning
434  /// of the block OpPhi instructions which has the following parameters:
435  ///
436  /// OpPhi id-for-i32 id-for-%arg0 id-for-%val0 id-for-^parent1
437  /// id-for-%val1 id-for-^parent2
438  ///
439  /// But we don't know the <id> for %val0 and %val1 yet. One way is to visit
440  /// all the blocks twice and use the first visit to assign an <id> to each
441  /// value. But it's paying the overheads just for OpPhi emission. Instead,
442  /// we still visit the blocks once for emission. When we emit the OpPhi
443  /// instructions, we use 0 as a placeholder for the <id>s for %val0 and %val1.
444  /// At the same time, we record their offsets in the emitted binary (which is
445  /// placed inside `functions`) here. And then after emitting all blocks, we
446  /// replace the dummy <id> 0 with the real result <id> by overwriting
447  /// `functions[offset]`.
448  DenseMap<Value, SmallVector<size_t, 1>> deferredPhiValues;
449 };
450 } // namespace spirv
451 } // namespace mlir
452 
453 #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:30
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:50
NoneType getNoneType()
Definition: Builders.cpp:104
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:63
NamedAttribute represents a combination of a name and an Attribute value.
Definition: Attributes.h:198
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:93
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:139
Serializer(spirv::ModuleOp module, const SerializationOptions &options)
Creates a serializer for the given SPIR-V module.
Definition: Serializer.cpp:85
LogicalResult serialize()
Serializes the remembered SPIR-V module.
Definition: Serializer.cpp:89
void collect(SmallVectorImpl< uint32_t > &binary)
Collects the final SPIR-V binary.
Definition: Serializer.cpp:113
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:78
This header declares functions that assist transformations in the MemRef dialect.
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
This class represents an efficient way to signal success or failure.
Definition: LogicalResult.h:26