• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2022 Google LLC.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "source/diff/diff.h"
16 
17 #include "source/diff/lcs.h"
18 #include "source/disassemble.h"
19 #include "source/ext_inst.h"
20 #include "source/latest_version_spirv_header.h"
21 #include "source/print.h"
22 #include "spirv-tools/libspirv.hpp"
23 
24 namespace spvtools {
25 namespace diff {
26 
27 namespace {
28 
29 // A map from an id to the instruction that defines it.
30 using IdToInstructionMap = std::vector<const opt::Instruction*>;
31 // A map from an id to the instructions that decorate it, or name it, etc.
32 using IdToInfoMap = std::vector<std::vector<const opt::Instruction*>>;
33 // A map from an instruction to another, used for instructions without id.
34 using InstructionToInstructionMap =
35     std::unordered_map<const opt::Instruction*, const opt::Instruction*>;
36 // A flat list of instructions in a function for easier iteration.
37 using InstructionList = std::vector<const opt::Instruction*>;
38 // A map from a function to its list of instructions.
39 using FunctionInstMap = std::map<uint32_t, InstructionList>;
40 // A list of ids with some similar property, for example functions with the same
41 // name.
42 using IdGroup = std::vector<uint32_t>;
43 // A map of names to ids with the same name.  This is an ordered map so
44 // different implementations produce identical results.
45 using IdGroupMapByName = std::map<std::string, IdGroup>;
46 using IdGroupMapByTypeId = std::map<uint32_t, IdGroup>;
47 using IdGroupMapByOp = std::map<spv::Op, IdGroup>;
48 using IdGroupMapByStorageClass = std::map<spv::StorageClass, IdGroup>;
49 
50 // A set of potential id mappings that haven't been resolved yet.  Any id in src
51 // may map in any id in dst.  Note that ids are added in the same order as they
52 // appear in src and dst to facilitate matching dependent instructions.  For
53 // example, this guarantees that when matching OpTypeVector, the basic type of
54 // the vector is already (potentially) matched.
55 struct PotentialIdMap {
56   std::vector<uint32_t> src_ids;
57   std::vector<uint32_t> dst_ids;
58 };
59 
CompactIds(std::vector<uint32_t> & ids)60 void CompactIds(std::vector<uint32_t>& ids) {
61   size_t write_index = 0;
62   for (size_t i = 0; i < ids.size(); ++i) {
63     if (ids[i] != 0) {
64       ids[write_index++] = ids[i];
65     }
66   }
67   ids.resize(write_index);
68 }
69 
70 // A mapping between src and dst ids.
71 class IdMap {
72  public:
IdMap(size_t id_bound)73   IdMap(size_t id_bound) { id_map_.resize(id_bound, 0); }
74 
MapIds(uint32_t from,uint32_t to)75   void MapIds(uint32_t from, uint32_t to) {
76     assert(from != 0);
77     assert(to != 0);
78     assert(from < id_map_.size());
79     assert(id_map_[from] == 0);
80 
81     id_map_[from] = to;
82   }
83 
MappedId(uint32_t from) const84   uint32_t MappedId(uint32_t from) const {
85     assert(from != 0);
86     return from < id_map_.size() ? id_map_[from] : 0;
87   }
MappedInst(const opt::Instruction * from_inst) const88   const opt::Instruction* MappedInst(const opt::Instruction* from_inst) const {
89     assert(from_inst != nullptr);
90     assert(!from_inst->HasResultId());
91 
92     auto mapped = inst_map_.find(from_inst);
93     if (mapped == inst_map_.end()) {
94       return nullptr;
95     }
96     return mapped->second;
97   }
98 
IsMapped(uint32_t from) const99   bool IsMapped(uint32_t from) const {
100     assert(from != 0);
101     return from < id_map_.size() && id_map_[from] != 0;
102   }
103 
IsMapped(const opt::Instruction * from_inst) const104   bool IsMapped(const opt::Instruction* from_inst) const {
105     assert(from_inst != nullptr);
106     assert(!from_inst->HasResultId());
107 
108     return inst_map_.find(from_inst) != inst_map_.end();
109   }
110 
111   // Some instructions don't have result ids.  Those are mapped by pointer.
MapInsts(const opt::Instruction * from_inst,const opt::Instruction * to_inst)112   void MapInsts(const opt::Instruction* from_inst,
113                 const opt::Instruction* to_inst) {
114     assert(from_inst != nullptr);
115     assert(to_inst != nullptr);
116     assert(inst_map_.find(from_inst) == inst_map_.end());
117 
118     inst_map_[from_inst] = to_inst;
119   }
120 
IdBound() const121   uint32_t IdBound() const { return static_cast<uint32_t>(id_map_.size()); }
122 
123   // Generate a fresh id in this mapping's domain.
MakeFreshId()124   uint32_t MakeFreshId() {
125     id_map_.push_back(0);
126     return static_cast<uint32_t>(id_map_.size()) - 1;
127   }
128 
129  private:
130   // Given an id, returns the corresponding id in the other module, or 0 if not
131   // matched yet.
132   std::vector<uint32_t> id_map_;
133 
134   // Same for instructions that don't have an id.
135   InstructionToInstructionMap inst_map_;
136 };
137 
138 // Two way mapping of ids.
139 class SrcDstIdMap {
140  public:
SrcDstIdMap(size_t src_id_bound,size_t dst_id_bound)141   SrcDstIdMap(size_t src_id_bound, size_t dst_id_bound)
142       : src_to_dst_(src_id_bound), dst_to_src_(dst_id_bound) {}
143 
MapIds(uint32_t src,uint32_t dst)144   void MapIds(uint32_t src, uint32_t dst) {
145     src_to_dst_.MapIds(src, dst);
146     dst_to_src_.MapIds(dst, src);
147   }
148 
MappedDstId(uint32_t src)149   uint32_t MappedDstId(uint32_t src) {
150     uint32_t dst = src_to_dst_.MappedId(src);
151     assert(dst == 0 || dst_to_src_.MappedId(dst) == src);
152     return dst;
153   }
MappedSrcId(uint32_t dst)154   uint32_t MappedSrcId(uint32_t dst) {
155     uint32_t src = dst_to_src_.MappedId(dst);
156     assert(src == 0 || src_to_dst_.MappedId(src) == dst);
157     return src;
158   }
159 
IsSrcMapped(uint32_t src)160   bool IsSrcMapped(uint32_t src) { return src_to_dst_.IsMapped(src); }
IsDstMapped(uint32_t dst)161   bool IsDstMapped(uint32_t dst) { return dst_to_src_.IsMapped(dst); }
IsDstMapped(const opt::Instruction * dst_inst)162   bool IsDstMapped(const opt::Instruction* dst_inst) {
163     return dst_to_src_.IsMapped(dst_inst);
164   }
165 
166   // Map any ids in src and dst that have not been mapped to new ids in dst and
167   // src respectively. Use src_insn_defined and dst_insn_defined to ignore ids
168   // that are simply never defined. (Since we assume the inputs are valid
169   // SPIR-V, this implies they are also never used.)
170   void MapUnmatchedIds(std::function<bool(uint32_t)> src_insn_defined,
171                        std::function<bool(uint32_t)> dst_insn_defined);
172 
173   // Some instructions don't have result ids.  Those are mapped by pointer.
MapInsts(const opt::Instruction * src_inst,const opt::Instruction * dst_inst)174   void MapInsts(const opt::Instruction* src_inst,
175                 const opt::Instruction* dst_inst) {
176     assert(src_inst->HasResultId() == dst_inst->HasResultId());
177     if (src_inst->HasResultId()) {
178       MapIds(src_inst->result_id(), dst_inst->result_id());
179     } else {
180       src_to_dst_.MapInsts(src_inst, dst_inst);
181       dst_to_src_.MapInsts(dst_inst, src_inst);
182     }
183   }
184 
SrcToDstMap() const185   const IdMap& SrcToDstMap() const { return src_to_dst_; }
DstToSrcMap() const186   const IdMap& DstToSrcMap() const { return dst_to_src_; }
187 
188  private:
189   IdMap src_to_dst_;
190   IdMap dst_to_src_;
191 };
192 
193 struct IdInstructions {
IdInstructionsspvtools::diff::__anon325584750111::IdInstructions194   IdInstructions(const opt::Module* module)
195       : inst_map_(module->IdBound(), nullptr),
196         name_map_(module->IdBound()),
197         decoration_map_(module->IdBound()),
198         forward_pointer_map_(module->IdBound()) {
199     // Map ids from all sections to instructions that define them.
200     MapIdsToInstruction(module->ext_inst_imports());
201     MapIdsToInstruction(module->types_values());
202     for (const opt::Function& function : *module) {
203       function.ForEachInst(
204           [this](const opt::Instruction* inst) {
205             if (inst->HasResultId()) {
206               MapIdToInstruction(inst->result_id(), inst);
207             }
208           },
209           true, true);
210     }
211 
212     // Gather decorations applied to ids that could be useful in matching them
213     // between src and dst modules.
214     MapIdsToInfos(module->debugs2());
215     MapIdsToInfos(module->annotations());
216     MapIdsToInfos(module->types_values());
217   }
218 
219   void MapIdToInstruction(uint32_t id, const opt::Instruction* inst);
220 
221   // Return true if id is mapped to any instruction, false otherwise.
IsDefinedspvtools::diff::__anon325584750111::IdInstructions222   bool IsDefined(uint32_t id) {
223     return id < inst_map_.size() && inst_map_[id] != nullptr;
224   }
225 
226   void MapIdsToInstruction(
227       opt::IteratorRange<opt::Module::const_inst_iterator> section);
228   void MapIdsToInfos(
229       opt::IteratorRange<opt::Module::const_inst_iterator> section);
230 
231   IdToInstructionMap inst_map_;
232   IdToInfoMap name_map_;
233   IdToInfoMap decoration_map_;
234   IdToInstructionMap forward_pointer_map_;
235 };
236 
237 class Differ {
238  public:
Differ(opt::IRContext * src,opt::IRContext * dst,std::ostream & out,Options options)239   Differ(opt::IRContext* src, opt::IRContext* dst, std::ostream& out,
240          Options options)
241       : src_context_(src),
242         dst_context_(dst),
243         src_(src->module()),
244         dst_(dst->module()),
245         options_(options),
246         out_(out),
247         src_id_to_(src_),
248         dst_id_to_(dst_),
249         id_map_(src_->IdBound(), dst_->IdBound()) {
250     // Cache function bodies in canonicalization order.
251     GetFunctionBodies(src_context_, &src_funcs_, &src_func_insts_);
252     GetFunctionBodies(dst_context_, &dst_funcs_, &dst_func_insts_);
253   }
254 
255   // Match ids or instructions of different sections.
256   void MatchCapabilities();
257   void MatchExtensions();
258   void MatchExtInstImportIds();
259   void MatchMemoryModel();
260   void MatchEntryPointIds();
261   void MatchExecutionModes();
262   void MatchTypeForwardPointers();
263   void MatchTypeIds();
264   void MatchConstants();
265   void MatchVariableIds();
266   void MatchFunctions();
267 
268   // Debug info and annotations are matched only after ids are matched.
269   void MatchDebugs1();
270   void MatchDebugs2();
271   void MatchDebugs3();
272   void MatchExtInstDebugInfo();
273   void MatchAnnotations();
274 
275   // Output the diff.
276   spv_result_t Output();
277 
DumpIdMap()278   void DumpIdMap() {
279     if (!options_.dump_id_map) {
280       return;
281     }
282 
283     out_ << " Src ->  Dst\n";
284     for (uint32_t src_id = 1; src_id < src_->IdBound(); ++src_id) {
285       uint32_t dst_id = id_map_.MappedDstId(src_id);
286       if (src_id_to_.inst_map_[src_id] != nullptr && dst_id != 0)
287         out_ << std::setw(4) << src_id << " -> " << std::setw(4) << dst_id
288              << " [" << spvOpcodeString(src_id_to_.inst_map_[src_id]->opcode())
289              << "]\n";
290     }
291   }
292 
293  private:
294   // Helper functions that match ids between src and dst
295   void PoolPotentialIds(
296       opt::IteratorRange<opt::Module::const_inst_iterator> section,
297       std::vector<uint32_t>& ids, bool is_src,
298       std::function<bool(const opt::Instruction&)> filter,
299       std::function<uint32_t(const opt::Instruction&)> get_id);
300   void MatchIds(
301       PotentialIdMap& potential,
302       std::function<bool(const opt::Instruction*, const opt::Instruction*)>
303           match);
304   // Helper functions that match id-less instructions between src and dst.
305   void MatchPreambleInstructions(
306       opt::IteratorRange<opt::Module::const_inst_iterator> src_insts,
307       opt::IteratorRange<opt::Module::const_inst_iterator> dst_insts);
308   InstructionList SortPreambleInstructions(
309       const opt::Module* module,
310       opt::IteratorRange<opt::Module::const_inst_iterator> insts);
311   int ComparePreambleInstructions(const opt::Instruction* a,
312                                   const opt::Instruction* b,
313                                   const opt::Module* src_inst_module,
314                                   const opt::Module* dst_inst_module);
315   // Helper functions that match debug and annotation instructions of already
316   // matched ids.
317   void MatchDebugAndAnnotationInstructions(
318       opt::IteratorRange<opt::Module::const_inst_iterator> src_insts,
319       opt::IteratorRange<opt::Module::const_inst_iterator> dst_insts);
320 
321   // Get various properties from an id.  These Helper functions are passed to
322   // `GroupIds` and `GroupIdsAndMatch` below (as the `get_group` argument).
323   uint32_t GroupIdsHelperGetTypeId(const IdInstructions& id_to, uint32_t id);
324   spv::StorageClass GroupIdsHelperGetTypePointerStorageClass(
325       const IdInstructions& id_to, uint32_t id);
326   spv::Op GroupIdsHelperGetTypePointerTypeOp(const IdInstructions& id_to,
327                                              uint32_t id);
328 
329   // Given a list of ids, groups them based on some value.  The `get_group`
330   // function extracts a piece of information corresponding to each id, and the
331   // ids are bucketed based on that (and output in `groups`).  This is useful to
332   // attempt to match ids between src and dst only when said property is
333   // identical.
334   template <typename T>
335   void GroupIds(const IdGroup& ids, bool is_src, std::map<T, IdGroup>* groups,
336                 T (Differ::*get_group)(const IdInstructions&, uint32_t));
337 
338   // Calls GroupIds to bucket ids in src and dst based on a property returned by
339   // `get_group`.  This function then calls `match_group` for each bucket (i.e.
340   // "group") with identical values for said property.
341   //
342   // For example, say src and dst ids have the following properties
343   // correspondingly:
344   //
345   // - src ids' properties: {id0: A, id1: A, id2: B, id3: C, id4: B}
346   // - dst ids' properties: {id0': B, id1': C, id2': B, id3': D, id4': B}
347   //
348   // Then `match_group` is called 2 times:
349   //
350   // - Once with: ([id2, id4], [id0', id2', id4']) corresponding to B
351   // - Once with: ([id3], [id2']) corresponding to C
352   //
353   // Ids corresponding to A and D cannot match based on this property.
354   template <typename T>
355   void GroupIdsAndMatch(
356       const IdGroup& src_ids, const IdGroup& dst_ids, T invalid_group_key,
357       T (Differ::*get_group)(const IdInstructions&, uint32_t),
358       std::function<void(const IdGroup& src_group, const IdGroup& dst_group)>
359           match_group);
360 
361   // Bucket `src_ids` and `dst_ids` by the key ids returned by `get_group`, and
362   // then call `match_group` on pairs of buckets whose key ids are matched with
363   // each other.
364   //
365   // For example, suppose we want to pair up groups of instructions with the
366   // same type. Naturally, the source instructions refer to their types by their
367   // ids in the source, and the destination instructions use destination type
368   // ids, so simply comparing source and destination type ids as integers, as
369   // `GroupIdsAndMatch` would do, is meaningless. But if a prior call to
370   // `MatchTypeIds` has established type matches between the two modules, then
371   // we can consult those to pair source and destination buckets whose types are
372   // equivalent.
373   //
374   // Suppose our input groups are as follows:
375   //
376   // - src_ids: { 1 -> 100, 2 -> 300, 3 -> 100, 4 -> 200 }
377   // - dst_ids: { 5 -> 10, 6 -> 20, 7 -> 10, 8 -> 300 }
378   //
379   // Here, `X -> Y` means that the instruction with SPIR-V id `X` is a member of
380   // the group, and `Y` is the id of its type. If we use
381   // `Differ::GroupIdsHelperGetTypeId` for `get_group`, then
382   // `get_group(X) == Y`.
383   //
384   // These instructions are bucketed by type as follows:
385   //
386   // - source:      [1, 3] -> 100
387   //                [4] -> 200
388   //                [2] -> 300
389   //
390   // - destination: [5, 7] -> 10
391   //                [6] -> 20
392   //                [8] -> 300
393   //
394   // Now suppose that we have previously matched up src type 100 with dst type
395   // 10, and src type 200 with dst type 20, but no other types are matched.
396   //
397   // Then `match_group` is called twice:
398   // - Once with ([1,3], [5, 7]), corresponding to 100/10
399   // - Once with ([4],[6]), corresponding to 200/20
400   //
401   // The source type 300 isn't matched with anything, so the fact that there's a
402   // destination type 300 is irrelevant, and thus 2 and 8 are never passed to
403   // `match_group`.
404   //
405   // This function isn't specific to types; it simply buckets by the ids
406   // returned from `get_group`, and consults existing matches to pair up the
407   // resulting buckets.
408   void GroupIdsAndMatchByMappedId(
409       const IdGroup& src_ids, const IdGroup& dst_ids,
410       uint32_t (Differ::*get_group)(const IdInstructions&, uint32_t),
411       std::function<void(const IdGroup& src_group, const IdGroup& dst_group)>
412           match_group);
413 
414   // Helper functions that determine if two instructions match
415   bool DoIdsMatch(uint32_t src_id, uint32_t dst_id);
416   bool DoesOperandMatch(const opt::Operand& src_operand,
417                         const opt::Operand& dst_operand);
418   bool DoOperandsMatch(const opt::Instruction* src_inst,
419                        const opt::Instruction* dst_inst,
420                        uint32_t in_operand_index_start,
421                        uint32_t in_operand_count);
422   bool DoInstructionsMatch(const opt::Instruction* src_inst,
423                            const opt::Instruction* dst_inst);
424   bool DoIdsMatchFuzzy(uint32_t src_id, uint32_t dst_id);
425   bool DoesOperandMatchFuzzy(const opt::Operand& src_operand,
426                              const opt::Operand& dst_operand);
427   bool DoInstructionsMatchFuzzy(const opt::Instruction* src_inst,
428                                 const opt::Instruction* dst_inst);
429   bool AreIdenticalUintConstants(uint32_t src_id, uint32_t dst_id);
430   bool DoDebugAndAnnotationInstructionsMatch(const opt::Instruction* src_inst,
431                                              const opt::Instruction* dst_inst);
432   bool AreVariablesMatchable(uint32_t src_id, uint32_t dst_id,
433                              uint32_t flexibility);
434   bool MatchOpTypeStruct(const opt::Instruction* src_inst,
435                          const opt::Instruction* dst_inst,
436                          uint32_t flexibility);
437   bool MatchOpConstant(const opt::Instruction* src_inst,
438                        const opt::Instruction* dst_inst, uint32_t flexibility);
439   bool MatchOpSpecConstant(const opt::Instruction* src_inst,
440                            const opt::Instruction* dst_inst);
441   bool MatchOpVariable(const opt::Instruction* src_inst,
442                        const opt::Instruction* dst_inst, uint32_t flexibility);
443   bool MatchPerVertexType(uint32_t src_type_id, uint32_t dst_type_id);
444   bool MatchPerVertexVariable(const opt::Instruction* src_inst,
445                               const opt::Instruction* dst_inst);
446 
447   // Helper functions for matching OpTypeForwardPointer
448   void MatchTypeForwardPointersByName(const IdGroup& src, const IdGroup& dst);
449   void MatchTypeForwardPointersByTypeOp(const IdGroup& src, const IdGroup& dst);
450 
451   // Helper functions for function matching.
452   using FunctionMap = std::map<uint32_t, const opt::Function*>;
453 
454   InstructionList GetFunctionBody(opt::IRContext* context,
455                                   opt::Function& function);
456   InstructionList GetFunctionHeader(const opt::Function& function);
457   void GetFunctionBodies(opt::IRContext* context, FunctionMap* functions,
458                          FunctionInstMap* function_insts);
459   void GetFunctionHeaderInstructions(const opt::Module* module,
460                                      FunctionInstMap* function_insts);
461   void BestEffortMatchFunctions(const IdGroup& src_func_ids,
462                                 const IdGroup& dst_func_ids,
463                                 const FunctionInstMap& src_func_insts,
464                                 const FunctionInstMap& dst_func_insts);
465 
466   // Calculates the diff of two function bodies.  Note that the matched
467   // instructions themselves may not be identical; output of exact matches
468   // should produce the exact instruction while inexact matches should produce a
469   // diff as well.
470   //
471   // Returns the similarity of the two bodies = 2*N_match / (N_src + N_dst)
472   void MatchFunctionParamIds(const opt::Function* src_func,
473                              const opt::Function* dst_func);
474   float MatchFunctionBodies(const InstructionList& src_body,
475                             const InstructionList& dst_body,
476                             DiffMatch* src_match_result,
477                             DiffMatch* dst_match_result);
478   void MatchIdsInFunctionBodies(const InstructionList& src_body,
479                                 const InstructionList& dst_body,
480                                 const DiffMatch& src_match_result,
481                                 const DiffMatch& dst_match_result,
482                                 uint32_t flexibility);
483   void MatchVariablesUsedByMatchedInstructions(const opt::Instruction* src_inst,
484                                                const opt::Instruction* dst_inst,
485                                                uint32_t flexibility);
486 
487   // Helper functions to retrieve information pertaining to an id
488   const opt::Instruction* GetInst(const IdInstructions& id_to, uint32_t id);
489   uint32_t GetConstantUint(const IdInstructions& id_to, uint32_t constant_id);
490   spv::ExecutionModel GetExecutionModel(const opt::Module* module,
491                                         uint32_t entry_point_id);
492   bool HasName(const IdInstructions& id_to, uint32_t id);
493   // Get the OpName associated with an id
494   std::string GetName(const IdInstructions& id_to, uint32_t id, bool* has_name);
495   // Get the OpName associated with an id, with argument types stripped for
496   // functions.  Some tools don't encode function argument types in the OpName
497   // string, and this improves diff between SPIR-V from those tools and others.
498   std::string GetSanitizedName(const IdInstructions& id_to, uint32_t id);
499   uint32_t GetVarTypeId(const IdInstructions& id_to, uint32_t var_id,
500                         spv::StorageClass* storage_class);
501   bool GetDecorationValue(const IdInstructions& id_to, uint32_t id,
502                           spv::Decoration decoration,
503                           uint32_t* decoration_value);
504   const opt::Instruction* GetForwardPointerInst(const IdInstructions& id_to,
505                                                 uint32_t id);
506   bool IsIntType(const IdInstructions& id_to, uint32_t type_id);
507   bool IsFloatType(const IdInstructions& id_to, uint32_t type_id);
508   bool IsConstantUint(const IdInstructions& id_to, uint32_t id);
509   bool IsVariable(const IdInstructions& id_to, uint32_t pointer_id);
510   bool IsOp(const IdInstructions& id_to, uint32_t id, spv::Op opcode);
511   bool IsPerVertexType(const IdInstructions& id_to, uint32_t type_id);
512   bool IsPerVertexVariable(const IdInstructions& id_to, uint32_t type_id);
513   spv::StorageClass GetPerVertexStorageClass(const opt::Module* module,
514                                              uint32_t type_id);
515   spv_ext_inst_type_t GetExtInstType(const IdInstructions& id_to,
516                                      uint32_t set_id);
517   spv_number_kind_t GetNumberKind(const IdInstructions& id_to,
518                                   const opt::Instruction& inst,
519                                   uint32_t operand_index,
520                                   uint32_t* number_bit_width);
521   spv_number_kind_t GetTypeNumberKind(const IdInstructions& id_to, uint32_t id,
522                                       uint32_t* number_bit_width);
523 
524   // Helper functions to output a diff line
525   const opt::Instruction* MappedDstInst(const opt::Instruction* src_inst);
526   const opt::Instruction* MappedSrcInst(const opt::Instruction* dst_inst);
527   const opt::Instruction* MappedInstImpl(const opt::Instruction* inst,
528                                          const IdMap& to_other,
529                                          const IdInstructions& other_id_to);
530   void OutputLine(std::function<bool()> are_lines_identical,
531                   std::function<void()> output_src_line,
532                   std::function<void()> output_dst_line);
533   template <typename InstList>
534   void OutputSection(
535       const InstList& src_insts, const InstList& dst_insts,
536       std::function<void(const opt::Instruction&, const IdInstructions&,
537                          const opt::Instruction&)>
538           write_inst);
539   void ToParsedInstruction(const opt::Instruction& inst,
540                            const IdInstructions& id_to,
541                            const opt::Instruction& original_inst,
542                            spv_parsed_instruction_t* parsed_inst,
543                            std::vector<spv_parsed_operand_t>& parsed_operands,
544                            std::vector<uint32_t>& inst_binary);
545   opt::Instruction ToMappedSrcIds(const opt::Instruction& dst_inst);
546 
OutputRed()547   void OutputRed() {
548     if (options_.color_output) out_ << spvtools::clr::red{true};
549   }
OutputGreen()550   void OutputGreen() {
551     if (options_.color_output) out_ << spvtools::clr::green{true};
552   }
OutputResetColor()553   void OutputResetColor() {
554     if (options_.color_output) out_ << spvtools::clr::reset{true};
555   }
556 
557   opt::IRContext* src_context_;
558   opt::IRContext* dst_context_;
559   const opt::Module* src_;
560   const opt::Module* dst_;
561   Options options_;
562   std::ostream& out_;
563 
564   // Helpers to look up instructions based on id.
565   IdInstructions src_id_to_;
566   IdInstructions dst_id_to_;
567 
568   // The ids that have been matched between src and dst so far.
569   SrcDstIdMap id_map_;
570 
571   // List of instructions in function bodies after canonicalization.  Cached
572   // here to avoid duplicate work.  More importantly, some maps use
573   // opt::Instruction pointers so they need to be unique.
574   FunctionInstMap src_func_insts_;
575   FunctionInstMap dst_func_insts_;
576   FunctionMap src_funcs_;
577   FunctionMap dst_funcs_;
578 };
579 
MapUnmatchedIds(std::function<bool (uint32_t)> src_insn_defined,std::function<bool (uint32_t)> dst_insn_defined)580 void SrcDstIdMap::MapUnmatchedIds(
581     std::function<bool(uint32_t)> src_insn_defined,
582     std::function<bool(uint32_t)> dst_insn_defined) {
583   const uint32_t src_id_bound = static_cast<uint32_t>(src_to_dst_.IdBound());
584   const uint32_t dst_id_bound = static_cast<uint32_t>(dst_to_src_.IdBound());
585 
586   for (uint32_t src_id = 1; src_id < src_id_bound; ++src_id) {
587     if (!src_to_dst_.IsMapped(src_id) && src_insn_defined(src_id)) {
588       uint32_t fresh_dst_id = dst_to_src_.MakeFreshId();
589       MapIds(src_id, fresh_dst_id);
590     }
591   }
592 
593   for (uint32_t dst_id = 1; dst_id < dst_id_bound; ++dst_id) {
594     if (!dst_to_src_.IsMapped(dst_id) && dst_insn_defined(dst_id)) {
595       uint32_t fresh_src_id = src_to_dst_.MakeFreshId();
596       MapIds(fresh_src_id, dst_id);
597     }
598   }
599 }
600 
MapIdToInstruction(uint32_t id,const opt::Instruction * inst)601 void IdInstructions::MapIdToInstruction(uint32_t id,
602                                         const opt::Instruction* inst) {
603   assert(id != 0);
604   assert(id < inst_map_.size());
605   assert(inst_map_[id] == nullptr);
606 
607   inst_map_[id] = inst;
608 }
609 
MapIdsToInstruction(opt::IteratorRange<opt::Module::const_inst_iterator> section)610 void IdInstructions::MapIdsToInstruction(
611     opt::IteratorRange<opt::Module::const_inst_iterator> section) {
612   for (const opt::Instruction& inst : section) {
613     uint32_t result_id = inst.result_id();
614     if (result_id == 0) {
615       continue;
616     }
617 
618     MapIdToInstruction(result_id, &inst);
619   }
620 }
621 
MapIdsToInfos(opt::IteratorRange<opt::Module::const_inst_iterator> section)622 void IdInstructions::MapIdsToInfos(
623     opt::IteratorRange<opt::Module::const_inst_iterator> section) {
624   for (const opt::Instruction& inst : section) {
625     IdToInfoMap* info_map = nullptr;
626     uint32_t id_operand = 0;
627 
628     switch (inst.opcode()) {
629       case spv::Op::OpName:
630         info_map = &name_map_;
631         break;
632       case spv::Op::OpMemberName:
633         info_map = &name_map_;
634         break;
635       case spv::Op::OpDecorate:
636         info_map = &decoration_map_;
637         break;
638       case spv::Op::OpMemberDecorate:
639         info_map = &decoration_map_;
640         break;
641       case spv::Op::OpTypeForwardPointer: {
642         uint32_t id = inst.GetSingleWordOperand(0);
643         assert(id != 0);
644 
645         assert(id < forward_pointer_map_.size());
646         forward_pointer_map_[id] = &inst;
647         continue;
648       }
649       default:
650         // Currently unsupported instruction, don't attempt to use it for
651         // matching.
652         break;
653     }
654 
655     if (info_map == nullptr) {
656       continue;
657     }
658 
659     uint32_t id = inst.GetOperand(id_operand).AsId();
660     assert(id != 0);
661 
662     assert(id < info_map->size());
663     assert(std::find((*info_map)[id].begin(), (*info_map)[id].end(), &inst) ==
664            (*info_map)[id].end());
665 
666     (*info_map)[id].push_back(&inst);
667   }
668 }
669 
PoolPotentialIds(opt::IteratorRange<opt::Module::const_inst_iterator> section,std::vector<uint32_t> & ids,bool is_src,std::function<bool (const opt::Instruction &)> filter,std::function<uint32_t (const opt::Instruction &)> get_id)670 void Differ::PoolPotentialIds(
671     opt::IteratorRange<opt::Module::const_inst_iterator> section,
672     std::vector<uint32_t>& ids, bool is_src,
673     std::function<bool(const opt::Instruction&)> filter,
674     std::function<uint32_t(const opt::Instruction&)> get_id) {
675   for (const opt::Instruction& inst : section) {
676     if (!filter(inst)) {
677       continue;
678     }
679 
680     uint32_t result_id = get_id(inst);
681     assert(result_id != 0);
682 
683     assert(std::find(ids.begin(), ids.end(), result_id) == ids.end());
684 
685     // Don't include ids that are already matched, for example through
686     // OpTypeForwardPointer.
687     const bool is_matched = is_src ? id_map_.IsSrcMapped(result_id)
688                                    : id_map_.IsDstMapped(result_id);
689     if (is_matched) {
690       continue;
691     }
692 
693     ids.push_back(result_id);
694   }
695 }
696 
MatchIds(PotentialIdMap & potential,std::function<bool (const opt::Instruction *,const opt::Instruction *)> match)697 void Differ::MatchIds(
698     PotentialIdMap& potential,
699     std::function<bool(const opt::Instruction*, const opt::Instruction*)>
700         match) {
701   for (size_t src_index = 0; src_index < potential.src_ids.size();
702        ++src_index) {
703     for (size_t dst_index = 0; dst_index < potential.dst_ids.size();
704          ++dst_index) {
705       const uint32_t src_id = potential.src_ids[src_index];
706       const uint32_t dst_id = potential.dst_ids[dst_index];
707 
708       if (dst_id == 0) {
709         // Already matched.
710         continue;
711       }
712 
713       const opt::Instruction* src_inst = src_id_to_.inst_map_[src_id];
714       const opt::Instruction* dst_inst = dst_id_to_.inst_map_[dst_id];
715 
716       if (match(src_inst, dst_inst)) {
717         id_map_.MapIds(src_id, dst_id);
718 
719         // Remove the ids from the potential list.
720         potential.src_ids[src_index] = 0;
721         potential.dst_ids[dst_index] = 0;
722 
723         // Find a match for the next src id.
724         break;
725       }
726     }
727   }
728 
729   // Remove matched ids to make the next iteration faster.
730   CompactIds(potential.src_ids);
731   CompactIds(potential.dst_ids);
732 }
733 
MatchPreambleInstructions(opt::IteratorRange<opt::Module::const_inst_iterator> src_insts,opt::IteratorRange<opt::Module::const_inst_iterator> dst_insts)734 void Differ::MatchPreambleInstructions(
735     opt::IteratorRange<opt::Module::const_inst_iterator> src_insts,
736     opt::IteratorRange<opt::Module::const_inst_iterator> dst_insts) {
737   // First, pool all instructions from each section and sort them.
738   InstructionList sorted_src_insts = SortPreambleInstructions(src_, src_insts);
739   InstructionList sorted_dst_insts = SortPreambleInstructions(dst_, dst_insts);
740 
741   // Then walk and match them.
742   size_t src_cur = 0;
743   size_t dst_cur = 0;
744 
745   while (src_cur < sorted_src_insts.size() &&
746          dst_cur < sorted_dst_insts.size()) {
747     const opt::Instruction* src_inst = sorted_src_insts[src_cur];
748     const opt::Instruction* dst_inst = sorted_dst_insts[dst_cur];
749 
750     int compare = ComparePreambleInstructions(src_inst, dst_inst, src_, dst_);
751     if (compare == 0) {
752       id_map_.MapInsts(src_inst, dst_inst);
753     }
754     if (compare <= 0) {
755       ++src_cur;
756     }
757     if (compare >= 0) {
758       ++dst_cur;
759     }
760   }
761 }
762 
SortPreambleInstructions(const opt::Module * module,opt::IteratorRange<opt::Module::const_inst_iterator> insts)763 InstructionList Differ::SortPreambleInstructions(
764     const opt::Module* module,
765     opt::IteratorRange<opt::Module::const_inst_iterator> insts) {
766   InstructionList sorted;
767   for (const opt::Instruction& inst : insts) {
768     sorted.push_back(&inst);
769   }
770   std::sort(
771       sorted.begin(), sorted.end(),
772       [this, module](const opt::Instruction* a, const opt::Instruction* b) {
773         return ComparePreambleInstructions(a, b, module, module) < 0;
774       });
775   return sorted;
776 }
777 
ComparePreambleInstructions(const opt::Instruction * a,const opt::Instruction * b,const opt::Module * src_inst_module,const opt::Module * dst_inst_module)778 int Differ::ComparePreambleInstructions(const opt::Instruction* a,
779                                         const opt::Instruction* b,
780                                         const opt::Module* src_inst_module,
781                                         const opt::Module* dst_inst_module) {
782   assert(a->opcode() == b->opcode());
783   assert(!a->HasResultId());
784   assert(!a->HasResultType());
785 
786   const uint32_t a_operand_count = a->NumOperands();
787   const uint32_t b_operand_count = b->NumOperands();
788 
789   if (a_operand_count < b_operand_count) {
790     return -1;
791   }
792   if (a_operand_count > b_operand_count) {
793     return 1;
794   }
795 
796   // Instead of comparing OpExecutionMode entry point ids as ids, compare them
797   // through their corresponding execution model.  This simplifies traversing
798   // the sorted list of instructions between src and dst modules.
799   if (a->opcode() == spv::Op::OpExecutionMode) {
800     const spv::ExecutionModel src_model =
801         GetExecutionModel(src_inst_module, a->GetSingleWordOperand(0));
802     const spv::ExecutionModel dst_model =
803         GetExecutionModel(dst_inst_module, b->GetSingleWordOperand(0));
804 
805     if (src_model < dst_model) {
806       return -1;
807     }
808     if (src_model > dst_model) {
809       return 1;
810     }
811   }
812 
813   // Match every operand of the instruction.
814   for (uint32_t operand_index = 0; operand_index < a_operand_count;
815        ++operand_index) {
816     const opt::Operand& a_operand = a->GetOperand(operand_index);
817     const opt::Operand& b_operand = b->GetOperand(operand_index);
818 
819     if (a_operand.type < b_operand.type) {
820       return -1;
821     }
822     if (a_operand.type > b_operand.type) {
823       return 1;
824     }
825 
826     switch (a_operand.type) {
827       case SPV_OPERAND_TYPE_ID:
828         // Don't compare ids, there can't be multiple instances of the
829         // OpExecutionMode with different ids of the same execution model.
830         break;
831       case SPV_OPERAND_TYPE_TYPE_ID:
832       case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
833       case SPV_OPERAND_TYPE_SCOPE_ID:
834         assert(false && "Unreachable");
835         break;
836       case SPV_OPERAND_TYPE_LITERAL_STRING: {
837         int str_compare =
838             strcmp(a_operand.AsString().c_str(), b_operand.AsString().c_str());
839         if (str_compare != 0) {
840           return str_compare;
841         }
842         break;
843       }
844       default:
845         // Expect literal values to match.
846         assert(a_operand.words.size() == 1);
847         assert(b_operand.words.size() == 1);
848 
849         if (a_operand.words[0] < b_operand.words[0]) {
850           return -1;
851         }
852         if (a_operand.words[0] > b_operand.words[0]) {
853           return 1;
854         }
855         break;
856     }
857   }
858 
859   return 0;
860 }
861 
MatchDebugAndAnnotationInstructions(opt::IteratorRange<opt::Module::const_inst_iterator> src_insts,opt::IteratorRange<opt::Module::const_inst_iterator> dst_insts)862 void Differ::MatchDebugAndAnnotationInstructions(
863     opt::IteratorRange<opt::Module::const_inst_iterator> src_insts,
864     opt::IteratorRange<opt::Module::const_inst_iterator> dst_insts) {
865   for (const opt::Instruction& src_inst : src_insts) {
866     for (const opt::Instruction& dst_inst : dst_insts) {
867       if (MappedSrcInst(&dst_inst) != nullptr) {
868         continue;
869       }
870 
871       // Map instructions as soon as they match.  Debug and annotation
872       // instructions are matched such that there can't be multiple matches.
873       if (DoDebugAndAnnotationInstructionsMatch(&src_inst, &dst_inst)) {
874         id_map_.MapInsts(&src_inst, &dst_inst);
875         break;
876       }
877     }
878   }
879 }
880 
GroupIdsHelperGetTypeId(const IdInstructions & id_to,uint32_t id)881 uint32_t Differ::GroupIdsHelperGetTypeId(const IdInstructions& id_to,
882                                          uint32_t id) {
883   return GetInst(id_to, id)->type_id();
884 }
885 
GroupIdsHelperGetTypePointerStorageClass(const IdInstructions & id_to,uint32_t id)886 spv::StorageClass Differ::GroupIdsHelperGetTypePointerStorageClass(
887     const IdInstructions& id_to, uint32_t id) {
888   const opt::Instruction* inst = GetInst(id_to, id);
889   assert(inst && inst->opcode() == spv::Op::OpTypePointer);
890   return spv::StorageClass(inst->GetSingleWordInOperand(0));
891 }
892 
GroupIdsHelperGetTypePointerTypeOp(const IdInstructions & id_to,uint32_t id)893 spv::Op Differ::GroupIdsHelperGetTypePointerTypeOp(const IdInstructions& id_to,
894                                                    uint32_t id) {
895   const opt::Instruction* inst = GetInst(id_to, id);
896   assert(inst && inst->opcode() == spv::Op::OpTypePointer);
897 
898   const uint32_t type_id = inst->GetSingleWordInOperand(1);
899   const opt::Instruction* type_inst = GetInst(id_to, type_id);
900   assert(type_inst);
901 
902   return type_inst->opcode();
903 }
904 
905 template <typename T>
GroupIds(const IdGroup & ids,bool is_src,std::map<T,IdGroup> * groups,T (Differ::* get_group)(const IdInstructions &,uint32_t))906 void Differ::GroupIds(const IdGroup& ids, bool is_src,
907                       std::map<T, IdGroup>* groups,
908                       T (Differ::*get_group)(const IdInstructions&, uint32_t)) {
909   assert(groups->empty());
910 
911   const IdInstructions& id_to = is_src ? src_id_to_ : dst_id_to_;
912 
913   for (const uint32_t id : ids) {
914     // Don't include ids that are already matched, for example through
915     // OpEntryPoint.
916     const bool is_matched =
917         is_src ? id_map_.IsSrcMapped(id) : id_map_.IsDstMapped(id);
918     if (is_matched) {
919       continue;
920     }
921 
922     T group = (this->*get_group)(id_to, id);
923     (*groups)[group].push_back(id);
924   }
925 }
926 
927 template <typename T>
GroupIdsAndMatch(const IdGroup & src_ids,const IdGroup & dst_ids,T invalid_group_key,T (Differ::* get_group)(const IdInstructions &,uint32_t),std::function<void (const IdGroup & src_group,const IdGroup & dst_group)> match_group)928 void Differ::GroupIdsAndMatch(
929     const IdGroup& src_ids, const IdGroup& dst_ids, T invalid_group_key,
930     T (Differ::*get_group)(const IdInstructions&, uint32_t),
931     std::function<void(const IdGroup& src_group, const IdGroup& dst_group)>
932         match_group) {
933   // Group the ids based on a key (get_group)
934   std::map<T, IdGroup> src_groups;
935   std::map<T, IdGroup> dst_groups;
936 
937   GroupIds<T>(src_ids, true, &src_groups, get_group);
938   GroupIds<T>(dst_ids, false, &dst_groups, get_group);
939 
940   // Iterate over the groups, and match those with identical keys
941   for (const auto& iter : src_groups) {
942     const T& key = iter.first;
943     const IdGroup& src_group = iter.second;
944 
945     if (key == invalid_group_key) {
946       continue;
947     }
948 
949     const IdGroup& dst_group = dst_groups[key];
950 
951     // Let the caller match the groups as appropriate.
952     match_group(src_group, dst_group);
953   }
954 }
955 
GroupIdsAndMatchByMappedId(const IdGroup & src_ids,const IdGroup & dst_ids,uint32_t (Differ::* get_group)(const IdInstructions &,uint32_t),std::function<void (const IdGroup & src_group,const IdGroup & dst_group)> match_group)956 void Differ::GroupIdsAndMatchByMappedId(
957     const IdGroup& src_ids, const IdGroup& dst_ids,
958     uint32_t (Differ::*get_group)(const IdInstructions&, uint32_t),
959     std::function<void(const IdGroup& src_group, const IdGroup& dst_group)>
960         match_group) {
961   // Group the ids based on a key (get_group)
962   std::map<uint32_t, IdGroup> src_groups;
963   std::map<uint32_t, IdGroup> dst_groups;
964 
965   GroupIds<uint32_t>(src_ids, true, &src_groups, get_group);
966   GroupIds<uint32_t>(dst_ids, false, &dst_groups, get_group);
967 
968   // Iterate over pairs of groups whose keys map to each other.
969   for (const auto& iter : src_groups) {
970     const uint32_t& src_key = iter.first;
971     const IdGroup& src_group = iter.second;
972 
973     if (src_key == 0) {
974       continue;
975     }
976 
977     if (id_map_.IsSrcMapped(src_key)) {
978       const uint32_t& dst_key = id_map_.MappedDstId(src_key);
979       const IdGroup& dst_group = dst_groups[dst_key];
980 
981       // Let the caller match the groups as appropriate.
982       match_group(src_group, dst_group);
983     }
984   }
985 }
986 
DoIdsMatch(uint32_t src_id,uint32_t dst_id)987 bool Differ::DoIdsMatch(uint32_t src_id, uint32_t dst_id) {
988   assert(dst_id != 0);
989   return id_map_.MappedDstId(src_id) == dst_id;
990 }
991 
DoesOperandMatch(const opt::Operand & src_operand,const opt::Operand & dst_operand)992 bool Differ::DoesOperandMatch(const opt::Operand& src_operand,
993                               const opt::Operand& dst_operand) {
994   assert(src_operand.type == dst_operand.type);
995 
996   switch (src_operand.type) {
997     case SPV_OPERAND_TYPE_ID:
998     case SPV_OPERAND_TYPE_TYPE_ID:
999     case SPV_OPERAND_TYPE_RESULT_ID:
1000     case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
1001     case SPV_OPERAND_TYPE_SCOPE_ID:
1002       // Match ids only if they are already matched in the id map.
1003       return DoIdsMatch(src_operand.AsId(), dst_operand.AsId());
1004     case SPV_OPERAND_TYPE_LITERAL_STRING:
1005       return src_operand.AsString() == dst_operand.AsString();
1006     default:
1007       // Otherwise expect them to match exactly.
1008       assert(src_operand.type != SPV_OPERAND_TYPE_LITERAL_STRING);
1009       if (src_operand.words.size() != dst_operand.words.size()) {
1010         return false;
1011       }
1012       for (size_t i = 0; i < src_operand.words.size(); ++i) {
1013         if (src_operand.words[i] != dst_operand.words[i]) {
1014           return false;
1015         }
1016       }
1017       return true;
1018   }
1019 }
1020 
DoOperandsMatch(const opt::Instruction * src_inst,const opt::Instruction * dst_inst,uint32_t in_operand_index_start,uint32_t in_operand_count)1021 bool Differ::DoOperandsMatch(const opt::Instruction* src_inst,
1022                              const opt::Instruction* dst_inst,
1023                              uint32_t in_operand_index_start,
1024                              uint32_t in_operand_count) {
1025   // Caller should have returned early for instructions with different opcode.
1026   assert(src_inst->opcode() == dst_inst->opcode());
1027 
1028   bool match = true;
1029   for (uint32_t i = 0; i < in_operand_count; ++i) {
1030     const uint32_t in_operand_index = in_operand_index_start + i;
1031 
1032     const opt::Operand& src_operand = src_inst->GetInOperand(in_operand_index);
1033     const opt::Operand& dst_operand = dst_inst->GetInOperand(in_operand_index);
1034 
1035     match = match && DoesOperandMatch(src_operand, dst_operand);
1036   }
1037 
1038   return match;
1039 }
1040 
DoInstructionsMatch(const opt::Instruction * src_inst,const opt::Instruction * dst_inst)1041 bool Differ::DoInstructionsMatch(const opt::Instruction* src_inst,
1042                                  const opt::Instruction* dst_inst) {
1043   // Check whether the two instructions are identical, that is the instructions
1044   // themselves are matched, every id is matched, and every other value is
1045   // identical.
1046   if (MappedDstInst(src_inst) != dst_inst) {
1047     return false;
1048   }
1049 
1050   assert(src_inst->opcode() == dst_inst->opcode());
1051   if (src_inst->NumOperands() != dst_inst->NumOperands()) {
1052     return false;
1053   }
1054 
1055   for (uint32_t operand_index = 0; operand_index < src_inst->NumOperands();
1056        ++operand_index) {
1057     const opt::Operand& src_operand = src_inst->GetOperand(operand_index);
1058     const opt::Operand& dst_operand = dst_inst->GetOperand(operand_index);
1059 
1060     if (!DoesOperandMatch(src_operand, dst_operand)) {
1061       return false;
1062     }
1063   }
1064 
1065   return true;
1066 }
1067 
DoIdsMatchFuzzy(uint32_t src_id,uint32_t dst_id)1068 bool Differ::DoIdsMatchFuzzy(uint32_t src_id, uint32_t dst_id) {
1069   assert(dst_id != 0);
1070   const uint32_t mapped_dst_id = id_map_.MappedDstId(src_id);
1071 
1072   // Consider unmatched ids as a match.  In function bodies, no result id is
1073   // matched yet and thus they are excluded from instruction matching when used
1074   // as parameters in subsequent instructions.
1075   if (mapped_dst_id == 0 || mapped_dst_id == dst_id) {
1076     return true;
1077   }
1078 
1079   // Int and Uint constants are interchangeable, match them in that case.
1080   if (AreIdenticalUintConstants(src_id, dst_id)) {
1081     return true;
1082   }
1083 
1084   return false;
1085 }
1086 
DoesOperandMatchFuzzy(const opt::Operand & src_operand,const opt::Operand & dst_operand)1087 bool Differ::DoesOperandMatchFuzzy(const opt::Operand& src_operand,
1088                                    const opt::Operand& dst_operand) {
1089   if (src_operand.type != dst_operand.type) {
1090     return false;
1091   }
1092 
1093   assert(src_operand.type != SPV_OPERAND_TYPE_RESULT_ID);
1094   assert(dst_operand.type != SPV_OPERAND_TYPE_RESULT_ID);
1095 
1096   switch (src_operand.type) {
1097     case SPV_OPERAND_TYPE_ID:
1098     case SPV_OPERAND_TYPE_TYPE_ID:
1099     case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
1100     case SPV_OPERAND_TYPE_SCOPE_ID:
1101       // Match id operands only if they are already matched in the id map.
1102       return DoIdsMatchFuzzy(src_operand.AsId(), dst_operand.AsId());
1103     default:
1104       // Otherwise allow everything to match.
1105       return true;
1106   }
1107 }
1108 
DoInstructionsMatchFuzzy(const opt::Instruction * src_inst,const opt::Instruction * dst_inst)1109 bool Differ::DoInstructionsMatchFuzzy(const opt::Instruction* src_inst,
1110                                       const opt::Instruction* dst_inst) {
1111   // Similar to DoOperandsMatch, but only checks that ids that have already been
1112   // matched are identical.  Ids that are unknown are allowed to match, as well
1113   // as any non-id operand.
1114   if (src_inst->opcode() != dst_inst->opcode()) {
1115     return false;
1116   }
1117   // For external instructions, make sure the set and opcode of the external
1118   // instruction matches too.
1119   if (src_inst->opcode() == spv::Op::OpExtInst) {
1120     if (!DoOperandsMatch(src_inst, dst_inst, 0, 2)) {
1121       return false;
1122     }
1123   }
1124 
1125   assert(src_inst->HasResultType() == dst_inst->HasResultType());
1126   if (src_inst->HasResultType() &&
1127       !DoIdsMatchFuzzy(src_inst->type_id(), dst_inst->type_id())) {
1128     return false;
1129   }
1130 
1131   // TODO: allow some instructions to match with different instruction lengths,
1132   // for example OpImage* with additional operands.
1133   if (src_inst->NumInOperandWords() != dst_inst->NumInOperandWords()) {
1134     return false;
1135   }
1136 
1137   bool match = true;
1138   for (uint32_t in_operand_index = 0;
1139        in_operand_index < src_inst->NumInOperandWords(); ++in_operand_index) {
1140     const opt::Operand& src_operand = src_inst->GetInOperand(in_operand_index);
1141     const opt::Operand& dst_operand = dst_inst->GetInOperand(in_operand_index);
1142 
1143     match = match && DoesOperandMatchFuzzy(src_operand, dst_operand);
1144   }
1145 
1146   return match;
1147 }
1148 
AreIdenticalUintConstants(uint32_t src_id,uint32_t dst_id)1149 bool Differ::AreIdenticalUintConstants(uint32_t src_id, uint32_t dst_id) {
1150   return IsConstantUint(src_id_to_, src_id) &&
1151          IsConstantUint(dst_id_to_, dst_id) &&
1152          GetConstantUint(src_id_to_, src_id) ==
1153              GetConstantUint(dst_id_to_, dst_id);
1154 }
1155 
DoDebugAndAnnotationInstructionsMatch(const opt::Instruction * src_inst,const opt::Instruction * dst_inst)1156 bool Differ::DoDebugAndAnnotationInstructionsMatch(
1157     const opt::Instruction* src_inst, const opt::Instruction* dst_inst) {
1158   if (src_inst->opcode() != dst_inst->opcode()) {
1159     return false;
1160   }
1161 
1162   switch (src_inst->opcode()) {
1163     case spv::Op::OpString:
1164     case spv::Op::OpSourceExtension:
1165     case spv::Op::OpModuleProcessed:
1166       return DoesOperandMatch(src_inst->GetOperand(0), dst_inst->GetOperand(0));
1167     case spv::Op::OpSource:
1168       return DoOperandsMatch(src_inst, dst_inst, 0, 2);
1169     case spv::Op::OpSourceContinued:
1170       return true;
1171     case spv::Op::OpName:
1172       return DoOperandsMatch(src_inst, dst_inst, 0, 1);
1173     case spv::Op::OpMemberName:
1174       return DoOperandsMatch(src_inst, dst_inst, 0, 2);
1175     case spv::Op::OpDecorate:
1176       return DoOperandsMatch(src_inst, dst_inst, 0, 2);
1177     case spv::Op::OpMemberDecorate:
1178       return DoOperandsMatch(src_inst, dst_inst, 0, 3);
1179     case spv::Op::OpExtInst:
1180     case spv::Op::OpDecorationGroup:
1181     case spv::Op::OpGroupDecorate:
1182     case spv::Op::OpGroupMemberDecorate:
1183       return false;
1184     default:
1185       return false;
1186   }
1187 }
1188 
AreVariablesMatchable(uint32_t src_id,uint32_t dst_id,uint32_t flexibility)1189 bool Differ::AreVariablesMatchable(uint32_t src_id, uint32_t dst_id,
1190                                    uint32_t flexibility) {
1191   // Variables must match by their built-in decorations.
1192   uint32_t src_built_in_decoration = 0, dst_built_in_decoration = 0;
1193   const bool src_is_built_in = GetDecorationValue(
1194       src_id_to_, src_id, spv::Decoration::BuiltIn, &src_built_in_decoration);
1195   const bool dst_is_built_in = GetDecorationValue(
1196       dst_id_to_, dst_id, spv::Decoration::BuiltIn, &dst_built_in_decoration);
1197 
1198   if (src_is_built_in != dst_is_built_in) {
1199     return false;
1200   }
1201   if (src_is_built_in && src_built_in_decoration != dst_built_in_decoration) {
1202     return false;
1203   }
1204 
1205   // Check their types and storage classes.
1206   spv::StorageClass src_storage_class, dst_storage_class;
1207   const uint32_t src_type_id =
1208       GetVarTypeId(src_id_to_, src_id, &src_storage_class);
1209   const uint32_t dst_type_id =
1210       GetVarTypeId(dst_id_to_, dst_id, &dst_storage_class);
1211 
1212   if (!DoIdsMatch(src_type_id, dst_type_id)) {
1213     return false;
1214   }
1215   switch (flexibility) {
1216     case 0:
1217       if (src_storage_class != dst_storage_class) {
1218         return false;
1219       }
1220       break;
1221     case 1:
1222       if (src_storage_class != dst_storage_class) {
1223         // Allow one of the two to be Private while the other is Input or
1224         // Output, this allows matching in/out variables that have been turned
1225         // global as part of linking two stages (as done in ANGLE).
1226         const bool src_is_io = src_storage_class == spv::StorageClass::Input ||
1227                                src_storage_class == spv::StorageClass::Output;
1228         const bool dst_is_io = dst_storage_class == spv::StorageClass::Input ||
1229                                dst_storage_class == spv::StorageClass::Output;
1230         const bool src_is_private =
1231             src_storage_class == spv::StorageClass::Private;
1232         const bool dst_is_private =
1233             dst_storage_class == spv::StorageClass::Private;
1234 
1235         if (!((src_is_io && dst_is_private) || (src_is_private && dst_is_io))) {
1236           return false;
1237         }
1238       }
1239       break;
1240     default:
1241       assert(false && "Unreachable");
1242       return false;
1243   }
1244 
1245   // TODO: Is there any other way to check compatiblity of the variables?  It's
1246   // easy to tell when the variables definitely don't match, but there's little
1247   // information that can be used for a definite match.
1248   return true;
1249 }
1250 
MatchOpTypeStruct(const opt::Instruction * src_inst,const opt::Instruction * dst_inst,uint32_t flexibility)1251 bool Differ::MatchOpTypeStruct(const opt::Instruction* src_inst,
1252                                const opt::Instruction* dst_inst,
1253                                uint32_t flexibility) {
1254   const uint32_t src_type_id = src_inst->result_id();
1255   const uint32_t dst_type_id = dst_inst->result_id();
1256 
1257   bool src_has_name = false, dst_has_name = false;
1258   std::string src_name = GetName(src_id_to_, src_type_id, &src_has_name);
1259   std::string dst_name = GetName(dst_id_to_, dst_type_id, &dst_has_name);
1260 
1261   // If debug info is present, always match the structs by name.
1262   if (src_has_name && dst_has_name) {
1263     if (src_name != dst_name) {
1264       return false;
1265     }
1266 
1267     // For gl_PerVertex, find the type pointer of this type (array) and make
1268     // sure the storage classes of src and dst match; geometry and tessellation
1269     // shaders have two instances of gl_PerVertex.
1270     if (src_name == "gl_PerVertex") {
1271       return MatchPerVertexType(src_type_id, dst_type_id);
1272     }
1273 
1274     return true;
1275   }
1276 
1277   // If debug info is not present, match the structs by their type.
1278 
1279   // For gl_PerVertex, find the type pointer of this type (array) and match by
1280   // storage class. The gl_PerVertex struct is itself found by the BuiltIn
1281   // decorations applied to its members.
1282   const bool src_is_per_vertex = IsPerVertexType(src_id_to_, src_type_id);
1283   const bool dst_is_per_vertex = IsPerVertexType(dst_id_to_, dst_type_id);
1284   if (src_is_per_vertex != dst_is_per_vertex) {
1285     return false;
1286   }
1287 
1288   if (src_is_per_vertex) {
1289     return MatchPerVertexType(src_type_id, dst_type_id);
1290   }
1291 
1292   switch (flexibility) {
1293     case 0:
1294       if (src_inst->NumInOperandWords() != dst_inst->NumInOperandWords()) {
1295         return false;
1296       }
1297       return DoOperandsMatch(src_inst, dst_inst, 0,
1298                              src_inst->NumInOperandWords());
1299     case 1:
1300       // TODO: match by taking a diff of the fields, and see if there's a >75%
1301       // match.  Need to then make sure OpMemberName, OpMemberDecorate,
1302       // OpAccessChain etc are aware of the struct field matching.
1303       return false;
1304     default:
1305       assert(false && "Unreachable");
1306       return false;
1307   }
1308 }
1309 
MatchOpConstant(const opt::Instruction * src_inst,const opt::Instruction * dst_inst,uint32_t flexibility)1310 bool Differ::MatchOpConstant(const opt::Instruction* src_inst,
1311                              const opt::Instruction* dst_inst,
1312                              uint32_t flexibility) {
1313   // The constants' type must match.  In flexibility == 1, match constants of
1314   // int and uint, as they are generally interchangeable.
1315   switch (flexibility) {
1316     case 0:
1317       if (!DoesOperandMatch(src_inst->GetOperand(0), dst_inst->GetOperand(0))) {
1318         return false;
1319       }
1320       break;
1321     case 1:
1322       if (!IsIntType(src_id_to_, src_inst->type_id()) ||
1323           !IsIntType(dst_id_to_, dst_inst->type_id())) {
1324         return false;
1325       }
1326       break;
1327     default:
1328       assert(false && "Unreachable");
1329       return false;
1330   }
1331 
1332   const opt::Operand& src_value_operand = src_inst->GetOperand(2);
1333   const opt::Operand& dst_value_operand = dst_inst->GetOperand(2);
1334 
1335   const uint64_t src_value = src_value_operand.AsLiteralUint64();
1336   const uint64_t dst_value = dst_value_operand.AsLiteralUint64();
1337 
1338   // If values are identical, it's a match.
1339   if (src_value == dst_value) {
1340     return true;
1341   }
1342 
1343   // Otherwise, only allow flexibility for float types.
1344   if (IsFloatType(src_id_to_, src_inst->type_id()) && flexibility == 1) {
1345     // Tolerance is:
1346     //
1347     // - For float: allow 4 bits of mantissa as error
1348     // - For double: allow 6 bits of mantissa as error
1349     //
1350     // TODO: the above values are arbitrary and a placeholder; investigate the
1351     // amount of error resulting from using `printf("%f", f)` and `printf("%lf",
1352     // d)` and having glslang parse them.
1353     const uint64_t tolerance = src_value_operand.words.size() == 1 ? 16 : 64;
1354     return src_value - dst_value < tolerance ||
1355            dst_value - src_value < tolerance;
1356   }
1357 
1358   return false;
1359 }
1360 
MatchOpSpecConstant(const opt::Instruction * src_inst,const opt::Instruction * dst_inst)1361 bool Differ::MatchOpSpecConstant(const opt::Instruction* src_inst,
1362                                  const opt::Instruction* dst_inst) {
1363   const uint32_t src_id = src_inst->result_id();
1364   const uint32_t dst_id = dst_inst->result_id();
1365 
1366   bool src_has_name = false, dst_has_name = false;
1367   std::string src_name = GetName(src_id_to_, src_id, &src_has_name);
1368   std::string dst_name = GetName(dst_id_to_, dst_id, &dst_has_name);
1369 
1370   // If debug info is present, always match the spec consts by name.
1371   if (src_has_name && dst_has_name) {
1372     return src_name == dst_name;
1373   }
1374 
1375   // Otherwise, match them by SpecId.
1376   uint32_t src_spec_id, dst_spec_id;
1377 
1378   if (GetDecorationValue(src_id_to_, src_id, spv::Decoration::SpecId,
1379                          &src_spec_id) &&
1380       GetDecorationValue(dst_id_to_, dst_id, spv::Decoration::SpecId,
1381                          &dst_spec_id)) {
1382     return src_spec_id == dst_spec_id;
1383   }
1384 
1385   // There is no SpecId decoration, while not practical, still valid.
1386   // SpecConstantOp don't have SpecId and can be matched by operands
1387   if (src_inst->opcode() == spv::Op::OpSpecConstantOp) {
1388     if (src_inst->NumInOperandWords() == dst_inst->NumInOperandWords()) {
1389       return DoOperandsMatch(src_inst, dst_inst, 0,
1390                              src_inst->NumInOperandWords());
1391     }
1392   }
1393 
1394   return false;
1395 }
1396 
MatchOpVariable(const opt::Instruction * src_inst,const opt::Instruction * dst_inst,uint32_t flexibility)1397 bool Differ::MatchOpVariable(const opt::Instruction* src_inst,
1398                              const opt::Instruction* dst_inst,
1399                              uint32_t flexibility) {
1400   const uint32_t src_id = src_inst->result_id();
1401   const uint32_t dst_id = dst_inst->result_id();
1402 
1403   const bool src_is_pervertex = IsPerVertexVariable(src_id_to_, src_id);
1404   const bool dst_is_pervertex = IsPerVertexVariable(dst_id_to_, dst_id);
1405 
1406   // For gl_PerVertex, make sure the input and output instances are matched
1407   // correctly.
1408   if (src_is_pervertex != dst_is_pervertex) {
1409     return false;
1410   }
1411   if (src_is_pervertex) {
1412     return MatchPerVertexVariable(src_inst, dst_inst);
1413   }
1414 
1415   bool src_has_name = false, dst_has_name = false;
1416   std::string src_name = GetName(src_id_to_, src_id, &src_has_name);
1417   std::string dst_name = GetName(dst_id_to_, dst_id, &dst_has_name);
1418 
1419   // If debug info is present, always match the variables by name.
1420   if (src_has_name && dst_has_name) {
1421     return src_name == dst_name;
1422   }
1423 
1424   // If debug info is not present, see if the variables can be matched by their
1425   // built-in decorations.
1426   uint32_t src_built_in_decoration;
1427   const bool src_is_built_in = GetDecorationValue(
1428       src_id_to_, src_id, spv::Decoration::BuiltIn, &src_built_in_decoration);
1429 
1430   if (src_is_built_in && AreVariablesMatchable(src_id, dst_id, flexibility)) {
1431     return true;
1432   }
1433 
1434   spv::StorageClass src_storage_class, dst_storage_class;
1435   GetVarTypeId(src_id_to_, src_id, &src_storage_class);
1436   GetVarTypeId(dst_id_to_, dst_id, &dst_storage_class);
1437 
1438   if (src_storage_class != dst_storage_class) {
1439     return false;
1440   }
1441 
1442   // If variables are decorated with set/binding, match by the value of those
1443   // decorations.
1444   if (!options_.ignore_set_binding) {
1445     uint32_t src_set = 0, dst_set = 0;
1446     uint32_t src_binding = 0, dst_binding = 0;
1447 
1448     const bool src_has_set = GetDecorationValue(
1449         src_id_to_, src_id, spv::Decoration::DescriptorSet, &src_set);
1450     const bool dst_has_set = GetDecorationValue(
1451         dst_id_to_, dst_id, spv::Decoration::DescriptorSet, &dst_set);
1452     const bool src_has_binding = GetDecorationValue(
1453         src_id_to_, src_id, spv::Decoration::Binding, &src_set);
1454     const bool dst_has_binding = GetDecorationValue(
1455         dst_id_to_, dst_id, spv::Decoration::Binding, &dst_set);
1456 
1457     if (src_has_set && dst_has_set && src_has_binding && dst_has_binding) {
1458       return src_set == dst_set && src_binding == dst_binding;
1459     }
1460   }
1461 
1462   // If variables are decorated with location, match by the value of that
1463   // decoration.
1464   if (!options_.ignore_location) {
1465     uint32_t src_location, dst_location;
1466 
1467     const bool src_has_location = GetDecorationValue(
1468         src_id_to_, src_id, spv::Decoration::Location, &src_location);
1469     const bool dst_has_location = GetDecorationValue(
1470         dst_id_to_, dst_id, spv::Decoration::Location, &dst_location);
1471 
1472     if (src_has_location && dst_has_location) {
1473       return src_location == dst_location;
1474     }
1475   }
1476 
1477   // Currently, there's no other way to match variables.
1478   return false;
1479 }
1480 
MatchPerVertexType(uint32_t src_type_id,uint32_t dst_type_id)1481 bool Differ::MatchPerVertexType(uint32_t src_type_id, uint32_t dst_type_id) {
1482   // For gl_PerVertex, find the type pointer of this type (array) and make sure
1483   // the storage classes of src and dst match; geometry and tessellation shaders
1484   // have two instances of gl_PerVertex.
1485   spv::StorageClass src_storage_class =
1486       GetPerVertexStorageClass(src_, src_type_id);
1487   spv::StorageClass dst_storage_class =
1488       GetPerVertexStorageClass(dst_, dst_type_id);
1489 
1490   assert(src_storage_class == spv::StorageClass::Input ||
1491          src_storage_class == spv::StorageClass::Output);
1492   assert(dst_storage_class == spv::StorageClass::Input ||
1493          dst_storage_class == spv::StorageClass::Output);
1494 
1495   return src_storage_class == dst_storage_class;
1496 }
1497 
MatchPerVertexVariable(const opt::Instruction * src_inst,const opt::Instruction * dst_inst)1498 bool Differ::MatchPerVertexVariable(const opt::Instruction* src_inst,
1499                                     const opt::Instruction* dst_inst) {
1500   spv::StorageClass src_storage_class =
1501       spv::StorageClass(src_inst->GetSingleWordInOperand(0));
1502   spv::StorageClass dst_storage_class =
1503       spv::StorageClass(dst_inst->GetSingleWordInOperand(0));
1504 
1505   return src_storage_class == dst_storage_class;
1506 }
1507 
MatchTypeForwardPointersByName(const IdGroup & src,const IdGroup & dst)1508 void Differ::MatchTypeForwardPointersByName(const IdGroup& src,
1509                                             const IdGroup& dst) {
1510   // Given two sets of compatible groups of OpTypeForwardPointer instructions,
1511   // attempts to match them by name.
1512 
1513   // Group them by debug info and loop over them.
1514   GroupIdsAndMatch<std::string>(
1515       src, dst, "", &Differ::GetSanitizedName,
1516       [this](const IdGroup& src_group, const IdGroup& dst_group) {
1517         // Match only if there's a unique forward declaration with this debug
1518         // name.
1519         if (src_group.size() == 1 && dst_group.size() == 1) {
1520           id_map_.MapIds(src_group[0], dst_group[0]);
1521         }
1522       });
1523 }
1524 
MatchTypeForwardPointersByTypeOp(const IdGroup & src,const IdGroup & dst)1525 void Differ::MatchTypeForwardPointersByTypeOp(const IdGroup& src,
1526                                               const IdGroup& dst) {
1527   // Given two sets of compatible groups of OpTypeForwardPointer instructions,
1528   // attempts to match them by type op.  Must be called after
1529   // MatchTypeForwardPointersByName to match as many as possible by debug info.
1530 
1531   // Remove ids that are matched with debug info in
1532   // MatchTypeForwardPointersByName.
1533   IdGroup src_unmatched_ids;
1534   IdGroup dst_unmatched_ids;
1535 
1536   std::copy_if(src.begin(), src.end(), std::back_inserter(src_unmatched_ids),
1537                [this](uint32_t id) { return !id_map_.IsSrcMapped(id); });
1538   std::copy_if(dst.begin(), dst.end(), std::back_inserter(dst_unmatched_ids),
1539                [this](uint32_t id) { return !id_map_.IsDstMapped(id); });
1540 
1541   // Match only if there's a unique forward declaration with this
1542   // storage class and type opcode.  If both have debug info, they
1543   // must not have been matchable.
1544   if (src_unmatched_ids.size() == 1 && dst_unmatched_ids.size() == 1) {
1545     uint32_t src_id = src_unmatched_ids[0];
1546     uint32_t dst_id = dst_unmatched_ids[0];
1547     if (!HasName(src_id_to_, src_id) || !HasName(dst_id_to_, dst_id)) {
1548       id_map_.MapIds(src_id, dst_id);
1549     }
1550   }
1551 }
1552 
GetFunctionBody(opt::IRContext * context,opt::Function & function)1553 InstructionList Differ::GetFunctionBody(opt::IRContext* context,
1554                                         opt::Function& function) {
1555   // Canonicalize the blocks of the function to produce better diff, for example
1556   // to not produce any diff if the src and dst have the same switch/case blocks
1557   // but with the cases simply reordered.
1558   std::list<opt::BasicBlock*> order;
1559   context->cfg()->ComputeStructuredOrder(&function, &*function.begin(), &order);
1560 
1561   // Go over the instructions of the function and add the instructions to a flat
1562   // list to simplify future iterations.
1563   InstructionList body;
1564   for (opt::BasicBlock* block : order) {
1565     block->ForEachInst(
1566         [&body](const opt::Instruction* inst) { body.push_back(inst); }, true);
1567   }
1568   body.push_back(function.EndInst());
1569 
1570   return body;
1571 }
1572 
GetFunctionHeader(const opt::Function & function)1573 InstructionList Differ::GetFunctionHeader(const opt::Function& function) {
1574   // Go over the instructions of the function and add the header instructions to
1575   // a flat list to simplify diff generation.
1576   InstructionList body;
1577   function.WhileEachInst(
1578       [&body](const opt::Instruction* inst) {
1579         if (inst->opcode() == spv::Op::OpLabel) {
1580           return false;
1581         }
1582         body.push_back(inst);
1583         return true;
1584       },
1585       true, true);
1586 
1587   return body;
1588 }
1589 
GetFunctionBodies(opt::IRContext * context,FunctionMap * functions,FunctionInstMap * function_insts)1590 void Differ::GetFunctionBodies(opt::IRContext* context, FunctionMap* functions,
1591                                FunctionInstMap* function_insts) {
1592   for (opt::Function& function : *context->module()) {
1593     uint32_t id = function.result_id();
1594     assert(functions->find(id) == functions->end());
1595     assert(function_insts->find(id) == function_insts->end());
1596 
1597     (*functions)[id] = &function;
1598 
1599     InstructionList body = GetFunctionBody(context, function);
1600     (*function_insts)[id] = std::move(body);
1601   }
1602 }
1603 
GetFunctionHeaderInstructions(const opt::Module * module,FunctionInstMap * function_insts)1604 void Differ::GetFunctionHeaderInstructions(const opt::Module* module,
1605                                            FunctionInstMap* function_insts) {
1606   for (opt::Function& function : *module) {
1607     InstructionList body = GetFunctionHeader(function);
1608     (*function_insts)[function.result_id()] = std::move(body);
1609   }
1610 }
1611 
BestEffortMatchFunctions(const IdGroup & src_func_ids,const IdGroup & dst_func_ids,const FunctionInstMap & src_func_insts,const FunctionInstMap & dst_func_insts)1612 void Differ::BestEffortMatchFunctions(const IdGroup& src_func_ids,
1613                                       const IdGroup& dst_func_ids,
1614                                       const FunctionInstMap& src_func_insts,
1615                                       const FunctionInstMap& dst_func_insts) {
1616   struct MatchResult {
1617     uint32_t src_id;
1618     uint32_t dst_id;
1619     DiffMatch src_match;
1620     DiffMatch dst_match;
1621     float match_rate;
1622     bool operator<(const MatchResult& other) const {
1623       return match_rate > other.match_rate;
1624     }
1625   };
1626   std::vector<MatchResult> all_match_results;
1627 
1628   for (const uint32_t src_func_id : src_func_ids) {
1629     if (id_map_.IsSrcMapped(src_func_id)) {
1630       continue;
1631     }
1632     const std::string src_name = GetSanitizedName(src_id_to_, src_func_id);
1633 
1634     for (const uint32_t dst_func_id : dst_func_ids) {
1635       if (id_map_.IsDstMapped(dst_func_id)) {
1636         continue;
1637       }
1638 
1639       // Don't match functions that are named, but the names are different.
1640       const std::string dst_name = GetSanitizedName(dst_id_to_, dst_func_id);
1641       if (src_name != "" && dst_name != "" && src_name != dst_name) {
1642         continue;
1643       }
1644 
1645       DiffMatch src_match_result, dst_match_result;
1646       float match_rate = MatchFunctionBodies(
1647           src_func_insts.at(src_func_id), dst_func_insts.at(dst_func_id),
1648           &src_match_result, &dst_match_result);
1649 
1650       // Only consider the functions a match if there's at least 60% match.
1651       // This is an arbitrary limit that should be tuned.
1652       constexpr float pass_match_rate = 0.6f;
1653       if (match_rate >= pass_match_rate) {
1654         all_match_results.emplace_back(
1655             MatchResult{src_func_id, dst_func_id, std::move(src_match_result),
1656                         std::move(dst_match_result), match_rate});
1657       }
1658     }
1659   }
1660 
1661   std::sort(all_match_results.begin(), all_match_results.end());
1662 
1663   for (const MatchResult& match_result : all_match_results) {
1664     if (id_map_.IsSrcMapped(match_result.src_id) ||
1665         id_map_.IsDstMapped(match_result.dst_id)) {
1666       continue;
1667     }
1668 
1669     id_map_.MapIds(match_result.src_id, match_result.dst_id);
1670 
1671     MatchFunctionParamIds(src_funcs_[match_result.src_id],
1672                           dst_funcs_[match_result.dst_id]);
1673     MatchIdsInFunctionBodies(src_func_insts.at(match_result.src_id),
1674                              dst_func_insts.at(match_result.dst_id),
1675                              match_result.src_match, match_result.dst_match, 0);
1676   }
1677 }
1678 
MatchFunctionParamIds(const opt::Function * src_func,const opt::Function * dst_func)1679 void Differ::MatchFunctionParamIds(const opt::Function* src_func,
1680                                    const opt::Function* dst_func) {
1681   IdGroup src_params;
1682   IdGroup dst_params;
1683   src_func->ForEachParam(
1684       [&src_params](const opt::Instruction* param) {
1685         src_params.push_back(param->result_id());
1686       },
1687       false);
1688   dst_func->ForEachParam(
1689       [&dst_params](const opt::Instruction* param) {
1690         dst_params.push_back(param->result_id());
1691       },
1692       false);
1693 
1694   GroupIdsAndMatch<std::string>(
1695       src_params, dst_params, "", &Differ::GetSanitizedName,
1696       [this](const IdGroup& src_group, const IdGroup& dst_group) {
1697         // There shouldn't be two parameters with the same name, so the ids
1698         // should match. There is nothing restricting the SPIR-V however to have
1699         // two parameters with the same name, so be resilient against that.
1700         if (src_group.size() == 1 && dst_group.size() == 1) {
1701           id_map_.MapIds(src_group[0], dst_group[0]);
1702         }
1703       });
1704 
1705   // Then match the parameters by their type.  If there are multiple of them,
1706   // match them by their order.
1707   GroupIdsAndMatchByMappedId(
1708       src_params, dst_params, &Differ::GroupIdsHelperGetTypeId,
1709       [this](const IdGroup& src_group_by_type_id,
1710              const IdGroup& dst_group_by_type_id) {
1711         const size_t shared_param_count =
1712             std::min(src_group_by_type_id.size(), dst_group_by_type_id.size());
1713 
1714         for (size_t param_index = 0; param_index < shared_param_count;
1715              ++param_index) {
1716           id_map_.MapIds(src_group_by_type_id[param_index],
1717                          dst_group_by_type_id[param_index]);
1718         }
1719       });
1720 }
1721 
MatchFunctionBodies(const InstructionList & src_body,const InstructionList & dst_body,DiffMatch * src_match_result,DiffMatch * dst_match_result)1722 float Differ::MatchFunctionBodies(const InstructionList& src_body,
1723                                   const InstructionList& dst_body,
1724                                   DiffMatch* src_match_result,
1725                                   DiffMatch* dst_match_result) {
1726   LongestCommonSubsequence<std::vector<const opt::Instruction*>> lcs(src_body,
1727                                                                      dst_body);
1728 
1729   uint32_t best_match_length = lcs.Get<const opt::Instruction*>(
1730       [this](const opt::Instruction* src_inst,
1731              const opt::Instruction* dst_inst) {
1732         return DoInstructionsMatchFuzzy(src_inst, dst_inst);
1733       },
1734       src_match_result, dst_match_result);
1735 
1736   // TODO: take the gaps in between matches and match those again with a relaxed
1737   // instruction-and-type-only comparison.  This can produce a better diff for
1738   // example if an array index is changed, causing the OpAccessChain id to not
1739   // match and subsequently every operation that's derived from that id.
1740   // Usually this mismatch cascades until the next OpStore which doesn't produce
1741   // an id.
1742 
1743   return static_cast<float>(best_match_length) * 2.0f /
1744          static_cast<float>(src_body.size() + dst_body.size());
1745 }
1746 
MatchIdsInFunctionBodies(const InstructionList & src_body,const InstructionList & dst_body,const DiffMatch & src_match_result,const DiffMatch & dst_match_result,uint32_t flexibility)1747 void Differ::MatchIdsInFunctionBodies(const InstructionList& src_body,
1748                                       const InstructionList& dst_body,
1749                                       const DiffMatch& src_match_result,
1750                                       const DiffMatch& dst_match_result,
1751                                       uint32_t flexibility) {
1752   size_t src_cur = 0;
1753   size_t dst_cur = 0;
1754 
1755   while (src_cur < src_body.size() && dst_cur < dst_body.size()) {
1756     if (src_match_result[src_cur] && dst_match_result[dst_cur]) {
1757       // Match instructions the src and dst instructions.
1758       //
1759       // TODO: count the matchings between variables discovered this way and
1760       // choose the "best match" after all functions have been diffed and all
1761       // instructions analyzed.
1762       const opt::Instruction* src_inst = src_body[src_cur++];
1763       const opt::Instruction* dst_inst = dst_body[dst_cur++];
1764 
1765       // Record the matching between the instructions.  This is done only once
1766       // (hence flexibility == 0).  Calls with non-zero flexibility values will
1767       // only deal with matching other ids based on the operands.
1768       if (flexibility == 0) {
1769         id_map_.MapInsts(src_inst, dst_inst);
1770       }
1771 
1772       // Match any unmatched variables referenced by the instructions.
1773       MatchVariablesUsedByMatchedInstructions(src_inst, dst_inst, flexibility);
1774       continue;
1775     }
1776     if (!src_match_result[src_cur]) {
1777       ++src_cur;
1778     }
1779     if (!dst_match_result[dst_cur]) {
1780       ++dst_cur;
1781     }
1782   }
1783 }
1784 
MatchVariablesUsedByMatchedInstructions(const opt::Instruction * src_inst,const opt::Instruction * dst_inst,uint32_t flexibility)1785 void Differ::MatchVariablesUsedByMatchedInstructions(
1786     const opt::Instruction* src_inst, const opt::Instruction* dst_inst,
1787     uint32_t flexibility) {
1788   // For OpAccessChain, OpLoad and OpStore instructions that reference unmatched
1789   // variables, match them as a best effort.
1790   assert(src_inst->opcode() == dst_inst->opcode());
1791   switch (src_inst->opcode()) {
1792     default:
1793       // TODO: match functions based on OpFunctionCall?
1794       break;
1795     case spv::Op::OpAccessChain:
1796     case spv::Op::OpInBoundsAccessChain:
1797     case spv::Op::OpPtrAccessChain:
1798     case spv::Op::OpInBoundsPtrAccessChain:
1799     case spv::Op::OpLoad:
1800     case spv::Op::OpStore:
1801       const uint32_t src_pointer_id = src_inst->GetSingleWordInOperand(0);
1802       const uint32_t dst_pointer_id = dst_inst->GetSingleWordInOperand(0);
1803       if (IsVariable(src_id_to_, src_pointer_id) &&
1804           IsVariable(dst_id_to_, dst_pointer_id) &&
1805           !id_map_.IsSrcMapped(src_pointer_id) &&
1806           !id_map_.IsDstMapped(dst_pointer_id) &&
1807           AreVariablesMatchable(src_pointer_id, dst_pointer_id, flexibility)) {
1808         id_map_.MapIds(src_pointer_id, dst_pointer_id);
1809       }
1810       break;
1811   }
1812 }
1813 
GetInst(const IdInstructions & id_to,uint32_t id)1814 const opt::Instruction* Differ::GetInst(const IdInstructions& id_to,
1815                                         uint32_t id) {
1816   assert(id != 0);
1817   assert(id < id_to.inst_map_.size());
1818 
1819   const opt::Instruction* inst = id_to.inst_map_[id];
1820   assert(inst != nullptr);
1821 
1822   return inst;
1823 }
1824 
GetConstantUint(const IdInstructions & id_to,uint32_t constant_id)1825 uint32_t Differ::GetConstantUint(const IdInstructions& id_to,
1826                                  uint32_t constant_id) {
1827   const opt::Instruction* constant_inst = GetInst(id_to, constant_id);
1828   assert(constant_inst->opcode() == spv::Op::OpConstant);
1829   assert(GetInst(id_to, constant_inst->type_id())->opcode() ==
1830          spv::Op::OpTypeInt);
1831 
1832   return constant_inst->GetSingleWordInOperand(0);
1833 }
1834 
GetExecutionModel(const opt::Module * module,uint32_t entry_point_id)1835 spv::ExecutionModel Differ::GetExecutionModel(const opt::Module* module,
1836                                               uint32_t entry_point_id) {
1837   for (const opt::Instruction& inst : module->entry_points()) {
1838     assert(inst.opcode() == spv::Op::OpEntryPoint);
1839     if (inst.GetSingleWordOperand(1) == entry_point_id) {
1840       return spv::ExecutionModel(inst.GetSingleWordOperand(0));
1841     }
1842   }
1843 
1844   assert(false && "Unreachable");
1845   return spv::ExecutionModel(0xFFF);
1846 }
1847 
HasName(const IdInstructions & id_to,uint32_t id)1848 bool Differ::HasName(const IdInstructions& id_to, uint32_t id) {
1849   assert(id != 0);
1850   assert(id < id_to.name_map_.size());
1851 
1852   for (const opt::Instruction* inst : id_to.name_map_[id]) {
1853     if (inst->opcode() == spv::Op::OpName) {
1854       return true;
1855     }
1856   }
1857 
1858   return false;
1859 }
1860 
GetName(const IdInstructions & id_to,uint32_t id,bool * has_name)1861 std::string Differ::GetName(const IdInstructions& id_to, uint32_t id,
1862                             bool* has_name) {
1863   assert(id != 0);
1864   assert(id < id_to.name_map_.size());
1865 
1866   for (const opt::Instruction* inst : id_to.name_map_[id]) {
1867     if (inst->opcode() == spv::Op::OpName) {
1868       *has_name = true;
1869       return inst->GetOperand(1).AsString();
1870     }
1871   }
1872 
1873   *has_name = false;
1874   return "";
1875 }
1876 
GetSanitizedName(const IdInstructions & id_to,uint32_t id)1877 std::string Differ::GetSanitizedName(const IdInstructions& id_to, uint32_t id) {
1878   bool has_name = false;
1879   std::string name = GetName(id_to, id, &has_name);
1880 
1881   if (!has_name) {
1882     return "";
1883   }
1884 
1885   // Remove args from the name, in case this is a function name
1886   return name.substr(0, name.find('('));
1887 }
1888 
GetVarTypeId(const IdInstructions & id_to,uint32_t var_id,spv::StorageClass * storage_class)1889 uint32_t Differ::GetVarTypeId(const IdInstructions& id_to, uint32_t var_id,
1890                               spv::StorageClass* storage_class) {
1891   const opt::Instruction* var_inst = GetInst(id_to, var_id);
1892   assert(var_inst->opcode() == spv::Op::OpVariable);
1893 
1894   *storage_class = spv::StorageClass(var_inst->GetSingleWordInOperand(0));
1895 
1896   // Get the type pointer from the variable.
1897   const uint32_t type_pointer_id = var_inst->type_id();
1898   const opt::Instruction* type_pointer_inst = GetInst(id_to, type_pointer_id);
1899 
1900   // Get the type from the type pointer.
1901   return type_pointer_inst->GetSingleWordInOperand(1);
1902 }
1903 
GetDecorationValue(const IdInstructions & id_to,uint32_t id,spv::Decoration decoration,uint32_t * decoration_value)1904 bool Differ::GetDecorationValue(const IdInstructions& id_to, uint32_t id,
1905                                 spv::Decoration decoration,
1906                                 uint32_t* decoration_value) {
1907   assert(id != 0);
1908   assert(id < id_to.decoration_map_.size());
1909 
1910   for (const opt::Instruction* inst : id_to.decoration_map_[id]) {
1911     if (inst->opcode() == spv::Op::OpDecorate &&
1912         inst->GetSingleWordOperand(0) == id &&
1913         spv::Decoration(inst->GetSingleWordOperand(1)) == decoration) {
1914       *decoration_value = inst->GetSingleWordOperand(2);
1915       return true;
1916     }
1917   }
1918 
1919   return false;
1920 }
1921 
GetForwardPointerInst(const IdInstructions & id_to,uint32_t id)1922 const opt::Instruction* Differ::GetForwardPointerInst(
1923     const IdInstructions& id_to, uint32_t id) {
1924   assert(id != 0);
1925   assert(id < id_to.forward_pointer_map_.size());
1926   return id_to.forward_pointer_map_[id];
1927 }
1928 
IsIntType(const IdInstructions & id_to,uint32_t type_id)1929 bool Differ::IsIntType(const IdInstructions& id_to, uint32_t type_id) {
1930   return IsOp(id_to, type_id, spv::Op::OpTypeInt);
1931 }
1932 
IsFloatType(const IdInstructions & id_to,uint32_t type_id)1933 bool Differ::IsFloatType(const IdInstructions& id_to, uint32_t type_id) {
1934   return IsOp(id_to, type_id, spv::Op::OpTypeFloat);
1935 }
1936 
IsConstantUint(const IdInstructions & id_to,uint32_t id)1937 bool Differ::IsConstantUint(const IdInstructions& id_to, uint32_t id) {
1938   const opt::Instruction* constant_inst = GetInst(id_to, id);
1939   if (constant_inst->opcode() != spv::Op::OpConstant) {
1940     return false;
1941   }
1942 
1943   const opt::Instruction* type_inst = GetInst(id_to, constant_inst->type_id());
1944   return type_inst->opcode() == spv::Op::OpTypeInt;
1945 }
1946 
IsVariable(const IdInstructions & id_to,uint32_t pointer_id)1947 bool Differ::IsVariable(const IdInstructions& id_to, uint32_t pointer_id) {
1948   return IsOp(id_to, pointer_id, spv::Op::OpVariable);
1949 }
1950 
IsOp(const IdInstructions & id_to,uint32_t id,spv::Op op)1951 bool Differ::IsOp(const IdInstructions& id_to, uint32_t id, spv::Op op) {
1952   return GetInst(id_to, id)->opcode() == op;
1953 }
1954 
IsPerVertexType(const IdInstructions & id_to,uint32_t type_id)1955 bool Differ::IsPerVertexType(const IdInstructions& id_to, uint32_t type_id) {
1956   assert(type_id != 0);
1957   assert(type_id < id_to.decoration_map_.size());
1958 
1959   for (const opt::Instruction* inst : id_to.decoration_map_[type_id]) {
1960     if (inst->opcode() == spv::Op::OpMemberDecorate &&
1961         inst->GetSingleWordOperand(0) == type_id &&
1962         spv::Decoration(inst->GetSingleWordOperand(2)) ==
1963             spv::Decoration::BuiltIn) {
1964       spv::BuiltIn built_in = spv::BuiltIn(inst->GetSingleWordOperand(3));
1965 
1966       // Only gl_PerVertex can have, and it can only have, the following
1967       // built-in decorations.
1968       return built_in == spv::BuiltIn::Position ||
1969              built_in == spv::BuiltIn::PointSize ||
1970              built_in == spv::BuiltIn::ClipDistance ||
1971              built_in == spv::BuiltIn::CullDistance;
1972     }
1973   }
1974 
1975   return false;
1976 }
1977 
IsPerVertexVariable(const IdInstructions & id_to,uint32_t var_id)1978 bool Differ::IsPerVertexVariable(const IdInstructions& id_to, uint32_t var_id) {
1979   // Get the type from the type pointer.
1980   spv::StorageClass storage_class;
1981   uint32_t type_id = GetVarTypeId(id_to, var_id, &storage_class);
1982   const opt::Instruction* type_inst = GetInst(id_to, type_id);
1983 
1984   // If array, get the element type.
1985   if (type_inst->opcode() == spv::Op::OpTypeArray) {
1986     type_id = type_inst->GetSingleWordInOperand(0);
1987   }
1988 
1989   // Now check if the type is gl_PerVertex.
1990   return IsPerVertexType(id_to, type_id);
1991 }
1992 
GetPerVertexStorageClass(const opt::Module * module,uint32_t type_id)1993 spv::StorageClass Differ::GetPerVertexStorageClass(const opt::Module* module,
1994                                                    uint32_t type_id) {
1995   for (const opt::Instruction& inst : module->types_values()) {
1996     switch (inst.opcode()) {
1997       case spv::Op::OpTypeArray:
1998         // The gl_PerVertex instance could be an array, look for a variable of
1999         // the array type instead.
2000         if (inst.GetSingleWordInOperand(0) == type_id) {
2001           type_id = inst.result_id();
2002         }
2003         break;
2004       case spv::Op::OpTypePointer:
2005         // Find the storage class of the pointer to this type.
2006         if (inst.GetSingleWordInOperand(1) == type_id) {
2007           return spv::StorageClass(inst.GetSingleWordInOperand(0));
2008         }
2009         break;
2010       default:
2011         break;
2012     }
2013   }
2014 
2015   // gl_PerVertex is declared, but is unused.  Return either of Input or Output
2016   // classes just so it matches one in the other module.  This should be highly
2017   // unlikely, perhaps except for ancient GS-used-to-emulate-CS scenarios.
2018   return spv::StorageClass::Output;
2019 }
2020 
GetExtInstType(const IdInstructions & id_to,uint32_t set_id)2021 spv_ext_inst_type_t Differ::GetExtInstType(const IdInstructions& id_to,
2022                                            uint32_t set_id) {
2023   const opt::Instruction* set_inst = GetInst(id_to, set_id);
2024   return spvExtInstImportTypeGet(set_inst->GetInOperand(0).AsString().c_str());
2025 }
2026 
GetNumberKind(const IdInstructions & id_to,const opt::Instruction & inst,uint32_t operand_index,uint32_t * number_bit_width)2027 spv_number_kind_t Differ::GetNumberKind(const IdInstructions& id_to,
2028                                         const opt::Instruction& inst,
2029                                         uint32_t operand_index,
2030                                         uint32_t* number_bit_width) {
2031   const opt::Operand& operand = inst.GetOperand(operand_index);
2032   *number_bit_width = 0;
2033 
2034   // A very limited version of Parser::parseOperand.
2035   switch (operand.type) {
2036     case SPV_OPERAND_TYPE_LITERAL_INTEGER:
2037     case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER:
2038       // Always unsigned integers.
2039       *number_bit_width = 32;
2040       return SPV_NUMBER_UNSIGNED_INT;
2041     case SPV_OPERAND_TYPE_LITERAL_FLOAT:
2042       // Always float.
2043       *number_bit_width = 32;
2044       return SPV_NUMBER_FLOATING;
2045     case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER:
2046     case SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER:
2047       switch (inst.opcode()) {
2048         case spv::Op::OpSwitch:
2049         case spv::Op::OpConstant:
2050         case spv::Op::OpSpecConstant:
2051           // Same kind of number as the selector (OpSwitch) or the type
2052           // (Op*Constant).
2053           return GetTypeNumberKind(id_to, inst.GetSingleWordOperand(0),
2054                                    number_bit_width);
2055         default:
2056           assert(false && "Unreachable");
2057           break;
2058       }
2059       break;
2060     default:
2061       break;
2062   }
2063 
2064   return SPV_NUMBER_NONE;
2065 }
2066 
GetTypeNumberKind(const IdInstructions & id_to,uint32_t id,uint32_t * number_bit_width)2067 spv_number_kind_t Differ::GetTypeNumberKind(const IdInstructions& id_to,
2068                                             uint32_t id,
2069                                             uint32_t* number_bit_width) {
2070   const opt::Instruction* type_inst = GetInst(id_to, id);
2071   if (!spvOpcodeIsScalarType(type_inst->opcode())) {
2072     type_inst = GetInst(id_to, type_inst->type_id());
2073   }
2074 
2075   switch (type_inst->opcode()) {
2076     case spv::Op::OpTypeInt:
2077       *number_bit_width = type_inst->GetSingleWordOperand(1);
2078       return type_inst->GetSingleWordOperand(2) == 0 ? SPV_NUMBER_UNSIGNED_INT
2079                                                      : SPV_NUMBER_SIGNED_INT;
2080       break;
2081     case spv::Op::OpTypeFloat:
2082       *number_bit_width = type_inst->GetSingleWordOperand(1);
2083       return SPV_NUMBER_FLOATING;
2084     default:
2085       assert(false && "Unreachable");
2086       return SPV_NUMBER_NONE;
2087   }
2088 }
2089 
MatchCapabilities()2090 void Differ::MatchCapabilities() {
2091   MatchPreambleInstructions(src_->capabilities(), dst_->capabilities());
2092 }
2093 
MatchExtensions()2094 void Differ::MatchExtensions() {
2095   MatchPreambleInstructions(src_->extensions(), dst_->extensions());
2096 }
2097 
MatchExtInstImportIds()2098 void Differ::MatchExtInstImportIds() {
2099   // Bunch all of this section's ids as potential matches.
2100   PotentialIdMap potential_id_map;
2101   auto get_result_id = [](const opt::Instruction& inst) {
2102     return inst.result_id();
2103   };
2104   auto accept_all = [](const opt::Instruction&) { return true; };
2105 
2106   PoolPotentialIds(src_->ext_inst_imports(), potential_id_map.src_ids, true,
2107                    accept_all, get_result_id);
2108   PoolPotentialIds(dst_->ext_inst_imports(), potential_id_map.dst_ids, false,
2109                    accept_all, get_result_id);
2110 
2111   // Then match the ids.
2112   MatchIds(potential_id_map, [](const opt::Instruction* src_inst,
2113                                 const opt::Instruction* dst_inst) {
2114     // Match OpExtInstImport by exact name, which is operand 1
2115     const opt::Operand& src_name = src_inst->GetOperand(1);
2116     const opt::Operand& dst_name = dst_inst->GetOperand(1);
2117 
2118     return src_name.AsString() == dst_name.AsString();
2119   });
2120 }
MatchMemoryModel()2121 void Differ::MatchMemoryModel() {
2122   // Always match the memory model instructions, there is always a single one of
2123   // it.
2124   id_map_.MapInsts(src_->GetMemoryModel(), dst_->GetMemoryModel());
2125 }
2126 
MatchEntryPointIds()2127 void Differ::MatchEntryPointIds() {
2128   // Match OpEntryPoint ids (at index 1) by ExecutionModel (at index 0) and
2129   // possibly name (at index 2).  OpEntryPoint doesn't produce a result id, so
2130   // this function doesn't use the helpers the other functions use.
2131 
2132   // Map from execution model to OpEntryPoint instructions of that model.
2133   using ExecutionModelMap =
2134       std::unordered_map<uint32_t, std::vector<const opt::Instruction*>>;
2135   ExecutionModelMap src_entry_points_map;
2136   ExecutionModelMap dst_entry_points_map;
2137   std::set<uint32_t> all_execution_models;
2138 
2139   for (const opt::Instruction& src_inst : src_->entry_points()) {
2140     uint32_t execution_model = src_inst.GetSingleWordOperand(0);
2141     src_entry_points_map[execution_model].push_back(&src_inst);
2142     all_execution_models.insert(execution_model);
2143   }
2144   for (const opt::Instruction& dst_inst : dst_->entry_points()) {
2145     uint32_t execution_model = dst_inst.GetSingleWordOperand(0);
2146     dst_entry_points_map[execution_model].push_back(&dst_inst);
2147     all_execution_models.insert(execution_model);
2148   }
2149 
2150   // Go through each model and match the ids.
2151   for (const uint32_t execution_model : all_execution_models) {
2152     auto& src_insts = src_entry_points_map[execution_model];
2153     auto& dst_insts = dst_entry_points_map[execution_model];
2154 
2155     // If there is only one entry point in src and dst with that model, match
2156     // them unconditionally.
2157     if (src_insts.size() == 1 && dst_insts.size() == 1) {
2158       uint32_t src_id = src_insts[0]->GetSingleWordOperand(1);
2159       uint32_t dst_id = dst_insts[0]->GetSingleWordOperand(1);
2160       id_map_.MapIds(src_id, dst_id);
2161       id_map_.MapInsts(src_insts[0], dst_insts[0]);
2162       continue;
2163     }
2164 
2165     // Otherwise match them by name.
2166     for (const opt::Instruction* src_inst : src_insts) {
2167       for (const opt::Instruction* dst_inst : dst_insts) {
2168         if (id_map_.IsDstMapped(dst_inst)) continue;
2169 
2170         const opt::Operand& src_name = src_inst->GetOperand(2);
2171         const opt::Operand& dst_name = dst_inst->GetOperand(2);
2172 
2173         if (src_name.AsString() == dst_name.AsString()) {
2174           uint32_t src_id = src_inst->GetSingleWordOperand(1);
2175           uint32_t dst_id = dst_inst->GetSingleWordOperand(1);
2176           id_map_.MapIds(src_id, dst_id);
2177           id_map_.MapInsts(src_inst, dst_inst);
2178           break;
2179         }
2180       }
2181     }
2182   }
2183 }
2184 
MatchExecutionModes()2185 void Differ::MatchExecutionModes() {
2186   MatchPreambleInstructions(src_->execution_modes(), dst_->execution_modes());
2187 }
2188 
MatchTypeForwardPointers()2189 void Differ::MatchTypeForwardPointers() {
2190   // Bunch all of type forward pointers as potential matches.
2191   PotentialIdMap potential_id_map;
2192   auto get_pointer_type_id = [](const opt::Instruction& inst) {
2193     return inst.GetSingleWordOperand(0);
2194   };
2195   auto accept_type_forward_pointer_ops = [](const opt::Instruction& inst) {
2196     return inst.opcode() == spv::Op::OpTypeForwardPointer;
2197   };
2198 
2199   PoolPotentialIds(src_->types_values(), potential_id_map.src_ids, true,
2200                    accept_type_forward_pointer_ops, get_pointer_type_id);
2201   PoolPotentialIds(dst_->types_values(), potential_id_map.dst_ids, false,
2202                    accept_type_forward_pointer_ops, get_pointer_type_id);
2203 
2204   // Matching types with cyclical references (i.e. in the style of linked lists)
2205   // can get very complex.  Currently, the diff tool matches types bottom up, so
2206   // on every instruction it expects to know if its operands are already matched
2207   // or not.  With cyclical references, it cannot know that.  Type matching may
2208   // need significant modifications to be able to support this use case.
2209   //
2210   // Currently, forwarded types are only matched by storage class and debug
2211   // info, with minimal matching of the type being forwarded:
2212   //
2213   // - Group by class
2214   //   - Group by OpType being pointed to
2215   //     - Group by debug info
2216   //       - If same name and unique, match
2217   //     - If leftover is unique, match
2218 
2219   // Group forwarded pointers by storage class first and loop over them.
2220   GroupIdsAndMatch<spv::StorageClass>(
2221       potential_id_map.src_ids, potential_id_map.dst_ids,
2222       spv::StorageClass::Max, &Differ::GroupIdsHelperGetTypePointerStorageClass,
2223       [this](const IdGroup& src_group_by_storage_class,
2224              const IdGroup& dst_group_by_storage_class) {
2225         // Group them further by the type they are pointing to and loop over
2226         // them.
2227         GroupIdsAndMatch<spv::Op>(
2228             src_group_by_storage_class, dst_group_by_storage_class,
2229             spv::Op::Max, &Differ::GroupIdsHelperGetTypePointerTypeOp,
2230             [this](const IdGroup& src_group_by_type_op,
2231                    const IdGroup& dst_group_by_type_op) {
2232               // Group them even further by debug info, if possible and match by
2233               // debug name.
2234               MatchTypeForwardPointersByName(src_group_by_type_op,
2235                                              dst_group_by_type_op);
2236 
2237               // Match the leftovers only if they lack debug info and there is
2238               // only one instance of them.
2239               MatchTypeForwardPointersByTypeOp(src_group_by_type_op,
2240                                                dst_group_by_type_op);
2241             });
2242       });
2243 
2244   // Match the instructions that forward declare the same type themselves
2245   for (uint32_t src_id : potential_id_map.src_ids) {
2246     uint32_t dst_id = id_map_.MappedDstId(src_id);
2247     if (dst_id == 0) continue;
2248 
2249     const opt::Instruction* src_forward_inst =
2250         GetForwardPointerInst(src_id_to_, src_id);
2251     const opt::Instruction* dst_forward_inst =
2252         GetForwardPointerInst(dst_id_to_, dst_id);
2253 
2254     assert(src_forward_inst);
2255     assert(dst_forward_inst);
2256 
2257     id_map_.MapInsts(src_forward_inst, dst_forward_inst);
2258   }
2259 }
2260 
MatchTypeIds()2261 void Differ::MatchTypeIds() {
2262   // Bunch all of type ids as potential matches.
2263   PotentialIdMap potential_id_map;
2264   auto get_result_id = [](const opt::Instruction& inst) {
2265     return inst.result_id();
2266   };
2267   auto accept_type_ops = [](const opt::Instruction& inst) {
2268     return spvOpcodeGeneratesType(inst.opcode());
2269   };
2270 
2271   PoolPotentialIds(src_->types_values(), potential_id_map.src_ids, true,
2272                    accept_type_ops, get_result_id);
2273   PoolPotentialIds(dst_->types_values(), potential_id_map.dst_ids, false,
2274                    accept_type_ops, get_result_id);
2275 
2276   // Then match the ids.  Start with exact matches, then match the leftover with
2277   // gradually loosening degrees of strictness.  For example, in the absence of
2278   // debug info, two block types will be matched if they differ only in a few of
2279   // the fields.
2280   for (uint32_t flexibility = 0; flexibility < 2; ++flexibility) {
2281     MatchIds(potential_id_map, [this, flexibility](
2282                                    const opt::Instruction* src_inst,
2283                                    const opt::Instruction* dst_inst) {
2284       const spv::Op src_op = src_inst->opcode();
2285       const spv::Op dst_op = dst_inst->opcode();
2286 
2287       // Don't match if the opcode is not the same.
2288       if (src_op != dst_op) {
2289         return false;
2290       }
2291 
2292       switch (src_op) {
2293         case spv::Op::OpTypeVoid:
2294         case spv::Op::OpTypeBool:
2295         case spv::Op::OpTypeSampler:
2296         case spv::Op::OpTypeAccelerationStructureNV:
2297         case spv::Op::OpTypeRayQueryKHR:
2298           // the above types have no operands and are unique, match them.
2299           return true;
2300         case spv::Op::OpTypeInt:
2301         case spv::Op::OpTypeFloat:
2302         case spv::Op::OpTypeVector:
2303         case spv::Op::OpTypeMatrix:
2304         case spv::Op::OpTypeSampledImage:
2305         case spv::Op::OpTypeRuntimeArray:
2306         case spv::Op::OpTypePointer:
2307           // Match these instructions when all operands match.
2308           assert(src_inst->NumInOperandWords() ==
2309                  dst_inst->NumInOperandWords());
2310           return DoOperandsMatch(src_inst, dst_inst, 0,
2311                                  src_inst->NumInOperandWords());
2312 
2313         case spv::Op::OpTypeFunction:
2314         case spv::Op::OpTypeImage:
2315           // Match function types only if they have the same number of operands,
2316           // and they all match.
2317           // Match image types similarly, expecting the optional final parameter
2318           // to match (if provided in both)
2319           if (src_inst->NumInOperandWords() != dst_inst->NumInOperandWords()) {
2320             return false;
2321           }
2322           return DoOperandsMatch(src_inst, dst_inst, 0,
2323                                  src_inst->NumInOperandWords());
2324 
2325         case spv::Op::OpTypeArray:
2326           // Match arrays only if the element type and length match.  The length
2327           // is an id of a constant, so the actual constant it's defining is
2328           // compared instead.
2329           if (!DoOperandsMatch(src_inst, dst_inst, 0, 1)) {
2330             return false;
2331           }
2332 
2333           if (AreIdenticalUintConstants(src_inst->GetSingleWordInOperand(1),
2334                                         dst_inst->GetSingleWordInOperand(1))) {
2335             return true;
2336           }
2337 
2338           // If size is not OpConstant, expect the ids to match exactly (for
2339           // example if a spec contant is used).
2340           return DoOperandsMatch(src_inst, dst_inst, 1, 1);
2341 
2342         case spv::Op::OpTypeStruct:
2343           return MatchOpTypeStruct(src_inst, dst_inst, flexibility);
2344 
2345         default:
2346           return false;
2347       }
2348     });
2349   }
2350 }
2351 
MatchConstants()2352 void Differ::MatchConstants() {
2353   // Bunch all of constant ids as potential matches.
2354   PotentialIdMap potential_id_map;
2355   auto get_result_id = [](const opt::Instruction& inst) {
2356     return inst.result_id();
2357   };
2358   auto accept_type_ops = [](const opt::Instruction& inst) {
2359     return spvOpcodeIsConstant(inst.opcode());
2360   };
2361 
2362   PoolPotentialIds(src_->types_values(), potential_id_map.src_ids, true,
2363                    accept_type_ops, get_result_id);
2364   PoolPotentialIds(dst_->types_values(), potential_id_map.dst_ids, false,
2365                    accept_type_ops, get_result_id);
2366 
2367   // Then match the ids.  Constants are matched exactly, except for float types
2368   // that are first matched exactly, then leftovers are matched with a small
2369   // error.
2370   for (uint32_t flexibility = 0; flexibility < 2; ++flexibility) {
2371     MatchIds(potential_id_map, [this, flexibility](
2372                                    const opt::Instruction* src_inst,
2373                                    const opt::Instruction* dst_inst) {
2374       const spv::Op src_op = src_inst->opcode();
2375       const spv::Op dst_op = dst_inst->opcode();
2376 
2377       // Don't match if the opcode is not the same.
2378       if (src_op != dst_op) {
2379         return false;
2380       }
2381 
2382       switch (src_op) {
2383         case spv::Op::OpConstantTrue:
2384         case spv::Op::OpConstantFalse:
2385           // true and false are unique, match them.
2386           return true;
2387         case spv::Op::OpConstant:
2388           return MatchOpConstant(src_inst, dst_inst, flexibility);
2389         case spv::Op::OpConstantComposite:
2390         case spv::Op::OpSpecConstantComposite:
2391           // Composite constants must match in type and value.
2392           //
2393           // TODO: match OpConstantNull with OpConstantComposite with all zeros
2394           // at flexibility == 1
2395           // TODO: match constants from structs that have been flexibly-matched.
2396           if (src_inst->NumInOperandWords() != dst_inst->NumInOperandWords()) {
2397             return false;
2398           }
2399           return DoesOperandMatch(src_inst->GetOperand(0),
2400                                   dst_inst->GetOperand(0)) &&
2401                  DoOperandsMatch(src_inst, dst_inst, 0,
2402                                  src_inst->NumInOperandWords());
2403         case spv::Op::OpConstantSampler:
2404           // Match sampler constants exactly.
2405           // TODO: Allow flexibility in parameters to better diff shaders where
2406           // the sampler param has changed.
2407           assert(src_inst->NumInOperandWords() ==
2408                  dst_inst->NumInOperandWords());
2409           return DoOperandsMatch(src_inst, dst_inst, 0,
2410                                  src_inst->NumInOperandWords());
2411         case spv::Op::OpConstantNull:
2412           // Match null constants as long as the type matches.
2413           return DoesOperandMatch(src_inst->GetOperand(0),
2414                                   dst_inst->GetOperand(0));
2415 
2416         case spv::Op::OpSpecConstantTrue:
2417         case spv::Op::OpSpecConstantFalse:
2418         case spv::Op::OpSpecConstant:
2419         case spv::Op::OpSpecConstantOp:
2420           // Match spec constants by name if available, then by the SpecId
2421           // decoration.
2422           return MatchOpSpecConstant(src_inst, dst_inst);
2423 
2424         default:
2425           return false;
2426       }
2427     });
2428   }
2429 }
2430 
MatchVariableIds()2431 void Differ::MatchVariableIds() {
2432   // Bunch all of variable ids as potential matches.
2433   PotentialIdMap potential_id_map;
2434   auto get_result_id = [](const opt::Instruction& inst) {
2435     return inst.result_id();
2436   };
2437   auto accept_type_ops = [](const opt::Instruction& inst) {
2438     return inst.opcode() == spv::Op::OpVariable;
2439   };
2440 
2441   PoolPotentialIds(src_->types_values(), potential_id_map.src_ids, true,
2442                    accept_type_ops, get_result_id);
2443   PoolPotentialIds(dst_->types_values(), potential_id_map.dst_ids, false,
2444                    accept_type_ops, get_result_id);
2445 
2446   // Then match the ids.  Start with exact matches, then match the leftover with
2447   // gradually loosening degrees of strictness.  For example, in the absence of
2448   // debug info, two otherwise identical variables will be matched if one of
2449   // them has a Private storage class and the other doesn't.
2450   for (uint32_t flexibility = 0; flexibility < 2; ++flexibility) {
2451     MatchIds(potential_id_map,
2452              [this, flexibility](const opt::Instruction* src_inst,
2453                                  const opt::Instruction* dst_inst) {
2454                assert(src_inst->opcode() == spv::Op::OpVariable);
2455                assert(dst_inst->opcode() == spv::Op::OpVariable);
2456 
2457                return MatchOpVariable(src_inst, dst_inst, flexibility);
2458              });
2459   }
2460 }
2461 
MatchFunctions()2462 void Differ::MatchFunctions() {
2463   IdGroup src_func_ids;
2464   IdGroup dst_func_ids;
2465 
2466   for (const auto& func : src_funcs_) {
2467     src_func_ids.push_back(func.first);
2468   }
2469   for (const auto& func : dst_funcs_) {
2470     dst_func_ids.push_back(func.first);
2471   }
2472 
2473   // Base the matching of functions on debug info when available.
2474   GroupIdsAndMatch<std::string>(
2475       src_func_ids, dst_func_ids, "", &Differ::GetSanitizedName,
2476       [this](const IdGroup& src_group, const IdGroup& dst_group) {
2477         // If there is a single function with this name in src and dst, it's a
2478         // definite match.
2479         if (src_group.size() == 1 && dst_group.size() == 1) {
2480           id_map_.MapIds(src_group[0], dst_group[0]);
2481           return;
2482         }
2483 
2484         // If there are multiple functions with the same name, group them by
2485         // type, and match only if the types match (and are unique).
2486         GroupIdsAndMatch<uint32_t>(src_group, dst_group, 0,
2487                                    &Differ::GroupIdsHelperGetTypeId,
2488                                    [this](const IdGroup& src_group_by_type_id,
2489                                           const IdGroup& dst_group_by_type_id) {
2490                                      if (src_group_by_type_id.size() == 1 &&
2491                                          dst_group_by_type_id.size() == 1) {
2492                                        id_map_.MapIds(src_group_by_type_id[0],
2493                                                       dst_group_by_type_id[0]);
2494                                      }
2495                                    });
2496       });
2497 
2498   // Any functions that are left are pooled together and matched as if unnamed,
2499   // with the only exception that two functions with mismatching names are not
2500   // matched.
2501   //
2502   // Before that however, the diff of the functions that are matched are taken
2503   // and processed, so that more of the global variables can be matched before
2504   // attempting to match the rest of the functions.  They can contribute to the
2505   // precision of the diff of those functions.
2506   for (const uint32_t src_func_id : src_func_ids) {
2507     const uint32_t dst_func_id = id_map_.MappedDstId(src_func_id);
2508     if (dst_func_id == 0) {
2509       continue;
2510     }
2511 
2512     // Since these functions are definite matches, match their parameters for a
2513     // better diff.
2514     MatchFunctionParamIds(src_funcs_[src_func_id], dst_funcs_[dst_func_id]);
2515 
2516     // Take the diff of the two functions.
2517     DiffMatch src_match_result, dst_match_result;
2518     MatchFunctionBodies(src_func_insts_[src_func_id],
2519                         dst_func_insts_[dst_func_id], &src_match_result,
2520                         &dst_match_result);
2521 
2522     // Match ids between the two function bodies; which can also result in
2523     // global variables getting matched.
2524     MatchIdsInFunctionBodies(src_func_insts_[src_func_id],
2525                              dst_func_insts_[dst_func_id], src_match_result,
2526                              dst_match_result, 0);
2527   }
2528 
2529   // Best effort match functions with matching type.
2530   GroupIdsAndMatch<uint32_t>(
2531       src_func_ids, dst_func_ids, 0, &Differ::GroupIdsHelperGetTypeId,
2532       [this](const IdGroup& src_group_by_type_id,
2533              const IdGroup& dst_group_by_type_id) {
2534         BestEffortMatchFunctions(src_group_by_type_id, dst_group_by_type_id,
2535                                  src_func_insts_, dst_func_insts_);
2536       });
2537 
2538   // Any function that's left, best effort match them.
2539   BestEffortMatchFunctions(src_func_ids, dst_func_ids, src_func_insts_,
2540                            dst_func_insts_);
2541 }
2542 
MatchDebugs1()2543 void Differ::MatchDebugs1() {
2544   // This section in cludes: OpString, OpSourceExtension, OpSource,
2545   // OpSourceContinued
2546   MatchDebugAndAnnotationInstructions(src_->debugs1(), dst_->debugs1());
2547 }
2548 
MatchDebugs2()2549 void Differ::MatchDebugs2() {
2550   // This section includes: OpName, OpMemberName
2551   MatchDebugAndAnnotationInstructions(src_->debugs2(), dst_->debugs2());
2552 }
2553 
MatchDebugs3()2554 void Differ::MatchDebugs3() {
2555   // This section includes: OpModuleProcessed
2556   MatchDebugAndAnnotationInstructions(src_->debugs3(), dst_->debugs3());
2557 }
2558 
MatchExtInstDebugInfo()2559 void Differ::MatchExtInstDebugInfo() {
2560   // This section includes OpExtInst for DebugInfo extension
2561   MatchDebugAndAnnotationInstructions(src_->ext_inst_debuginfo(),
2562                                       dst_->ext_inst_debuginfo());
2563 }
2564 
MatchAnnotations()2565 void Differ::MatchAnnotations() {
2566   // This section includes OpDecorate and family.
2567   MatchDebugAndAnnotationInstructions(src_->annotations(), dst_->annotations());
2568 }
2569 
MappedDstInst(const opt::Instruction * src_inst)2570 const opt::Instruction* Differ::MappedDstInst(
2571     const opt::Instruction* src_inst) {
2572   return MappedInstImpl(src_inst, id_map_.SrcToDstMap(), dst_id_to_);
2573 }
2574 
MappedSrcInst(const opt::Instruction * dst_inst)2575 const opt::Instruction* Differ::MappedSrcInst(
2576     const opt::Instruction* dst_inst) {
2577   return MappedInstImpl(dst_inst, id_map_.DstToSrcMap(), src_id_to_);
2578 }
2579 
MappedInstImpl(const opt::Instruction * inst,const IdMap & to_other,const IdInstructions & other_id_to)2580 const opt::Instruction* Differ::MappedInstImpl(
2581     const opt::Instruction* inst, const IdMap& to_other,
2582     const IdInstructions& other_id_to) {
2583   if (inst->HasResultId()) {
2584     if (to_other.IsMapped(inst->result_id())) {
2585       const uint32_t other_result_id = to_other.MappedId(inst->result_id());
2586 
2587       assert(other_result_id < other_id_to.inst_map_.size());
2588       return other_id_to.inst_map_[other_result_id];
2589     }
2590 
2591     return nullptr;
2592   }
2593 
2594   return to_other.MappedInst(inst);
2595 }
2596 
OutputLine(std::function<bool ()> are_lines_identical,std::function<void ()> output_src_line,std::function<void ()> output_dst_line)2597 void Differ::OutputLine(std::function<bool()> are_lines_identical,
2598                         std::function<void()> output_src_line,
2599                         std::function<void()> output_dst_line) {
2600   if (are_lines_identical()) {
2601     out_ << " ";
2602     output_src_line();
2603   } else {
2604     OutputRed();
2605     out_ << "-";
2606     output_src_line();
2607 
2608     OutputGreen();
2609     out_ << "+";
2610     output_dst_line();
2611 
2612     OutputResetColor();
2613   }
2614 }
2615 
IterInst(opt::Module::const_inst_iterator & iter)2616 const opt::Instruction* IterInst(opt::Module::const_inst_iterator& iter) {
2617   return &*iter;
2618 }
2619 
IterInst(InstructionList::const_iterator & iter)2620 const opt::Instruction* IterInst(InstructionList::const_iterator& iter) {
2621   return *iter;
2622 }
2623 
2624 template <typename InstList>
OutputSection(const InstList & src_insts,const InstList & dst_insts,std::function<void (const opt::Instruction &,const IdInstructions &,const opt::Instruction &)> write_inst)2625 void Differ::OutputSection(
2626     const InstList& src_insts, const InstList& dst_insts,
2627     std::function<void(const opt::Instruction&, const IdInstructions&,
2628                        const opt::Instruction&)>
2629         write_inst) {
2630   auto src_iter = src_insts.begin();
2631   auto dst_iter = dst_insts.begin();
2632 
2633   // - While src_inst doesn't have a match, output it with -
2634   // - While dst_inst doesn't have a match, output it with +
2635   // - Now src_inst and dst_inst both have matches; might not match each other!
2636   //   * If section is unordered, just process src_inst and its match (dst_inst
2637   //   or not),
2638   //     dst_inst will eventually be processed when its match is seen.
2639   //   * If section is ordered, also just process src_inst and its match.  Its
2640   //   match must
2641   //     necessarily be dst_inst.
2642   while (src_iter != src_insts.end() || dst_iter != dst_insts.end()) {
2643     OutputRed();
2644     while (src_iter != src_insts.end() &&
2645            MappedDstInst(IterInst(src_iter)) == nullptr) {
2646       out_ << "-";
2647       write_inst(*IterInst(src_iter), src_id_to_, *IterInst(src_iter));
2648       ++src_iter;
2649     }
2650     OutputGreen();
2651     while (dst_iter != dst_insts.end() &&
2652            MappedSrcInst(IterInst(dst_iter)) == nullptr) {
2653       out_ << "+";
2654       write_inst(ToMappedSrcIds(*IterInst(dst_iter)), dst_id_to_,
2655                  *IterInst(dst_iter));
2656       ++dst_iter;
2657     }
2658     OutputResetColor();
2659 
2660     if (src_iter != src_insts.end() && dst_iter != dst_insts.end()) {
2661       const opt::Instruction* src_inst = IterInst(src_iter);
2662       const opt::Instruction* matched_dst_inst = MappedDstInst(src_inst);
2663 
2664       assert(matched_dst_inst != nullptr);
2665       assert(MappedSrcInst(IterInst(dst_iter)) != nullptr);
2666 
2667       OutputLine(
2668           [this, src_inst, matched_dst_inst]() {
2669             return DoInstructionsMatch(src_inst, matched_dst_inst);
2670           },
2671           [this, src_inst, &write_inst]() {
2672             write_inst(*src_inst, src_id_to_, *src_inst);
2673           },
2674           [this, matched_dst_inst, &write_inst]() {
2675             write_inst(ToMappedSrcIds(*matched_dst_inst), dst_id_to_,
2676                        *matched_dst_inst);
2677           });
2678 
2679       ++src_iter;
2680       ++dst_iter;
2681     }
2682   }
2683 }
2684 
ToParsedInstruction(const opt::Instruction & inst,const IdInstructions & id_to,const opt::Instruction & original_inst,spv_parsed_instruction_t * parsed_inst,std::vector<spv_parsed_operand_t> & parsed_operands,std::vector<uint32_t> & inst_binary)2685 void Differ::ToParsedInstruction(
2686     const opt::Instruction& inst, const IdInstructions& id_to,
2687     const opt::Instruction& original_inst,
2688     spv_parsed_instruction_t* parsed_inst,
2689     std::vector<spv_parsed_operand_t>& parsed_operands,
2690     std::vector<uint32_t>& inst_binary) {
2691   inst.ToBinaryWithoutAttachedDebugInsts(&inst_binary);
2692   parsed_operands.resize(inst.NumOperands());
2693 
2694   parsed_inst->words = inst_binary.data();
2695   parsed_inst->num_words = static_cast<uint16_t>(inst_binary.size());
2696   parsed_inst->opcode = static_cast<uint16_t>(inst.opcode());
2697   parsed_inst->ext_inst_type =
2698       inst.opcode() == spv::Op::OpExtInst
2699           ? GetExtInstType(id_to, original_inst.GetSingleWordInOperand(0))
2700           : SPV_EXT_INST_TYPE_NONE;
2701   parsed_inst->type_id =
2702       inst.HasResultType() ? inst.GetSingleWordOperand(0) : 0;
2703   parsed_inst->result_id = inst.HasResultId() ? inst.result_id() : 0;
2704   parsed_inst->operands = parsed_operands.data();
2705   parsed_inst->num_operands = static_cast<uint16_t>(parsed_operands.size());
2706 
2707   // Word 0 is always op and num_words, so operands start at offset 1.
2708   uint32_t offset = 1;
2709   for (uint16_t operand_index = 0; operand_index < parsed_inst->num_operands;
2710        ++operand_index) {
2711     const opt::Operand& operand = inst.GetOperand(operand_index);
2712     spv_parsed_operand_t& parsed_operand = parsed_operands[operand_index];
2713 
2714     parsed_operand.offset = static_cast<uint16_t>(offset);
2715     parsed_operand.num_words = static_cast<uint16_t>(operand.words.size());
2716     parsed_operand.type = operand.type;
2717     parsed_operand.number_kind = GetNumberKind(
2718         id_to, original_inst, operand_index, &parsed_operand.number_bit_width);
2719 
2720     offset += parsed_operand.num_words;
2721   }
2722 }
2723 
ToMappedSrcIds(const opt::Instruction & dst_inst)2724 opt::Instruction Differ::ToMappedSrcIds(const opt::Instruction& dst_inst) {
2725   // Create an identical instruction to dst_inst, except ids are changed to the
2726   // mapped one.
2727   opt::Instruction mapped_inst = dst_inst;
2728 
2729   for (uint32_t operand_index = 0; operand_index < mapped_inst.NumOperands();
2730        ++operand_index) {
2731     opt::Operand& operand = mapped_inst.GetOperand(operand_index);
2732 
2733     if (spvIsIdType(operand.type)) {
2734       assert(id_map_.IsDstMapped(operand.AsId()));
2735       operand.words[0] = id_map_.MappedSrcId(operand.AsId());
2736     }
2737   }
2738 
2739   return mapped_inst;
2740 }
2741 
Output()2742 spv_result_t Differ::Output() {
2743   id_map_.MapUnmatchedIds(
2744       [this](uint32_t src_id) { return src_id_to_.IsDefined(src_id); },
2745       [this](uint32_t dst_id) { return dst_id_to_.IsDefined(dst_id); });
2746   src_id_to_.inst_map_.resize(id_map_.SrcToDstMap().IdBound(), nullptr);
2747   dst_id_to_.inst_map_.resize(id_map_.DstToSrcMap().IdBound(), nullptr);
2748 
2749   const spv_target_env target_env = SPV_ENV_UNIVERSAL_1_6;
2750   spv_opcode_table opcode_table;
2751   spv_operand_table operand_table;
2752   spv_ext_inst_table ext_inst_table;
2753   spv_result_t result;
2754 
2755   result = spvOpcodeTableGet(&opcode_table, target_env);
2756   if (result != SPV_SUCCESS) return result;
2757 
2758   result = spvOperandTableGet(&operand_table, target_env);
2759   if (result != SPV_SUCCESS) return result;
2760 
2761   result = spvExtInstTableGet(&ext_inst_table, target_env);
2762   if (result != SPV_SUCCESS) return result;
2763 
2764   spv_context_t context{
2765       target_env,
2766       opcode_table,
2767       operand_table,
2768       ext_inst_table,
2769   };
2770 
2771   const AssemblyGrammar grammar(&context);
2772   if (!grammar.isValid()) return SPV_ERROR_INVALID_TABLE;
2773 
2774   uint32_t disassembly_options = SPV_BINARY_TO_TEXT_OPTION_PRINT;
2775   if (options_.indent) {
2776     disassembly_options |= SPV_BINARY_TO_TEXT_OPTION_INDENT;
2777   }
2778 
2779   NameMapper name_mapper = GetTrivialNameMapper();
2780   disassemble::InstructionDisassembler dis(grammar, out_, disassembly_options,
2781                                            name_mapper);
2782 
2783   if (!options_.no_header) {
2784     // Output the header
2785     // TODO: when using diff with text, the assembler overrides the version and
2786     // generator, so these aren't reflected correctly in the output.  Could
2787     // potentially extract this info from the header comment.
2788     OutputLine([]() { return true; }, [&dis]() { dis.EmitHeaderSpirv(); },
2789                []() { assert(false && "Unreachable"); });
2790     OutputLine([this]() { return src_->version() == dst_->version(); },
2791                [this, &dis]() { dis.EmitHeaderVersion(src_->version()); },
2792                [this, &dis]() { dis.EmitHeaderVersion(dst_->version()); });
2793     OutputLine([this]() { return src_->generator() == dst_->generator(); },
2794                [this, &dis]() { dis.EmitHeaderGenerator(src_->generator()); },
2795                [this, &dis]() { dis.EmitHeaderGenerator(dst_->generator()); });
2796     OutputLine(
2797         [this]() { return src_->IdBound() == id_map_.SrcToDstMap().IdBound(); },
2798         [this, &dis]() { dis.EmitHeaderIdBound(src_->IdBound()); },
2799         [this, &dis]() {
2800           dis.EmitHeaderIdBound(id_map_.SrcToDstMap().IdBound());
2801         });
2802     OutputLine([this]() { return src_->schema() == dst_->schema(); },
2803                [this, &dis]() { dis.EmitHeaderSchema(src_->schema()); },
2804                [this, &dis]() { dis.EmitHeaderSchema(dst_->schema()); });
2805   }
2806 
2807   // For each section, iterate both modules and output the disassembly.
2808   auto write_inst = [this, &dis](const opt::Instruction& inst,
2809                                  const IdInstructions& id_to,
2810                                  const opt::Instruction& original_inst) {
2811     spv_parsed_instruction_t parsed_inst;
2812     std::vector<spv_parsed_operand_t> parsed_operands;
2813     std::vector<uint32_t> inst_binary;
2814 
2815     ToParsedInstruction(inst, id_to, original_inst, &parsed_inst,
2816                         parsed_operands, inst_binary);
2817 
2818     dis.EmitInstruction(parsed_inst, 0);
2819   };
2820 
2821   OutputSection(src_->capabilities(), dst_->capabilities(), write_inst);
2822   OutputSection(src_->extensions(), dst_->extensions(), write_inst);
2823   OutputSection(src_->ext_inst_imports(), dst_->ext_inst_imports(), write_inst);
2824 
2825   // There is only one memory model.
2826   OutputLine(
2827       [this]() {
2828         return DoInstructionsMatch(src_->GetMemoryModel(),
2829                                    dst_->GetMemoryModel());
2830       },
2831       [this, &write_inst]() {
2832         write_inst(*src_->GetMemoryModel(), src_id_to_,
2833                    *src_->GetMemoryModel());
2834       },
2835       [this, &write_inst]() {
2836         write_inst(*dst_->GetMemoryModel(), dst_id_to_,
2837                    *dst_->GetMemoryModel());
2838       });
2839 
2840   OutputSection(src_->entry_points(), dst_->entry_points(), write_inst);
2841   OutputSection(src_->execution_modes(), dst_->execution_modes(), write_inst);
2842   OutputSection(src_->debugs1(), dst_->debugs1(), write_inst);
2843   OutputSection(src_->debugs2(), dst_->debugs2(), write_inst);
2844   OutputSection(src_->debugs3(), dst_->debugs3(), write_inst);
2845   OutputSection(src_->ext_inst_debuginfo(), dst_->ext_inst_debuginfo(),
2846                 write_inst);
2847   OutputSection(src_->annotations(), dst_->annotations(), write_inst);
2848   OutputSection(src_->types_values(), dst_->types_values(), write_inst);
2849 
2850   // Get the body of all the functions.
2851   FunctionInstMap src_func_header_insts;
2852   FunctionInstMap dst_func_header_insts;
2853 
2854   GetFunctionHeaderInstructions(src_, &src_func_header_insts);
2855   GetFunctionHeaderInstructions(dst_, &dst_func_header_insts);
2856 
2857   for (const auto& src_func : src_func_insts_) {
2858     const uint32_t src_func_id = src_func.first;
2859     const InstructionList& src_insts = src_func.second;
2860     const InstructionList& src_header_insts =
2861         src_func_header_insts[src_func_id];
2862 
2863     const uint32_t dst_func_id = id_map_.MappedDstId(src_func_id);
2864     if (dst_func_insts_.find(dst_func_id) == dst_func_insts_.end()) {
2865       OutputSection(src_header_insts, InstructionList(), write_inst);
2866       OutputSection(src_insts, InstructionList(), write_inst);
2867       continue;
2868     }
2869 
2870     const InstructionList& dst_insts = dst_func_insts_[dst_func_id];
2871     const InstructionList& dst_header_insts =
2872         dst_func_header_insts[dst_func_id];
2873     OutputSection(src_header_insts, dst_header_insts, write_inst);
2874     OutputSection(src_insts, dst_insts, write_inst);
2875   }
2876 
2877   for (const auto& dst_func : dst_func_insts_) {
2878     const uint32_t dst_func_id = dst_func.first;
2879     const InstructionList& dst_insts = dst_func.second;
2880     const InstructionList& dst_header_insts =
2881         dst_func_header_insts[dst_func_id];
2882 
2883     const uint32_t src_func_id = id_map_.MappedSrcId(dst_func_id);
2884     if (src_func_insts_.find(src_func_id) == src_func_insts_.end()) {
2885       OutputSection(InstructionList(), dst_header_insts, write_inst);
2886       OutputSection(InstructionList(), dst_insts, write_inst);
2887     }
2888   }
2889 
2890   out_ << std::flush;
2891 
2892   return SPV_SUCCESS;
2893 }
2894 
2895 }  // anonymous namespace
2896 
Diff(opt::IRContext * src,opt::IRContext * dst,std::ostream & out,Options options)2897 spv_result_t Diff(opt::IRContext* src, opt::IRContext* dst, std::ostream& out,
2898                   Options options) {
2899   // High level algorithm:
2900   //
2901   // - Some sections of SPIR-V don't deal with ids; instructions in those
2902   //   sections are matched identically.  For example OpCapability instructions.
2903   // - Some sections produce ids, and they can be trivially matched by their
2904   //   parameters.  For example OpExtInstImport instructions.
2905   // - Some sections annotate ids.  These are matched at the end, after the ids
2906   //   themselves are matched.  For example OpName or OpDecorate instructions.
2907   // - Some sections produce ids that depend on other ids and they can be
2908   //   recursively matched.  For example OpType* instructions.
2909   // - Some sections produce ids that are not trivially matched.  For these ids,
2910   //   the debug info is used when possible, or a best guess (such as through
2911   //   decorations) is used.  For example OpVariable instructions.
2912   // - Matching functions is done with multiple attempts:
2913   //   * Functions with identical debug names are matched if there are no
2914   //     overloads.
2915   //   * Otherwise, functions with identical debug names and types are matched.
2916   //   * The rest of the functions are best-effort matched, first in groups of
2917   //     identical type, then any with any.
2918   //     * The best-effort matching takes the diff of every pair of functions in
2919   //       a group and selects the top matches that also meet a similarity
2920   //       index.
2921   //   * Once a pair of functions are matched, the fuzzy diff of the
2922   //     instructions is used to match the instructions in the function body.
2923   //     The fuzzy diff makes sure that sufficiently similar instructions are
2924   //     matched and that yet-to-be-matched result ids don't result in a larger
2925   //     diff.
2926   //
2927   // Once the instructions are matched between the src and dst SPIR-V, the src
2928   // is traversed and its disassembly is output.  In the process, any unmatched
2929   // instruction is prefixed with -, and any unmatched instruction in dst in the
2930   // same section is output prefixed with +.  To avoid confusion, the
2931   // instructions in dst are output with matching ids in src so the output
2932   // assembly is consistent.
2933 
2934   Differ differ(src, dst, out, options);
2935 
2936   // First, match instructions between the different non-annotation sections of
2937   // the SPIR-V.
2938   differ.MatchCapabilities();
2939   differ.MatchExtensions();
2940   differ.MatchExtInstImportIds();
2941   differ.MatchMemoryModel();
2942   differ.MatchEntryPointIds();
2943   differ.MatchExecutionModes();
2944   differ.MatchTypeForwardPointers();
2945   differ.MatchTypeIds();
2946   differ.MatchConstants();
2947   differ.MatchVariableIds();
2948   differ.MatchFunctions();
2949 
2950   // Match instructions that annotate previously-matched ids.
2951   differ.MatchDebugs1();
2952   differ.MatchDebugs2();
2953   differ.MatchDebugs3();
2954   differ.MatchExtInstDebugInfo();
2955   differ.MatchAnnotations();
2956 
2957   // Show the disassembly with the diff.
2958   //
2959   // TODO: Based on an option, output either based on src or dst, i.e. the diff
2960   // can show the ids and instruction/function order either from src or dst.
2961   spv_result_t result = differ.Output();
2962 
2963   differ.DumpIdMap();
2964 
2965   return result;
2966 }
2967 
2968 }  // namespace diff
2969 }  // namespace spvtools
2970