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