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