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