• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 ART_LIBARTBASE_BASE_BIT_TABLE_H_
18 #define ART_LIBARTBASE_BASE_BIT_TABLE_H_
19 
20 #include <array>
21 #include <initializer_list>
22 #include <numeric>
23 #include <string.h>
24 #include <type_traits>
25 #include <unordered_map>
26 
27 #include "base/bit_memory_region.h"
28 #include "base/casts.h"
29 #include "base/iteration_range.h"
30 #include "base/memory_region.h"
31 #include "base/scoped_arena_containers.h"
32 #include "base/stl_util.h"
33 
34 namespace art {
35 
36 // Generic purpose table of uint32_t values, which are tightly packed at bit level.
37 // It has its own header with the number of rows and the bit-widths of all columns.
38 // The values are accessible by (row, column).  The value -1 is stored efficiently.
39 template<uint32_t kNumColumns>
40 class BitTableBase {
41  public:
42   static constexpr uint32_t kNoValue = std::numeric_limits<uint32_t>::max();  // == -1.
43   static constexpr uint32_t kValueBias = kNoValue;  // Bias so that -1 is encoded as 0.
44 
BitTableBase()45   BitTableBase() {}
BitTableBase(BitMemoryReader & reader)46   explicit BitTableBase(BitMemoryReader& reader) {
47     Decode(reader);
48   }
49 
Decode(BitMemoryReader & reader)50   ALWAYS_INLINE void Decode(BitMemoryReader& reader) {
51     // Decode row count and column sizes from the table header.
52     std::array<uint32_t, 1+kNumColumns> header = reader.ReadInterleavedVarints<1+kNumColumns>();
53     num_rows_ = header[0];
54     column_offset_[0] = 0;
55     for (uint32_t i = 0; i < kNumColumns; i++) {
56       size_t column_end = column_offset_[i] + header[i + 1];
57       column_offset_[i + 1] = dchecked_integral_cast<uint16_t>(column_end);
58     }
59 
60     // Record the region which contains the table data and skip past it.
61     table_data_ = reader.ReadRegion(num_rows_ * NumRowBits());
62   }
63 
64   ALWAYS_INLINE uint32_t Get(uint32_t row, uint32_t column = 0) const {
65     DCHECK(table_data_.IsValid()) << "Table has not been loaded";
66     DCHECK_LT(row, num_rows_);
67     DCHECK_LT(column, kNumColumns);
68     size_t offset = row * NumRowBits() + column_offset_[column];
69     return table_data_.LoadBits(offset, NumColumnBits(column)) + kValueBias;
70   }
71 
72   ALWAYS_INLINE BitMemoryRegion GetBitMemoryRegion(uint32_t row, uint32_t column = 0) const {
73     DCHECK(table_data_.IsValid()) << "Table has not been loaded";
74     DCHECK_LT(row, num_rows_);
75     DCHECK_LT(column, kNumColumns);
76     size_t offset = row * NumRowBits() + column_offset_[column];
77     return table_data_.Subregion(offset, NumColumnBits(column));
78   }
79 
NumRows()80   uint32_t NumRows() const { return num_rows_; }
81 
NumRowBits()82   uint32_t NumRowBits() const { return column_offset_[kNumColumns]; }
83 
NumColumns()84   constexpr uint32_t NumColumns() const { return kNumColumns; }
85 
NumColumnBits(uint32_t column)86   uint32_t NumColumnBits(uint32_t column) const {
87     return column_offset_[column + 1] - column_offset_[column];
88   }
89 
DataBitSize()90   size_t DataBitSize() const { return table_data_.size_in_bits(); }
91 
Equals(const BitTableBase & other)92   bool Equals(const BitTableBase& other) const {
93     return num_rows_ == other.num_rows_ &&
94         std::equal(column_offset_, column_offset_ + kNumColumns, other.column_offset_) &&
95         BitMemoryRegion::Equals(table_data_, other.table_data_);
96   }
97 
98  protected:
99   BitMemoryRegion table_data_;
100   uint32_t num_rows_ = 0;
101   uint16_t column_offset_[kNumColumns + 1] = {};
102 };
103 
104 // Helper class which can be used to create BitTable accessors with named getters.
105 template<uint32_t NumColumns>
106 class BitTableAccessor {
107  public:
108   static constexpr uint32_t kNumColumns = NumColumns;
109   static constexpr uint32_t kNoValue = BitTableBase<kNumColumns>::kNoValue;
110 
111   BitTableAccessor() = default;
BitTableAccessor(const BitTableBase<kNumColumns> * table,uint32_t row)112   BitTableAccessor(const BitTableBase<kNumColumns>* table, uint32_t row)
113       : table_(table), row_(row) {
114     DCHECK(table_ != nullptr);
115   }
116 
Row()117   ALWAYS_INLINE uint32_t Row() const { return row_; }
118 
IsValid()119   ALWAYS_INLINE bool IsValid() const { return row_ < table_->NumRows(); }
120 
Equals(const BitTableAccessor & other)121   ALWAYS_INLINE bool Equals(const BitTableAccessor& other) {
122     return this->table_ == other.table_ && this->row_ == other.row_;
123   }
124 
125 // Helper macro to create constructors and per-table utilities in derived class.
126 #define BIT_TABLE_HEADER(NAME)                                                       \
127   using BitTableAccessor<kNumColumns>::BitTableAccessor; /* inherit constructors */  \
128   template<int COLUMN, int UNUSED /*needed to compile*/> struct ColumnName;          \
129   static constexpr const char* kTableName = #NAME;                                   \
130 
131 // Helper macro to create named column accessors in derived class.
132 #define BIT_TABLE_COLUMN(COLUMN, NAME)                                               \
133   static constexpr uint32_t k##NAME = COLUMN;                                        \
134   ALWAYS_INLINE uint32_t Get##NAME() const { return table_->Get(row_, COLUMN); }     \
135   ALWAYS_INLINE bool Has##NAME() const { return Get##NAME() != kNoValue; }           \
136   template<int UNUSED> struct ColumnName<COLUMN, UNUSED> {                           \
137     static constexpr const char* Value = #NAME;                                      \
138   };                                                                                 \
139 
140  protected:
141   const BitTableBase<kNumColumns>* table_ = nullptr;
142   uint32_t row_ = -1;
143 };
144 
145 // Template meta-programming helper.
146 template<typename Accessor, size_t... Columns>
GetBitTableColumnNamesImpl(std::index_sequence<Columns...>)147 static const char* const* GetBitTableColumnNamesImpl(std::index_sequence<Columns...>) {
148   static const char* names[] = { Accessor::template ColumnName<Columns, 0>::Value... };
149   return names;
150 }
151 
152 // Wrapper which makes it easier to use named accessors for the individual rows.
153 template<typename Accessor>
154 class BitTable : public BitTableBase<Accessor::kNumColumns> {
155  public:
156   class const_iterator {
157    public:
158     using iterator_category = std::random_access_iterator_tag;
159     using value_type = Accessor;
160     using difference_type = int32_t;
161     using pointer = void;
162     using reference = void;
const_iterator()163     const_iterator() {}
const_iterator(const BitTable * table,uint32_t row)164     const_iterator(const BitTable* table, uint32_t row) : table_(table), row_(row) {}
165     const_iterator operator+(difference_type n) { return const_iterator(table_, row_ + n); }
166     const_iterator operator-(difference_type n) { return const_iterator(table_, row_ - n); }
167     difference_type operator-(const const_iterator& other) { return row_ - other.row_; }
168     void operator+=(difference_type rows) { row_ += rows; }
169     void operator-=(difference_type rows) { row_ -= rows; }
170     const_iterator operator++() { return const_iterator(table_, ++row_); }
171     const_iterator operator--() { return const_iterator(table_, --row_); }
172     const_iterator operator++(int) { return const_iterator(table_, row_++); }
173     const_iterator operator--(int) { return const_iterator(table_, row_--); }
174     bool operator==(const_iterator i) const { DCHECK(table_ == i.table_); return row_ == i.row_; }
175     bool operator!=(const_iterator i) const { DCHECK(table_ == i.table_); return row_ != i.row_; }
176     bool operator<=(const_iterator i) const { DCHECK(table_ == i.table_); return row_ <= i.row_; }
177     bool operator>=(const_iterator i) const { DCHECK(table_ == i.table_); return row_ >= i.row_; }
178     bool operator<(const_iterator i) const { DCHECK(table_ == i.table_); return row_ < i.row_; }
179     bool operator>(const_iterator i) const { DCHECK(table_ == i.table_); return row_ > i.row_; }
180     Accessor operator*() {
181       DCHECK_LT(row_, table_->NumRows());
182       return Accessor(table_, row_);
183     }
184     Accessor operator->() {
185       DCHECK_LT(row_, table_->NumRows());
186       return Accessor(table_, row_);
187     }
188     Accessor operator[](size_t index) {
189       DCHECK_LT(row_ + index, table_->NumRows());
190       return Accessor(table_, row_ + index);
191     }
192 
193    private:
194     const BitTable* table_ = nullptr;
195     uint32_t row_ = 0;
196   };
197 
198   using BitTableBase<Accessor::kNumColumns>::BitTableBase;  // Constructors.
199 
begin()200   ALWAYS_INLINE const_iterator begin() const { return const_iterator(this, 0); }
end()201   ALWAYS_INLINE const_iterator end() const { return const_iterator(this, this->NumRows()); }
202 
GetRow(uint32_t row)203   ALWAYS_INLINE Accessor GetRow(uint32_t row) const {
204     return Accessor(this, row);
205   }
206 
GetInvalidRow()207   ALWAYS_INLINE Accessor GetInvalidRow() const {
208     return Accessor(this, static_cast<uint32_t>(-1));
209   }
210 
GetName()211   const char* GetName() const {
212     return Accessor::kTableName;
213   }
214 
GetColumnNames()215   const char* const* GetColumnNames() const {
216     return GetBitTableColumnNamesImpl<Accessor>(std::make_index_sequence<Accessor::kNumColumns>());
217   }
218 };
219 
220 template<typename Accessor>
221 typename BitTable<Accessor>::const_iterator operator+(
222     typename BitTable<Accessor>::const_iterator::difference_type n,
223     typename BitTable<Accessor>::const_iterator a) {
224   return a + n;
225 }
226 
227 template<typename Accessor>
228 class BitTableRange : public IterationRange<typename BitTable<Accessor>::const_iterator> {
229  public:
230   using const_iterator = typename BitTable<Accessor>::const_iterator;
231 
232   using IterationRange<const_iterator>::IterationRange;
BitTableRange()233   BitTableRange() : IterationRange<const_iterator>(const_iterator(), const_iterator()) { }
234 
empty()235   bool empty() const { return this->begin() == this->end(); }
size()236   size_t size() const { return this->end() - this->begin(); }
237 
238   Accessor operator[](size_t index) const {
239     const_iterator it = this->begin() + index;
240     DCHECK(it < this->end());
241     return *it;
242   }
243 
back()244   Accessor back() const {
245     DCHECK(!empty());
246     return *(this->end() - 1);
247   }
248 
pop_back()249   void pop_back() {
250     DCHECK(!empty());
251     --this->last_;
252   }
253 };
254 
255 // Helper class for encoding BitTable. It can optionally de-duplicate the inputs.
256 template<uint32_t kNumColumns>
257 class BitTableBuilderBase {
258  public:
259   static constexpr uint32_t kNoValue = BitTableBase<kNumColumns>::kNoValue;
260   static constexpr uint32_t kValueBias = BitTableBase<kNumColumns>::kValueBias;
261 
262   class Entry {
263    public:
Entry()264     Entry() {
265       // The definition of kLocalNoValue here is for host and target debug builds which
266       // complain about missing a symbol definition for BitTableBase<N>::kNovValue when
267       // optimization is off.
268       static constexpr uint32_t kLocalNoValue = BitTableBase<kNumColumns>::kNoValue;
269       std::fill_n(data_, kNumColumns, kLocalNoValue);
270     }
271 
Entry(std::initializer_list<uint32_t> values)272     Entry(std::initializer_list<uint32_t> values) {
273       DCHECK_EQ(values.size(), kNumColumns);
274       std::copy(values.begin(), values.end(), data_);
275     }
276 
277     uint32_t& operator[](size_t column) {
278       DCHECK_LT(column, kNumColumns);
279       return data_[column];
280     }
281 
282     uint32_t operator[](size_t column) const {
283       DCHECK_LT(column, kNumColumns);
284       return data_[column];
285     }
286 
287    private:
288     uint32_t data_[kNumColumns];
289   };
290 
BitTableBuilderBase(ScopedArenaAllocator * allocator)291   explicit BitTableBuilderBase(ScopedArenaAllocator* allocator)
292       : rows_(allocator->Adapter(kArenaAllocBitTableBuilder)),
293         dedup_(8, allocator->Adapter(kArenaAllocBitTableBuilder)) {
294   }
295 
296   Entry& operator[](size_t row) { return rows_[row]; }
297   const Entry& operator[](size_t row) const { return rows_[row]; }
back()298   const Entry& back() const { return rows_.back(); }
size()299   size_t size() const { return rows_.size(); }
300 
301   // Append given value to the vector without de-duplication.
302   // This will not add the element to the dedup map to avoid its associated costs.
Add(Entry value)303   void Add(Entry value) {
304     rows_.push_back(value);
305   }
306 
307   // Append given list of values and return the index of the first value.
308   // If the exact same set of values was already added, return the old index.
309   uint32_t Dedup(Entry* values, size_t count = 1) {
310     FNVHash<MemoryRegion> hasher;
311     uint32_t hash = hasher(MemoryRegion(values, sizeof(Entry) * count));
312 
313     // Check if we have already added identical set of values.
314     auto range = dedup_.equal_range(hash);
315     for (auto it = range.first; it != range.second; ++it) {
316       uint32_t index = it->second;
317       if (count <= size() - index &&
318           std::equal(values,
319                      values + count,
320                      rows_.begin() + index,
321                      [](const Entry& lhs, const Entry& rhs) {
322                        return memcmp(&lhs, &rhs, sizeof(Entry)) == 0;
323                      })) {
324         return index;
325       }
326     }
327 
328     // Add the set of values and add the index to the dedup map.
329     uint32_t index = size();
330     rows_.insert(rows_.end(), values, values + count);
331     dedup_.emplace(hash, index);
332     return index;
333   }
334 
Dedup(Entry value)335   uint32_t Dedup(Entry value) {
336     return Dedup(&value, /* count */ 1);
337   }
338 
339   // Calculate the column bit widths based on the current data.
Measure(uint32_t * column_bits)340   void Measure(/*out*/ uint32_t* column_bits) const {
341     uint32_t max_column_value[kNumColumns];
342     std::fill_n(max_column_value, kNumColumns, 0);
343     for (uint32_t r = 0; r < size(); r++) {
344       for (uint32_t c = 0; c < kNumColumns; c++) {
345         max_column_value[c] |= rows_[r][c] - kValueBias;
346       }
347     }
348     for (uint32_t c = 0; c < kNumColumns; c++) {
349       column_bits[c] = MinimumBitsToStore(max_column_value[c]);
350     }
351   }
352 
353   // Encode the stored data into a BitTable.
354   template<typename Vector>
Encode(BitMemoryWriter<Vector> & out)355   void Encode(BitMemoryWriter<Vector>& out) const {
356     size_t initial_bit_offset = out.NumberOfWrittenBits();
357 
358     // Write table header.
359     std::array<uint32_t, 1 + kNumColumns> header;
360     header[0] = size();
361     uint32_t* column_bits = header.data() + 1;
362     Measure(column_bits);
363     out.WriteInterleavedVarints(header);
364 
365     // Write table data.
366     for (uint32_t r = 0; r < size(); r++) {
367       for (uint32_t c = 0; c < kNumColumns; c++) {
368         out.WriteBits(rows_[r][c] - kValueBias, column_bits[c]);
369       }
370     }
371 
372     // Verify the written data.
373     if (kIsDebugBuild) {
374       BitTableBase<kNumColumns> table;
375       BitMemoryReader reader(out.GetWrittenRegion().Subregion(initial_bit_offset));
376       table.Decode(reader);
377       DCHECK_EQ(size(), table.NumRows());
378       for (uint32_t c = 0; c < kNumColumns; c++) {
379         DCHECK_EQ(column_bits[c], table.NumColumnBits(c));
380       }
381       for (uint32_t r = 0; r < size(); r++) {
382         for (uint32_t c = 0; c < kNumColumns; c++) {
383           DCHECK_EQ(rows_[r][c], table.Get(r, c)) << " (" << r << ", " << c << ")";
384         }
385       }
386     }
387   }
388 
389  protected:
390   ScopedArenaDeque<Entry> rows_;
391   ScopedArenaUnorderedMultimap<uint32_t, uint32_t> dedup_;  // Hash -> row index.
392 };
393 
394 template<typename Accessor>
395 class BitTableBuilder : public BitTableBuilderBase<Accessor::kNumColumns> {
396  public:
397   using BitTableBuilderBase<Accessor::kNumColumns>::BitTableBuilderBase;  // Constructors.
398 };
399 
400 // Helper class for encoding single-column BitTable of bitmaps (allows more than 32 bits).
401 class BitmapTableBuilder {
402  public:
BitmapTableBuilder(ScopedArenaAllocator * const allocator)403   explicit BitmapTableBuilder(ScopedArenaAllocator* const allocator)
404       : allocator_(allocator),
405         rows_(allocator->Adapter(kArenaAllocBitTableBuilder)),
406         dedup_(8, allocator_->Adapter(kArenaAllocBitTableBuilder)) {
407   }
408 
409   MemoryRegion operator[](size_t row) { return rows_[row]; }
410   const MemoryRegion operator[](size_t row) const { return rows_[row]; }
size()411   size_t size() const { return rows_.size(); }
412 
413   // Add the given bitmap to the table and return its index.
414   // If the bitmap was already added it will be deduplicated.
415   // The last bit must be set and any padding bits in the last byte must be zero.
Dedup(const void * bitmap,size_t num_bits)416   uint32_t Dedup(const void* bitmap, size_t num_bits) {
417     MemoryRegion region(const_cast<void*>(bitmap), BitsToBytesRoundUp(num_bits));
418     DCHECK(num_bits == 0 || BitMemoryRegion(region).LoadBit(num_bits - 1) == 1);
419     DCHECK_EQ(BitMemoryRegion(region).LoadBits(num_bits, region.size_in_bits() - num_bits), 0u);
420     FNVHash<MemoryRegion> hasher;
421     uint32_t hash = hasher(region);
422 
423     // Check if we have already added identical bitmap.
424     auto range = dedup_.equal_range(hash);
425     for (auto it = range.first; it != range.second; ++it) {
426       if (MemoryRegion::ContentEquals()(region, rows_[it->second])) {
427         return it->second;
428       }
429     }
430 
431     // Add the bitmap and add the index to the dedup map.
432     uint32_t index = size();
433     void* copy = allocator_->Alloc(region.size(), kArenaAllocBitTableBuilder);
434     memcpy(copy, region.pointer(), region.size());
435     rows_.push_back(MemoryRegion(copy, region.size()));
436     dedup_.emplace(hash, index);
437     max_num_bits_ = std::max(max_num_bits_, num_bits);
438     return index;
439   }
440 
441   // Encode the stored data into a BitTable.
442   template<typename Vector>
Encode(BitMemoryWriter<Vector> & out)443   void Encode(BitMemoryWriter<Vector>& out) const {
444     size_t initial_bit_offset = out.NumberOfWrittenBits();
445 
446     // Write table header.
447     out.WriteInterleavedVarints(std::array<uint32_t, 2>{
448       dchecked_integral_cast<uint32_t>(size()),
449       dchecked_integral_cast<uint32_t>(max_num_bits_),
450     });
451 
452     // Write table data.
453     for (MemoryRegion row : rows_) {
454       size_t bits_to_copy = std::min(max_num_bits_, row.size_in_bits());
455       BitMemoryRegion src(row, /*bit_offset=*/ 0u, bits_to_copy);
456       BitMemoryRegion dst = out.Allocate(max_num_bits_);
457       dst.Subregion(/*bit_offset=*/ 0, bits_to_copy).CopyBits(src);
458     }
459 
460     // Verify the written data.
461     if (kIsDebugBuild) {
462       BitTableBase<1> table;
463       BitMemoryReader reader(out.GetWrittenRegion().Subregion(initial_bit_offset));
464       table.Decode(reader);
465       DCHECK_EQ(size(), table.NumRows());
466       DCHECK_EQ(max_num_bits_, table.NumColumnBits(0));
467       for (uint32_t r = 0; r < size(); r++) {
468         BitMemoryRegion expected(rows_[r]);
469         BitMemoryRegion seen = table.GetBitMemoryRegion(r);
470         size_t num_bits = std::max(expected.size_in_bits(), seen.size_in_bits());
471         for (size_t b = 0; b < num_bits; b++) {
472           bool e = b < expected.size_in_bits() && expected.LoadBit(b);
473           bool s = b < seen.size_in_bits() && seen.LoadBit(b);
474           DCHECK_EQ(e, s) << " (" << r << ")[" << b << "]";
475         }
476       }
477     }
478   }
479 
480  private:
481   ScopedArenaAllocator* const allocator_;
482   ScopedArenaDeque<MemoryRegion> rows_;
483   ScopedArenaUnorderedMultimap<uint32_t, uint32_t> dedup_;  // Hash -> row index.
484   size_t max_num_bits_ = 0u;
485 };
486 
487 }  // namespace art
488 
489 #endif  // ART_LIBARTBASE_BASE_BIT_TABLE_H_
490