xref: /aosp_15_r20/external/zucchini/equivalence_map.cc (revision a03ca8b91e029cd15055c20c78c2e087c84792e4)
1*a03ca8b9SKrzysztof Kosiński // Copyright 2017 The Chromium Authors. All rights reserved.
2*a03ca8b9SKrzysztof Kosiński // Use of this source code is governed by a BSD-style license that can be
3*a03ca8b9SKrzysztof Kosiński // found in the LICENSE file.
4*a03ca8b9SKrzysztof Kosiński 
5*a03ca8b9SKrzysztof Kosiński #include "components/zucchini/equivalence_map.h"
6*a03ca8b9SKrzysztof Kosiński 
7*a03ca8b9SKrzysztof Kosiński #include <algorithm>
8*a03ca8b9SKrzysztof Kosiński #include <utility>
9*a03ca8b9SKrzysztof Kosiński 
10*a03ca8b9SKrzysztof Kosiński #include "base/containers/cxx20_erase.h"
11*a03ca8b9SKrzysztof Kosiński #include "base/logging.h"
12*a03ca8b9SKrzysztof Kosiński #include "base/numerics/safe_conversions.h"
13*a03ca8b9SKrzysztof Kosiński #include "components/zucchini/encoded_view.h"
14*a03ca8b9SKrzysztof Kosiński #include "components/zucchini/patch_reader.h"
15*a03ca8b9SKrzysztof Kosiński #include "components/zucchini/suffix_array.h"
16*a03ca8b9SKrzysztof Kosiński 
17*a03ca8b9SKrzysztof Kosiński namespace zucchini {
18*a03ca8b9SKrzysztof Kosiński 
19*a03ca8b9SKrzysztof Kosiński namespace {
20*a03ca8b9SKrzysztof Kosiński 
21*a03ca8b9SKrzysztof Kosiński // TODO(haungs): Tune these numbers to improve pathological case results.
22*a03ca8b9SKrzysztof Kosiński 
23*a03ca8b9SKrzysztof Kosiński // In pathological cases Zucchini can exhibit O(n^2) behavior if the seed
24*a03ca8b9SKrzysztof Kosiński // selection process runs to completion. To prevent this we impose a quota for
25*a03ca8b9SKrzysztof Kosiński // the total length of equivalences the seed selection process can perform
26*a03ca8b9SKrzysztof Kosiński // trials on. For regular use cases it is unlikely this quota will be exceeded,
27*a03ca8b9SKrzysztof Kosiński // and if it is the effects on patch size are expected to be small.
28*a03ca8b9SKrzysztof Kosiński constexpr uint64_t kSeedSelectionTotalVisitLengthQuota = 1 << 18;  // 256 KiB
29*a03ca8b9SKrzysztof Kosiński 
30*a03ca8b9SKrzysztof Kosiński // The aforementioned quota alone is insufficient, as exploring backwards will
31*a03ca8b9SKrzysztof Kosiński // still be very successful resulting in O(n) behavior in the case of a limited
32*a03ca8b9SKrzysztof Kosiński // seed selection trials. This results in O(n^2) behavior returning. To mitigate
33*a03ca8b9SKrzysztof Kosiński // this we also impose a cap on the ExtendEquivalenceBackward() exploration.
34*a03ca8b9SKrzysztof Kosiński constexpr offset_t kBackwardsExtendLimit = 1 << 16;  // 64 KiB
35*a03ca8b9SKrzysztof Kosiński 
36*a03ca8b9SKrzysztof Kosiński }  // namespace
37*a03ca8b9SKrzysztof Kosiński 
38*a03ca8b9SKrzysztof Kosiński /******** Utility Functions ********/
39*a03ca8b9SKrzysztof Kosiński 
GetTokenSimilarity(const ImageIndex & old_image_index,const ImageIndex & new_image_index,const std::vector<TargetsAffinity> & targets_affinities,offset_t src,offset_t dst)40*a03ca8b9SKrzysztof Kosiński double GetTokenSimilarity(
41*a03ca8b9SKrzysztof Kosiński     const ImageIndex& old_image_index,
42*a03ca8b9SKrzysztof Kosiński     const ImageIndex& new_image_index,
43*a03ca8b9SKrzysztof Kosiński     const std::vector<TargetsAffinity>& targets_affinities,
44*a03ca8b9SKrzysztof Kosiński     offset_t src,
45*a03ca8b9SKrzysztof Kosiński     offset_t dst) {
46*a03ca8b9SKrzysztof Kosiński   DCHECK(old_image_index.IsToken(src));
47*a03ca8b9SKrzysztof Kosiński   DCHECK(new_image_index.IsToken(dst));
48*a03ca8b9SKrzysztof Kosiński 
49*a03ca8b9SKrzysztof Kosiński   TypeTag old_type = old_image_index.LookupType(src);
50*a03ca8b9SKrzysztof Kosiński   TypeTag new_type = new_image_index.LookupType(dst);
51*a03ca8b9SKrzysztof Kosiński   if (old_type != new_type)
52*a03ca8b9SKrzysztof Kosiński     return kMismatchFatal;
53*a03ca8b9SKrzysztof Kosiński 
54*a03ca8b9SKrzysztof Kosiński   // Raw comparison.
55*a03ca8b9SKrzysztof Kosiński   if (!old_image_index.IsReference(src) && !new_image_index.IsReference(dst)) {
56*a03ca8b9SKrzysztof Kosiński     return old_image_index.GetRawValue(src) == new_image_index.GetRawValue(dst)
57*a03ca8b9SKrzysztof Kosiński                ? 1.0
58*a03ca8b9SKrzysztof Kosiński                : -1.5;
59*a03ca8b9SKrzysztof Kosiński   }
60*a03ca8b9SKrzysztof Kosiński 
61*a03ca8b9SKrzysztof Kosiński   const ReferenceSet& old_ref_set = old_image_index.refs(old_type);
62*a03ca8b9SKrzysztof Kosiński   const ReferenceSet& new_ref_set = new_image_index.refs(new_type);
63*a03ca8b9SKrzysztof Kosiński   Reference old_reference = old_ref_set.at(src);
64*a03ca8b9SKrzysztof Kosiński   Reference new_reference = new_ref_set.at(dst);
65*a03ca8b9SKrzysztof Kosiński   PoolTag pool_tag = old_ref_set.pool_tag();
66*a03ca8b9SKrzysztof Kosiński 
67*a03ca8b9SKrzysztof Kosiński   double affinity = targets_affinities[pool_tag.value()].AffinityBetween(
68*a03ca8b9SKrzysztof Kosiński       old_ref_set.target_pool().KeyForOffset(old_reference.target),
69*a03ca8b9SKrzysztof Kosiński       new_ref_set.target_pool().KeyForOffset(new_reference.target));
70*a03ca8b9SKrzysztof Kosiński 
71*a03ca8b9SKrzysztof Kosiński   // Both targets are not associated, which implies a weak match.
72*a03ca8b9SKrzysztof Kosiński   if (affinity == 0.0)
73*a03ca8b9SKrzysztof Kosiński     return 0.5 * old_ref_set.width();
74*a03ca8b9SKrzysztof Kosiński 
75*a03ca8b9SKrzysztof Kosiński   // At least one target is associated, so values are compared.
76*a03ca8b9SKrzysztof Kosiński   return affinity > 0.0 ? old_ref_set.width() : -2.0;
77*a03ca8b9SKrzysztof Kosiński }
78*a03ca8b9SKrzysztof Kosiński 
GetEquivalenceSimilarity(const ImageIndex & old_image_index,const ImageIndex & new_image_index,const std::vector<TargetsAffinity> & targets_affinities,const Equivalence & equivalence)79*a03ca8b9SKrzysztof Kosiński double GetEquivalenceSimilarity(
80*a03ca8b9SKrzysztof Kosiński     const ImageIndex& old_image_index,
81*a03ca8b9SKrzysztof Kosiński     const ImageIndex& new_image_index,
82*a03ca8b9SKrzysztof Kosiński     const std::vector<TargetsAffinity>& targets_affinities,
83*a03ca8b9SKrzysztof Kosiński     const Equivalence& equivalence) {
84*a03ca8b9SKrzysztof Kosiński   double similarity = 0.0;
85*a03ca8b9SKrzysztof Kosiński   for (offset_t k = 0; k < equivalence.length; ++k) {
86*a03ca8b9SKrzysztof Kosiński     // Non-tokens are joined with the nearest previous token: skip until we
87*a03ca8b9SKrzysztof Kosiński     // cover the unit.
88*a03ca8b9SKrzysztof Kosiński     if (!new_image_index.IsToken(equivalence.dst_offset + k))
89*a03ca8b9SKrzysztof Kosiński       continue;
90*a03ca8b9SKrzysztof Kosiński 
91*a03ca8b9SKrzysztof Kosiński     similarity += GetTokenSimilarity(
92*a03ca8b9SKrzysztof Kosiński         old_image_index, new_image_index, targets_affinities,
93*a03ca8b9SKrzysztof Kosiński         equivalence.src_offset + k, equivalence.dst_offset + k);
94*a03ca8b9SKrzysztof Kosiński     if (similarity == kMismatchFatal)
95*a03ca8b9SKrzysztof Kosiński       return kMismatchFatal;
96*a03ca8b9SKrzysztof Kosiński   }
97*a03ca8b9SKrzysztof Kosiński   return similarity;
98*a03ca8b9SKrzysztof Kosiński }
99*a03ca8b9SKrzysztof Kosiński 
ExtendEquivalenceForward(const ImageIndex & old_image_index,const ImageIndex & new_image_index,const std::vector<TargetsAffinity> & targets_affinities,const EquivalenceCandidate & candidate,double min_similarity)100*a03ca8b9SKrzysztof Kosiński EquivalenceCandidate ExtendEquivalenceForward(
101*a03ca8b9SKrzysztof Kosiński     const ImageIndex& old_image_index,
102*a03ca8b9SKrzysztof Kosiński     const ImageIndex& new_image_index,
103*a03ca8b9SKrzysztof Kosiński     const std::vector<TargetsAffinity>& targets_affinities,
104*a03ca8b9SKrzysztof Kosiński     const EquivalenceCandidate& candidate,
105*a03ca8b9SKrzysztof Kosiński     double min_similarity) {
106*a03ca8b9SKrzysztof Kosiński   Equivalence equivalence = candidate.eq;
107*a03ca8b9SKrzysztof Kosiński   offset_t best_k = equivalence.length;
108*a03ca8b9SKrzysztof Kosiński   double current_similarity = candidate.similarity;
109*a03ca8b9SKrzysztof Kosiński   double best_similarity = current_similarity;
110*a03ca8b9SKrzysztof Kosiński   double current_penalty = min_similarity;
111*a03ca8b9SKrzysztof Kosiński   for (offset_t k = best_k;
112*a03ca8b9SKrzysztof Kosiński        equivalence.src_offset + k < old_image_index.size() &&
113*a03ca8b9SKrzysztof Kosiński        equivalence.dst_offset + k < new_image_index.size();
114*a03ca8b9SKrzysztof Kosiński        ++k) {
115*a03ca8b9SKrzysztof Kosiński     // Mismatch in type, |candidate| cannot be extended further.
116*a03ca8b9SKrzysztof Kosiński     if (old_image_index.LookupType(equivalence.src_offset + k) !=
117*a03ca8b9SKrzysztof Kosiński         new_image_index.LookupType(equivalence.dst_offset + k)) {
118*a03ca8b9SKrzysztof Kosiński       break;
119*a03ca8b9SKrzysztof Kosiński     }
120*a03ca8b9SKrzysztof Kosiński 
121*a03ca8b9SKrzysztof Kosiński     if (!new_image_index.IsToken(equivalence.dst_offset + k)) {
122*a03ca8b9SKrzysztof Kosiński       // Non-tokens are joined with the nearest previous token: skip until we
123*a03ca8b9SKrzysztof Kosiński       // cover the unit, and extend |best_k| if applicable.
124*a03ca8b9SKrzysztof Kosiński       if (best_k == k)
125*a03ca8b9SKrzysztof Kosiński         best_k = k + 1;
126*a03ca8b9SKrzysztof Kosiński       continue;
127*a03ca8b9SKrzysztof Kosiński     }
128*a03ca8b9SKrzysztof Kosiński 
129*a03ca8b9SKrzysztof Kosiński     double similarity = GetTokenSimilarity(
130*a03ca8b9SKrzysztof Kosiński         old_image_index, new_image_index, targets_affinities,
131*a03ca8b9SKrzysztof Kosiński         equivalence.src_offset + k, equivalence.dst_offset + k);
132*a03ca8b9SKrzysztof Kosiński     current_similarity += similarity;
133*a03ca8b9SKrzysztof Kosiński     current_penalty = std::max(0.0, current_penalty) - similarity;
134*a03ca8b9SKrzysztof Kosiński 
135*a03ca8b9SKrzysztof Kosiński     if (current_similarity < 0.0 || current_penalty >= min_similarity)
136*a03ca8b9SKrzysztof Kosiński       break;
137*a03ca8b9SKrzysztof Kosiński     if (current_similarity >= best_similarity) {
138*a03ca8b9SKrzysztof Kosiński       best_similarity = current_similarity;
139*a03ca8b9SKrzysztof Kosiński       best_k = k + 1;
140*a03ca8b9SKrzysztof Kosiński     }
141*a03ca8b9SKrzysztof Kosiński   }
142*a03ca8b9SKrzysztof Kosiński   equivalence.length = best_k;
143*a03ca8b9SKrzysztof Kosiński   return {equivalence, best_similarity};
144*a03ca8b9SKrzysztof Kosiński }
145*a03ca8b9SKrzysztof Kosiński 
ExtendEquivalenceBackward(const ImageIndex & old_image_index,const ImageIndex & new_image_index,const std::vector<TargetsAffinity> & targets_affinities,const EquivalenceCandidate & candidate,double min_similarity)146*a03ca8b9SKrzysztof Kosiński EquivalenceCandidate ExtendEquivalenceBackward(
147*a03ca8b9SKrzysztof Kosiński     const ImageIndex& old_image_index,
148*a03ca8b9SKrzysztof Kosiński     const ImageIndex& new_image_index,
149*a03ca8b9SKrzysztof Kosiński     const std::vector<TargetsAffinity>& targets_affinities,
150*a03ca8b9SKrzysztof Kosiński     const EquivalenceCandidate& candidate,
151*a03ca8b9SKrzysztof Kosiński     double min_similarity) {
152*a03ca8b9SKrzysztof Kosiński   Equivalence equivalence = candidate.eq;
153*a03ca8b9SKrzysztof Kosiński   offset_t best_k = 0;
154*a03ca8b9SKrzysztof Kosiński   double current_similarity = candidate.similarity;
155*a03ca8b9SKrzysztof Kosiński   double best_similarity = current_similarity;
156*a03ca8b9SKrzysztof Kosiński   double current_penalty = 0.0;
157*a03ca8b9SKrzysztof Kosiński   offset_t k_min = std::min(
158*a03ca8b9SKrzysztof Kosiński       {equivalence.dst_offset, equivalence.src_offset, kBackwardsExtendLimit});
159*a03ca8b9SKrzysztof Kosiński   for (offset_t k = 1; k <= k_min; ++k) {
160*a03ca8b9SKrzysztof Kosiński     // Mismatch in type, |candidate| cannot be extended further.
161*a03ca8b9SKrzysztof Kosiński     if (old_image_index.LookupType(equivalence.src_offset - k) !=
162*a03ca8b9SKrzysztof Kosiński         new_image_index.LookupType(equivalence.dst_offset - k)) {
163*a03ca8b9SKrzysztof Kosiński       break;
164*a03ca8b9SKrzysztof Kosiński     }
165*a03ca8b9SKrzysztof Kosiński 
166*a03ca8b9SKrzysztof Kosiński     // Non-tokens are joined with the nearest previous token: skip until we
167*a03ca8b9SKrzysztof Kosiński     // reach the next token.
168*a03ca8b9SKrzysztof Kosiński     if (!new_image_index.IsToken(equivalence.dst_offset - k))
169*a03ca8b9SKrzysztof Kosiński       continue;
170*a03ca8b9SKrzysztof Kosiński 
171*a03ca8b9SKrzysztof Kosiński     DCHECK_EQ(old_image_index.LookupType(equivalence.src_offset - k),
172*a03ca8b9SKrzysztof Kosiński               new_image_index.LookupType(equivalence.dst_offset -
173*a03ca8b9SKrzysztof Kosiński                                          k));  // Sanity check.
174*a03ca8b9SKrzysztof Kosiński     double similarity = GetTokenSimilarity(
175*a03ca8b9SKrzysztof Kosiński         old_image_index, new_image_index, targets_affinities,
176*a03ca8b9SKrzysztof Kosiński         equivalence.src_offset - k, equivalence.dst_offset - k);
177*a03ca8b9SKrzysztof Kosiński 
178*a03ca8b9SKrzysztof Kosiński     current_similarity += similarity;
179*a03ca8b9SKrzysztof Kosiński     current_penalty = std::max(0.0, current_penalty) - similarity;
180*a03ca8b9SKrzysztof Kosiński 
181*a03ca8b9SKrzysztof Kosiński     if (current_similarity < 0.0 || current_penalty >= min_similarity)
182*a03ca8b9SKrzysztof Kosiński       break;
183*a03ca8b9SKrzysztof Kosiński     if (current_similarity >= best_similarity) {
184*a03ca8b9SKrzysztof Kosiński       best_similarity = current_similarity;
185*a03ca8b9SKrzysztof Kosiński       best_k = k;
186*a03ca8b9SKrzysztof Kosiński     }
187*a03ca8b9SKrzysztof Kosiński   }
188*a03ca8b9SKrzysztof Kosiński 
189*a03ca8b9SKrzysztof Kosiński   equivalence.dst_offset -= best_k;
190*a03ca8b9SKrzysztof Kosiński   equivalence.src_offset -= best_k;
191*a03ca8b9SKrzysztof Kosiński   equivalence.length += best_k;
192*a03ca8b9SKrzysztof Kosiński   return {equivalence, best_similarity};
193*a03ca8b9SKrzysztof Kosiński }
194*a03ca8b9SKrzysztof Kosiński 
VisitEquivalenceSeed(const ImageIndex & old_image_index,const ImageIndex & new_image_index,const std::vector<TargetsAffinity> & targets_affinities,offset_t src,offset_t dst,double min_similarity)195*a03ca8b9SKrzysztof Kosiński EquivalenceCandidate VisitEquivalenceSeed(
196*a03ca8b9SKrzysztof Kosiński     const ImageIndex& old_image_index,
197*a03ca8b9SKrzysztof Kosiński     const ImageIndex& new_image_index,
198*a03ca8b9SKrzysztof Kosiński     const std::vector<TargetsAffinity>& targets_affinities,
199*a03ca8b9SKrzysztof Kosiński     offset_t src,
200*a03ca8b9SKrzysztof Kosiński     offset_t dst,
201*a03ca8b9SKrzysztof Kosiński     double min_similarity) {
202*a03ca8b9SKrzysztof Kosiński   EquivalenceCandidate candidate{{src, dst, 0}, 0.0};  // Empty.
203*a03ca8b9SKrzysztof Kosiński   if (!old_image_index.IsToken(src))
204*a03ca8b9SKrzysztof Kosiński     return candidate;
205*a03ca8b9SKrzysztof Kosiński   candidate =
206*a03ca8b9SKrzysztof Kosiński       ExtendEquivalenceForward(old_image_index, new_image_index,
207*a03ca8b9SKrzysztof Kosiński                                targets_affinities, candidate, min_similarity);
208*a03ca8b9SKrzysztof Kosiński   if (candidate.similarity < min_similarity)
209*a03ca8b9SKrzysztof Kosiński     return candidate;  // Not worth exploring any more.
210*a03ca8b9SKrzysztof Kosiński   return ExtendEquivalenceBackward(old_image_index, new_image_index,
211*a03ca8b9SKrzysztof Kosiński                                    targets_affinities, candidate,
212*a03ca8b9SKrzysztof Kosiński                                    min_similarity);
213*a03ca8b9SKrzysztof Kosiński }
214*a03ca8b9SKrzysztof Kosiński 
215*a03ca8b9SKrzysztof Kosiński /******** OffsetMapper ********/
216*a03ca8b9SKrzysztof Kosiński 
OffsetMapper(std::deque<Equivalence> && equivalences,offset_t old_image_size,offset_t new_image_size)217*a03ca8b9SKrzysztof Kosiński OffsetMapper::OffsetMapper(std::deque<Equivalence>&& equivalences,
218*a03ca8b9SKrzysztof Kosiński                            offset_t old_image_size,
219*a03ca8b9SKrzysztof Kosiński                            offset_t new_image_size)
220*a03ca8b9SKrzysztof Kosiński     : equivalences_(std::move(equivalences)),
221*a03ca8b9SKrzysztof Kosiński       old_image_size_(old_image_size),
222*a03ca8b9SKrzysztof Kosiński       new_image_size_(new_image_size) {
223*a03ca8b9SKrzysztof Kosiński   DCHECK_GT(new_image_size_, 0U);
224*a03ca8b9SKrzysztof Kosiński   DCHECK(std::is_sorted(equivalences_.begin(), equivalences_.end(),
225*a03ca8b9SKrzysztof Kosiński                         [](const Equivalence& a, const Equivalence& b) {
226*a03ca8b9SKrzysztof Kosiński                           return a.src_offset < b.src_offset;
227*a03ca8b9SKrzysztof Kosiński                         }));
228*a03ca8b9SKrzysztof Kosiński   // This is for testing. Assume pruned.
229*a03ca8b9SKrzysztof Kosiński }
230*a03ca8b9SKrzysztof Kosiński 
OffsetMapper(EquivalenceSource && equivalence_source,offset_t old_image_size,offset_t new_image_size)231*a03ca8b9SKrzysztof Kosiński OffsetMapper::OffsetMapper(EquivalenceSource&& equivalence_source,
232*a03ca8b9SKrzysztof Kosiński                            offset_t old_image_size,
233*a03ca8b9SKrzysztof Kosiński                            offset_t new_image_size)
234*a03ca8b9SKrzysztof Kosiński     : old_image_size_(old_image_size), new_image_size_(new_image_size) {
235*a03ca8b9SKrzysztof Kosiński   DCHECK_GT(new_image_size_, 0U);
236*a03ca8b9SKrzysztof Kosiński   for (auto e = equivalence_source.GetNext(); e.has_value();
237*a03ca8b9SKrzysztof Kosiński        e = equivalence_source.GetNext()) {
238*a03ca8b9SKrzysztof Kosiński     equivalences_.push_back(*e);
239*a03ca8b9SKrzysztof Kosiński   }
240*a03ca8b9SKrzysztof Kosiński   PruneEquivalencesAndSortBySource(&equivalences_);
241*a03ca8b9SKrzysztof Kosiński }
242*a03ca8b9SKrzysztof Kosiński 
OffsetMapper(const EquivalenceMap & equivalence_map,offset_t old_image_size,offset_t new_image_size)243*a03ca8b9SKrzysztof Kosiński OffsetMapper::OffsetMapper(const EquivalenceMap& equivalence_map,
244*a03ca8b9SKrzysztof Kosiński                            offset_t old_image_size,
245*a03ca8b9SKrzysztof Kosiński                            offset_t new_image_size)
246*a03ca8b9SKrzysztof Kosiński     : equivalences_(equivalence_map.size()),
247*a03ca8b9SKrzysztof Kosiński       old_image_size_(old_image_size),
248*a03ca8b9SKrzysztof Kosiński       new_image_size_(new_image_size) {
249*a03ca8b9SKrzysztof Kosiński   DCHECK_GT(new_image_size_, 0U);
250*a03ca8b9SKrzysztof Kosiński   std::transform(equivalence_map.begin(), equivalence_map.end(),
251*a03ca8b9SKrzysztof Kosiński                  equivalences_.begin(),
252*a03ca8b9SKrzysztof Kosiński                  [](const EquivalenceCandidate& c) { return c.eq; });
253*a03ca8b9SKrzysztof Kosiński   PruneEquivalencesAndSortBySource(&equivalences_);
254*a03ca8b9SKrzysztof Kosiński }
255*a03ca8b9SKrzysztof Kosiński 
256*a03ca8b9SKrzysztof Kosiński OffsetMapper::~OffsetMapper() = default;
257*a03ca8b9SKrzysztof Kosiński 
258*a03ca8b9SKrzysztof Kosiński // Safely evaluates |offset - unit.src_offset + unit.dst_offset| with signed
259*a03ca8b9SKrzysztof Kosiński // arithmetic, then clips the result to |[0, new_image_size_)|.
NaiveExtendedForwardProject(const Equivalence & unit,offset_t offset) const260*a03ca8b9SKrzysztof Kosiński offset_t OffsetMapper::NaiveExtendedForwardProject(const Equivalence& unit,
261*a03ca8b9SKrzysztof Kosiński                                                    offset_t offset) const {
262*a03ca8b9SKrzysztof Kosiński   int64_t old_offset64 = offset;
263*a03ca8b9SKrzysztof Kosiński   int64_t src_offset64 = unit.src_offset;
264*a03ca8b9SKrzysztof Kosiński   int64_t dst_offset64 = unit.dst_offset;
265*a03ca8b9SKrzysztof Kosiński   uint64_t new_offset64 = std::min<uint64_t>(
266*a03ca8b9SKrzysztof Kosiński       std::max<int64_t>(0LL, old_offset64 - src_offset64 + dst_offset64),
267*a03ca8b9SKrzysztof Kosiński       new_image_size_ - 1);
268*a03ca8b9SKrzysztof Kosiński   return base::checked_cast<offset_t>(new_offset64);
269*a03ca8b9SKrzysztof Kosiński }
270*a03ca8b9SKrzysztof Kosiński 
ExtendedForwardProject(offset_t offset) const271*a03ca8b9SKrzysztof Kosiński offset_t OffsetMapper::ExtendedForwardProject(offset_t offset) const {
272*a03ca8b9SKrzysztof Kosiński   DCHECK(!equivalences_.empty());
273*a03ca8b9SKrzysztof Kosiński   if (offset < old_image_size_) {
274*a03ca8b9SKrzysztof Kosiński     // Finds the equivalence unit whose "old" block is nearest to |offset|,
275*a03ca8b9SKrzysztof Kosiński     // favoring the block with lower offset in case of a tie.
276*a03ca8b9SKrzysztof Kosiński     auto pos = std::upper_bound(
277*a03ca8b9SKrzysztof Kosiński         equivalences_.begin(), equivalences_.end(), offset,
278*a03ca8b9SKrzysztof Kosiński         [](offset_t a, const Equivalence& b) { return a < b.src_offset; });
279*a03ca8b9SKrzysztof Kosiński     // For tiebreaking: |offset - pos[-1].src_end()| is actually 1 less than
280*a03ca8b9SKrzysztof Kosiński     // |offset|'s distance to "old" block of |pos[-1]|. Therefore "<" is used.
281*a03ca8b9SKrzysztof Kosiński     if (pos != equivalences_.begin() &&
282*a03ca8b9SKrzysztof Kosiński         (pos == equivalences_.end() || offset < pos[-1].src_end() ||
283*a03ca8b9SKrzysztof Kosiński          offset - pos[-1].src_end() < pos->src_offset - offset)) {
284*a03ca8b9SKrzysztof Kosiński       --pos;
285*a03ca8b9SKrzysztof Kosiński     }
286*a03ca8b9SKrzysztof Kosiński     return NaiveExtendedForwardProject(*pos, offset);
287*a03ca8b9SKrzysztof Kosiński   }
288*a03ca8b9SKrzysztof Kosiński   // Fake offsets.
289*a03ca8b9SKrzysztof Kosiński   offset_t delta = offset - old_image_size_;
290*a03ca8b9SKrzysztof Kosiński   return delta < kOffsetBound - new_image_size_ ? new_image_size_ + delta
291*a03ca8b9SKrzysztof Kosiński                                                 : kOffsetBound - 1;
292*a03ca8b9SKrzysztof Kosiński }
293*a03ca8b9SKrzysztof Kosiński 
ForwardProjectAll(std::deque<offset_t> * offsets) const294*a03ca8b9SKrzysztof Kosiński void OffsetMapper::ForwardProjectAll(std::deque<offset_t>* offsets) const {
295*a03ca8b9SKrzysztof Kosiński   DCHECK(std::is_sorted(offsets->begin(), offsets->end()));
296*a03ca8b9SKrzysztof Kosiński   auto current = equivalences_.begin();
297*a03ca8b9SKrzysztof Kosiński   for (auto& src : *offsets) {
298*a03ca8b9SKrzysztof Kosiński     while (current != end() && current->src_end() <= src) {
299*a03ca8b9SKrzysztof Kosiński       ++current;
300*a03ca8b9SKrzysztof Kosiński     }
301*a03ca8b9SKrzysztof Kosiński 
302*a03ca8b9SKrzysztof Kosiński     if (current != end() && current->src_offset <= src) {
303*a03ca8b9SKrzysztof Kosiński       src = src - current->src_offset + current->dst_offset;
304*a03ca8b9SKrzysztof Kosiński     } else {
305*a03ca8b9SKrzysztof Kosiński       src = kInvalidOffset;
306*a03ca8b9SKrzysztof Kosiński     }
307*a03ca8b9SKrzysztof Kosiński   }
308*a03ca8b9SKrzysztof Kosiński   base::Erase(*offsets, kInvalidOffset);
309*a03ca8b9SKrzysztof Kosiński   offsets->shrink_to_fit();
310*a03ca8b9SKrzysztof Kosiński }
311*a03ca8b9SKrzysztof Kosiński 
PruneEquivalencesAndSortBySource(std::deque<Equivalence> * equivalences)312*a03ca8b9SKrzysztof Kosiński void OffsetMapper::PruneEquivalencesAndSortBySource(
313*a03ca8b9SKrzysztof Kosiński     std::deque<Equivalence>* equivalences) {
314*a03ca8b9SKrzysztof Kosiński   std::sort(equivalences->begin(), equivalences->end(),
315*a03ca8b9SKrzysztof Kosiński             [](const Equivalence& a, const Equivalence& b) {
316*a03ca8b9SKrzysztof Kosiński               return a.src_offset < b.src_offset;
317*a03ca8b9SKrzysztof Kosiński             });
318*a03ca8b9SKrzysztof Kosiński 
319*a03ca8b9SKrzysztof Kosiński   for (auto current = equivalences->begin(); current != equivalences->end();
320*a03ca8b9SKrzysztof Kosiński        ++current) {
321*a03ca8b9SKrzysztof Kosiński     // A "reaper" is an equivalence after |current| that overlaps with it, but
322*a03ca8b9SKrzysztof Kosiński     // is longer, and so truncates |current|.  For example:
323*a03ca8b9SKrzysztof Kosiński     //  ******  <=  |current|
324*a03ca8b9SKrzysztof Kosiński     //    **
325*a03ca8b9SKrzysztof Kosiński     //    ****
326*a03ca8b9SKrzysztof Kosiński     //      ****
327*a03ca8b9SKrzysztof Kosiński     //      **********  <= |next| as reaper.
328*a03ca8b9SKrzysztof Kosiński     // If a reaper is found (as |next|), every equivalence strictly between
329*a03ca8b9SKrzysztof Kosiński     // |current| and |next| would be truncated to 0 and discarded. Handling this
330*a03ca8b9SKrzysztof Kosiński     // case is important to avoid O(n^2) behavior.
331*a03ca8b9SKrzysztof Kosiński     bool next_is_reaper = false;
332*a03ca8b9SKrzysztof Kosiński 
333*a03ca8b9SKrzysztof Kosiński     // Look ahead to resolve overlaps, until a better candidate is found.
334*a03ca8b9SKrzysztof Kosiński     auto next = current + 1;
335*a03ca8b9SKrzysztof Kosiński     for (; next != equivalences->end(); ++next) {
336*a03ca8b9SKrzysztof Kosiński       DCHECK_GE(next->src_offset, current->src_offset);
337*a03ca8b9SKrzysztof Kosiński       if (next->src_offset >= current->src_end())
338*a03ca8b9SKrzysztof Kosiński         break;  // No more overlap.
339*a03ca8b9SKrzysztof Kosiński 
340*a03ca8b9SKrzysztof Kosiński       if (current->length < next->length) {
341*a03ca8b9SKrzysztof Kosiński         // |next| is better: So it is a reaper that shrinks |current|.
342*a03ca8b9SKrzysztof Kosiński         offset_t delta = current->src_end() - next->src_offset;
343*a03ca8b9SKrzysztof Kosiński         current->length -= delta;
344*a03ca8b9SKrzysztof Kosiński         next_is_reaper = true;
345*a03ca8b9SKrzysztof Kosiński         break;
346*a03ca8b9SKrzysztof Kosiński       }
347*a03ca8b9SKrzysztof Kosiński     }
348*a03ca8b9SKrzysztof Kosiński 
349*a03ca8b9SKrzysztof Kosiński     if (next_is_reaper) {
350*a03ca8b9SKrzysztof Kosiński       // Discard all equivalences strictly between |cur| and |next|.
351*a03ca8b9SKrzysztof Kosiński       for (auto reduced = current + 1; reduced != next; ++reduced)
352*a03ca8b9SKrzysztof Kosiński         reduced->length = 0;
353*a03ca8b9SKrzysztof Kosiński       current = next - 1;
354*a03ca8b9SKrzysztof Kosiński     } else {
355*a03ca8b9SKrzysztof Kosiński       // Shrink all equivalences that overlap with |current|. These are all
356*a03ca8b9SKrzysztof Kosiński       // worse than |current| since no reaper is found.
357*a03ca8b9SKrzysztof Kosiński       for (auto reduced = current + 1; reduced != next; ++reduced) {
358*a03ca8b9SKrzysztof Kosiński         offset_t delta = current->src_end() - reduced->src_offset;
359*a03ca8b9SKrzysztof Kosiński         reduced->length -= std::min(reduced->length, delta);
360*a03ca8b9SKrzysztof Kosiński         reduced->src_offset += delta;
361*a03ca8b9SKrzysztof Kosiński         reduced->dst_offset += delta;
362*a03ca8b9SKrzysztof Kosiński         DCHECK_EQ(reduced->src_offset, current->src_end());
363*a03ca8b9SKrzysztof Kosiński       }
364*a03ca8b9SKrzysztof Kosiński     }
365*a03ca8b9SKrzysztof Kosiński   }
366*a03ca8b9SKrzysztof Kosiński 
367*a03ca8b9SKrzysztof Kosiński   // Discard all equivalences with length == 0.
368*a03ca8b9SKrzysztof Kosiński   base::EraseIf(*equivalences, [](const Equivalence& equivalence) {
369*a03ca8b9SKrzysztof Kosiński     return equivalence.length == 0;
370*a03ca8b9SKrzysztof Kosiński   });
371*a03ca8b9SKrzysztof Kosiński   equivalences->shrink_to_fit();
372*a03ca8b9SKrzysztof Kosiński }
373*a03ca8b9SKrzysztof Kosiński 
374*a03ca8b9SKrzysztof Kosiński /******** EquivalenceMap ********/
375*a03ca8b9SKrzysztof Kosiński 
376*a03ca8b9SKrzysztof Kosiński EquivalenceMap::EquivalenceMap() = default;
377*a03ca8b9SKrzysztof Kosiński 
EquivalenceMap(std::vector<EquivalenceCandidate> && equivalences)378*a03ca8b9SKrzysztof Kosiński EquivalenceMap::EquivalenceMap(std::vector<EquivalenceCandidate>&& equivalences)
379*a03ca8b9SKrzysztof Kosiński     : candidates_(std::move(equivalences)) {
380*a03ca8b9SKrzysztof Kosiński   SortByDestination();
381*a03ca8b9SKrzysztof Kosiński }
382*a03ca8b9SKrzysztof Kosiński 
383*a03ca8b9SKrzysztof Kosiński EquivalenceMap::EquivalenceMap(EquivalenceMap&&) = default;
384*a03ca8b9SKrzysztof Kosiński 
385*a03ca8b9SKrzysztof Kosiński EquivalenceMap::~EquivalenceMap() = default;
386*a03ca8b9SKrzysztof Kosiński 
Build(const std::vector<offset_t> & old_sa,const EncodedView & old_view,const EncodedView & new_view,const std::vector<TargetsAffinity> & targets_affinities,double min_similarity)387*a03ca8b9SKrzysztof Kosiński void EquivalenceMap::Build(
388*a03ca8b9SKrzysztof Kosiński     const std::vector<offset_t>& old_sa,
389*a03ca8b9SKrzysztof Kosiński     const EncodedView& old_view,
390*a03ca8b9SKrzysztof Kosiński     const EncodedView& new_view,
391*a03ca8b9SKrzysztof Kosiński     const std::vector<TargetsAffinity>& targets_affinities,
392*a03ca8b9SKrzysztof Kosiński     double min_similarity) {
393*a03ca8b9SKrzysztof Kosiński   DCHECK_EQ(old_sa.size(), old_view.size());
394*a03ca8b9SKrzysztof Kosiński 
395*a03ca8b9SKrzysztof Kosiński   CreateCandidates(old_sa, old_view, new_view, targets_affinities,
396*a03ca8b9SKrzysztof Kosiński                    min_similarity);
397*a03ca8b9SKrzysztof Kosiński   SortByDestination();
398*a03ca8b9SKrzysztof Kosiński   Prune(old_view, new_view, targets_affinities, min_similarity);
399*a03ca8b9SKrzysztof Kosiński 
400*a03ca8b9SKrzysztof Kosiński   offset_t coverage = 0;
401*a03ca8b9SKrzysztof Kosiński   offset_t current_offset = 0;
402*a03ca8b9SKrzysztof Kosiński   for (auto candidate : candidates_) {
403*a03ca8b9SKrzysztof Kosiński     DCHECK_GE(candidate.eq.dst_offset, current_offset);
404*a03ca8b9SKrzysztof Kosiński     coverage += candidate.eq.length;
405*a03ca8b9SKrzysztof Kosiński     current_offset = candidate.eq.dst_end();
406*a03ca8b9SKrzysztof Kosiński   }
407*a03ca8b9SKrzysztof Kosiński   LOG(INFO) << "Equivalence Count: " << size();
408*a03ca8b9SKrzysztof Kosiński   LOG(INFO) << "Coverage / Extra / Total: " << coverage << " / "
409*a03ca8b9SKrzysztof Kosiński             << new_view.size() - coverage << " / " << new_view.size();
410*a03ca8b9SKrzysztof Kosiński }
411*a03ca8b9SKrzysztof Kosiński 
CreateCandidates(const std::vector<offset_t> & old_sa,const EncodedView & old_view,const EncodedView & new_view,const std::vector<TargetsAffinity> & targets_affinities,double min_similarity)412*a03ca8b9SKrzysztof Kosiński void EquivalenceMap::CreateCandidates(
413*a03ca8b9SKrzysztof Kosiński     const std::vector<offset_t>& old_sa,
414*a03ca8b9SKrzysztof Kosiński     const EncodedView& old_view,
415*a03ca8b9SKrzysztof Kosiński     const EncodedView& new_view,
416*a03ca8b9SKrzysztof Kosiński     const std::vector<TargetsAffinity>& targets_affinities,
417*a03ca8b9SKrzysztof Kosiński     double min_similarity) {
418*a03ca8b9SKrzysztof Kosiński   candidates_.clear();
419*a03ca8b9SKrzysztof Kosiński 
420*a03ca8b9SKrzysztof Kosiński   // This is an heuristic to find 'good' equivalences on encoded views.
421*a03ca8b9SKrzysztof Kosiński   // Equivalences are found in ascending order of |new_image|.
422*a03ca8b9SKrzysztof Kosiński   offset_t dst_offset = 0;
423*a03ca8b9SKrzysztof Kosiński 
424*a03ca8b9SKrzysztof Kosiński   while (dst_offset < new_view.size()) {
425*a03ca8b9SKrzysztof Kosiński     if (!new_view.IsToken(dst_offset)) {
426*a03ca8b9SKrzysztof Kosiński       ++dst_offset;
427*a03ca8b9SKrzysztof Kosiński       continue;
428*a03ca8b9SKrzysztof Kosiński     }
429*a03ca8b9SKrzysztof Kosiński     auto match =
430*a03ca8b9SKrzysztof Kosiński         SuffixLowerBound(old_sa, old_view.begin(),
431*a03ca8b9SKrzysztof Kosiński                          new_view.begin() + dst_offset, new_view.end());
432*a03ca8b9SKrzysztof Kosiński 
433*a03ca8b9SKrzysztof Kosiński     offset_t next_dst_offset = dst_offset + 1;
434*a03ca8b9SKrzysztof Kosiński     // TODO(huangs): Clean up.
435*a03ca8b9SKrzysztof Kosiński     double best_similarity = min_similarity;
436*a03ca8b9SKrzysztof Kosiński     uint64_t total_visit_length = 0;
437*a03ca8b9SKrzysztof Kosiński     EquivalenceCandidate best_candidate = {{0, 0, 0}, 0.0};
438*a03ca8b9SKrzysztof Kosiński     for (auto it = match; it != old_sa.end(); ++it) {
439*a03ca8b9SKrzysztof Kosiński       EquivalenceCandidate candidate = VisitEquivalenceSeed(
440*a03ca8b9SKrzysztof Kosiński           old_view.image_index(), new_view.image_index(), targets_affinities,
441*a03ca8b9SKrzysztof Kosiński           static_cast<offset_t>(*it), dst_offset, min_similarity);
442*a03ca8b9SKrzysztof Kosiński       if (candidate.similarity > best_similarity) {
443*a03ca8b9SKrzysztof Kosiński         best_candidate = candidate;
444*a03ca8b9SKrzysztof Kosiński         best_similarity = candidate.similarity;
445*a03ca8b9SKrzysztof Kosiński         next_dst_offset = candidate.eq.dst_end();
446*a03ca8b9SKrzysztof Kosiński         total_visit_length += candidate.eq.length;
447*a03ca8b9SKrzysztof Kosiński         if (total_visit_length > kSeedSelectionTotalVisitLengthQuota) {
448*a03ca8b9SKrzysztof Kosiński           break;
449*a03ca8b9SKrzysztof Kosiński         }
450*a03ca8b9SKrzysztof Kosiński       } else {
451*a03ca8b9SKrzysztof Kosiński         break;
452*a03ca8b9SKrzysztof Kosiński       }
453*a03ca8b9SKrzysztof Kosiński     }
454*a03ca8b9SKrzysztof Kosiński     total_visit_length = 0;
455*a03ca8b9SKrzysztof Kosiński     for (auto it = match; it != old_sa.begin(); --it) {
456*a03ca8b9SKrzysztof Kosiński       EquivalenceCandidate candidate = VisitEquivalenceSeed(
457*a03ca8b9SKrzysztof Kosiński           old_view.image_index(), new_view.image_index(), targets_affinities,
458*a03ca8b9SKrzysztof Kosiński           static_cast<offset_t>(it[-1]), dst_offset, min_similarity);
459*a03ca8b9SKrzysztof Kosiński       if (candidate.similarity > best_similarity) {
460*a03ca8b9SKrzysztof Kosiński         best_candidate = candidate;
461*a03ca8b9SKrzysztof Kosiński         best_similarity = candidate.similarity;
462*a03ca8b9SKrzysztof Kosiński         next_dst_offset = candidate.eq.dst_end();
463*a03ca8b9SKrzysztof Kosiński         total_visit_length += candidate.eq.length;
464*a03ca8b9SKrzysztof Kosiński         if (total_visit_length > kSeedSelectionTotalVisitLengthQuota) {
465*a03ca8b9SKrzysztof Kosiński           break;
466*a03ca8b9SKrzysztof Kosiński         }
467*a03ca8b9SKrzysztof Kosiński       } else {
468*a03ca8b9SKrzysztof Kosiński         break;
469*a03ca8b9SKrzysztof Kosiński       }
470*a03ca8b9SKrzysztof Kosiński     }
471*a03ca8b9SKrzysztof Kosiński     if (best_candidate.similarity >= min_similarity) {
472*a03ca8b9SKrzysztof Kosiński       candidates_.push_back(best_candidate);
473*a03ca8b9SKrzysztof Kosiński     }
474*a03ca8b9SKrzysztof Kosiński 
475*a03ca8b9SKrzysztof Kosiński     dst_offset = next_dst_offset;
476*a03ca8b9SKrzysztof Kosiński   }
477*a03ca8b9SKrzysztof Kosiński }
478*a03ca8b9SKrzysztof Kosiński 
SortByDestination()479*a03ca8b9SKrzysztof Kosiński void EquivalenceMap::SortByDestination() {
480*a03ca8b9SKrzysztof Kosiński   std::sort(candidates_.begin(), candidates_.end(),
481*a03ca8b9SKrzysztof Kosiński             [](const EquivalenceCandidate& a, const EquivalenceCandidate& b) {
482*a03ca8b9SKrzysztof Kosiński               return a.eq.dst_offset < b.eq.dst_offset;
483*a03ca8b9SKrzysztof Kosiński             });
484*a03ca8b9SKrzysztof Kosiński }
485*a03ca8b9SKrzysztof Kosiński 
Prune(const EncodedView & old_view,const EncodedView & new_view,const std::vector<TargetsAffinity> & target_affinities,double min_similarity)486*a03ca8b9SKrzysztof Kosiński void EquivalenceMap::Prune(
487*a03ca8b9SKrzysztof Kosiński     const EncodedView& old_view,
488*a03ca8b9SKrzysztof Kosiński     const EncodedView& new_view,
489*a03ca8b9SKrzysztof Kosiński     const std::vector<TargetsAffinity>& target_affinities,
490*a03ca8b9SKrzysztof Kosiński     double min_similarity) {
491*a03ca8b9SKrzysztof Kosiński   // TODO(etiennep): unify with
492*a03ca8b9SKrzysztof Kosiński   // OffsetMapper::PruneEquivalencesAndSortBySource().
493*a03ca8b9SKrzysztof Kosiński   for (auto current = candidates_.begin(); current != candidates_.end();
494*a03ca8b9SKrzysztof Kosiński        ++current) {
495*a03ca8b9SKrzysztof Kosiński     if (current->similarity < min_similarity)
496*a03ca8b9SKrzysztof Kosiński       continue;  // This candidate will be discarded anyways.
497*a03ca8b9SKrzysztof Kosiński 
498*a03ca8b9SKrzysztof Kosiński     bool next_is_reaper = false;
499*a03ca8b9SKrzysztof Kosiński 
500*a03ca8b9SKrzysztof Kosiński     // Look ahead to resolve overlaps, until a better candidate is found.
501*a03ca8b9SKrzysztof Kosiński     auto next = current + 1;
502*a03ca8b9SKrzysztof Kosiński     for (; next != candidates_.end(); ++next) {
503*a03ca8b9SKrzysztof Kosiński       DCHECK_GE(next->eq.dst_offset, current->eq.dst_offset);
504*a03ca8b9SKrzysztof Kosiński       if (next->eq.dst_offset >= current->eq.dst_offset + current->eq.length)
505*a03ca8b9SKrzysztof Kosiński         break;  // No more overlap.
506*a03ca8b9SKrzysztof Kosiński 
507*a03ca8b9SKrzysztof Kosiński       if (current->similarity < next->similarity) {
508*a03ca8b9SKrzysztof Kosiński         // |next| is better: So it is a reaper that shrinks |current|.
509*a03ca8b9SKrzysztof Kosiński         offset_t delta = current->eq.dst_end() - next->eq.dst_offset;
510*a03ca8b9SKrzysztof Kosiński         current->eq.length -= delta;
511*a03ca8b9SKrzysztof Kosiński         current->similarity = GetEquivalenceSimilarity(
512*a03ca8b9SKrzysztof Kosiński             old_view.image_index(), new_view.image_index(), target_affinities,
513*a03ca8b9SKrzysztof Kosiński             current->eq);
514*a03ca8b9SKrzysztof Kosiński 
515*a03ca8b9SKrzysztof Kosiński         next_is_reaper = true;
516*a03ca8b9SKrzysztof Kosiński         break;
517*a03ca8b9SKrzysztof Kosiński       }
518*a03ca8b9SKrzysztof Kosiński     }
519*a03ca8b9SKrzysztof Kosiński 
520*a03ca8b9SKrzysztof Kosiński     if (next_is_reaper) {
521*a03ca8b9SKrzysztof Kosiński       // Discard all equivalences strictly between |cur| and |next|.
522*a03ca8b9SKrzysztof Kosiński       for (auto reduced = current + 1; reduced != next; ++reduced) {
523*a03ca8b9SKrzysztof Kosiński         reduced->eq.length = 0;
524*a03ca8b9SKrzysztof Kosiński         reduced->similarity = 0;
525*a03ca8b9SKrzysztof Kosiński       }
526*a03ca8b9SKrzysztof Kosiński       current = next - 1;
527*a03ca8b9SKrzysztof Kosiński     } else {
528*a03ca8b9SKrzysztof Kosiński       // Shrinks all overlapping candidates following and worse than |current|.
529*a03ca8b9SKrzysztof Kosiński       for (auto reduced = current + 1; reduced != next; ++reduced) {
530*a03ca8b9SKrzysztof Kosiński         offset_t delta = current->eq.dst_end() - reduced->eq.dst_offset;
531*a03ca8b9SKrzysztof Kosiński         reduced->eq.length -= std::min(reduced->eq.length, delta);
532*a03ca8b9SKrzysztof Kosiński         reduced->eq.src_offset += delta;
533*a03ca8b9SKrzysztof Kosiński         reduced->eq.dst_offset += delta;
534*a03ca8b9SKrzysztof Kosiński         reduced->similarity = GetEquivalenceSimilarity(
535*a03ca8b9SKrzysztof Kosiński             old_view.image_index(), new_view.image_index(), target_affinities,
536*a03ca8b9SKrzysztof Kosiński             reduced->eq);
537*a03ca8b9SKrzysztof Kosiński         DCHECK_EQ(reduced->eq.dst_offset, current->eq.dst_end());
538*a03ca8b9SKrzysztof Kosiński       }
539*a03ca8b9SKrzysztof Kosiński     }
540*a03ca8b9SKrzysztof Kosiński   }
541*a03ca8b9SKrzysztof Kosiński 
542*a03ca8b9SKrzysztof Kosiński   // Discard all candidates with similarity smaller than |min_similarity|.
543*a03ca8b9SKrzysztof Kosiński   base::EraseIf(candidates_,
544*a03ca8b9SKrzysztof Kosiński                 [min_similarity](const EquivalenceCandidate& candidate) {
545*a03ca8b9SKrzysztof Kosiński                   return candidate.similarity < min_similarity;
546*a03ca8b9SKrzysztof Kosiński                 });
547*a03ca8b9SKrzysztof Kosiński }
548*a03ca8b9SKrzysztof Kosiński 
549*a03ca8b9SKrzysztof Kosiński }  // namespace zucchini
550