mem-cache: Standardize data parsing in compressors
[gem5.git] / src / mem / cache / compressors / dictionary_compressor.hh
1 /*
2 * Copyright (c) 2018-2020 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
29 /** @file
30 * Definition of a dictionary based cache compressor. Each entry is compared
31 * against a dictionary to search for matches.
32 *
33 * The dictionary is composed of 32-bit entries, and the comparison is done
34 * byte per byte.
35 *
36 * The patterns are implemented as individual classes that have a checking
37 * function isPattern(), to determine if the data fits the pattern, and a
38 * decompress() function, which decompresses the contents of a pattern.
39 * Every new pattern must inherit from the Pattern class and be added to the
40 * patternFactory.
41 */
42
43 #ifndef __MEM_CACHE_COMPRESSORS_DICTIONARY_COMPRESSOR_HH__
44 #define __MEM_CACHE_COMPRESSORS_DICTIONARY_COMPRESSOR_HH__
45
46 #include <array>
47 #include <cstdint>
48 #include <map>
49 #include <memory>
50 #include <string>
51 #include <type_traits>
52 #include <vector>
53
54 #include "base/statistics.hh"
55 #include "base/types.hh"
56 #include "mem/cache/compressors/base.hh"
57
58 struct BaseDictionaryCompressorParams;
59
60 namespace Compressor {
61
62 class BaseDictionaryCompressor : public Base
63 {
64 protected:
65 /** Dictionary size. */
66 const std::size_t dictionarySize;
67
68 /** Number of valid entries in the dictionary. */
69 std::size_t numEntries;
70
71 struct DictionaryStats : public Stats::Group
72 {
73 const BaseDictionaryCompressor& compressor;
74
75 DictionaryStats(BaseStats &base_group,
76 BaseDictionaryCompressor& _compressor);
77
78 void regStats() override;
79
80 /** Number of data entries that were compressed to each pattern. */
81 Stats::Vector patterns;
82 } dictionaryStats;
83
84 /**
85 * Trick function to get the number of patterns.
86 *
87 * @return The number of defined patterns.
88 */
89 virtual uint64_t getNumPatterns() const = 0;
90
91 /**
92 * Get meta-name assigned to the given pattern.
93 *
94 * @param number The number of the pattern.
95 * @return The meta-name of the pattern.
96 */
97 virtual std::string getName(int number) const = 0;
98
99 public:
100 typedef BaseDictionaryCompressorParams Params;
101 BaseDictionaryCompressor(const Params *p);
102 ~BaseDictionaryCompressor() = default;
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 * Instantiate a compression data of the sub-class compressor.
226 *
227 * @return The new compression data entry.
228 */
229 virtual std::unique_ptr<DictionaryCompressor::CompData>
230 instantiateDictionaryCompData() const;
231
232 /**
233 * Apply compression.
234 *
235 * @param chunks The cache line to be compressed.
236 * @return Cache line after compression.
237 */
238 std::unique_ptr<Base::CompressionData> compress(
239 const std::vector<Chunk>& chunks);
240
241 using BaseDictionaryCompressor::compress;
242
243 /**
244 * Decompress data.
245 *
246 * @param comp_data Compressed cache line.
247 * @param data The cache line to be decompressed.
248 */
249 void decompress(const CompressionData* comp_data, uint64_t* data) override;
250
251 /**
252 * Turn a value into a dictionary entry.
253 *
254 * @param value The value to turn.
255 * @return A dictionary entry containing the value.
256 */
257 static DictionaryEntry toDictionaryEntry(T value);
258
259 /**
260 * Turn a dictionary entry into a value.
261 *
262 * @param The dictionary entry to turn.
263 * @return The value that the dictionary entry contained.
264 */
265 static T fromDictionaryEntry(const DictionaryEntry& entry);
266
267 public:
268 typedef BaseDictionaryCompressorParams Params;
269 DictionaryCompressor(const Params *p);
270 ~DictionaryCompressor() = default;
271 };
272
273 /**
274 * The compressed data is composed of multiple pattern entries. To add a new
275 * pattern one should inherit from this class and implement isPattern() and
276 * decompress(). Then the new pattern must be added to the PatternFactory
277 * declaration in crescent order of size (in the DictionaryCompressor class).
278 */
279 template <class T>
280 class DictionaryCompressor<T>::Pattern
281 {
282 protected:
283 /** Pattern enum number. */
284 const int patternNumber;
285
286 /** Code associated to the pattern. */
287 const uint8_t code;
288
289 /** Length, in bits, of the code and match location. */
290 const uint8_t length;
291
292 /** Number of unmatched bits. */
293 const uint8_t numUnmatchedBits;
294
295 /** Index representing the the match location. */
296 const int matchLocation;
297
298 /** Wether the pattern allocates a dictionary entry or not. */
299 const bool allocate;
300
301 public:
302 /**
303 * Default constructor.
304 *
305 * @param number Pattern number.
306 * @param code Code associated to this pattern.
307 * @param metadata_length Length, in bits, of the code and match location.
308 * @param num_unmatched_bits Number of unmatched bits.
309 * @param match_location Index of the match location.
310 */
311 Pattern(const int number, const uint64_t code,
312 const uint64_t metadata_length, const uint64_t num_unmatched_bits,
313 const int match_location, const bool allocate = true)
314 : patternNumber(number), code(code), length(metadata_length),
315 numUnmatchedBits(num_unmatched_bits),
316 matchLocation(match_location), allocate(allocate)
317 {
318 }
319
320 /** Default destructor. */
321 virtual ~Pattern() = default;
322
323 /**
324 * Get enum number associated to this pattern.
325 *
326 * @return The pattern enum number.
327 */
328 int getPatternNumber() const { return patternNumber; };
329
330 /**
331 * Get code of this pattern.
332 *
333 * @return The code.
334 */
335 uint8_t getCode() const { return code; }
336
337 /**
338 * Get the index of the dictionary match location.
339 *
340 * @return The index of the match location.
341 */
342 uint8_t getMatchLocation() const { return matchLocation; }
343
344 /**
345 * Get size, in bits, of the pattern (excluding prefix). Corresponds to
346 * unmatched_data_size + code_length.
347 *
348 * @return The size.
349 */
350 std::size_t
351 getSizeBits() const
352 {
353 return numUnmatchedBits + length;
354 }
355
356 /**
357 * Determine if pattern allocates a dictionary entry.
358 *
359 * @return True if should allocate a dictionary entry.
360 */
361 bool shouldAllocate() const { return allocate; }
362
363 /**
364 * Extract pattern's information to a string.
365 *
366 * @return A string containing the relevant pattern metadata.
367 */
368 std::string
369 print() const
370 {
371 return csprintf("pattern %s (encoding %x, size %u bits)",
372 getPatternNumber(), getCode(), getSizeBits());
373 }
374
375 /**
376 * Decompress the pattern. Each pattern has its own way of interpreting
377 * its data.
378 *
379 * @param dict_bytes The bytes in the corresponding matching entry.
380 * @return The decompressed pattern.
381 */
382 virtual DictionaryEntry decompress(
383 const DictionaryEntry dict_bytes) const = 0;
384 };
385
386 template <class T>
387 class DictionaryCompressor<T>::CompData : public CompressionData
388 {
389 public:
390 /** The patterns matched in the original line. */
391 std::vector<std::unique_ptr<Pattern>> entries;
392
393 CompData();
394 ~CompData() = default;
395
396 /**
397 * Add a pattern entry to the list of patterns.
398 *
399 * @param entry The new pattern entry.
400 */
401 virtual void addEntry(std::unique_ptr<Pattern>);
402 };
403
404 /**
405 * A pattern containing the original uncompressed data. This should be the
406 * worst case of every pattern factory, where if all other patterns fail,
407 * an instance of this pattern is created.
408 */
409 template <class T>
410 class DictionaryCompressor<T>::UncompressedPattern
411 : public DictionaryCompressor<T>::Pattern
412 {
413 private:
414 /** A copy of the original data. */
415 const DictionaryEntry data;
416
417 public:
418 UncompressedPattern(const int number,
419 const uint64_t code,
420 const uint64_t metadata_length,
421 const int match_location,
422 const DictionaryEntry bytes)
423 : DictionaryCompressor<T>::Pattern(number, code, metadata_length,
424 sizeof(T) * 8, match_location, true),
425 data(bytes)
426 {
427 }
428
429 static bool
430 isPattern(const DictionaryEntry& bytes, const DictionaryEntry& dict_bytes,
431 const int match_location)
432 {
433 // An entry can always be uncompressed
434 return true;
435 }
436
437 DictionaryEntry
438 decompress(const DictionaryEntry dict_bytes) const override
439 {
440 return data;
441 }
442 };
443
444 /**
445 * A pattern that compares masked values against dictionary entries. If
446 * the masked dictionary entry matches perfectly the masked value to be
447 * compressed, there is a pattern match.
448 *
449 * For example, if the mask is 0xFF00 (that is, this pattern matches the MSB),
450 * the value (V) 0xFF20 is being compressed, and the dictionary contains
451 * the value (D) 0xFF03, this is a match (V & mask == 0xFF00 == D & mask),
452 * and 0x0020 is added to the list of unmatched bits.
453 *
454 * @tparam mask A mask containing the bits that must match.
455 */
456 template <class T>
457 template <T mask>
458 class DictionaryCompressor<T>::MaskedPattern
459 : public DictionaryCompressor<T>::Pattern
460 {
461 private:
462 static_assert(mask != 0, "The pattern's value mask must not be zero. Use "
463 "the uncompressed pattern instead.");
464
465 /** A copy of the bits that do not belong to the mask. */
466 const T bits;
467
468 public:
469 MaskedPattern(const int number,
470 const uint64_t code,
471 const uint64_t metadata_length,
472 const int match_location,
473 const DictionaryEntry bytes,
474 const bool allocate = true)
475 : DictionaryCompressor<T>::Pattern(number, code, metadata_length,
476 popCount(static_cast<T>(~mask)), match_location, allocate),
477 bits(DictionaryCompressor<T>::fromDictionaryEntry(bytes) & ~mask)
478 {
479 }
480
481 static bool
482 isPattern(const DictionaryEntry& bytes, const DictionaryEntry& dict_bytes,
483 const int match_location)
484 {
485 const T masked_bytes =
486 DictionaryCompressor<T>::fromDictionaryEntry(bytes) & mask;
487 const T masked_dict_bytes =
488 DictionaryCompressor<T>::fromDictionaryEntry(dict_bytes) & mask;
489 return (match_location >= 0) && (masked_bytes == masked_dict_bytes);
490 }
491
492 DictionaryEntry
493 decompress(const DictionaryEntry dict_bytes) const override
494 {
495 const T masked_dict_bytes =
496 DictionaryCompressor<T>::fromDictionaryEntry(dict_bytes) & mask;
497 return DictionaryCompressor<T>::toDictionaryEntry(
498 bits | masked_dict_bytes);
499 }
500 };
501
502 /**
503 * A pattern that compares masked values to a masked portion of a fixed value.
504 * If all the masked bits match the provided non-dictionary value, there is a
505 * pattern match.
506 *
507 * For example, assume the mask is 0xFF00 (that is, this pattern matches the
508 * MSB), and we are searching for data containing only ones (i.e., the fixed
509 * value is 0xFFFF).
510 * If the value (V) 0xFF20 is being compressed, this is a match (V & mask ==
511 * 0xFF00 == 0xFFFF & mask), and 0x20 is added to the list of unmatched bits.
512 * If the value (V2) 0x0120 is being compressed, this is not a match
513 * ((V2 & mask == 0x0100) != (0xFF00 == 0xFFFF & mask).
514 *
515 * @tparam value The value that is being matched against.
516 * @tparam mask A mask containing the bits that must match the given value.
517 */
518 template <class T>
519 template <T value, T mask>
520 class DictionaryCompressor<T>::MaskedValuePattern
521 : public MaskedPattern<mask>
522 {
523 private:
524 static_assert(mask != 0, "The pattern's value mask must not be zero.");
525
526 public:
527 MaskedValuePattern(const int number,
528 const uint64_t code,
529 const uint64_t metadata_length,
530 const int match_location,
531 const DictionaryEntry bytes,
532 const bool allocate = false)
533 : MaskedPattern<mask>(number, code, metadata_length, match_location,
534 bytes, allocate)
535 {
536 }
537
538 static bool
539 isPattern(const DictionaryEntry& bytes, const DictionaryEntry& dict_bytes,
540 const int match_location)
541 {
542 // Compare the masked fixed value to the value being checked for
543 // patterns. Since the dictionary is not being used the match_location
544 // is irrelevant.
545 const T masked_bytes =
546 DictionaryCompressor<T>::fromDictionaryEntry(bytes) & mask;
547 return ((value & mask) == masked_bytes);
548 }
549
550 DictionaryEntry
551 decompress(const DictionaryEntry dict_bytes) const override
552 {
553 return MaskedPattern<mask>::decompress(
554 DictionaryCompressor<T>::toDictionaryEntry(value));
555 }
556 };
557
558 /**
559 * A pattern that narrows the MaskedPattern by allowing a only single possible
560 * dictionary entry to be matched against.
561 *
562 * @tparam mask A mask containing the bits that must match.
563 * @tparam location The index of the single entry allowed to match.
564 */
565 template <class T>
566 template <T mask, int location>
567 class DictionaryCompressor<T>::LocatedMaskedPattern
568 : public MaskedPattern<mask>
569 {
570 public:
571 LocatedMaskedPattern(const int number,
572 const uint64_t code,
573 const uint64_t metadata_length,
574 const int match_location,
575 const DictionaryEntry bytes,
576 const bool allocate = true)
577 : MaskedPattern<mask>(number, code, metadata_length, match_location,
578 bytes, allocate)
579 {
580 }
581
582 static bool
583 isPattern(const DictionaryEntry& bytes, const DictionaryEntry& dict_bytes,
584 const int match_location)
585 {
586 // Besides doing the regular masked pattern matching, the match
587 // location must match perfectly with this instance's
588 return (match_location == location) &&
589 MaskedPattern<mask>::isPattern(bytes, dict_bytes, match_location);
590 }
591 };
592
593 /**
594 * A pattern that checks if dictionary entry sized values are solely composed
595 * of multiple copies of a single value.
596 *
597 * For example, if we are looking for repeated bytes in a 1-byte granularity
598 * (RepT is uint8_t), the value 0x3232 would match, however 0x3332 wouldn't.
599 *
600 * @tparam RepT The type of the repeated value, which must fit in a dictionary
601 * entry.
602 */
603 template <class T>
604 template <class RepT>
605 class DictionaryCompressor<T>::RepeatedValuePattern
606 : public DictionaryCompressor<T>::Pattern
607 {
608 private:
609 static_assert(sizeof(T) > sizeof(RepT), "The repeated value's type must "
610 "be smaller than the dictionary entry's type.");
611
612 /** The repeated value. */
613 RepT value;
614
615 public:
616 RepeatedValuePattern(const int number,
617 const uint64_t code,
618 const uint64_t metadata_length,
619 const int match_location,
620 const DictionaryEntry bytes,
621 const bool allocate = true)
622 : DictionaryCompressor<T>::Pattern(number, code, metadata_length,
623 8 * sizeof(RepT), match_location, allocate),
624 value(DictionaryCompressor<T>::fromDictionaryEntry(bytes))
625 {
626 }
627
628 static bool
629 isPattern(const DictionaryEntry& bytes, const DictionaryEntry& dict_bytes,
630 const int match_location)
631 {
632 // Parse the dictionary entry in a RepT granularity, and if all values
633 // are equal, this is a repeated value pattern. Since the dictionary
634 // is not being used, the match_location is irrelevant
635 T bytes_value = DictionaryCompressor<T>::fromDictionaryEntry(bytes);
636 const RepT rep_value = bytes_value;
637 for (int i = 0; i < (sizeof(T) / sizeof(RepT)); i++) {
638 RepT cur_value = bytes_value;
639 if (cur_value != rep_value) {
640 return false;
641 }
642 bytes_value >>= 8 * sizeof(RepT);
643 }
644 return true;
645 }
646
647 DictionaryEntry
648 decompress(const DictionaryEntry dict_bytes) const override
649 {
650 // The decompressed value is just multiple consecutive instances of
651 // the same value
652 T decomp_value = 0;
653 for (int i = 0; i < (sizeof(T) / sizeof(RepT)); i++) {
654 decomp_value <<= 8 * sizeof(RepT);
655 decomp_value |= value;
656 }
657 return DictionaryCompressor<T>::toDictionaryEntry(decomp_value);
658 }
659 };
660
661 /**
662 * A pattern that checks whether the difference of the value and the dictionary
663 * entries' is below a certain threshold. If so, the pattern is successful,
664 * and only the delta bits need to be stored.
665 *
666 * For example, if the delta can only contain up to 4 bits, and the dictionary
667 * contains the entry 0xA231, the value 0xA232 would be compressible, and
668 * the delta 0x1 would be stored. The value 0xA249, on the other hand, would
669 * not be compressible, since its delta (0x18) needs 5 bits to be stored.
670 *
671 * @tparam DeltaSizeBits Size of a delta entry, in number of bits, which
672 * determines the threshold. Must always be smaller
673 * than the dictionary entry type's size.
674 */
675 template <class T>
676 template <std::size_t DeltaSizeBits>
677 class DictionaryCompressor<T>::DeltaPattern
678 : public DictionaryCompressor<T>::Pattern
679 {
680 private:
681 static_assert(DeltaSizeBits < (sizeof(T) * 8),
682 "Delta size must be smaller than base size");
683
684 /**
685 * The original value. In theory we should keep only the deltas, but
686 * the dictionary entry is not inserted in the dictionary before the
687 * call to the constructor, so the delta cannot be calculated then.
688 */
689 const DictionaryEntry bytes;
690
691 public:
692 DeltaPattern(const int number,
693 const uint64_t code,
694 const uint64_t metadata_length,
695 const int match_location,
696 const DictionaryEntry bytes)
697 : DictionaryCompressor<T>::Pattern(number, code, metadata_length,
698 DeltaSizeBits, match_location, false),
699 bytes(bytes)
700 {
701 }
702
703 /**
704 * Compares a given value against a base to calculate their delta, and
705 * then determines whether it fits a limited sized container.
706 *
707 * @param bytes Value to be compared against base.
708 * @param base_bytes Base value.
709 * @return Whether the value fits in the container.
710 */
711 static bool
712 isValidDelta(const DictionaryEntry& bytes,
713 const DictionaryEntry& base_bytes)
714 {
715 const typename std::make_signed<T>::type limit = DeltaSizeBits ?
716 mask(DeltaSizeBits - 1) : 0;
717 const T value =
718 DictionaryCompressor<T>::fromDictionaryEntry(bytes);
719 const T base =
720 DictionaryCompressor<T>::fromDictionaryEntry(base_bytes);
721 const typename std::make_signed<T>::type delta = value - base;
722 return (delta >= -limit) && (delta <= limit);
723 }
724
725 static bool
726 isPattern(const DictionaryEntry& bytes,
727 const DictionaryEntry& dict_bytes, const int match_location)
728 {
729 return (match_location >= 0) && isValidDelta(bytes, dict_bytes);
730 }
731
732 DictionaryEntry
733 decompress(const DictionaryEntry dict_bytes) const override
734 {
735 return bytes;
736 }
737 };
738
739 } // namespace Compressor
740
741 #endif //__MEM_CACHE_COMPRESSORS_DICTIONARY_COMPRESSOR_HH__