packages feed

souffle-haskell-1.0.0: cbits/souffle/RecordTable.h

/*
 * Souffle - A Datalog Compiler
 * Copyright (c) 2020, The Souffle Developers. All rights reserved.
 * Licensed under the Universal Permissive License v 1.0 as shown at:
 * - https://opensource.org/licenses/UPL
 * - <souffle root>/licenses/SOUFFLE-UPL.txt
 */

/************************************************************************
 *
 * @file RecordTable.h
 *
 * Data container implementing a map between records and their references.
 * Records are separated by arity, i.e., stored in different RecordMaps.
 *
 ***********************************************************************/

#pragma once

#include "CompiledTuple.h"
#include "RamTypes.h"
#include <cassert>
#include <cstddef>
#include <limits>
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>

namespace souffle {

/** @brief Bidirectional mappping between records and record references */
class RecordMap {
    /** arity of record */
    const size_t arity;

    /** hash function for unordered record map */
    struct RecordHash {
        std::size_t operator()(std::vector<RamDomain> record) const {
            std::size_t seed = 0;
            std::hash<RamDomain> domainHash;
            for (RamDomain value : record) {
                seed ^= domainHash(value) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
            }
            return seed;
        }
    };

    /** map from records to references */
    // TODO (b-scholz): replace vector<RamDomain> with something more memory-frugal
    std::unordered_map<std::vector<RamDomain>, RamDomain, RecordHash> recordToIndex;

    /** array of records; index represents record reference */
    // TODO (b-scholz): replace vector<RamDomain> with something more memory-frugal
    std::vector<std::vector<RamDomain>> indexToRecord;

public:
    explicit RecordMap(size_t arity) : arity(arity), indexToRecord(1) {}  // note: index 0 element left free

    /** @brief converts record to a record reference */
    // TODO (b-scholz): replace vector<RamDomain> with something more memory-frugal
    RamDomain pack(const std::vector<RamDomain>& vector) {
        RamDomain index;
#pragma omp critical(record_pack)
        {
            auto pos = recordToIndex.find(vector);
            if (pos != recordToIndex.end()) {
                index = pos->second;
            } else {
#pragma omp critical(record_unpack)
                {
                    indexToRecord.push_back(vector);
                    index = indexToRecord.size() - 1;
                    recordToIndex[vector] = index;

                    // assert that new index is smaller than the range
                    assert(index != std::numeric_limits<RamDomain>::max());
                }
            }
        }
        return index;
    }

    /** @brief convert record pointer to a record reference */
    RamDomain pack(const RamDomain* tuple) {
        // TODO (b-scholz): data is unnecessarily copied
        // for a successful lookup. To avoid this, we should
        // compute a hash of the pointer-array and traverse through
        // the bucket list of the unordered map finding the record.
        // Note that in case of non-existence, the record still needs to be
        // copied for the newly created entry but this will be the less
        // frequent case.
        std::vector<RamDomain> tmp(arity);
        for (size_t i = 0; i < arity; i++) {
            tmp[i] = tuple[i];
        }
        return pack(tmp);
    }

    /** @brief convert record reference to a record pointer */
    const RamDomain* unpack(RamDomain index) const {
        const RamDomain* res;
#pragma omp critical(record_unpack)
        res = indexToRecord[index].data();
        return res;
    }
};

class RecordTable {
public:
    RecordTable() = default;
    virtual ~RecordTable() = default;

    /** @brief convert record to record reference */
    RamDomain pack(RamDomain* tuple, size_t arity) {
        return lookupArity(arity).pack(tuple);
    }
    /** @brief convert record reference to a record */
    const RamDomain* unpack(RamDomain ref, size_t arity) const {
        auto iter = maps.find(arity);
        assert(iter != maps.end() && "Attempting to unpack non-existing record");
        return (iter->second).unpack(ref);
    }

private:
    /** @brief lookup RecordMap for a given arity; if it does not exist, create new RecordMap */
    RecordMap& lookupArity(size_t arity) {
        std::unordered_map<size_t, RecordMap>::iterator mapsIterator;
#pragma omp critical(RecordTableGetForArity)
        {
            // This will create a new map if it doesn't exist yet.
            mapsIterator = maps.emplace(arity, arity).first;
        }
        return mapsIterator->second;
    }

    /** Arity/RecordMap association */
    std::unordered_map<size_t, RecordMap> maps;
};

/** @brief helper to convert tuple to record reference for the synthesiser */
template <std::size_t Arity>
inline RamDomain pack(RecordTable& recordTab, Tuple<RamDomain, Arity> tuple) {
    return recordTab.pack(static_cast<RamDomain*>(tuple.data), Arity);
}

}  // namespace souffle