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