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