Fix a corner case in EliasFanoReader::previousValue
[folly.git] / folly / experimental / EliasFanoCoding.h
index b428d54384da3d2d0d16772e19f62e25a2cc0ebc..f75fabde98c8ecd1e134cf627d9dfa5583d8d355 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Facebook, Inc.
+ * Copyright 2017 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,9 +21,9 @@
  * "Quasi-succinct indices" (arxiv:1206.4300).
  */
 
-#ifndef FOLLY_EXPERIMENTAL_ELIAS_FANO_CODING_H
-#define FOLLY_EXPERIMENTAL_ELIAS_FANO_CODING_H
+#pragma once
 
+#include <algorithm>
 #include <cstdlib>
 #include <limits>
 #include <type_traits>
 #include <folly/experimental/Select64.h>
 #include <glog/logging.h>
 
-#ifndef __GNUC__
-#error EliasFanoCoding.h requires GCC
-#endif
-
 #if !FOLLY_X64
 #error EliasFanoCoding.h requires x86_64
 #endif
 
-#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
-#error EliasFanoCoding.h requires little endianness
-#endif
-
 namespace folly { namespace compression {
 
+static_assert(kIsLittleEndian, "EliasFanoCoding.h requires little endianness");
+
 template <class Pointer>
 struct EliasFanoCompressedListBase {
   EliasFanoCompressedListBase() = default;
@@ -70,7 +64,9 @@ struct EliasFanoCompressedListBase {
     return ::free(data.data());
   }
 
-  size_t upperSize() const { return data.end() - upper; }
+  size_t upperSize() const {
+    return size_t(data.end() - upper);
+  }
 
   size_t size = 0;
   uint8_t numLowerBits = 0;
@@ -108,11 +104,17 @@ struct EliasFanoEncoderV2 {
   static constexpr size_t forwardQuantum = kForwardQuantum;
 
   static uint8_t defaultNumLowerBits(size_t upperBound, size_t size) {
-    if (size == 0 || upperBound < size) {
+    if (UNLIKELY(size == 0 || upperBound < size)) {
       return 0;
     }
-    // floor(log(upperBound / size));
-    return folly::findLastSet(upperBound / size) - 1;
+    // Result that should be returned is "floor(log(upperBound / size))".
+    // In order to avoid expensive division, we rely on
+    // "floor(a) - floor(b) - 1 <= floor(a - b) <= floor(a) - floor(b)".
+    // Assuming "candidate = floor(log(upperBound)) - floor(log(upperBound))",
+    // then result is either "candidate - 1" or "candidate".
+    auto candidate = folly::findLastSet(upperBound) - folly::findLastSet(size);
+    // NOTE: As size != 0, "candidate" is always < 64.
+    return (size > (upperBound >> candidate)) ? candidate - 1 : candidate;
   }
 
   // Requires: input range (begin, end) is sorted (encoding
@@ -126,7 +128,7 @@ struct EliasFanoEncoderV2 {
     if (begin == end) {
       return MutableCompressedList();
     }
-    EliasFanoEncoderV2 encoder(end - begin, *(end - 1));
+    EliasFanoEncoderV2 encoder(size_t(end - begin), *(end - 1));
     for (; begin != end; ++begin) {
       encoder.add(*begin);
     }
@@ -141,7 +143,7 @@ struct EliasFanoEncoderV2 {
         forwardPointers_(reinterpret_cast<SkipValueType*>(
               result.forwardPointers)),
         result_(result) {
-    memset(result.data.data(), 0, result.data.size());
+    std::fill(result.data.begin(), result.data.end(), 0);
   }
 
   EliasFanoEncoderV2(size_t size, ValueType upperBound)
@@ -167,15 +169,15 @@ struct EliasFanoEncoderV2 {
     /* static */ if (skipQuantum != 0) {
       while ((skipPointersSize_ + 1) * skipQuantum <= upperBits) {
         // Store the number of preceding 1-bits.
-        skipPointers_[skipPointersSize_++] = size_;
+        skipPointers_[skipPointersSize_++] = SkipValue(size_);
       }
     }
 
     /* static */ if (forwardQuantum != 0) {
       if ((size_ + 1) % forwardQuantum == 0) {
-        const auto pos = size_ / forwardQuantum;
+        const auto k = size_ / forwardQuantum;
         // Store the number of preceding 0-bits.
-        forwardPointers_[pos] = upperBits;
+        forwardPointers_[k] = upperBits;
       }
     }
 
@@ -261,9 +263,7 @@ struct EliasFanoEncoderV2<Value,
       // more serialization-friendly way (upperSizeBits doesn't need
       // to be known by this function, unlike upper).
 
-      // '?: 1' is a workaround for false 'division by zero'
-      // compile-time error.
-      size_t numSkipPointers = (8 * upper - size) / (skipQuantum ?: 1);
+      size_t numSkipPointers = (8 * upper - size) / skipQuantum;
       layout.skipPointers = numSkipPointers * sizeof(SkipValueType);
     }
 
@@ -271,7 +271,7 @@ struct EliasFanoEncoderV2<Value,
     // Store (1-indexed) position of every forwardQuantum-th
     // 1-bit in upper bits sequence.
     /* static */ if (forwardQuantum != 0) {
-      size_t numForwardPointers = size / (forwardQuantum ?: 1);
+      size_t numForwardPointers = size / forwardQuantum;
       layout.forwardPointers = numForwardPointers * sizeof(SkipValueType);
     }
 
@@ -346,8 +346,8 @@ class UpperBitsReader {
   void reset() {
     block_ = start_ != nullptr ? folly::loadUnaligned<block_t>(start_) : 0;
     outer_ = 0;
-    inner_ = -1;
-    position_ = -1;
+    inner_ = std::numeric_limits<size_t>::max();
+    position_ = std::numeric_limits<size_t>::max();
     value_ = 0;
   }
 
@@ -362,7 +362,7 @@ class UpperBitsReader {
     }
 
     ++position_;
-    inner_ = Instructions::ctz(block_);
+    inner_ = size_t(Instructions::ctz(block_));
     block_ = Instructions::blsr(block_);
 
     return setValue();
@@ -375,16 +375,13 @@ class UpperBitsReader {
 
     // Use forward pointer.
     if (Encoder::forwardQuantum > 0 && n > Encoder::forwardQuantum) {
-      // Workaround to avoid 'division by zero' compile-time error.
-      constexpr size_t q = Encoder::forwardQuantum ?: 1;
-
-      const size_t steps = position_ / q;
+      const size_t steps = position_ / Encoder::forwardQuantum;
       const size_t dest =
         folly::loadUnaligned<SkipValueType>(
             forwardPointers_ + (steps - 1) * sizeof(SkipValueType));
 
-      reposition(dest + steps * q);
-      n = position_ + 1 - steps * q;  // n is > 0.
+      reposition(dest + steps * Encoder::forwardQuantum);
+      n = position_ + 1 - steps * Encoder::forwardQuantum; // n is > 0.
       // Correct inner_ will be set at the end.
     }
 
@@ -411,15 +408,12 @@ class UpperBitsReader {
 
     // Use skip pointer.
     if (Encoder::skipQuantum > 0 && v >= value_ + Encoder::skipQuantum) {
-      // Workaround to avoid 'division by zero' compile-time error.
-      constexpr size_t q = Encoder::skipQuantum ?: 1;
-
-      const size_t steps = v / q;
+      const size_t steps = v / Encoder::skipQuantum;
       const size_t dest =
         folly::loadUnaligned<SkipValueType>(
             skipPointers_ + (steps - 1) * sizeof(SkipValueType));
 
-      reposition(dest + q * steps);
+      reposition(dest + Encoder::skipQuantum * steps);
       position_ = dest - 1;
 
       // Correct inner_ and value_ will be set during the next()
@@ -456,7 +450,7 @@ class UpperBitsReader {
     if (Encoder::forwardQuantum == 0 || n <= Encoder::forwardQuantum) {
       reset();
     } else {
-      position_ = -1;  // Avoid reading the head, skip() will reposition.
+      position_ = size_t(-1); // Avoid reading the head, skip() will reposition.
     }
     return skip(n);
   }
@@ -479,8 +473,8 @@ class UpperBitsReader {
     block &= (block_t(1) << inner_) - 1;
 
     while (UNLIKELY(block == 0)) {
-      DCHECK_GE(outer, sizeof(block_t));
-      outer -= sizeof(block_t);
+      DCHECK_GT(outer, 0);
+      outer -= std::min(sizeof(block_t), outer);
       block = folly::loadUnaligned<block_t>(start_ + outer);
     }
 
@@ -543,7 +537,7 @@ class EliasFanoReader {
       lastValue_ = 0;
       return;
     }
-    ValueType lastUpperValue = 8 * list.upperSize() - size_;
+    ValueType lastUpperValue = ValueType(8 * list.upperSize() - size_);
     auto it = list.upper + list.upperSize() - 1;
     DCHECK_NE(*it, 0);
     lastUpperValue -= 8 - folly::findLastSet(*it);
@@ -592,8 +586,8 @@ class EliasFanoReader {
       return true;
     }
 
-    size_t upperValue = (value >> numLowerBits_);
-    size_t upperSkip = upperValue - upper_.value();
+    ValueType upperValue = (value >> numLowerBits_);
+    ValueType upperSkip = upperValue - upper_.value();
     // The average density of ones in upper bits is 1/2.
     // LIKELY here seems to make things worse, even for small skips.
     if (upperSkip < 2 * kLinearScanThreshold) {
@@ -684,5 +678,3 @@ class EliasFanoReader {
 };
 
 }}  // namespaces
-
-#endif  // FOLLY_EXPERIMENTAL_ELIAS_FANO_CODING_H