xref: /aosp_15_r20/external/perfetto/src/trace_processor/importers/common/address_range.h (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_ADDRESS_RANGE_H_
18 #define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_ADDRESS_RANGE_H_
19 
20 #include <algorithm>
21 #include <cstdint>
22 #include <iterator>
23 #include <map>
24 #include <set>
25 #include <tuple>
26 #include <utility>
27 
28 #include "perfetto/base/logging.h"
29 
30 namespace perfetto {
31 namespace trace_processor {
32 
33 // A range in the form [start, end), i.e. start is inclusive and end is
34 // exclusive.
35 // Note: This means that you can not have a range containing int64_max
36 class AddressRange {
37  public:
38   struct CompareByEnd {
39     // Allow heterogeneous lookups (https://abseil.io/tips/144)
40     using is_transparent = void;
41     // Keeps ranges sorted by end address
operatorCompareByEnd42     bool operator()(const AddressRange& lhs, const AddressRange& rhs) const {
43       return lhs.end() < rhs.end();
44     }
45 
46     // Overload to implement PC lookup via upper_bound.
operatorCompareByEnd47     bool operator()(const AddressRange& lhs, uint64_t pc) const {
48       return lhs.end() < pc;
49     }
50 
51     // Overload to implement PC lookup via upper_bound.
operatorCompareByEnd52     bool operator()(uint64_t pc, const AddressRange& rhs) const {
53       return pc < rhs.end();
54     }
55   };
56 
FromStartAndSize(uint64_t start,uint64_t size)57   static constexpr AddressRange FromStartAndSize(uint64_t start,
58                                                  uint64_t size) {
59     return AddressRange(start, start + size);
60   }
AddressRange()61   constexpr AddressRange() : AddressRange(0, 0) {}
62 
AddressRange(uint64_t start,uint64_t end)63   constexpr AddressRange(uint64_t start, uint64_t end)
64       : start_(start), end_(end) {
65     PERFETTO_CHECK(start <= end);
66   }
67 
68   // Checks whether the given `addr` lies withing this range.
Contains(uint64_t addr)69   constexpr bool Contains(uint64_t addr) const {
70     return start_ <= addr && addr < end_;
71   }
72 
73   // Checks whether the given `other` range is fully contained in this range.
Contains(const AddressRange & other)74   constexpr bool Contains(const AddressRange& other) const {
75     return start_ <= other.start_ && other.end_ <= end_;
76   }
77 
78   // Computes the intersection of the two ranges, that is, it returns a range
79   // with all the points in common between the two.
IntersectWith(const AddressRange & other)80   constexpr AddressRange IntersectWith(const AddressRange& other) const {
81     auto start = std::max(start_, other.start_);
82     auto end = std::min(end_, other.end_);
83     return start < end ? AddressRange(start, end) : AddressRange();
84   }
85 
86   // Checks whether there is any overlap between the two ranges, that it, if
87   // there exists a point such that Contains(point) would return true for both
88   // ranges.
Overlaps(const AddressRange & other)89   constexpr bool Overlaps(const AddressRange& other) const {
90     return !empty() && !other.empty() && start_ < other.end_ &&
91            other.start_ < end_;
92   }
93 
94   // Two ranges are the same is their respective limits are the same, that is A
95   // contains A and B contains A
96   constexpr bool operator==(const AddressRange& other) const {
97     return start_ == other.start_ && end_ == other.end_;
98   }
99   constexpr bool operator!=(const AddressRange& other) const {
100     return !(*this == other);
101   }
102 
103   // Start of range, inclusive
start()104   constexpr uint64_t start() const { return start_; }
105   // Start of range, exclusive
end()106   constexpr uint64_t end() const { return end_; }
107 
length()108   constexpr uint64_t length() const { return end_ - start_; }
size()109   constexpr uint64_t size() const { return end_ - start_; }
110 
111   // Check whether the length is zero, that is no point will is contained by
112   // this range.
empty()113   constexpr bool empty() const { return length() == 0; }
114 
115  private:
116   uint64_t start_;
117   uint64_t end_;
118 };
119 
120 // Contains unique collection of addresses. These addresses are kept as
121 // sorted collection of non contiguous and non overlapping AddressRange
122 // instances. As addresses are added or removed these AddressRange might be
123 // merged or spliced as needed to keep the ranges non contiguous and non
124 // overlapping.
125 class AddressSet {
126  public:
127   // TODO(carlscab): Consider using base::FlatSet. As of now this class is used
128   // so little that it does not really matter.
129   using Impl = std::set<AddressRange, AddressRange::CompareByEnd>;
130 
131   using value_type = typename Impl::value_type;
132   using const_iterator = typename Impl::const_iterator;
133   using size_type = typename Impl::size_type;
134 
begin()135   const_iterator begin() const { return ranges_.begin(); }
end()136   const_iterator end() const { return ranges_.end(); }
137 
138   // Adds all the addresses in the given range to the set.
Add(AddressRange range)139   void Add(AddressRange range) {
140     if (range.empty()) {
141       return;
142     }
143     uint64_t start = range.start();
144     uint64_t end = range.end();
145     // Note lower_bound here as we might need to merge with the range just
146     // before.
147     auto it = ranges_.lower_bound(start);
148 
149     PERFETTO_DCHECK(it == ranges_.end() || range.start() <= it->end());
150 
151     while (it != ranges_.end() && range.end() >= it->start()) {
152       start = std::min(start, it->start());
153       end = std::max(end, it->end());
154       it = ranges_.erase(it);
155     }
156     ranges_.emplace_hint(it, AddressRange(start, end));
157   }
158 
159   // Removes all the addresses in the given range from the set.
Remove(AddressRange range)160   void Remove(AddressRange range) {
161     if (range.empty()) {
162       return;
163     }
164     auto it = ranges_.upper_bound(range.start());
165     PERFETTO_DCHECK(it == ranges_.end() || range.start() < it->end());
166 
167     while (it != ranges_.end() && range.end() > it->start()) {
168       if (range.start() > it->start()) {
169         // range.start() is contained in *it. Split *it at range.start() into
170         // two ranges. Continue the loop at the second of them.
171         PERFETTO_DCHECK(it->Contains(range.start()));
172         auto old = *it;
173         it = ranges_.erase(it);
174         ranges_.emplace_hint(it, old.start(), range.start());
175         it = ranges_.emplace_hint(it, range.start(), old.end());
176       } else if (range.end() < it->end()) {
177         // range.end() is contained in *it. Split *it at range.end() into two
178         // ranges. The first of them needs to be deleted.
179         PERFETTO_DCHECK(it->Contains(range.end()));
180         auto old_end = it->end();
181         it = ranges_.erase(it);
182         ranges_.emplace_hint(it, range.end(), old_end);
183       } else {
184         // range fully contains *it, so it can be removed
185         PERFETTO_DCHECK(range.Contains(*it));
186         it = ranges_.erase(it);
187       }
188     }
189   }
190 
191   bool operator==(const AddressSet& o) const { return ranges_ == o.ranges_; }
192   bool operator!=(const AddressSet& o) const { return ranges_ != o.ranges_; }
193 
194  private:
195   // Invariants:
196   //   * There are no overlapping ranges.
197   //   * There are no empty ranges.
198   //   * There are no two ranges a, b where a.end == b.start, that is there are
199   //     no contiguous mappings.
200   //   * Ranges are sorted by end
201   // Thus lookups are O(log N) and point lookups are trivial using upper_bound()
202   Impl ranges_;
203 };
204 
205 // Maps AddressRange instances to a given value. These AddressRange instances
206 // (basically the keys of the map)  will never overlap, as insertions of
207 // overlapping ranges will always fail.
208 template <typename Value>
209 class AddressRangeMap {
210  public:
211   using Impl = std::map<AddressRange, Value, AddressRange::CompareByEnd>;
212 
213   using value_type = typename Impl::value_type;
214   using iterator = typename Impl::iterator;
215   using const_iterator = typename Impl::const_iterator;
216   using size_type = typename Impl::size_type;
217 
218   // Fails if the new range overlaps with any existing one or when inserting an
219   // empty range (as there is effectively no key to map from).
220   template <typename... Args>
Emplace(AddressRange range,Args &&...args)221   bool Emplace(AddressRange range, Args&&... args) {
222     if (range.empty()) {
223       return false;
224     }
225     auto it = ranges_.upper_bound(range.start());
226     if (it != ranges_.end() && range.end() > it->first.start()) {
227       return false;
228     }
229     ranges_.emplace_hint(it, std::piecewise_construct,
230                          std::forward_as_tuple(range),
231                          std::forward_as_tuple(std::forward<Args>(args)...));
232     return true;
233   }
234 
235   // Finds the map entry that fully contains the given `range` or `end()` if not
236   // such entry can be found.
237   // ATTENTION: `range` can not be empty. Strictly speaking any range contains
238   // the empty range but that would mean we need to return all the ranges here.
239   // So we chose to just ban that case.
FindRangeThatContains(AddressRange range)240   iterator FindRangeThatContains(AddressRange range) {
241     PERFETTO_CHECK(!range.empty());
242     auto it = Find(range.start());
243     if (it != end() && it->first.end() >= range.end()) {
244       return it;
245     }
246     return end();
247   }
248 
249   // Finds the range that contains a given address.
Find(uint64_t address)250   iterator Find(uint64_t address) {
251     auto it = ranges_.upper_bound(address);
252     if (it != ranges_.end() && address >= it->first.start()) {
253       return it;
254     }
255     return end();
256   }
257 
258   // Finds the range that contains a given address.
Find(uint64_t address)259   const_iterator Find(uint64_t address) const {
260     auto it = ranges_.upper_bound(address);
261     if (it != ranges_.end() && address >= it->first.start()) {
262       return it;
263     }
264     return end();
265   }
266 
267   // std::map like methods
268 
empty()269   bool empty() const { return ranges_.empty(); }
size()270   bool size() const { return ranges_.size(); }
begin()271   iterator begin() { return ranges_.begin(); }
begin()272   const_iterator begin() const { return ranges_.begin(); }
end()273   iterator end() { return ranges_.end(); }
end()274   const_iterator end() const { return ranges_.end(); }
erase(const_iterator pos)275   iterator erase(const_iterator pos) { return ranges_.erase(pos); }
276 
277   // Emplaces a new value into the map by first trimming all overlapping
278   // intervals, deleting them if the overlap is fully contained in the new
279   // range, and splitting into two entries pointing to the same value if a
280   // single entry fully contains the new range. Returns true on success, fails
281   // if the new range is empty (as there is effectively no key to map from).
282   template <typename... Args>
TrimOverlapsAndEmplace(AddressRange range,Args &&...args)283   bool TrimOverlapsAndEmplace(AddressRange range, Args&&... args) {
284     if (range.empty()) {
285       return false;
286     }
287     auto it = ranges_.upper_bound(range.start());
288     PERFETTO_DCHECK(it == ranges_.end() || range.start() < it->first.end());
289 
290     // First check if we need to trim the first overlapping range, if any.
291     if (it != ranges_.end() && it->first.start() < range.start()) {
292       // Range starts after `it->first` starts, but before `it->first` ends, and
293       // so overlaps it:
294       //   it->first:   |-----------?
295       //       range:        |------?
296       PERFETTO_DCHECK(it->first.Overlaps(range));
297 
298       // Cache it->first since we'll be mutating it in TrimEntryRange.
299       AddressRange existing_range = it->first;
300 
301       // Trim the first overlap to end at the start of the range.
302       //   it->first:   |----|
303       //       range:        |------?
304       it = TrimEntryRange(it,
305                           AddressRange(existing_range.start(), range.start()));
306 
307       if (range.end() < existing_range.end()) {
308         // Range also ends before existing_range, thus strictly containing it.
309         //   existing_range:   |-----------|    (previously it->first)
310         //            range:        |----|
311         PERFETTO_DCHECK(existing_range.Contains(range));
312 
313         // In this special case, we need to split existing_range into two
314         // ranges, with the same value, and insert the new range between them:
315         //        it->first:   |----|
316         //            range:        |----|
317         //             tail:             |-|
318         // We've already trimmed existing_range (as it->first), so just add
319         // the tail now.
320 
321         AddressRange tail(range.end(), existing_range.end());
322         ranges_.emplace_hint(std::next(it, 1), tail, Value(it->second));
323       }
324 
325       // After trimming, the current iterated range is now before the new
326       // range. This means it no longer ends after the new range starts, and we
327       // need to advance the iterator to the new upper_bound.
328       ++it;
329       PERFETTO_DCHECK(it == ranges_.upper_bound(range.start()));
330     }
331 
332     // Now, check for any ranges which are _fully_ contained inside
333     // the existing range.
334     while (it != ranges_.end() && it->first.end() <= range.end()) {
335       // Range fully contains `it->first`:
336       //   it->first:     |----|
337       //       range:   |-----------|
338       //
339       // We're testing for whether it ends after it->first, and we know it
340       // starts before it->first (because we've already handled the first
341       // overlap), so this existing range is fully contained inside the new
342       // range
343       PERFETTO_DCHECK(range.Contains(it->first));
344       it = ranges_.erase(it);
345     }
346 
347     // Finally, check if we need to trim the last range. We know that it
348     // ends after the new range, but it might also start after the new
349     // range, or we might have reached the end, so this is really a check for
350     // overlap.
351     if (it != ranges_.end() && it->first.start() < range.end()) {
352       // Range overlaps with it->first, and ends before `it->first`:
353       //   it->first:     |----------|
354       //       range:   |-----|
355       PERFETTO_DCHECK(range.Overlaps(it->first));
356 
357       // Trim this overlap to end after the end of the range, and insert it
358       // after where the range will be inserted.
359       //       range:   |-----|
360       //   it->first:         |-----|
361       it = TrimEntryRange(it, AddressRange(range.end(), it->first.end()));
362 
363       // `it` now points to the newly trimmed range, which is _after_ where
364       // we want to insert the new range. This is what we want as an insertion
365       // hint, so keep it as is.
366     }
367 
368     ranges_.emplace_hint(it, std::piecewise_construct,
369                          std::forward_as_tuple(range),
370                          std::forward_as_tuple(std::forward<Args>(args)...));
371 
372     return true;
373   }
374 
375   // Emplaces a new value into the map by first deleting all overlapping
376   // intervals. It takes an optional (set to nullptr to ignore) callback `cb`
377   // that will be called for each deleted map entry.
378   // Returns true on success, fails if the new range is empty (as there is
379   // effectively no key to map from).
380   template <typename Callback, typename... Args>
DeleteOverlapsAndEmplace(Callback cb,AddressRange range,Args &&...args)381   bool DeleteOverlapsAndEmplace(Callback cb,
382                                 AddressRange range,
383                                 Args&&... args) {
384     if (range.empty()) {
385       return false;
386     }
387     auto it = ranges_.upper_bound(range.start());
388     PERFETTO_DCHECK(it == ranges_.end() || range.start() < it->first.end());
389 
390     while (it != ranges_.end() && range.end() > it->first.start()) {
391       cb(*it);
392       it = ranges_.erase(it);
393     }
394 
395     ranges_.emplace_hint(it, std::piecewise_construct,
396                          std::forward_as_tuple(range),
397                          std::forward_as_tuple(std::forward<Args>(args)...));
398     return true;
399   }
400 
401   // Calls `cb` for each entry in the map that overlaps the given `range`. That
402   // is, there is a point so for which `AddressRange::Contains` returns true for
403   // both the entry and the given `range'
404   template <typename Callback>
ForOverlaps(AddressRange range,Callback cb)405   void ForOverlaps(AddressRange range, Callback cb) {
406     if (range.empty()) {
407       return;
408     }
409     for (auto it = ranges_.upper_bound(range.start());
410          it != ranges_.end() && range.end() > it->first.start(); ++it) {
411       cb(*it);
412     }
413   }
414 
415  private:
416   // Trim an entry's address range to a new value. The new value must be fully
417   // contained inside the existing range's value, to guarantee that the ranges
418   // stay in the same order. Returns a new iterator to the trimmed entry, since
419   // the trimming process invalidates the iterator.
TrimEntryRange(typename Impl::iterator it,AddressRange new_range)420   typename Impl::iterator TrimEntryRange(typename Impl::iterator it,
421                                          AddressRange new_range) {
422     PERFETTO_DCHECK(it->first.Contains(new_range));
423     PERFETTO_DCHECK(!new_range.empty());
424 
425     // Advance the iterator so that it stays valid -- it now also conveniently
426     // points to the entry after the current entry, which is exactly the hint we
427     // want for re-inserting in the same place.
428     auto extracted = ranges_.extract(it++);
429     extracted.key() = new_range;
430     // Reinsert in the same place, using the advanced iterator as a hint.
431     return ranges_.insert(it, std::move(extracted));
432   }
433 
434   // Invariants:
435   //   * There are no overlapping ranges.
436   //   * There are no empty ranges.
437   //   * Ranges are sorted by end
438   // Thus lookups are O(log N) and point lookups are trivial using upper_bound()
439   Impl ranges_;
440 };
441 
442 }  // namespace trace_processor
443 }  // namespace perfetto
444 
445 #endif  // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_ADDRESS_RANGE_H_
446