mem-cache: Implement BDI sub-compressors
[gem5.git] / src / mem / cache / compressors / dictionary_compressor.hh
1 /*
2 * Copyright (c) 2018-2019 Inria
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met: redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer;
9 * redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution;
12 * neither the name of the copyright holders nor the names of its
13 * contributors may be used to endorse or promote products derived from
14 * this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 *
28 * Authors: Daniel Carvalho
29 */
30
31 /** @file
32 * Definition of a dictionary based cache compressor. Each entry is compared
33 * against a dictionary to search for matches.
34 *
35 * The dictionary is composed of 32-bit entries, and the comparison is done
36 * byte per byte.
37 *
38 * The patterns are implemented as individual classes that have a checking
39 * function isPattern(), to determine if the data fits the pattern, and a
40 * decompress() function, which decompresses the contents of a pattern.
41 * Every new pattern must inherit from the Pattern class and be added to the
42 * patternFactory.
43 */
44
45 #ifndef __MEM_CACHE_COMPRESSORS_DICTIONARY_COMPRESSOR_HH__
46 #define __MEM_CACHE_COMPRESSORS_DICTIONARY_COMPRESSOR_HH__
47
48 #include <array>
49 #include <cstdint>
50 #include <map>
51 #include <memory>
52 #include <string>
53 #include <type_traits>
54 #include <vector>
55
56 #include "base/types.hh"
57 #include "mem/cache/compressors/base.hh"
58
59 struct BaseDictionaryCompressorParams;
60
61 class BaseDictionaryCompressor : public BaseCacheCompressor
62 {
63 protected:
64 /** Dictionary size. */
65 const std::size_t dictionarySize;
66
67 /** Number of valid entries in the dictionary. */
68 std::size_t numEntries;
69
70 /**
71 * @defgroup CompressionStats Compression specific statistics.
72 * @{
73 */
74
75 /** Number of data entries that were compressed to each pattern. */
76 Stats::Vector patternStats;
77
78 /**
79 * @}
80 */
81
82 /**
83 * Trick function to get the number of patterns.
84 *
85 * @return The number of defined patterns.
86 */
87 virtual uint64_t getNumPatterns() const = 0;
88
89 /**
90 * Get meta-name assigned to the given pattern.
91 *
92 * @param number The number of the pattern.
93 * @return The meta-name of the pattern.
94 */
95 virtual std::string getName(int number) const = 0;
96
97 public:
98 typedef BaseDictionaryCompressorParams Params;
99 BaseDictionaryCompressor(const Params *p);
100 ~BaseDictionaryCompressor() = default;
101
102 void regStats() override;
103 };
104
105 /**
106 * A template version of the dictionary compressor that allows to choose the
107 * dictionary size.
108 *
109 * @tparam The type of a dictionary entry (e.g., uint16_t, uint32_t, etc).
110 */
111 template <class T>
112 class DictionaryCompressor : public BaseDictionaryCompressor
113 {
114 protected:
115 /** Convenience typedef for a dictionary entry. */
116 typedef std::array<uint8_t, sizeof(T)> DictionaryEntry;
117
118 /**
119 * Compression data for the dictionary compressor. It consists of a vector
120 * of patterns.
121 */
122 class CompData;
123
124 // Forward declaration of a pattern
125 class Pattern;
126 class UncompressedPattern;
127 template <T mask>
128 class MaskedPattern;
129 template <T value, T mask>
130 class MaskedValuePattern;
131 template <T mask, int location>
132 class LocatedMaskedPattern;
133 template <class RepT>
134 class RepeatedValuePattern;
135 template <std::size_t DeltaSizeBits>
136 class DeltaPattern;
137
138 /**
139 * Create a factory to determine if input matches a pattern. The if else
140 * chains are constructed by recursion. The patterns should be explored
141 * sorted by size for correct behaviour.
142 */
143 template <class Head, class... Tail>
144 struct Factory
145 {
146 static std::unique_ptr<Pattern> getPattern(
147 const DictionaryEntry& bytes, const DictionaryEntry& dict_bytes,
148 const int match_location)
149 {
150 // If match this pattern, instantiate it. If a negative match
151 // location is used, the patterns that use the dictionary bytes
152 // must return false. This is used when there are no dictionary
153 // entries yet
154 if (Head::isPattern(bytes, dict_bytes, match_location)) {
155 return std::unique_ptr<Pattern>(
156 new Head(bytes, match_location));
157 // Otherwise, go for next pattern
158 } else {
159 return Factory<Tail...>::getPattern(bytes, dict_bytes,
160 match_location);
161 }
162 }
163 };
164
165 /**
166 * Specialization to end the recursion. This must be called when all
167 * other patterns failed, and there is no choice but to leave data
168 * uncompressed. As such, this pattern must inherit from the uncompressed
169 * pattern.
170 */
171 template <class Head>
172 struct Factory<Head>
173 {
174 static_assert(std::is_base_of<UncompressedPattern, Head>::value,
175 "The last pattern must always be derived from the uncompressed "
176 "pattern.");
177
178 static std::unique_ptr<Pattern>
179 getPattern(const DictionaryEntry& bytes,
180 const DictionaryEntry& dict_bytes, const int match_location)
181 {
182 return std::unique_ptr<Pattern>(new Head(bytes, match_location));
183 }
184 };
185
186 /** The dictionary. */
187 std::vector<DictionaryEntry> dictionary;
188
189 /**
190 * Since the factory cannot be instantiated here, classes that inherit
191 * from this base class have to implement the call to their factory's
192 * getPattern.
193 */
194 virtual std::unique_ptr<Pattern>
195 getPattern(const DictionaryEntry& bytes, const DictionaryEntry& dict_bytes,
196 const int match_location) const = 0;
197
198 /**
199 * Compress data.
200 *
201 * @param data Data to be compressed.
202 * @return The pattern this data matches.
203 */
204 std::unique_ptr<Pattern> compressValue(const T data);
205
206 /**
207 * Decompress a pattern into a value that fits in a dictionary entry.
208 *
209 * @param pattern The pattern to be decompressed.
210 * @return The decompressed word.
211 */
212 T decompressValue(const Pattern* pattern);
213
214 /** Clear all dictionary entries. */
215 virtual void resetDictionary();
216
217 /**
218 * Add an entry to the dictionary.
219 *
220 * @param data The new entry.
221 */
222 virtual void addToDictionary(const DictionaryEntry data) = 0;
223
224 /**
225 * Apply compression.
226 *
227 * @param data The cache line to be compressed.
228 * @return Cache line after compression.
229 */
230 std::unique_ptr<BaseCacheCompressor::CompressionData> compress(
231 const uint64_t* data);
232
233 /**
234 * Decompress data.
235 *
236 * @param comp_data Compressed cache line.
237 * @param data The cache line to be decompressed.
238 */
239 void decompress(const CompressionData* comp_data, uint64_t* data) override;
240
241 /**
242 * Turn a value into a dictionary entry.
243 *
244 * @param value The value to turn.
245 * @return A dictionary entry containing the value.
246 */
247 static DictionaryEntry toDictionaryEntry(T value);
248
249 /**
250 * Turn a dictionary entry into a value.
251 *
252 * @param The dictionary entry to turn.
253 * @return The value that the dictionary entry contained.
254 */
255 static T fromDictionaryEntry(const DictionaryEntry& entry);
256
257 public:
258 typedef BaseDictionaryCompressorParams Params;
259 DictionaryCompressor(const Params *p);
260 ~DictionaryCompressor() = default;
261 };
262
263 /**
264 * The compressed data is composed of multiple pattern entries. To add a new
265 * pattern one should inherit from this class and implement isPattern() and
266 * decompress(). Then the new pattern must be added to the PatternFactory
267 * declaration in crescent order of size (in the DictionaryCompressor class).
268 */
269 template <class T>
270 class DictionaryCompressor<T>::Pattern
271 {
272 protected:
273 /** Pattern enum number. */
274 const int patternNumber;
275
276 /** Code associated to the pattern. */
277 const uint8_t code;
278
279 /** Length, in bits, of the code and match location. */
280 const uint8_t length;
281
282 /** Number of unmatched bits. */
283 const uint8_t numUnmatchedBits;
284
285 /** Index representing the the match location. */
286 const int matchLocation;
287
288 /** Wether the pattern allocates a dictionary entry or not. */
289 const bool allocate;
290
291 public:
292 /**
293 * Default constructor.
294 *
295 * @param number Pattern number.
296 * @param code Code associated to this pattern.
297 * @param metadata_length Length, in bits, of the code and match location.
298 * @param num_unmatched_bits Number of unmatched bits.
299 * @param match_location Index of the match location.
300 */
301 Pattern(const int number, const uint64_t code,
302 const uint64_t metadata_length, const uint64_t num_unmatched_bits,
303 const int match_location, const bool allocate = true)
304 : patternNumber(number), code(code), length(metadata_length),
305 numUnmatchedBits(num_unmatched_bits),
306 matchLocation(match_location), allocate(allocate)
307 {
308 }
309
310 /** Default destructor. */
311 virtual ~Pattern() = default;
312
313 /**
314 * Get enum number associated to this pattern.
315 *
316 * @return The pattern enum number.
317 */
318 int getPatternNumber() const { return patternNumber; };
319
320 /**
321 * Get code of this pattern.
322 *
323 * @return The code.
324 */
325 uint8_t getCode() const { return code; }
326
327 /**
328 * Get the index of the dictionary match location.
329 *
330 * @return The index of the match location.
331 */
332 uint8_t getMatchLocation() const { return matchLocation; }
333
334 /**
335 * Get size, in bits, of the pattern (excluding prefix). Corresponds to
336 * unmatched_data_size + code_length.
337 *
338 * @return The size.
339 */
340 std::size_t
341 getSizeBits() const
342 {
343 return numUnmatchedBits + length;
344 }
345
346 /**
347 * Determine if pattern allocates a dictionary entry.
348 *
349 * @return True if should allocate a dictionary entry.
350 */
351 bool shouldAllocate() const { return allocate; }
352
353 /**
354 * Extract pattern's information to a string.
355 *
356 * @return A string containing the relevant pattern metadata.
357 */
358 std::string
359 print() const
360 {
361 return csprintf("pattern %s (encoding %x, size %u bits)",
362 getPatternNumber(), getCode(), getSizeBits());
363 }
364
365 /**
366 * Decompress the pattern. Each pattern has its own way of interpreting
367 * its data.
368 *
369 * @param dict_bytes The bytes in the corresponding matching entry.
370 * @return The decompressed pattern.
371 */
372 virtual DictionaryEntry decompress(
373 const DictionaryEntry dict_bytes) const = 0;
374 };
375
376 template <class T>
377 class DictionaryCompressor<T>::CompData : public CompressionData
378 {
379 public:
380 /** The patterns matched in the original line. */
381 std::vector<std::unique_ptr<Pattern>> entries;
382
383 CompData();
384 ~CompData() = default;
385
386 /**
387 * Add a pattern entry to the list of patterns.
388 *
389 * @param entry The new pattern entry.
390 */
391 virtual void addEntry(std::unique_ptr<Pattern>);
392 };
393
394 /**
395 * A pattern containing the original uncompressed data. This should be the
396 * worst case of every pattern factory, where if all other patterns fail,
397 * an instance of this pattern is created.
398 */
399 template <class T>
400 class DictionaryCompressor<T>::UncompressedPattern
401 : public DictionaryCompressor<T>::Pattern
402 {
403 private:
404 /** A copy of the original data. */
405 const DictionaryEntry data;
406
407 public:
408 UncompressedPattern(const int number,
409 const uint64_t code,
410 const uint64_t metadata_length,
411 const int match_location,
412 const DictionaryEntry bytes)
413 : DictionaryCompressor<T>::Pattern(number, code, metadata_length,
414 sizeof(T) * 8, match_location, true),
415 data(bytes)
416 {
417 }
418
419 static bool
420 isPattern(const DictionaryEntry& bytes, const DictionaryEntry& dict_bytes,
421 const int match_location)
422 {
423 // An entry can always be uncompressed
424 return true;
425 }
426
427 DictionaryEntry
428 decompress(const DictionaryEntry dict_bytes) const override
429 {
430 return data;
431 }
432 };
433
434 /**
435 * A pattern that compares masked values against dictionary entries. If
436 * the masked dictionary entry matches perfectly the masked value to be
437 * compressed, there is a pattern match.
438 *
439 * For example, if the mask is 0xFF00 (that is, this pattern matches the MSB),
440 * the value (V) 0xFF20 is being compressed, and the dictionary contains
441 * the value (D) 0xFF03, this is a match (V & mask == 0xFF00 == D & mask),
442 * and 0x0020 is added to the list of unmatched bits.
443 *
444 * @tparam mask A mask containing the bits that must match.
445 */
446 template <class T>
447 template <T mask>
448 class DictionaryCompressor<T>::MaskedPattern
449 : public DictionaryCompressor<T>::Pattern
450 {
451 private:
452 static_assert(mask != 0, "The pattern's value mask must not be zero. Use "
453 "the uncompressed pattern instead.");
454
455 /** A copy of the bits that do not belong to the mask. */
456 const T bits;
457
458 public:
459 MaskedPattern(const int number,
460 const uint64_t code,
461 const uint64_t metadata_length,
462 const int match_location,
463 const DictionaryEntry bytes,
464 const bool allocate = true)
465 : DictionaryCompressor<T>::Pattern(number, code, metadata_length,
466 popCount(~mask), match_location, allocate),
467 bits(DictionaryCompressor<T>::fromDictionaryEntry(bytes) & ~mask)
468 {
469 }
470
471 static bool
472 isPattern(const DictionaryEntry& bytes, const DictionaryEntry& dict_bytes,
473 const int match_location)
474 {
475 const T masked_bytes =
476 DictionaryCompressor<T>::fromDictionaryEntry(bytes) & mask;
477 const T masked_dict_bytes =
478 DictionaryCompressor<T>::fromDictionaryEntry(dict_bytes) & mask;
479 return (match_location >= 0) && (masked_bytes == masked_dict_bytes);
480 }
481
482 DictionaryEntry
483 decompress(const DictionaryEntry dict_bytes) const override
484 {
485 const T masked_dict_bytes =
486 DictionaryCompressor<T>::fromDictionaryEntry(dict_bytes) & mask;
487 return DictionaryCompressor<T>::toDictionaryEntry(
488 bits | masked_dict_bytes);
489 }
490 };
491
492 /**
493 * A pattern that compares masked values to a masked portion of a fixed value.
494 * If all the masked bits match the provided non-dictionary value, there is a
495 * pattern match.
496 *
497 * For example, assume the mask is 0xFF00 (that is, this pattern matches the
498 * MSB), and we are searching for data containing only ones (i.e., the fixed
499 * value is 0xFFFF).
500 * If the value (V) 0xFF20 is being compressed, this is a match (V & mask ==
501 * 0xFF00 == 0xFFFF & mask), and 0x20 is added to the list of unmatched bits.
502 * If the value (V2) 0x0120 is being compressed, this is not a match
503 * ((V2 & mask == 0x0100) != (0xFF00 == 0xFFFF & mask).
504 *
505 * @tparam value The value that is being matched against.
506 * @tparam mask A mask containing the bits that must match the given value.
507 */
508 template <class T>
509 template <T value, T mask>
510 class DictionaryCompressor<T>::MaskedValuePattern
511 : public MaskedPattern<mask>
512 {
513 private:
514 static_assert(mask != 0, "The pattern's value mask must not be zero.");
515
516 public:
517 MaskedValuePattern(const int number,
518 const uint64_t code,
519 const uint64_t metadata_length,
520 const int match_location,
521 const DictionaryEntry bytes,
522 const bool allocate = false)
523 : MaskedPattern<mask>(number, code, metadata_length, match_location,
524 bytes, allocate)
525 {
526 }
527
528 static bool
529 isPattern(const DictionaryEntry& bytes, const DictionaryEntry& dict_bytes,
530 const int match_location)
531 {
532 // Compare the masked fixed value to the value being checked for
533 // patterns. Since the dictionary is not being used the match_location
534 // is irrelevant.
535 const T masked_bytes =
536 DictionaryCompressor<T>::fromDictionaryEntry(bytes) & mask;
537 return ((value & mask) == masked_bytes);
538 }
539
540 DictionaryEntry
541 decompress(const DictionaryEntry dict_bytes) const override
542 {
543 return MaskedPattern<mask>::decompress(
544 DictionaryCompressor<T>::toDictionaryEntry(value));
545 }
546 };
547
548 /**
549 * A pattern that narrows the MaskedPattern by allowing a only single possible
550 * dictionary entry to be matched against.
551 *
552 * @tparam mask A mask containing the bits that must match.
553 * @tparam location The index of the single entry allowed to match.
554 */
555 template <class T>
556 template <T mask, int location>
557 class DictionaryCompressor<T>::LocatedMaskedPattern
558 : public MaskedPattern<mask>
559 {
560 public:
561 LocatedMaskedPattern(const int number,
562 const uint64_t code,
563 const uint64_t metadata_length,
564 const int match_location,
565 const DictionaryEntry bytes)
566 : MaskedPattern<mask>(number, code, metadata_length, match_location,
567 bytes)
568 {
569 }
570
571 static bool
572 isPattern(const DictionaryEntry& bytes, const DictionaryEntry& dict_bytes,
573 const int match_location)
574 {
575 // Besides doing the regular masked pattern matching, the match
576 // location must match perfectly with this instance's
577 return (match_location == location) &&
578 MaskedPattern<mask>::isPattern(bytes, dict_bytes, match_location);
579 }
580 };
581
582 /**
583 * A pattern that checks if dictionary entry sized values are solely composed
584 * of multiple copies of a single value.
585 *
586 * For example, if we are looking for repeated bytes in a 1-byte granularity
587 * (RepT is uint8_t), the value 0x3232 would match, however 0x3332 wouldn't.
588 *
589 * @tparam RepT The type of the repeated value, which must fit in a dictionary
590 * entry.
591 */
592 template <class T>
593 template <class RepT>
594 class DictionaryCompressor<T>::RepeatedValuePattern
595 : public DictionaryCompressor<T>::Pattern
596 {
597 private:
598 static_assert(sizeof(T) > sizeof(RepT), "The repeated value's type must "
599 "be smaller than the dictionary entry's type.");
600
601 /** The repeated value. */
602 RepT value;
603
604 public:
605 RepeatedValuePattern(const int number,
606 const uint64_t code,
607 const uint64_t metadata_length,
608 const int match_location,
609 const DictionaryEntry bytes,
610 const bool allocate = true)
611 : DictionaryCompressor<T>::Pattern(number, code, metadata_length,
612 8 * sizeof(RepT), match_location, allocate),
613 value(DictionaryCompressor<T>::fromDictionaryEntry(bytes))
614 {
615 }
616
617 static bool
618 isPattern(const DictionaryEntry& bytes, const DictionaryEntry& dict_bytes,
619 const int match_location)
620 {
621 // Parse the dictionary entry in a RepT granularity, and if all values
622 // are equal, this is a repeated value pattern. Since the dictionary
623 // is not being used, the match_location is irrelevant
624 T bytes_value = DictionaryCompressor<T>::fromDictionaryEntry(bytes);
625 const RepT rep_value = bytes_value;
626 for (int i = 0; i < (sizeof(T) / sizeof(RepT)); i++) {
627 RepT cur_value = bytes_value;
628 if (cur_value != rep_value) {
629 return false;
630 }
631 bytes_value >>= 8 * sizeof(RepT);
632 }
633 return true;
634 }
635
636 DictionaryEntry
637 decompress(const DictionaryEntry dict_bytes) const override
638 {
639 // The decompressed value is just multiple consecutive instances of
640 // the same value
641 T decomp_value = 0;
642 for (int i = 0; i < (sizeof(T) / sizeof(RepT)); i++) {
643 decomp_value <<= 8 * sizeof(RepT);
644 decomp_value |= value;
645 }
646 return DictionaryCompressor<T>::toDictionaryEntry(decomp_value);
647 }
648 };
649
650 /**
651 * A pattern that checks whether the difference of the value and the dictionary
652 * entries' is below a certain threshold. If so, the pattern is successful,
653 * and only the delta bits need to be stored.
654 *
655 * For example, if the delta can only contain up to 4 bits, and the dictionary
656 * contains the entry 0xA231, the value 0xA232 would be compressible, and
657 * the delta 0x1 would be stored. The value 0xA249, on the other hand, would
658 * not be compressible, since its delta (0x18) needs 5 bits to be stored.
659 *
660 * @tparam DeltaSizeBits Size of a delta entry, in number of bits, which
661 * determines the threshold. Must always be smaller
662 * than the dictionary entry type's size.
663 */
664 template <class T>
665 template <std::size_t DeltaSizeBits>
666 class DictionaryCompressor<T>::DeltaPattern
667 : public DictionaryCompressor<T>::Pattern
668 {
669 private:
670 static_assert(DeltaSizeBits < (sizeof(T) * 8),
671 "Delta size must be smaller than base size");
672
673 /**
674 * The original value. In theory we should keep only the deltas, but
675 * the dictionary entry is not inserted in the dictionary before the
676 * call to the constructor, so the delta cannot be calculated then.
677 */
678 const DictionaryEntry bytes;
679
680 public:
681 DeltaPattern(const int number,
682 const uint64_t code,
683 const uint64_t metadata_length,
684 const int match_location,
685 const DictionaryEntry bytes)
686 : DictionaryCompressor<T>::Pattern(number, code, metadata_length,
687 DeltaSizeBits, match_location, false),
688 bytes(bytes)
689 {
690 }
691
692 /**
693 * Compares a given value against a base to calculate their delta, and
694 * then determines whether it fits a limited sized container.
695 *
696 * @param bytes Value to be compared against base.
697 * @param base_bytes Base value.
698 * @return Whether the value fits in the container.
699 */
700 static bool
701 isValidDelta(const DictionaryEntry& bytes,
702 const DictionaryEntry& base_bytes)
703 {
704 const typename std::make_signed<T>::type limit = DeltaSizeBits ?
705 mask(DeltaSizeBits - 1) : 0;
706 const T value =
707 DictionaryCompressor<T>::fromDictionaryEntry(bytes);
708 const T base =
709 DictionaryCompressor<T>::fromDictionaryEntry(base_bytes);
710 const typename std::make_signed<T>::type delta = value - base;
711 return (delta >= -limit) && (delta <= limit);
712 }
713
714 static bool
715 isPattern(const DictionaryEntry& bytes,
716 const DictionaryEntry& dict_bytes, const int match_location)
717 {
718 return (match_location >= 0) && isValidDelta(bytes, dict_bytes);
719 }
720
721 DictionaryEntry
722 decompress(const DictionaryEntry dict_bytes) const override
723 {
724 return bytes;
725 }
726 };
727
728 #endif //__MEM_CACHE_COMPRESSORS_DICTIONARY_COMPRESSOR_HH__