#include "PostingList.h"
#include "index/dex/Iterator.h"
#include "index/dex/Token.h"
#include "llvm/Support/MathExtras.h"
#include <optional>
namespace clang {
namespace clangd {
namespace dex {
namespace {
class ChunkIterator : public Iterator {
public:
explicit ChunkIterator(const Token *Tok, llvm::ArrayRef<Chunk> Chunks)
: Tok(Tok), Chunks(Chunks), CurrentChunk(Chunks.begin()) {
if (!Chunks.empty()) {
DecompressedChunk = CurrentChunk->decompress();
CurrentID = DecompressedChunk.begin();
}
}
bool reachedEnd() const override { return CurrentChunk == Chunks.end(); }
void advance() override {
assert(!reachedEnd() &&
"Posting List iterator can't advance() at the end.");
++CurrentID;
normalizeCursor();
}
void advanceTo(DocID ID) override {
assert(!reachedEnd() &&
"Posting List iterator can't advance() at the end.");
if (ID <= peek())
return;
advanceToChunk(ID);
CurrentID = std::partition_point(CurrentID, DecompressedChunk.end(),
[&](const DocID D) { return D < ID; });
normalizeCursor();
}
DocID peek() const override {
assert(!reachedEnd() && "Posting List iterator can't peek() at the end.");
return *CurrentID;
}
float consume() override {
assert(!reachedEnd() &&
"Posting List iterator can't consume() at the end.");
return 1;
}
size_t estimateSize() const override {
return Chunks.size() * ApproxEntriesPerChunk;
}
private:
llvm::raw_ostream &dump(llvm::raw_ostream &OS) const override {
if (Tok != nullptr)
return OS << *Tok;
OS << '[';
const char *Sep = "";
for (const Chunk &C : Chunks)
for (const DocID Doc : C.decompress()) {
OS << Sep << Doc;
Sep = " ";
}
return OS << ']';
}
void normalizeCursor() {
if (CurrentID != std::end(DecompressedChunk))
return;
++CurrentChunk;
if (CurrentChunk == Chunks.end())
return;
DecompressedChunk = CurrentChunk->decompress();
CurrentID = DecompressedChunk.begin();
}
void advanceToChunk(DocID ID) {
if ((CurrentChunk != Chunks.end() - 1) &&
((CurrentChunk + 1)->Head <= ID)) {
CurrentChunk =
std::partition_point(CurrentChunk + 1, Chunks.end(),
[&](const Chunk &C) { return C.Head < ID; });
--CurrentChunk;
DecompressedChunk = CurrentChunk->decompress();
CurrentID = DecompressedChunk.begin();
}
}
const Token *Tok;
llvm::ArrayRef<Chunk> Chunks;
decltype(Chunks)::const_iterator CurrentChunk;
llvm::SmallVector<DocID, Chunk::PayloadSize + 1> DecompressedChunk;
decltype(DecompressedChunk)::iterator CurrentID;
static constexpr size_t ApproxEntriesPerChunk = 15;
};
static constexpr size_t BitsPerEncodingByte = 7;
bool encodeVByte(DocID Delta, llvm::MutableArrayRef<uint8_t> &Payload) {
assert(Delta != 0 && "0 is not a valid PostingList delta.");
unsigned Width = 1 + llvm::Log2_64(Delta) / BitsPerEncodingByte;
if (Width > Payload.size())
return false;
do {
uint8_t Encoding = Delta & 0x7f;
Delta >>= 7;
Payload.front() = Delta ? Encoding | 0x80 : Encoding;
Payload = Payload.drop_front();
} while (Delta != 0);
return true;
}
std::vector<Chunk> encodeStream(llvm::ArrayRef<DocID> Documents) {
assert(!Documents.empty() && "Can't encode empty sequence.");
std::vector<Chunk> Result;
Result.emplace_back();
DocID Last = Result.back().Head = Documents.front();
llvm::MutableArrayRef<uint8_t> RemainingPayload = Result.back().Payload;
for (DocID Doc : Documents.drop_front()) {
if (!encodeVByte(Doc - Last, RemainingPayload)) {
Result.emplace_back();
Result.back().Head = Doc;
RemainingPayload = Result.back().Payload;
}
Last = Doc;
}
return std::vector<Chunk>(Result);
}
std::optional<DocID> readVByte(llvm::ArrayRef<uint8_t> &Bytes) {
if (Bytes.front() == 0 || Bytes.empty())
return std::nullopt;
DocID Result = 0;
bool HasNextByte = true;
for (size_t Length = 0; HasNextByte && !Bytes.empty(); ++Length) {
assert(Length <= 5 && "Malformed VByte encoding sequence.");
Result |= (Bytes.front() & 0x7f) << (BitsPerEncodingByte * Length);
if ((Bytes.front() & 0x80) == 0)
HasNextByte = false;
Bytes = Bytes.drop_front();
}
return Result;
}
}
llvm::SmallVector<DocID, Chunk::PayloadSize + 1> Chunk::decompress() const {
llvm::SmallVector<DocID, Chunk::PayloadSize + 1> Result{Head};
llvm::ArrayRef<uint8_t> Bytes(Payload);
DocID Delta;
for (DocID Current = Head; !Bytes.empty(); Current += Delta) {
auto MaybeDelta = readVByte(Bytes);
if (!MaybeDelta)
break;
Delta = *MaybeDelta;
Result.push_back(Current + Delta);
}
return llvm::SmallVector<DocID, Chunk::PayloadSize + 1>{Result};
}
PostingList::PostingList(llvm::ArrayRef<DocID> Documents)
: Chunks(encodeStream(Documents)) {}
std::unique_ptr<Iterator> PostingList::iterator(const Token *Tok) const {
return std::make_unique<ChunkIterator>(Tok, Chunks);
}
}
}
}