blob: 4a15ef70fbb732a150c50c1f8033734b0a8850c4 [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 =
711 GetExecutionModel(src_inst_module, a->GetOperand(0).AsId());
712 const SpvExecutionModel dst_model =
713 GetExecutionModel(dst_inst_module, b->GetOperand(0).AsId());
714
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 =
1352 SpvStorageClass(src_inst->GetInOperand(0).words[0]);
1353 SpvStorageClass dst_storage_class =
1354 SpvStorageClass(dst_inst->GetInOperand(0).words[0]);
1355
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:
1606 const uint32_t src_pointer_id = src_inst->GetInOperand(0).AsId();
1607 const uint32_t dst_pointer_id = dst_inst->GetInOperand(0).AsId();
1608 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
1636 return constant_inst->GetInOperand(0).words[0];
1637}
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);
1643 if (inst.GetOperand(1).AsId() == entry_point_id) {
1644 return SpvExecutionModel(inst.GetOperand(0).words[0]);
1645 }
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
1685 *storage_class = SpvStorageClass(var_inst->GetInOperand(0).words[0]);
1686
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.
1692 return type_pointer_inst->GetInOperand(1).AsId();
1693}
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]) {
1702 if (inst->opcode() == SpvOpDecorate && inst->GetOperand(0).AsId() == id &&
1703 inst->GetOperand(1).words[0] == decoration) {
1704 *decoration_value = inst->GetOperand(2).words[0];
1705 return true;
1706 }
1707 }
1708
1709 return false;
1710}
1711
1712bool Differ::IsIntType(const IdInstructions& id_to, uint32_t type_id) {
1713 return IsOp(id_to, type_id, SpvOpTypeInt);
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05001714}
1715
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05001716bool Differ::IsFloatType(const IdInstructions& id_to, uint32_t type_id) {
1717 return IsOp(id_to, type_id, SpvOpTypeFloat);
1718}
1719
1720bool Differ::IsConstantUint(const IdInstructions& id_to, uint32_t id) {
1721 const opt::Instruction* constant_inst = GetInst(id_to, id);
1722 if (constant_inst->opcode() != SpvOpConstant) {
1723 return false;
1724 }
1725
1726 const opt::Instruction* type_inst = GetInst(id_to, constant_inst->type_id());
1727 return type_inst->opcode() == SpvOpTypeInt;
1728}
1729
1730bool Differ::IsVariable(const IdInstructions& id_to, uint32_t pointer_id) {
1731 return IsOp(id_to, pointer_id, SpvOpVariable);
1732}
1733
1734bool Differ::IsOp(const IdInstructions& id_to, uint32_t id, SpvOp op) {
1735 return GetInst(id_to, id)->opcode() == op;
1736}
1737
1738bool Differ::IsPerVertexType(const IdInstructions& id_to, uint32_t type_id) {
1739 assert(type_id != 0);
1740 assert(type_id < id_to.decoration_map_.size());
1741
1742 for (const opt::Instruction* inst : id_to.decoration_map_[type_id]) {
1743 if (inst->opcode() == SpvOpMemberDecorate &&
1744 inst->GetOperand(0).AsId() == type_id &&
1745 inst->GetOperand(2).words[0] == SpvDecorationBuiltIn) {
1746 SpvBuiltIn built_in = SpvBuiltIn(inst->GetOperand(3).words[0]);
1747
1748 // Only gl_PerVertex can have, and it can only have, the following
1749 // built-in decorations.
1750 return built_in == SpvBuiltInPosition ||
1751 built_in == SpvBuiltInPointSize ||
1752 built_in == SpvBuiltInClipDistance ||
1753 built_in == SpvBuiltInCullDistance;
1754 }
1755 }
1756
1757 return false;
1758}
1759
1760bool Differ::IsPerVertexVariable(const IdInstructions& id_to, uint32_t var_id) {
1761 // Get the type from the type pointer.
1762 SpvStorageClass storage_class;
1763 uint32_t type_id = GetVarTypeId(id_to, var_id, &storage_class);
1764 const opt::Instruction* type_inst = GetInst(id_to, type_id);
1765
1766 // If array, get the element type.
1767 if (type_inst->opcode() == SpvOpTypeArray) {
1768 type_id = type_inst->GetInOperand(0).AsId();
1769 }
1770
1771 // Now check if the type is gl_PerVertex.
1772 return IsPerVertexType(id_to, type_id);
1773}
1774
1775SpvStorageClass Differ::GetPerVertexStorageClass(const opt::Module* module,
1776 uint32_t type_id) {
1777 for (const opt::Instruction& inst : module->types_values()) {
1778 switch (inst.opcode()) {
1779 case SpvOpTypeArray:
1780 // The gl_PerVertex instance could be an array, look for a variable of
1781 // the array type instead.
1782 if (inst.GetInOperand(0).AsId() == type_id) {
1783 type_id = inst.result_id();
1784 }
1785 break;
1786 case SpvOpTypePointer:
1787 // Find the storage class of the pointer to this type.
1788 if (inst.GetInOperand(1).AsId() == type_id) {
1789 return SpvStorageClass(inst.GetInOperand(0).words[0]);
1790 }
1791 break;
1792 default:
1793 break;
1794 }
1795 }
1796
1797 // gl_PerVertex is declared, but is unused. Return either of Input or Output
1798 // classes just so it matches one in the other module. This should be highly
1799 // unlikely, perhaps except for ancient GS-used-to-emulate-CS scenarios.
1800 return SpvStorageClassOutput;
1801}
1802
1803spv_ext_inst_type_t Differ::GetExtInstType(const IdInstructions& id_to,
1804 uint32_t set_id) {
1805 const opt::Instruction* set_inst = GetInst(id_to, set_id);
1806 return spvExtInstImportTypeGet(set_inst->GetInOperand(0).AsString().c_str());
1807}
1808
1809spv_number_kind_t Differ::GetNumberKind(const IdInstructions& id_to,
1810 const opt::Instruction& inst,
1811 uint32_t operand_index,
1812 uint32_t* number_bit_width) {
1813 const opt::Operand& operand = inst.GetOperand(operand_index);
1814 *number_bit_width = 0;
1815
1816 // A very limited version of Parser::parseOperand.
1817 switch (operand.type) {
1818 case SPV_OPERAND_TYPE_LITERAL_INTEGER:
1819 case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER:
1820 // Always unsigned integers.
1821 *number_bit_width = 32;
1822 return SPV_NUMBER_UNSIGNED_INT;
1823 case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER:
1824 case SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER:
1825 switch (inst.opcode()) {
1826 case SpvOpSwitch:
1827 case SpvOpConstant:
1828 case SpvOpSpecConstant:
1829 // Same kind of number as the selector (OpSwitch) or the type
1830 // (Op*Constant).
1831 return GetTypeNumberKind(id_to, inst.GetOperand(0).AsId(),
1832 number_bit_width);
1833 default:
1834 assert(false && "Unreachable");
1835 break;
1836 }
1837 break;
1838 default:
1839 break;
1840 }
1841
1842 return SPV_NUMBER_NONE;
1843}
1844
1845spv_number_kind_t Differ::GetTypeNumberKind(const IdInstructions& id_to,
1846 uint32_t id,
1847 uint32_t* number_bit_width) {
1848 const opt::Instruction* type_inst = GetInst(id_to, id);
1849 if (!spvOpcodeIsScalarType(type_inst->opcode())) {
1850 type_inst = GetInst(id_to, type_inst->type_id());
1851 }
1852
1853 switch (type_inst->opcode()) {
1854 case SpvOpTypeInt:
1855 *number_bit_width = type_inst->GetOperand(1).words[0];
1856 return type_inst->GetOperand(2).words[0] == 0 ? SPV_NUMBER_UNSIGNED_INT
1857 : SPV_NUMBER_SIGNED_INT;
1858 break;
1859 case SpvOpTypeFloat:
1860 *number_bit_width = type_inst->GetOperand(1).words[0];
1861 return SPV_NUMBER_FLOATING;
1862 default:
1863 assert(false && "Unreachable");
1864 return SPV_NUMBER_NONE;
1865 }
1866}
1867
1868void Differ::MatchCapabilities() {
1869 MatchPreambleInstructions(src_->capabilities(), dst_->capabilities());
1870}
1871
1872void Differ::MatchExtensions() {
1873 MatchPreambleInstructions(src_->extensions(), dst_->extensions());
1874}
1875
1876void Differ::MatchExtInstImportIds() {
1877 // Bunch all of this section's ids as potential matches.
1878 PotentialIdMap potential_id_map;
1879 auto get_result_id = [](const opt::Instruction& inst) {
1880 return inst.result_id();
1881 };
1882 auto accept_all = [](const opt::Instruction&) { return true; };
1883
Shahbaz Youssefi48c83632022-03-24 14:04:48 -04001884 PoolPotentialIds(src_->ext_inst_imports(), potential_id_map.src_ids, true,
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05001885 accept_all, get_result_id);
Shahbaz Youssefi48c83632022-03-24 14:04:48 -04001886 PoolPotentialIds(dst_->ext_inst_imports(), potential_id_map.dst_ids, false,
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05001887 accept_all, get_result_id);
1888
1889 // Then match the ids.
1890 MatchIds(potential_id_map, [](const opt::Instruction* src_inst,
1891 const opt::Instruction* dst_inst) {
1892 // Match OpExtInstImport by exact name, which is operand 1
1893 const opt::Operand& src_name = src_inst->GetOperand(1);
1894 const opt::Operand& dst_name = dst_inst->GetOperand(1);
1895
1896 return src_name.AsString() == dst_name.AsString();
1897 });
1898}
1899void Differ::MatchMemoryModel() {
1900 // Always match the memory model instructions, there is always a single one of
1901 // it.
1902 id_map_.MapInsts(src_->GetMemoryModel(), dst_->GetMemoryModel());
1903}
1904
1905void Differ::MatchEntryPointIds() {
1906 // Match OpEntryPoint ids (at index 1) by ExecutionModel (at index 0) and
1907 // possibly name (at index 2). OpEntryPoint doesn't produce a result id, so
1908 // this function doesn't use the helpers the other functions use.
1909
1910 // Map from execution model to OpEntryPoint instructions of that model.
1911 using ExecutionModelMap =
1912 std::unordered_map<uint32_t, std::vector<const opt::Instruction*>>;
1913 ExecutionModelMap src_entry_points_map;
1914 ExecutionModelMap dst_entry_points_map;
1915 std::set<uint32_t> all_execution_models;
1916
1917 for (const opt::Instruction& src_inst : src_->entry_points()) {
1918 uint32_t execution_model = src_inst.GetOperand(0).words[0];
1919 src_entry_points_map[execution_model].push_back(&src_inst);
1920 all_execution_models.insert(execution_model);
1921 }
1922 for (const opt::Instruction& dst_inst : dst_->entry_points()) {
1923 uint32_t execution_model = dst_inst.GetOperand(0).words[0];
1924 dst_entry_points_map[execution_model].push_back(&dst_inst);
1925 all_execution_models.insert(execution_model);
1926 }
1927
1928 // Go through each model and match the ids.
1929 for (const uint32_t execution_model : all_execution_models) {
1930 auto& src_insts = src_entry_points_map[execution_model];
1931 auto& dst_insts = dst_entry_points_map[execution_model];
1932
1933 // If there is only one entry point in src and dst with that model, match
1934 // them unconditionally.
1935 if (src_insts.size() == 1 && dst_insts.size() == 1) {
1936 uint32_t src_id = src_insts[0]->GetOperand(1).AsId();
1937 uint32_t dst_id = dst_insts[0]->GetOperand(1).AsId();
1938 id_map_.MapIds(src_id, dst_id);
1939 id_map_.MapInsts(src_insts[0], dst_insts[0]);
1940 continue;
1941 }
1942
1943 // Otherwise match them by name.
1944 bool matched = false;
1945 for (const opt::Instruction* src_inst : src_insts) {
1946 for (const opt::Instruction* dst_inst : dst_insts) {
1947 const opt::Operand& src_name = src_inst->GetOperand(2);
1948 const opt::Operand& dst_name = dst_inst->GetOperand(2);
1949
1950 if (src_name.AsString() == dst_name.AsString()) {
1951 uint32_t src_id = src_inst->GetOperand(1).AsId();
1952 uint32_t dst_id = dst_inst->GetOperand(1).AsId();
1953 id_map_.MapIds(src_id, dst_id);
1954 id_map_.MapInsts(src_inst, dst_inst);
1955 matched = true;
1956 break;
1957 }
1958 }
1959 if (matched) {
1960 break;
1961 }
1962 }
1963 }
1964}
1965
1966void Differ::MatchExecutionModes() {
1967 MatchPreambleInstructions(src_->execution_modes(), dst_->execution_modes());
1968}
1969
1970void Differ::MatchTypeIds() {
1971 // Bunch all of type ids as potential matches.
1972 PotentialIdMap potential_id_map;
1973 auto get_result_id = [](const opt::Instruction& inst) {
1974 return inst.result_id();
1975 };
1976 auto accept_type_ops = [](const opt::Instruction& inst) {
1977 return spvOpcodeGeneratesType(inst.opcode());
1978 };
1979
Shahbaz Youssefi48c83632022-03-24 14:04:48 -04001980 PoolPotentialIds(src_->types_values(), potential_id_map.src_ids, true,
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05001981 accept_type_ops, get_result_id);
Shahbaz Youssefi48c83632022-03-24 14:04:48 -04001982 PoolPotentialIds(dst_->types_values(), potential_id_map.dst_ids, false,
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05001983 accept_type_ops, get_result_id);
1984
1985 // Then match the ids. Start with exact matches, then match the leftover with
1986 // gradually loosening degrees of strictness. For example, in the absence of
1987 // debug info, two block types will be matched if they differ only in a few of
1988 // the fields.
1989 for (uint32_t flexibility = 0; flexibility < 2; ++flexibility) {
1990 MatchIds(potential_id_map, [this, flexibility](
1991 const opt::Instruction* src_inst,
1992 const opt::Instruction* dst_inst) {
1993 const SpvOp src_op = src_inst->opcode();
1994 const SpvOp dst_op = dst_inst->opcode();
1995
1996 // Don't match if the opcode is not the same.
1997 if (src_op != dst_op) {
1998 return false;
1999 }
2000
2001 switch (src_op) {
2002 case SpvOpTypeVoid:
2003 case SpvOpTypeBool:
2004 case SpvOpTypeSampler:
2005 // void, bool and sampler are unique, match them.
2006 return true;
2007 case SpvOpTypeInt:
2008 case SpvOpTypeFloat:
2009 case SpvOpTypeVector:
2010 case SpvOpTypeMatrix:
2011 case SpvOpTypeSampledImage:
2012 case SpvOpTypeRuntimeArray:
2013 case SpvOpTypePointer:
2014 case SpvOpTypeFunction:
2015 // Match these instructions when all operands match.
2016 assert(src_inst->NumInOperandWords() ==
2017 dst_inst->NumInOperandWords());
2018 return DoOperandsMatch(src_inst, dst_inst, 0,
2019 src_inst->NumInOperandWords());
2020
2021 case SpvOpTypeImage:
2022 // Match these instructions when all operands match, including the
2023 // optional final parameter (if provided in both).
2024 if (src_inst->NumInOperandWords() != dst_inst->NumInOperandWords()) {
2025 return false;
2026 }
2027 return DoOperandsMatch(src_inst, dst_inst, 0,
2028 src_inst->NumInOperandWords());
2029
2030 case SpvOpTypeArray:
2031 // Match arrays only if the element type and length match. The length
2032 // is an id of a constant, so the actual constant it's defining is
2033 // compared instead.
2034 if (!DoOperandsMatch(src_inst, dst_inst, 0, 1)) {
2035 return false;
2036 }
2037
Shahbaz Youssefi332475d2022-02-09 09:06:46 -05002038 if (AreIdenticalUintConstants(src_inst->GetInOperand(1).AsId(),
2039 dst_inst->GetInOperand(1).AsId())) {
2040 return true;
2041 }
2042
2043 // If size is not OpConstant, expect the ids to match exactly (for
2044 // example if a spec contant is used).
2045 return DoOperandsMatch(src_inst, dst_inst, 1, 1);
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05002046
2047 case SpvOpTypeStruct:
2048 return MatchOpTypeStruct(src_inst, dst_inst, flexibility);
2049
2050 default:
2051 return false;
2052 }
2053 });
2054 }
2055}
2056
2057void Differ::MatchConstants() {
2058 // Bunch all of constant ids as potential matches.
2059 PotentialIdMap potential_id_map;
2060 auto get_result_id = [](const opt::Instruction& inst) {
2061 return inst.result_id();
2062 };
2063 auto accept_type_ops = [](const opt::Instruction& inst) {
2064 return spvOpcodeIsConstant(inst.opcode());
2065 };
2066
Shahbaz Youssefi48c83632022-03-24 14:04:48 -04002067 PoolPotentialIds(src_->types_values(), potential_id_map.src_ids, true,
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05002068 accept_type_ops, get_result_id);
Shahbaz Youssefi48c83632022-03-24 14:04:48 -04002069 PoolPotentialIds(dst_->types_values(), potential_id_map.dst_ids, false,
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05002070 accept_type_ops, get_result_id);
2071
2072 // Then match the ids. Constants are matched exactly, except for float types
2073 // that are first matched exactly, then leftovers are matched with a small
2074 // error.
2075 for (uint32_t flexibility = 0; flexibility < 2; ++flexibility) {
2076 MatchIds(potential_id_map, [this, flexibility](
2077 const opt::Instruction* src_inst,
2078 const opt::Instruction* dst_inst) {
2079 const SpvOp src_op = src_inst->opcode();
2080 const SpvOp dst_op = dst_inst->opcode();
2081
2082 // Don't match if the opcode is not the same.
2083 if (src_op != dst_op) {
2084 return false;
2085 }
2086
2087 switch (src_op) {
2088 case SpvOpConstantTrue:
2089 case SpvOpConstantFalse:
2090 // true and false are unique, match them.
2091 return true;
2092 case SpvOpConstant:
2093 return MatchOpConstant(src_inst, dst_inst, flexibility);
2094 case SpvOpConstantComposite:
Shahbaz Youssefi0ad83f92022-02-11 10:29:42 -05002095 case SpvOpSpecConstantComposite:
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05002096 // Composite constants must match in type and value.
2097 //
2098 // TODO: match OpConstantNull with OpConstantComposite with all zeros
2099 // at flexibility == 1
2100 // TODO: match constants from structs that have been flexibly-matched.
2101 if (src_inst->NumInOperandWords() != dst_inst->NumInOperandWords()) {
2102 return false;
2103 }
2104 return DoesOperandMatch(src_inst->GetOperand(0),
2105 dst_inst->GetOperand(0)) &&
2106 DoOperandsMatch(src_inst, dst_inst, 0,
2107 src_inst->NumInOperandWords());
2108 case SpvOpConstantSampler:
2109 // Match sampler constants exactly.
2110 // TODO: Allow flexibility in parameters to better diff shaders where
2111 // the sampler param has changed.
2112 assert(src_inst->NumInOperandWords() ==
2113 dst_inst->NumInOperandWords());
2114 return DoOperandsMatch(src_inst, dst_inst, 0,
2115 src_inst->NumInOperandWords());
2116 case SpvOpConstantNull:
2117 // Match null constants as long as the type matches.
2118 return DoesOperandMatch(src_inst->GetOperand(0),
2119 dst_inst->GetOperand(0));
2120
2121 case SpvOpSpecConstantTrue:
2122 case SpvOpSpecConstantFalse:
2123 case SpvOpSpecConstant:
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05002124 case SpvOpSpecConstantOp:
2125 // Match spec constants by name if available, then by the SpecId
2126 // decoration.
2127 return MatchOpSpecConstant(src_inst, dst_inst);
2128
2129 default:
2130 return false;
2131 }
2132 });
2133 }
2134}
2135
2136void Differ::MatchVariableIds() {
2137 // Bunch all of variable ids as potential matches.
2138 PotentialIdMap potential_id_map;
2139 auto get_result_id = [](const opt::Instruction& inst) {
2140 return inst.result_id();
2141 };
2142 auto accept_type_ops = [](const opt::Instruction& inst) {
2143 return inst.opcode() == SpvOpVariable;
2144 };
2145
Shahbaz Youssefi48c83632022-03-24 14:04:48 -04002146 PoolPotentialIds(src_->types_values(), potential_id_map.src_ids, true,
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05002147 accept_type_ops, get_result_id);
Shahbaz Youssefi48c83632022-03-24 14:04:48 -04002148 PoolPotentialIds(dst_->types_values(), potential_id_map.dst_ids, false,
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05002149 accept_type_ops, get_result_id);
2150
2151 // Then match the ids. Start with exact matches, then match the leftover with
2152 // gradually loosening degrees of strictness. For example, in the absence of
2153 // debug info, two otherwise identical variables will be matched if one of
2154 // them has a Private storage class and the other doesn't.
2155 for (uint32_t flexibility = 0; flexibility < 2; ++flexibility) {
2156 MatchIds(potential_id_map,
2157 [this, flexibility](const opt::Instruction* src_inst,
2158 const opt::Instruction* dst_inst) {
2159 assert(src_inst->opcode() == SpvOpVariable);
2160 assert(dst_inst->opcode() == SpvOpVariable);
2161
2162 return MatchOpVariable(src_inst, dst_inst, flexibility);
2163 });
2164 }
2165}
2166
2167void Differ::MatchFunctions() {
2168 IdGroup src_func_ids;
2169 IdGroup dst_func_ids;
2170
2171 for (const auto& func : src_funcs_) {
2172 src_func_ids.push_back(func.first);
2173 }
2174 for (const auto& func : dst_funcs_) {
2175 dst_func_ids.push_back(func.first);
2176 }
2177
2178 // Base the matching of functions on debug info when available.
Shahbaz Youssefi48c83632022-03-24 14:04:48 -04002179 GroupIdsAndMatch<std::string>(
2180 src_func_ids, dst_func_ids, "", &Differ::GetSanitizedName,
2181 [this](const IdGroup& src_group, const IdGroup& dst_group) {
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05002182
Shahbaz Youssefi48c83632022-03-24 14:04:48 -04002183 // If there is a single function with this name in src and dst, it's a
2184 // definite match.
2185 if (src_group.size() == 1 && dst_group.size() == 1) {
2186 id_map_.MapIds(src_group[0], dst_group[0]);
2187 return;
2188 }
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05002189
Shahbaz Youssefi48c83632022-03-24 14:04:48 -04002190 // If there are multiple functions with the same name, group them by
2191 // type, and match only if the types match (and are unique).
2192 GroupIdsAndMatch<uint32_t>(src_group, dst_group, 0,
2193 &Differ::GroupIdsHelperGetTypeId,
2194 [this](const IdGroup& src_group_by_type_id,
2195 const IdGroup& dst_group_by_type_id) {
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05002196
Shahbaz Youssefi48c83632022-03-24 14:04:48 -04002197 if (src_group_by_type_id.size() == 1 &&
2198 dst_group_by_type_id.size() == 1) {
2199 id_map_.MapIds(src_group_by_type_id[0],
2200 dst_group_by_type_id[0]);
2201 }
2202 });
2203 });
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05002204
2205 // Any functions that are left are pooled together and matched as if unnamed,
2206 // with the only exception that two functions with mismatching names are not
2207 // matched.
2208 //
2209 // Before that however, the diff of the functions that are matched are taken
2210 // and processed, so that more of the global variables can be matched before
2211 // attempting to match the rest of the functions. They can contribute to the
2212 // precision of the diff of those functions.
2213 for (const uint32_t src_func_id : src_func_ids) {
2214 const uint32_t dst_func_id = id_map_.MappedDstId(src_func_id);
2215 if (dst_func_id == 0) {
2216 continue;
2217 }
2218
2219 // Since these functions are definite matches, match their parameters for a
2220 // better diff.
2221 MatchFunctionParamIds(src_funcs_[src_func_id], dst_funcs_[dst_func_id]);
2222
2223 // Take the diff of the two functions.
2224 DiffMatch src_match_result, dst_match_result;
2225 MatchFunctionBodies(src_func_insts_[src_func_id],
2226 dst_func_insts_[dst_func_id], &src_match_result,
2227 &dst_match_result);
2228
2229 // Match ids between the two function bodies; which can also result in
2230 // global variables getting matched.
2231 MatchIdsInFunctionBodies(src_func_insts_[src_func_id],
2232 dst_func_insts_[dst_func_id], src_match_result,
2233 dst_match_result, 0);
2234 }
2235
2236 // Best effort match functions with matching type.
Shahbaz Youssefi48c83632022-03-24 14:04:48 -04002237 GroupIdsAndMatch<uint32_t>(
2238 src_func_ids, dst_func_ids, 0, &Differ::GroupIdsHelperGetTypeId,
2239 [this](const IdGroup& src_group_by_type_id,
2240 const IdGroup& dst_group_by_type_id) {
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05002241
Shahbaz Youssefi48c83632022-03-24 14:04:48 -04002242 BestEffortMatchFunctions(src_group_by_type_id, dst_group_by_type_id,
2243 src_func_insts_, dst_func_insts_);
2244 });
Shahbaz Youssefi7fa9e742022-02-02 10:33:18 -05002245
2246 // Any function that's left, best effort match them.
2247 BestEffortMatchFunctions(src_func_ids, dst_func_ids, src_func_insts_,
2248 dst_func_insts_);
2249}
2250
2251void Differ::MatchDebugs1() {
2252 // This section in cludes: OpString, OpSourceExtension, OpSource,
2253 // OpSourceContinued
2254 MatchDebugAndAnnotationInstructions(src_->debugs1(), dst_->debugs1());
2255}
2256
2257void Differ::MatchDebugs2() {
2258 // This section includes: OpName, OpMemberName
2259 MatchDebugAndAnnotationInstructions(src_->debugs2(), dst_->debugs2());
2260}
2261
2262void Differ::MatchDebugs3() {
2263 // This section includes: OpModuleProcessed
2264 MatchDebugAndAnnotationInstructions(src_->debugs3(), dst_->debugs3());
2265}
2266
2267void Differ::MatchExtInstDebugInfo() {
2268 // This section includes OpExtInst for DebugInfo extension
2269 MatchDebugAndAnnotationInstructions(src_->ext_inst_debuginfo(),
2270 dst_->ext_inst_debuginfo());
2271}
2272
2273void Differ::MatchAnnotations() {
2274 // This section includes OpDecorate and family.
2275 MatchDebugAndAnnotationInstructions(src_->annotations(), dst_->annotations());
2276}
2277
2278const opt::Instruction* Differ::MappedDstInst(
2279 const opt::Instruction* src_inst) {
2280 return MappedInstImpl(src_inst, id_map_.SrcToDstMap(), dst_id_to_);
2281}
2282
2283const opt::Instruction* Differ::MappedSrcInst(
2284 const opt::Instruction* dst_inst) {
2285 return MappedInstImpl(dst_inst, id_map_.DstToSrcMap(), src_id_to_);
2286}
2287
2288const opt::Instruction* Differ::MappedInstImpl(
2289 const opt::Instruction* inst, const IdMap& to_other,
2290 const IdInstructions& other_id_to) {
2291 if (inst->HasResultId()) {
2292 if (to_other.IsMapped(inst->result_id())) {
2293 const uint32_t other_result_id = to_other.MappedId(inst->result_id());
2294
2295 assert(other_result_id < other_id_to.inst_map_.size());
2296 return other_id_to.inst_map_[other_result_id];
2297 }
2298
2299 return nullptr;
2300 }
2301
2302 return to_other.MappedInst(inst);
2303}
2304
2305void Differ::OutputLine(std::function<bool()> are_lines_identical,
2306 std::function<void()> output_src_line,
2307 std::function<void()> output_dst_line) {
2308 if (are_lines_identical()) {
2309 out_ << " ";
2310 output_src_line();
2311 } else {
2312 OutputRed();
2313 out_ << "-";
2314 output_src_line();
2315
2316 OutputGreen();
2317 out_ << "+";
2318 output_dst_line();
2319
2320 OutputResetColor();
2321 }
2322}
2323
2324const opt::Instruction* IterInst(opt::Module::const_inst_iterator& iter) {
2325 return &*iter;
2326}
2327
2328const opt::Instruction* IterInst(InstructionList::const_iterator& iter) {
2329 return *iter;
2330}
2331
2332template <typename InstList>
2333void Differ::OutputSection(
2334 const InstList& src_insts, const InstList& dst_insts,
2335 std::function<void(const opt::Instruction&, const IdInstructions&,
2336 const opt::Instruction&)>
2337 write_inst) {
2338 auto src_iter = src_insts.begin();
2339 auto dst_iter = dst_insts.begin();
2340
2341 // - While src_inst doesn't have a match, output it with -
2342 // - While dst_inst doesn't have a match, output it with +
2343 // - Now src_inst and dst_inst both have matches; might not match each other!
2344 // * If section is unordered, just process src_inst and its match (dst_inst
2345 // or not),
2346 // dst_inst will eventually be processed when its match is seen.
2347 // * If section is ordered, also just process src_inst and its match. Its
2348 // match must
2349 // necessarily be dst_inst.
2350 while (src_iter != src_insts.end() || dst_iter != dst_insts.end()) {
2351 OutputRed();
2352 while (src_iter != src_insts.end() &&
2353 MappedDstInst(IterInst(src_iter)) == nullptr) {
2354 out_ << "-";
2355 write_inst(*IterInst(src_iter), src_id_to_, *IterInst(src_iter));
2356 ++src_iter;
2357 }
2358 OutputGreen();
2359 while (dst_iter != dst_insts.end() &&
2360 MappedSrcInst(IterInst(dst_iter)) == nullptr) {
2361 out_ << "+";
2362 write_inst(ToMappedSrcIds(*IterInst(dst_iter)), dst_id_to_,
2363 *IterInst(dst_iter));
2364 ++dst_iter;
2365 }
2366 OutputResetColor();
2367
2368 if (src_iter != src_insts.end() && dst_iter != dst_insts.end()) {
2369 const opt::Instruction* src_inst = IterInst(src_iter);
2370 const opt::Instruction* matched_dst_inst = MappedDstInst(src_inst);
2371
2372 assert(matched_dst_inst != nullptr);
2373 assert(MappedSrcInst(IterInst(dst_iter)) != nullptr);
2374
2375 OutputLine(
2376 [this, src_inst, matched_dst_inst]() {
2377 return DoInstructionsMatch(src_inst, matched_dst_inst);
2378 },
2379 [this, src_inst, &write_inst]() {
2380 write_inst(*src_inst, src_id_to_, *src_inst);
2381 },
2382 [this, matched_dst_inst, &write_inst]() {
2383 write_inst(ToMappedSrcIds(*matched_dst_inst), dst_id_to_,
2384 *matched_dst_inst);
2385 });
2386
2387 ++src_iter;
2388 ++dst_iter;
2389 }
2390 }
2391}
2392
2393void Differ::ToParsedInstruction(
2394 const opt::Instruction& inst, const IdInstructions& id_to,
2395 const opt::Instruction& original_inst,
2396 spv_parsed_instruction_t* parsed_inst,
2397 std::vector<spv_parsed_operand_t>& parsed_operands,
2398 std::vector<uint32_t>& inst_binary) {
2399 inst.ToBinaryWithoutAttachedDebugInsts(&inst_binary);
2400 parsed_operands.resize(inst.NumOperands());
2401
2402 parsed_inst->words = inst_binary.data();
2403 parsed_inst->num_words = static_cast<uint16_t>(inst_binary.size());
2404 parsed_inst->opcode = static_cast<uint16_t>(inst.opcode());
2405 parsed_inst->ext_inst_type =
2406 inst.opcode() == SpvOpExtInst
2407 ? GetExtInstType(id_to, original_inst.GetInOperand(0).AsId())
2408 : SPV_EXT_INST_TYPE_NONE;
2409 parsed_inst->type_id = inst.HasResultType() ? inst.GetOperand(0).AsId() : 0;
2410 parsed_inst->result_id = inst.HasResultId() ? inst.result_id() : 0;
2411 parsed_inst->operands = parsed_operands.data();
2412 parsed_inst->num_operands = static_cast<uint16_t>(parsed_operands.size());
2413
2414 // Word 0 is always op and num_words, so operands start at offset 1.
2415 uint32_t offset = 1;
2416 for (uint16_t operand_index = 0; operand_index < parsed_inst->num_operands;
2417 ++operand_index) {
2418 const opt::Operand& operand = inst.GetOperand(operand_index);
2419 spv_parsed_operand_t& parsed_operand = parsed_operands[operand_index];
2420
2421 parsed_operand.offset = static_cast<uint16_t>(offset);
2422 parsed_operand.num_words = static_cast<uint16_t>(operand.words.size());
2423 parsed_operand.type = operand.type;
2424 parsed_operand.number_kind = GetNumberKind(
2425 id_to, original_inst, operand_index, &parsed_operand.number_bit_width);
2426
2427 offset += parsed_operand.num_words;
2428 }
2429}
2430
2431opt::Instruction Differ::ToMappedSrcIds(const opt::Instruction& dst_inst) {
2432 // Create an identical instruction to dst_inst, except ids are changed to the
2433 // mapped one.
2434 opt::Instruction mapped_inst = dst_inst;
2435
2436 for (uint32_t operand_index = 0; operand_index < mapped_inst.NumOperands();
2437 ++operand_index) {
2438 opt::Operand& operand = mapped_inst.GetOperand(operand_index);
2439
2440 if (spvIsIdType(operand.type)) {
2441 assert(id_map_.IsDstMapped(operand.AsId()));
2442 operand.words[0] = id_map_.MappedSrcId(operand.AsId());
2443 }
2444 }
2445
2446 return mapped_inst;
2447}
2448
2449spv_result_t Differ::Output() {
2450 id_map_.MapUnmatchedIds();
2451 src_id_to_.inst_map_.resize(id_map_.SrcToDstMap().IdBound(), nullptr);
2452 dst_id_to_.inst_map_.resize(id_map_.DstToSrcMap().IdBound(), nullptr);
2453
2454 const spv_target_env target_env = SPV_ENV_UNIVERSAL_1_6;
2455 spv_opcode_table opcode_table;
2456 spv_operand_table operand_table;
2457 spv_ext_inst_table ext_inst_table;
2458 spv_result_t result;
2459
2460 result = spvOpcodeTableGet(&opcode_table, target_env);
2461 if (result != SPV_SUCCESS) return result;
2462
2463 result = spvOperandTableGet(&operand_table, target_env);
2464 if (result != SPV_SUCCESS) return result;
2465
2466 result = spvExtInstTableGet(&ext_inst_table, target_env);
2467 if (result != SPV_SUCCESS) return result;
2468
2469 spv_context_t context{
2470 target_env,
2471 opcode_table,
2472 operand_table,
2473 ext_inst_table,
2474 };
2475
2476 const AssemblyGrammar grammar(&context);
2477 if (!grammar.isValid()) return SPV_ERROR_INVALID_TABLE;
2478
2479 uint32_t disassembly_options = SPV_BINARY_TO_TEXT_OPTION_PRINT;
2480 if (options_.indent) {
2481 disassembly_options |= SPV_BINARY_TO_TEXT_OPTION_INDENT;
2482 }
2483
2484 NameMapper name_mapper = GetTrivialNameMapper();
2485 disassemble::InstructionDisassembler dis(grammar, out_, disassembly_options,
2486 name_mapper);
2487
2488 if (!options_.no_header) {
2489 // Output the header
2490 // TODO: when using diff with text, the assembler overrides the version and
2491 // generator, so these aren't reflected correctly in the output. Could
2492 // potentially extract this info from the header comment.
2493 OutputLine([]() { return true; }, [&dis]() { dis.EmitHeaderSpirv(); },
2494 []() { assert(false && "Unreachable"); });
2495 OutputLine([this]() { return src_->version() == dst_->version(); },
2496 [this, &dis]() { dis.EmitHeaderVersion(src_->version()); },
2497 [this, &dis]() { dis.EmitHeaderVersion(dst_->version()); });
2498 OutputLine([this]() { return src_->generator() == dst_->generator(); },
2499 [this, &dis]() { dis.EmitHeaderGenerator(src_->generator()); },
2500 [this, &dis]() { dis.EmitHeaderGenerator(dst_->generator()); });
2501 OutputLine(
2502 [this]() { return src_->IdBound() == id_map_.SrcToDstMap().IdBound(); },
2503 [this, &dis]() { dis.EmitHeaderIdBound(src_->IdBound()); },
2504 [this, &dis]() {
2505 dis.EmitHeaderIdBound(id_map_.SrcToDstMap().IdBound());
2506 });
2507 OutputLine([this]() { return src_->schema() == dst_->schema(); },
2508 [this, &dis]() { dis.EmitHeaderSchema(src_->schema()); },
2509 [this, &dis]() { dis.EmitHeaderSchema(dst_->schema()); });
2510 }
2511
2512 // For each section, iterate both modules and output the disassembly.
2513 auto write_inst = [this, &dis](const opt::Instruction& inst,
2514 const IdInstructions& id_to,
2515 const opt::Instruction& original_inst) {
2516 spv_parsed_instruction_t parsed_inst;
2517 std::vector<spv_parsed_operand_t> parsed_operands;
2518 std::vector<uint32_t> inst_binary;
2519
2520 ToParsedInstruction(inst, id_to, original_inst, &parsed_inst,
2521 parsed_operands, inst_binary);
2522
2523 dis.EmitInstruction(parsed_inst, 0);
2524 };
2525
2526 OutputSection(src_->capabilities(), dst_->capabilities(), write_inst);
2527 OutputSection(src_->extensions(), dst_->extensions(), write_inst);
2528 OutputSection(src_->ext_inst_imports(), dst_->ext_inst_imports(), write_inst);
2529
2530 // There is only one memory model.
2531 OutputLine(
2532 [this]() {
2533 return DoInstructionsMatch(src_->GetMemoryModel(),
2534 dst_->GetMemoryModel());
2535 },
2536 [this, &write_inst]() {
2537 write_inst(*src_->GetMemoryModel(), src_id_to_,
2538 *src_->GetMemoryModel());
2539 },
2540 [this, &write_inst]() {
2541 write_inst(*dst_->GetMemoryModel(), dst_id_to_,
2542 *dst_->GetMemoryModel());
2543 });
2544
2545 OutputSection(src_->entry_points(), dst_->entry_points(), write_inst);
2546 OutputSection(src_->execution_modes(), dst_->execution_modes(), write_inst);
2547 OutputSection(src_->debugs1(), dst_->debugs1(), write_inst);
2548 OutputSection(src_->debugs2(), dst_->debugs2(), write_inst);
2549 OutputSection(src_->debugs3(), dst_->debugs3(), write_inst);
2550 OutputSection(src_->ext_inst_debuginfo(), dst_->ext_inst_debuginfo(),
2551 write_inst);
2552 OutputSection(src_->annotations(), dst_->annotations(), write_inst);
2553 OutputSection(src_->types_values(), dst_->types_values(), write_inst);
2554
2555 // Get the body of all the functions.
2556 FunctionInstMap src_func_header_insts;
2557 FunctionInstMap dst_func_header_insts;
2558
2559 GetFunctionHeaderInstructions(src_, &src_func_header_insts);
2560 GetFunctionHeaderInstructions(dst_, &dst_func_header_insts);
2561
2562 for (const auto& src_func : src_func_insts_) {
2563 const uint32_t src_func_id = src_func.first;
2564 const InstructionList& src_insts = src_func.second;
2565 const InstructionList& src_header_insts =
2566 src_func_header_insts[src_func_id];
2567
2568 const uint32_t dst_func_id = id_map_.MappedDstId(src_func_id);
2569 if (dst_func_insts_.find(dst_func_id) == dst_func_insts_.end()) {
2570 OutputSection(src_header_insts, InstructionList(), write_inst);
2571 OutputSection(src_insts, InstructionList(), write_inst);
2572 continue;
2573 }
2574
2575 const InstructionList& dst_insts = dst_func_insts_[dst_func_id];
2576 const InstructionList& dst_header_insts =
2577 dst_func_header_insts[dst_func_id];
2578 OutputSection(src_header_insts, dst_header_insts, write_inst);
2579 OutputSection(src_insts, dst_insts, write_inst);
2580 }
2581
2582 for (const auto& dst_func : dst_func_insts_) {
2583 const uint32_t dst_func_id = dst_func.first;
2584 const InstructionList& dst_insts = dst_func.second;
2585 const InstructionList& dst_header_insts =
2586 dst_func_header_insts[dst_func_id];
2587
2588 const uint32_t src_func_id = id_map_.MappedSrcId(dst_func_id);
2589 if (src_func_insts_.find(src_func_id) == src_func_insts_.end()) {
2590 OutputSection(InstructionList(), dst_header_insts, write_inst);
2591 OutputSection(InstructionList(), dst_insts, write_inst);
2592 }
2593 }
2594
2595 out_ << std::flush;
2596
2597 return SPV_SUCCESS;
2598}
2599
2600} // anonymous namespace
2601
2602spv_result_t Diff(opt::IRContext* src, opt::IRContext* dst, std::ostream& out,
2603 Options options) {
2604 // High level algorithm:
2605 //
2606 // - Some sections of SPIR-V don't deal with ids; instructions in those
2607 // sections are matched identically. For example OpCapability instructions.
2608 // - Some sections produce ids, and they can be trivially matched by their
2609 // parameters. For example OpExtInstImport instructions.
2610 // - Some sections annotate ids. These are matched at the end, after the ids
2611 // themselves are matched. For example OpName or OpDecorate instructions.
2612 // - Some sections produce ids that depend on other ids and they can be
2613 // recursively matched. For example OpType* instructions.
2614 // - Some sections produce ids that are not trivially matched. For these ids,
2615 // the debug info is used when possible, or a best guess (such as through
2616 // decorations) is used. For example OpVariable instructions.
2617 // - Matching functions is done with multiple attempts:
2618 // * Functions with identical debug names are matched if there are no
2619 // overloads.
2620 // * Otherwise, functions with identical debug names and types are matched.
2621 // * The rest of the functions are best-effort matched, first in groups of
2622 // identical type, then any with any.
2623 // * The best-effort matching takes the diff of every pair of functions in
2624 // a group and selects the top matches that also meet a similarity
2625 // index.
2626 // * Once a pair of functions are matched, the fuzzy diff of the
2627 // instructions is used to match the instructions in the function body.
2628 // The fuzzy diff makes sure that sufficiently similar instructions are
2629 // matched and that yet-to-be-matched result ids don't result in a larger
2630 // diff.
2631 //
2632 // Once the instructions are matched between the src and dst SPIR-V, the src
2633 // is traversed and its disassembly is output. In the process, any unmatched
2634 // instruction is prefixed with -, and any unmatched instruction in dst in the
2635 // same section is output prefixed with +. To avoid confusion, the
2636 // instructions in dst are output with matching ids in src so the output
2637 // assembly is consistent.
2638
2639 Differ differ(src, dst, out, options);
2640
2641 // First, match instructions between the different non-annotation sections of
2642 // the SPIR-V.
2643 differ.MatchCapabilities();
2644 differ.MatchExtensions();
2645 differ.MatchExtInstImportIds();
2646 differ.MatchMemoryModel();
2647 differ.MatchEntryPointIds();
2648 differ.MatchExecutionModes();
2649 differ.MatchTypeIds();
2650 differ.MatchConstants();
2651 differ.MatchVariableIds();
2652 differ.MatchFunctions();
2653
2654 // Match instructions that annotate previously-matched ids.
2655 differ.MatchDebugs1();
2656 differ.MatchDebugs2();
2657 differ.MatchDebugs3();
2658 differ.MatchExtInstDebugInfo();
2659 differ.MatchAnnotations();
2660
2661 // Show the disassembly with the diff.
2662 //
2663 // TODO: Based on an option, output either based on src or dst, i.e. the diff
2664 // can show the ids and instruction/function order either from src or dst.
2665 spv_result_t result = differ.Output();
2666
2667 differ.DumpIdMap();
2668
2669 return result;
2670}
2671
2672} // namespace diff
2673} // namespace spvtools