/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef SRC_PROFILING_MEMORY_INTERNER_H_ #define SRC_PROFILING_MEMORY_INTERNER_H_ #include <stddef.h> #include <stdint.h> #include <functional> #include <unordered_set> #include "perfetto/base/logging.h" namespace perfetto { namespace profiling { using InternID = uint64_t; template <typename T> class Interner { private: struct Entry { template <typename... U> Entry(Interner<T>* in, uint64_t i, U... args) : data(std::forward<U...>(args...)), id(i), interner(in) {} bool operator<(const Entry& other) const { return data < other.data; } bool operator==(const Entry& other) const { return data == other.data; } struct Hash { size_t operator()(const Entry& e) const noexcept { return std::hash<T>{}(e.data); } }; const T data; size_t ref_count = 0; uint64_t id; Interner<T>* interner; }; public: class Interned { public: friend class Interner<T>; Interned(Entry* entry) : entry_(entry) {} Interned(const Interned& other) : entry_(other.entry_) { if (entry_ != nullptr) entry_->ref_count++; } Interned(Interned&& other) noexcept : entry_(other.entry_) { other.entry_ = nullptr; } Interned& operator=(Interned other) noexcept { using std::swap; swap(*this, other); return *this; } const T& data() const { return entry_->data; } InternID id() const { return entry_->id; } ~Interned() { if (entry_ != nullptr) entry_->interner->Return(entry_); } bool operator<(const Interned& other) const { return entry_ < other.entry_; } bool operator==(const Interned& other) const { return entry_ == other.entry_; } const T* operator->() const { return &entry_->data; } private: Interner::Entry* entry_; }; template <typename... U> Interned Intern(U... args) { Entry item(this, next_id, std::forward<U...>(args...)); auto it = entries_.find(item); if (it == entries_.cend()) { // This does not invalidate pointers to entries we hold in Interned. See // https://timsong-cpp.github.io/cppwp/n3337/unord.req#8 auto it_and_inserted = entries_.emplace(std::move(item)); next_id++; it = it_and_inserted.first; PERFETTO_DCHECK(it_and_inserted.second); } Entry& entry = const_cast<Entry&>(*it); entry.ref_count++; return Interned(&entry); } ~Interner() { PERFETTO_DCHECK(entries_.empty()); } size_t entry_count_for_testing() { return entries_.size(); } private: void Return(Entry* entry) { if (--entry->ref_count == 0) entries_.erase(*entry); } uint64_t next_id = 1; std::unordered_set<Entry, typename Entry::Hash> entries_; static_assert(sizeof(Interned) == sizeof(void*), "interned things should be small"); }; template <typename T> void swap(typename Interner<T>::Interned a, typename Interner<T>::Interned b) { std::swap(a.entry_, b.entry_); } template <typename T> using Interned = typename Interner<T>::Interned; } // namespace profiling } // namespace perfetto #endif // SRC_PROFILING_MEMORY_INTERNER_H_