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