packages feed

souffle-haskell 3.0.0 → 3.1.0

raw patch · 46 files changed

+7462/−4531 lines, 46 files

Files

CHANGELOG.md view
@@ -3,6 +3,18 @@ All notable changes to this project (as seen by library users) will be documented in this file. The CHANGELOG is available on [Github](https://github.com/luc-tielen/souffle-haskell.git/CHANGELOG.md). ++## [3.1.0] - 2021-09-30++### Changed++- souffle-haskell now supports Souffle version 2.1.++### Fixed++- Bug in some C++ assertions that caused the actual assertion message to be+  wrongly computed.+ ## [3.0.0] - 2021-05-03  ### Changed
cbits/souffle.cpp view
@@ -71,11 +71,13 @@  namespace helpers {+using souffle_type = char;+ inline auto parse_signature(const souffle::Relation& relation) {     const auto arity = relation.getArity(); -    std::vector<char> types;+    std::vector<souffle_type> types;     types.reserve(arity);      for (size_t i = 0; i < arity; ++i)@@ -131,7 +133,7 @@ }  using deserializer_t = void(*)(souffle::tuple&, char*, offset_t&);-using deserializer_map = std::unordered_map<char, deserializer_t>;+using deserializer_map = std::unordered_map<souffle_type, deserializer_t>;  static const deserializer_map deserializers_map = {     {'s', deserialize_symbol},@@ -141,7 +143,7 @@ };  using serializer_t = void(*)(souffle::tuple&, char*, offset_t&);-using serializer_map = std::unordered_map<char, serializer_t>;+using serializer_map = std::unordered_map<souffle_type, serializer_t>;  static const serializer_map serializers_map = {     {'i', serialize_value<number_t>},@@ -149,16 +151,21 @@     {'f', serialize_value<float_t>} }; -inline auto types_to_deserializer(const std::vector<char>& types)+inline std::string unknown_souffle_type(souffle_type ty) {+    std::string base_message = "Found unknown Souffle primitive type: ";+    return base_message + std::string(1, ty);+}++inline auto types_to_deserializer(const std::vector<souffle_type>& types)+{     std::vector<deserializer_t> deserializers;     deserializers.reserve(types.size());      for (const auto& type: types)     {         const auto match = deserializers_map.find(type);-        assert(match != deserializers_map.end() &&-                ("Found unknown Souffle primitive type: " + match->first));+        assert(match != deserializers_map.end() && unknown_souffle_type(match->first).c_str());         deserializers.push_back(match->second);     } @@ -171,7 +178,7 @@     }; } -inline auto types_to_serializer(const std::vector<char>& types)+inline auto types_to_serializer(const std::vector<souffle_type>& types) {     std::vector<serializer_t> serializers;     serializers.reserve(types.size());@@ -179,8 +186,7 @@     for (const auto& type: types)     {         const auto match = serializers_map.find(type);-        assert(match != serializers_map.end() &&-                ("Found unknown Souffle primitive type: " + match->first));+        assert(match != serializers_map.end() && unknown_souffle_type(match->first).c_str());         serializers.push_back(match->second);     } @@ -193,7 +199,7 @@     }; } -inline auto guess_tuple_size(const std::vector<char>& types)+inline auto guess_tuple_size(const std::vector<souffle_type>& types) {     size_t size = 0; @@ -229,7 +235,7 @@     inline const Serializer& serialize()     {         using serializer_t = void(*)(Serializer*, souffle::tuple&);-        using serializer_map_t = std::unordered_map<char, serializer_t>;+        using serializer_map_t = std::unordered_map<souffle_type, serializer_t>;          serializer_t do_serialize_symbol = [](auto s, auto& t) {             s->serialize_symbol(t);@@ -257,8 +263,7 @@         for (const auto& type: m_types)         {             const auto match = serializers_map.find(type);-            assert(match != serializers_map.end() &&-                    ("Found unknown Souffle primitive type: " + match->first));+            assert(match != serializers_map.end() && unknown_souffle_type(match->first).c_str());             serializers.push_back(match->second);         } @@ -359,7 +364,7 @@  private:     const souffle::Relation& m_relation;-    std::vector<char> m_types;+    std::vector<souffle_type> m_types;     size_t m_fact_count;     buf_data& m_buf;     size_t m_num_bytes;
cbits/souffle/CompiledSouffle.h view
@@ -16,7 +16,6 @@  #pragma once -#include "souffle/CompiledTuple.h" #include "souffle/RamTypes.h" #include "souffle/RecordTable.h" #include "souffle/SignalHandler.h"@@ -71,31 +70,39 @@ /**  * Relation wrapper used internally in the generated Datalog program  */-template <uint32_t id, class RelType, class TupleType, size_t Arity, size_t NumAuxAttributes>+template <class RelType> class RelationWrapper : public souffle::Relation {+public:+    static constexpr arity_type Arity = RelType::Arity;+    using TupleType = Tuple<RamDomain, Arity>;+    using AttrStrSeq = std::array<const char*, Arity>;+ private:     RelType& relation;-    SymbolTable& symTable;+    SouffleProgram& program;     std::string name;-    std::array<const char*, Arity> tupleType;-    std::array<const char*, Arity> tupleName;+    AttrStrSeq attrTypes;+    AttrStrSeq attrNames;+    const uint32_t id;+    const arity_type numAuxAttribs; +    // NB: internal wrapper. does not satisfy the `iterator` concept.     class iterator_wrapper : public iterator_base {         typename RelType::iterator it;         const Relation* relation;         tuple t;      public:-        iterator_wrapper(uint32_t arg_id, const Relation* rel, const typename RelType::iterator& arg_it)-                : iterator_base(arg_id), it(arg_it), relation(rel), t(rel) {}+        iterator_wrapper(uint32_t arg_id, const Relation* rel, typename RelType::iterator arg_it)+                : iterator_base(arg_id), it(std::move(arg_it)), relation(rel), t(rel) {}         void operator++() override {             ++it;         }         tuple& operator*() override {+            auto&& value = *it;             t.rewind();-            for (size_t i = 0; i < Arity; i++) {-                t[i] = (*it)[i];-            }+            for (std::size_t i = 0; i < Arity; i++)+                t[i] = value[i];             return t;         }         iterator_base* clone() const override {@@ -104,26 +111,29 @@      protected:         bool equal(const iterator_base& o) const override {-            const auto& casted = static_cast<const iterator_wrapper&>(o);+            const auto& casted = asAssert<iterator_wrapper>(o);             return it == casted.it;         }     };  public:-    RelationWrapper(RelType& r, SymbolTable& s, std::string name, const std::array<const char*, Arity>& t,-            const std::array<const char*, Arity>& n)-            : relation(r), symTable(s), name(std::move(name)), tupleType(t), tupleName(n) {}+    RelationWrapper(uint32_t id, RelType& r, SouffleProgram& p, std::string name, const AttrStrSeq& t,+            const AttrStrSeq& n, arity_type numAuxAttribs)+            : relation(r), program(p), name(std::move(name)), attrTypes(t), attrNames(n), id(id),+              numAuxAttribs(numAuxAttribs) {}+     iterator begin() const override {-        return iterator(new iterator_wrapper(id, this, relation.begin()));+        return iterator(mk<iterator_wrapper>(id, this, relation.begin()));     }     iterator end() const override {-        return iterator(new iterator_wrapper(id, this, relation.end()));+        return iterator(mk<iterator_wrapper>(id, this, relation.end()));     }+     void insert(const tuple& arg) override {         TupleType t;         assert(&arg.getRelation() == this && "wrong relation");         assert(arg.size() == Arity && "wrong tuple arity");-        for (size_t i = 0; i < Arity; i++) {+        for (std::size_t i = 0; i < Arity; i++) {             t[i] = arg[i];         }         relation.insert(t);@@ -131,7 +141,7 @@     bool contains(const tuple& arg) const override {         TupleType t;         assert(arg.size() == Arity && "wrong tuple arity");-        for (size_t i = 0; i < Arity; i++) {+        for (std::size_t i = 0; i < Arity; i++) {             t[i] = arg[i];         }         return relation.contains(t);@@ -142,22 +152,22 @@     std::string getName() const override {         return name;     }-    const char* getAttrType(size_t arg) const override {+    const char* getAttrType(std::size_t arg) const override {         assert(arg < Arity && "attribute out of bound");-        return tupleType[arg];+        return attrTypes[arg];     }-    const char* getAttrName(size_t arg) const override {+    const char* getAttrName(std::size_t arg) const override {         assert(arg < Arity && "attribute out of bound");-        return tupleName[arg];+        return attrNames[arg];     }-    size_t getArity() const override {+    arity_type getArity() const override {         return Arity;     }-    size_t getAuxiliaryArity() const override {-        return NumAuxAttributes;+    arity_type getAuxiliaryArity() const override {+        return numAuxAttribs;     }     SymbolTable& getSymbolTable() const override {-        return symTable;+        return program.getSymbolTable();     }      /** Eliminate all the tuples in relation*/@@ -172,6 +182,8 @@     std::atomic<bool> data{false};  public:+    static constexpr Relation::arity_type Arity = 0;+     t_nullaries() = default;     using t_tuple = Tuple<RamDomain, 0>;     struct context {};@@ -183,10 +195,10 @@      public:         typedef std::forward_iterator_tag iterator_category;-        typedef RamDomain* value_type;-        typedef ptrdiff_t difference_type;-        typedef value_type* pointer;-        typedef value_type& reference;+        using value_type = RamDomain*;+        using difference_type = ptrdiff_t;+        using pointer = value_type*;+        using reference = value_type&;          iterator(bool v = false) : value(v) {} @@ -247,14 +259,12 @@     void printStatistics(std::ostream& /* o */) const {} }; -/** info relations */-template <int Arity>+/** Info relations */+template <Relation::arity_type Arity_> class t_info {-private:-    std::vector<Tuple<RamDomain, Arity>> data;-    Lock insert_lock;- public:+    static constexpr Relation::arity_type Arity = Arity_;+     t_info() = default;     using t_tuple = Tuple<RamDomain, Arity>;     struct context {};@@ -303,7 +313,7 @@     void insert(const RamDomain* ramDomain) {         insert_lock.lock();         t_tuple t;-        for (size_t i = 0; i < Arity; ++i) {+        for (std::size_t i = 0; i < Arity; ++i) {             t.data[i] = ramDomain[i];         }         data.push_back(t);@@ -328,6 +338,166 @@     }     void purge() {         data.clear();+    }+    void printStatistics(std::ostream& /* o */) const {}++private:+    std::vector<Tuple<RamDomain, Arity>> data;+    Lock insert_lock;+};++/** Equivalence relations */+struct t_eqrel {+    static constexpr Relation::arity_type Arity = 2;+    using t_tuple = Tuple<RamDomain, 2>;+    using t_ind = EquivalenceRelation<t_tuple>;+    t_ind ind;+    class iterator_0 : public std::iterator<std::forward_iterator_tag, t_tuple> {+        using nested_iterator = typename t_ind::iterator;+        nested_iterator nested;+        t_tuple value;++    public:+        iterator_0() = default;+        iterator_0(const nested_iterator& iter) : nested(iter), value(*iter) {}+        iterator_0(const iterator_0& other) = default;+        iterator_0& operator=(const iterator_0& other) = default;+        bool operator==(const iterator_0& other) const {+            return nested == other.nested;+        }+        bool operator!=(const iterator_0& other) const {+            return !(*this == other);+        }+        const t_tuple& operator*() const {+            return value;+        }+        const t_tuple* operator->() const {+            return &value;+        }+        iterator_0& operator++() {+            ++nested;+            value = *nested;+            return *this;+        }+    };+    class iterator_1 : public std::iterator<std::forward_iterator_tag, t_tuple> {+        using nested_iterator = typename t_ind::iterator;+        nested_iterator nested;+        t_tuple value;++    public:+        iterator_1() = default;+        iterator_1(const nested_iterator& iter) : nested(iter), value(reorder(*iter)) {}+        iterator_1(const iterator_1& other) = default;+        iterator_1& operator=(const iterator_1& other) = default;+        bool operator==(const iterator_1& other) const {+            return nested == other.nested;+        }+        bool operator!=(const iterator_1& other) const {+            return !(*this == other);+        }+        const t_tuple& operator*() const {+            return value;+        }+        const t_tuple* operator->() const {+            return &value;+        }+        iterator_1& operator++() {+            ++nested;+            value = reorder(*nested);+            return *this;+        }+    };+    using iterator = iterator_0;+    struct context {+        t_ind::operation_hints hints;+    };+    context createContext() {+        return context();+    }+    bool insert(const t_tuple& t) {+        return ind.insert(t[0], t[1]);+    }+    bool insert(const t_tuple& t, context& h) {+        return ind.insert(t[0], t[1], h.hints);+    }+    bool insert(const RamDomain* ramDomain) {+        RamDomain data[2];+        std::copy(ramDomain, ramDomain + 2, data);+        const t_tuple& tuple = reinterpret_cast<const t_tuple&>(data);+        context h;+        return insert(tuple, h);+    }+    bool insert(RamDomain a1, RamDomain a2) {+        RamDomain data[2] = {a1, a2};+        return insert(data);+    }+    void extend(const t_eqrel& other) {+        ind.extend(other.ind);+    }+    bool contains(const t_tuple& t) const {+        return ind.contains(t[0], t[1]);+    }+    bool contains(const t_tuple& t, context& h) const {+        return ind.contains(t[0], t[1]);+    }+    std::size_t size() const {+        return ind.size();+    }+    iterator find(const t_tuple& t) const {+        return ind.find(t);+    }+    iterator find(const t_tuple& t, context& h) const {+        return ind.find(t);+    }+    range<iterator> lowerUpperRange_10(const t_tuple& lower, const t_tuple& upper, context& h) const {+        auto r = ind.template getBoundaries<1>((lower), h.hints);+        return make_range(iterator(r.begin()), iterator(r.end()));+    }+    range<iterator> lowerUpperRange_10(const t_tuple& lower, const t_tuple& upper) const {+        context h;+        return lowerUpperRange_10(lower, upper, h);+    }+    range<iterator_1> lowerUpperRange_01(const t_tuple& lower, const t_tuple& upper, context& h) const {+        auto r = ind.template getBoundaries<1>(reorder(lower), h.hints);+        return make_range(iterator_1(r.begin()), iterator_1(r.end()));+    }+    range<iterator_1> lowerUpperRange_01(const t_tuple& lower, const t_tuple& upper) const {+        context h;+        return lowerUpperRange_01(lower, upper, h);+    }+    range<iterator> lowerUpperRange_11(const t_tuple& lower, const t_tuple& upper, context& h) const {+        auto r = ind.template getBoundaries<2>((lower), h.hints);+        return make_range(iterator(r.begin()), iterator(r.end()));+    }+    range<iterator> lowerUpperRange_11(const t_tuple& lower, const t_tuple& upper) const {+        context h;+        return lowerUpperRange_11(lower, upper, h);+    }+    bool empty() const {+        return ind.size() == 0;+    }+    std::vector<range<iterator>> partition() const {+        std::vector<range<iterator>> res;+        for (const auto& cur : ind.partition(10000)) {+            res.push_back(make_range(iterator(cur.begin()), iterator(cur.end())));+        }+        return res;+    }+    void purge() {+        ind.clear();+    }+    iterator begin() const {+        return iterator(ind.begin());+    }+    iterator end() const {+        return iterator(ind.end());+    }+    static t_tuple reorder(const t_tuple& t) {+        t_tuple res;+        res[0] = t[1];+        res[1] = t[0];+        return res;     }     void printStatistics(std::ostream& /* o */) const {} };
− cbits/souffle/CompiledTuple.h
@@ -1,186 +0,0 @@-/*- * Souffle - A Datalog Compiler- * Copyright (c) 2013, 2015, Oracle and/or its affiliates. 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 CompiledTuple.h- *- * The central file covering the data structure utilized by- * the souffle compiler for representing relations in compiled queries.- *- ***********************************************************************/--#pragma once--#include <cstddef>-#include <functional>-#include <iostream>-#include <system_error>--namespace souffle {--/**- * The type of object stored within relations representing the actual- * tuple value. Each tuple consists of a constant number of components.- *- * @tparam Domain the domain of the component values- * @tparam arity the number of components within an instance- */-template <typename Domain, std::size_t _arity>-struct Tuple {-    // some features for template meta programming-    using value_type = Domain;-    static constexpr size_t arity = _arity;--    // the stored data-    Domain data[arity];--    // constructors, destructors and assignment are default--    // provide access to components-    const Domain& operator[](std::size_t index) const {-        return data[index];-    }--    // provide access to components-    Domain& operator[](std::size_t index) {-        return data[index];-    }--    // a comparison operation-    bool operator==(const Tuple& other) const {-        for (std::size_t i = 0; i < arity; i++) {-            if (data[i] != other.data[i]) return false;-        }-        return true;-    }--    // inequality comparison-    bool operator!=(const Tuple& other) const {-        return !(*this == other);-    }--    // required to put tuples into e.g. a std::set container-    bool operator<(const Tuple& other) const {-        for (std::size_t i = 0; i < arity; ++i) {-            if (data[i] < other.data[i]) return true;-            if (data[i] > other.data[i]) return false;-        }-        return false;-    }--    // required to put tuples into e.g. a btree container-    bool operator>(const Tuple& other) const {-        for (std::size_t i = 0; i < arity; ++i) {-            if (data[i] > other.data[i]) return true;-            if (data[i] < other.data[i]) return false;-        }-        return false;-    }--    // allow tuples to be printed-    friend std::ostream& operator<<(std::ostream& out, const Tuple& tuple) {-        if (arity == 0) return out << "[]";-        out << "[";-        for (std::size_t i = 0; i < (std::size_t)(arity - 1); ++i) {-            out << tuple.data[i];-            out << ",";-        }-        return out << tuple.data[arity - 1] << "]";-    }-};--#ifdef _MSC_VER-/**- * A template specialization for 0-arity tuples when compiling with microsoft's- * compiler, because it doesn't like the 0 length array even though it is the- * last member of the struct.- */-template <typename Domain>-struct Tuple<Domain, 0> {-    // some features for template meta programming-    using value_type = Domain;-    enum { arity = 0 };--    // the stored data-    Domain data[1];--    // constructores, destructors and assignment are default--    // provide access to components-    const Domain& operator[](std::size_t index) const {-        return data[index];-    }--    // provide access to components-    Domain& operator[](std::size_t index) {-        return data[index];-    }--    // a comparison operation-    bool operator==(const Tuple& other) const {-        for (std::size_t i = 0; i < arity; i++) {-            if (data[i] != other.data[i]) return false;-        }-        return true;-    }--    // inequality comparison-    bool operator!=(const Tuple& other) const {-        return !(*this == other);-    }--    // required to put tuples into e.g. a std::set container-    bool operator<(const Tuple& other) const {-        for (std::size_t i = 0; i < arity; ++i) {-            if (data[i] < other.data[i]) return true;-            if (data[i] > other.data[i]) return false;-        }-        return false;-    }--    // required to put tuples into e.g. a btree container-    bool operator>(const Tuple& other) const {-        for (std::size_t i = 0; i < arity; ++i) {-            if (data[i] > other.data[i]) return true;-            if (data[i] < other.data[i]) return false;-        }-        return false;-    }--    // allow tuples to be printed-    friend std::ostream& operator<<(std::ostream& out, const Tuple& tuple) {-        if (arity == 0) return out << "[]";-        out << "[";-        for (std::size_t i = 0; i < (std::size_t)(arity - 1); ++i) {-            out << tuple.data[i];-            out << ",";-        }-        return out << tuple.data[arity - 1] << "]";-    }-};-#endif  // _MSC_VER-}  // end of namespace souffle--// -- add hashing support ----namespace std {--template <typename Domain, std::size_t arity>-struct hash<souffle::Tuple<Domain, arity>> {-    size_t operator()(const souffle::Tuple<Domain, arity>& value) const {-        std::hash<Domain> hash;-        size_t res = 0;-        for (unsigned i = 0; i < arity; i++) {-            // from boost hash combine-            res ^= hash(value[i]) + 0x9e3779b9 + (res << 6) + (res >> 2);-        }-        return res;-    }-};-}  // namespace std
cbits/souffle/RamTypes.h view
@@ -16,6 +16,7 @@  #pragma once +#include <array> #include <cstdint> #include <cstring> #include <iostream>@@ -23,6 +24,10 @@ #include <type_traits>  namespace souffle {++// deprecated. use `std::array` directly.+template <typename A, std::size_t N>+using Tuple = std::array<A, N>;  /**  * Types of elements in a tuple.
cbits/souffle/RecordTable.h view
@@ -1,6 +1,6 @@ /*  * Souffle - A Datalog Compiler- * Copyright (c) 2020, The Souffle Developers. All rights reserved.+ * Copyright (c) 2013, 2014, Oracle and/or its affiliates. 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@@ -17,136 +17,602 @@  #pragma once -#include "souffle/CompiledTuple.h" #include "souffle/RamTypes.h"+#include "souffle/datastructure/ConcurrentFlyweight.h"+#include "souffle/utility/span.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;+namespace details { -    /** 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;+// Helper to unroll for loop+template <auto Start, auto End, auto Inc, class F>+constexpr void constexpr_for(F&& f) {+    if constexpr (Start < End) {+        f(std::integral_constant<decltype(Start), Start>());+        constexpr_for<Start + Inc, End, Inc>(f);+    }+}++/// @brief The data-type of RamDomain records of any size.+using GenericRecord = std::vector<RamDomain>;++/// @brief The data-type of RamDomain records of specialized size.+template <std::size_t Arity>+using SpecializedRecord = std::array<RamDomain, Arity>;++/// @brief A view in a sequence of RamDomain value.+// TODO: use a `span`.+struct GenericRecordView {+    explicit GenericRecordView(const RamDomain* Data, const std::size_t Arity) : Data(Data), Arity(Arity) {}+    GenericRecordView(const GenericRecordView& Other) : Data(Other.Data), Arity(Other.Arity) {}+    GenericRecordView(GenericRecordView&& Other) : Data(Other.Data), Arity(Other.Arity) {}++    const RamDomain* const Data;+    const std::size_t Arity;++    const RamDomain* data() const {+        return Data;+    }++    const RamDomain& operator[](int I) const {+        assert(I >= 0 && static_cast<std::size_t>(I) < Arity);+        return Data[I];+    }+};++template <std::size_t Arity>+struct SpecializedRecordView {+    explicit SpecializedRecordView(const RamDomain* Data) : Data(Data) {}+    SpecializedRecordView(const SpecializedRecordView& Other) : Data(Other.Data) {}+    SpecializedRecordView(SpecializedRecordView&& Other) : Data(Other.Data) {}++    const RamDomain* const Data;++    const RamDomain* data() const {+        return Data;+    }++    const RamDomain& operator[](int I) const {+        assert(I >= 0 && static_cast<std::size_t>(I) < Arity);+        return Data[I];+    }+};++/// @brief Hash function object for a RamDomain record.+struct GenericRecordHash {+    explicit GenericRecordHash(const std::size_t Arity) : Arity(Arity) {}+    GenericRecordHash(const GenericRecordHash& Other) : Arity(Other.Arity) {}+    GenericRecordHash(GenericRecordHash&& Other) : Arity(Other.Arity) {}++    const std::size_t Arity;+    std::hash<RamDomain> domainHash;++    template <typename T>+    std::size_t operator()(const T& Record) const {+        std::size_t Seed = 0;+        for (std::size_t I = 0; I < Arity; ++I) {+            Seed ^= domainHash(Record[I]) + 0x9e3779b9 + (Seed << 6) + (Seed >> 2);         }+        return Seed;+    }+};++template <std::size_t Arity>+struct SpecializedRecordHash {+    explicit SpecializedRecordHash() {}+    SpecializedRecordHash(const SpecializedRecordHash& Other) : DomainHash(Other.DomainHash) {}+    SpecializedRecordHash(SpecializedRecordHash&& Other) : DomainHash(Other.DomainHash) {}++    std::hash<RamDomain> DomainHash;++    template <typename T>+    std::size_t operator()(const T& Record) const {+        std::size_t Seed = 0;+        constexpr_for<0, Arity, 1>(+                [&](auto I) { Seed ^= DomainHash(Record[I]) + 0x9e3779b9 + (Seed << 6) + (Seed >> 2); });+        return Seed;+    }+};++template <>+struct SpecializedRecordHash<0> {+    explicit SpecializedRecordHash() {}+    SpecializedRecordHash(const SpecializedRecordHash&) {}+    SpecializedRecordHash(SpecializedRecordHash&&) {}++    template <typename T>+    std::size_t operator()(const T&) const {+        return 0;+    }+};++/// @brief Equality function object for RamDomain records.+struct GenericRecordEqual {+    explicit GenericRecordEqual(const std::size_t Arity) : Arity(Arity) {}+    GenericRecordEqual(const GenericRecordEqual& Other) : Arity(Other.Arity) {}+    GenericRecordEqual(GenericRecordEqual&& Other) : Arity(Other.Arity) {}++    const std::size_t Arity;++    template <typename T, typename U>+    bool operator()(const T& A, const U& B) const {+        return (std::memcmp(A.data(), B.data(), Arity * sizeof(RamDomain)) == 0);+    }+};++template <std::size_t Arity>+struct SpecializedRecordEqual {+    explicit SpecializedRecordEqual() {}+    SpecializedRecordEqual(const SpecializedRecordEqual&) {}+    SpecializedRecordEqual(SpecializedRecordEqual&&) {}++    template <typename T, typename U>+    bool operator()(const T& A, const U& B) const {+        constexpr std::size_t Len = Arity * sizeof(RamDomain);+        return (std::memcmp(A.data(), B.data(), Len) == 0);+    }+};++template <>+struct SpecializedRecordEqual<0> {+    explicit SpecializedRecordEqual() {}+    SpecializedRecordEqual(const SpecializedRecordEqual&) {}+    SpecializedRecordEqual(SpecializedRecordEqual&&) {}++    template <typename T, typename U>+    bool operator()(const T&, const U&) const {+        return true;+    }+};++/// @brief Less function object for RamDomain records.+struct GenericRecordLess {+    explicit GenericRecordLess(const std::size_t Arity) : Arity(Arity) {}+    GenericRecordLess(const GenericRecordLess& Other) : Arity(Other.Arity) {}+    GenericRecordLess(GenericRecordLess&& Other) : Arity(Other.Arity) {}++    const std::size_t Arity;++    template <typename T, typename U>+    bool operator()(const T& A, const U& B) const {+        return (std::memcmp(A.data(), B.data(), Arity * sizeof(RamDomain)) < 0);+    }+};++template <std::size_t Arity>+struct SpecializedRecordLess {+    explicit SpecializedRecordLess() {}+    SpecializedRecordLess(const SpecializedRecordLess&) {}+    SpecializedRecordLess(SpecializedRecordLess&&) {}++    template <typename T, typename U>+    bool operator()(const T& A, const U& B) const {+        constexpr std::size_t Len = Arity * sizeof(RamDomain);+        return (std::memcmp(A.data(), B.data(), Len) < 0);+    }+};++template <>+struct SpecializedRecordLess<0> {+    explicit SpecializedRecordLess() {}+    SpecializedRecordLess(const SpecializedRecordLess&) {}+    SpecializedRecordLess(SpecializedRecordLess&&) {}++    template <typename T, typename U>+    bool operator()(const T&, const U&) const {+        return false;+    }+};++/// @brief Compare function object for RamDomain records.+struct GenericRecordCmp {+    explicit GenericRecordCmp(const std::size_t Arity) : Arity(Arity) {}+    GenericRecordCmp(const GenericRecordCmp& Other) : Arity(Other.Arity) {}+    GenericRecordCmp(GenericRecordCmp&& Other) : Arity(Other.Arity) {}++    const std::size_t Arity;++    template <typename T, typename U>+    int operator()(const T& A, const U& B) const {+        return std::memcmp(A.data(), B.data(), Arity * sizeof(RamDomain));+    }+};++template <std::size_t Arity>+struct SpecializedRecordCmp {+    explicit SpecializedRecordCmp() {}+    SpecializedRecordCmp(const SpecializedRecordCmp&) {}+    SpecializedRecordCmp(SpecializedRecordCmp&&) {}++    template <typename T, typename U>+    bool operator()(const T& A, const U& B) const {+        constexpr std::size_t Len = Arity * sizeof(RamDomain);+        return std::memcmp(A.data(), B.data(), Len);+    }+};++template <>+struct SpecializedRecordCmp<0> {+    explicit SpecializedRecordCmp() {}+    SpecializedRecordCmp(const SpecializedRecordCmp&) {}+    SpecializedRecordCmp(SpecializedRecordCmp&&) {}++    template <typename T, typename U>+    bool operator()(const T&, const U&) const {+        return 0;+    }+};++/// @brief Factory of RamDomain record.+struct GenericRecordFactory {+    using value_type = GenericRecord;+    using pointer = GenericRecord*;+    using reference = GenericRecord&;++    explicit GenericRecordFactory(const std::size_t Arity) : Arity(Arity) {}+    GenericRecordFactory(const GenericRecordFactory& Other) : Arity(Other.Arity) {}+    GenericRecordFactory(GenericRecordFactory&& Other) : Arity(Other.Arity) {}++    const std::size_t Arity;++    reference replace(reference Place, const std::vector<RamDomain>& V) {+        assert(V.size() == Arity);+        Place = V;+        return Place;+    }++    reference replace(reference Place, const GenericRecordView& V) {+        Place.clear();+        Place.insert(Place.begin(), V.data(), V.data() + Arity);+        return Place;+    }++    reference replace(reference Place, const RamDomain* V) {+        Place.clear();+        Place.insert(Place.begin(), V, V + Arity);+        return Place;+    }+};++template <std::size_t Arity>+struct SpecializedRecordFactory {+    using value_type = SpecializedRecord<Arity>;+    using pointer = SpecializedRecord<Arity>*;+    using reference = SpecializedRecord<Arity>&;++    explicit SpecializedRecordFactory() {}+    SpecializedRecordFactory(const SpecializedRecordFactory&) {}+    SpecializedRecordFactory(SpecializedRecordFactory&&) {}++    reference replace(reference Place, const SpecializedRecord<Arity>& V) {+        assert(V.size() == Arity);+        Place = V;+        return Place;+    }++    reference replace(reference Place, const SpecializedRecordView<Arity>& V) {+        constexpr std::size_t Len = Arity * sizeof(RamDomain);+        std::memcpy(Place.data(), V.data(), Len);+        return Place;+    }++    reference replace(reference Place, const RamDomain* V) {+        constexpr std::size_t Len = Arity * sizeof(RamDomain);+        std::memcpy(Place.data(), V, Len);+        return Place;+    }+};++template <>+struct SpecializedRecordFactory<0> {+    using value_type = SpecializedRecord<0>;+    using pointer = SpecializedRecord<0>*;+    using reference = SpecializedRecord<0>&;++    explicit SpecializedRecordFactory() {}+    SpecializedRecordFactory(const SpecializedRecordFactory&) {}+    SpecializedRecordFactory(SpecializedRecordFactory&&) {}++    reference replace(reference Place, const SpecializedRecord<0>&) {+        return Place;+    }++    reference replace(reference Place, const SpecializedRecordView<0>&) {+        return Place;+    }++    reference replace(reference Place, const RamDomain*) {+        return Place;+    }+};++}  // namespace details++/** @brief Interface of bidirectional mappping between records and record references. */+class RecordMap {+public:+    virtual ~RecordMap() {}+    virtual void setNumLanes(const std::size_t NumLanes) = 0;+    virtual RamDomain pack(const std::vector<RamDomain>& Vector) = 0;+    virtual RamDomain pack(const RamDomain* Tuple) = 0;+    virtual RamDomain pack(const std::initializer_list<RamDomain>& List) = 0;+    virtual const RamDomain* unpack(RamDomain index) const = 0;+};++/** @brief Bidirectional mappping between records and record references, for any record arity. */+class GenericRecordMap : public RecordMap,+                         protected FlyweightImpl<details::GenericRecord, details::GenericRecordHash,+                                 details::GenericRecordEqual, details::GenericRecordFactory> {+    using Base = FlyweightImpl<details::GenericRecord, details::GenericRecordHash,+            details::GenericRecordEqual, details::GenericRecordFactory>;++    const std::size_t Arity;++public:+    explicit GenericRecordMap(const std::size_t lane_count, const std::size_t arity)+            : Base(lane_count, 8, true, details::GenericRecordHash(arity), details::GenericRecordEqual(arity),+                      details::GenericRecordFactory(arity)),+              Arity(arity) {}++    virtual ~GenericRecordMap() {}++    void setNumLanes(const std::size_t NumLanes) override {+        Base::setNumLanes(NumLanes);+    }++    /** @brief converts record to a record reference */+    RamDomain pack(const std::vector<RamDomain>& Vector) override {+        return findOrInsert(Vector).first;     }; -    /** 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;+    /** @brief converts record to a record reference */+    RamDomain pack(const RamDomain* Tuple) override {+        details::GenericRecordView View{Tuple, Arity};+        return findOrInsert(View).first;+    } -    /** array of records; index represents record reference */-    // TODO (b-scholz): replace vector<RamDomain> with something more memory-frugal-    std::vector<std::vector<RamDomain>> indexToRecord;+    /** @brief converts record to a record reference */+    RamDomain pack(const std::initializer_list<RamDomain>& List) override {+        details::GenericRecordView View{std::data(List), Arity};+        return findOrInsert(View).first;+    } +    /** @brief convert record reference to a record pointer */+    const RamDomain* unpack(RamDomain Index) const override {+        return fetch(Index).data();+    }+};++/** @brief Bidirectional mappping between records and record references, specialized for a record arity. */+template <std::size_t Arity>+class SpecializedRecordMap+        : public RecordMap,+          protected FlyweightImpl<details::SpecializedRecord<Arity>, details::SpecializedRecordHash<Arity>,+                  details::SpecializedRecordEqual<Arity>, details::SpecializedRecordFactory<Arity>> {+    using Record = details::SpecializedRecord<Arity>;+    using RecordView = details::SpecializedRecordView<Arity>;+    using RecordHash = details::SpecializedRecordHash<Arity>;+    using RecordEqual = details::SpecializedRecordEqual<Arity>;+    using RecordFactory = details::SpecializedRecordFactory<Arity>;+    using Base = FlyweightImpl<Record, RecordHash, RecordEqual, RecordFactory>;+ public:-    explicit RecordMap(size_t arity) : arity(arity), indexToRecord(1) {}  // note: index 0 element left free+    SpecializedRecordMap(const std::size_t LaneCount)+            : Base(LaneCount, 8, true, RecordHash(), RecordEqual(), RecordFactory()) {} +    virtual ~SpecializedRecordMap() {}++    void setNumLanes(const std::size_t NumLanes) override {+        Base::setNumLanes(NumLanes);+    }+     /** @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 = static_cast<RamDomain>(indexToRecord.size()) - 1;-                    recordToIndex[vector] = index;+    RamDomain pack(const std::vector<RamDomain>& Vector) override {+        assert(Vector.size() == Arity);+        RecordView View{Vector.data()};+        return Base::findOrInsert(View).first;+    }; -                    // assert that new index is smaller than the range-                    assert(index != std::numeric_limits<RamDomain>::max());-                }-            }-        }-        return index;+    /** @brief converts record to a record reference */+    RamDomain pack(const RamDomain* Tuple) override {+        RecordView View{Tuple};+        return Base::findOrInsert(View).first;     } -    /** @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 converts record to a record reference */+    RamDomain pack(const std::initializer_list<RamDomain>& List) override {+        assert(List.size() == Arity);+        RecordView View{std::data(List)};+        return Base::findOrInsert(View).first;     }      /** @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;+    const RamDomain* unpack(RamDomain Index) const override {+        return Base::fetch(Index).data();     } }; -class RecordTable {+/** Record map specialized for arity 0 */+template <>+class SpecializedRecordMap<0> : public RecordMap {+    // The empty record always at index 1+    // The index 0 of each map is reserved.+    static constexpr RamDomain EmptyRecordIndex = 1;++    // To comply with previous behavior, the empty record+    // has no data:+    const RamDomain* EmptyRecordData = nullptr;+ public:-    RecordTable() = default;-    virtual ~RecordTable() = default;+    SpecializedRecordMap(const std::size_t /* LaneCount */) {} +    virtual ~SpecializedRecordMap() {}++    void setNumLanes(const std::size_t) override {}++    /** @brief converts record to a record reference */+    RamDomain pack(const std::vector<RamDomain>& Vector) override {+        assert(Vector.size() == 0);+        return EmptyRecordIndex;+    };++    /** @brief converts record to a record reference */+    RamDomain pack(const RamDomain*) override {+        return EmptyRecordIndex;+    }++    /** @brief converts record to a record reference */+    RamDomain pack(const std::initializer_list<RamDomain>& List) override {+        assert(List.size() == 0);+        return EmptyRecordIndex;+    }++    /** @brief convert record reference to a record pointer */+    const RamDomain* unpack(RamDomain Index) const override {+        assert(Index == EmptyRecordIndex);+        return EmptyRecordData;+    }+};++/** The interface of any Record Table. */+class RecordTableInterface {+public:+    virtual ~RecordTableInterface() {}++    virtual void setNumLanes(const std::size_t NumLanes) = 0;++    virtual RamDomain pack(const RamDomain* Tuple, const std::size_t Arity) = 0;++    virtual const RamDomain* unpack(const RamDomain Ref, const std::size_t Arity) const = 0;+};++/** A concurrent Record Table with some specialized record maps. */+template <std::size_t... SpecializedArities>+class SpecializedRecordTable : public RecordTableInterface {+private:+    // The current size of the Maps vector.+    std::size_t Size;++    // The record maps, indexed by arity.+    std::vector<RecordMap*> Maps;++    // The concurrency manager.+    mutable ConcurrentLanes Lanes;++    template <std::size_t Arity, std::size_t... Arities>+    void CreateSpecializedMaps() {+        if (Arity >= Size) {+            Size = Arity + 1;+            Maps.reserve(Size);+            Maps.resize(Size);+        }+        Maps[Arity] = new SpecializedRecordMap<Arity>(Lanes.lanes());+        if constexpr (sizeof...(Arities) > 0) {+            CreateSpecializedMaps<Arities...>();+        }+    }++public:+    /** @brief Construct a record table with the number of concurrent access lanes. */+    SpecializedRecordTable(const std::size_t LaneCount) : Size(0), Lanes(LaneCount) {+        CreateSpecializedMaps<SpecializedArities...>();+    }++    SpecializedRecordTable() : SpecializedRecordTable(1) {}++    virtual ~SpecializedRecordTable() {+        for (auto Map : Maps) {+            delete Map;+        }+    }++    /**+     * @brief set the number of concurrent access lanes.+     * Not thread-safe, use only when the datastructure is not being used.+     */+    virtual void setNumLanes(const std::size_t NumLanes) override {+        Lanes.setNumLanes(NumLanes);+        for (auto& Map : Maps) {+            Map->setNumLanes(NumLanes);+        }+    }+     /** @brief convert record to record reference */-    RamDomain pack(RamDomain* tuple, size_t arity) {-        return lookupArity(arity).pack(tuple);+    virtual RamDomain pack(const RamDomain* Tuple, const std::size_t Arity) override {+        auto Guard = Lanes.guard();+        return lookupMap(Arity).pack(Tuple);     }+     /** @brief convert record reference to a record */-    const RamDomain* unpack(RamDomain ref, size_t arity) const {-        std::unordered_map<size_t, RecordMap>::const_iterator iter;-#pragma omp critical(RecordTableGetForArity)-        {-            // Find a previously emplaced map-            iter = maps.find(arity);-        }-        assert(iter != maps.end() && "Attempting to unpack record for non-existing arity");-        return (iter->second).unpack(ref);+    virtual const RamDomain* unpack(const RamDomain Ref, const std::size_t Arity) const override {+        auto Guard = Lanes.guard();+        return lookupMap(Arity).unpack(Ref);     }  private:+    /** @brief lookup RecordMap for a given arity; the map for that arity must exist. */+    RecordMap& lookupMap(const std::size_t Arity) const {+        assert(Arity < Size && "Lookup for an arity while there is no record for that arity.");+        auto* Map = Maps[Arity];+        assert(Map != nullptr && "Lookup for an arity while there is no record for that arity.");+        return *Map;+    }+     /** @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;+    RecordMap& lookupMap(const std::size_t Arity) {+        if (Arity < Size) {+            auto* Map = Maps[Arity];+            if (Map) {+                return *Map;+            }         }-        return mapsIterator->second;++        createMap(Arity);+        return *Maps[Arity];     } -    /** Arity/RecordMap association */-    std::unordered_map<size_t, RecordMap> maps;+    /** @brief create the RecordMap for the given arity. */+    void createMap(const std::size_t Arity) {+        Lanes.beforeLockAllBut();+        if (Arity < Size && Maps[Arity] != nullptr) {+            // Map of required arity has been created concurrently+            Lanes.beforeUnlockAllBut();+            return;+        }+        Lanes.lockAllBut();++        if (Arity >= Size) {+            Size = Arity + 1;+            Maps.reserve(Size);+            Maps.resize(Size);+        }+        Maps[Arity] = new GenericRecordMap(Lanes.lanes(), Arity);++        Lanes.beforeUnlockAllBut();+        Lanes.unlockAllBut();+    } }; +/** Default record table uses specialized record maps for arities 0 to 12. */+using RecordTable = SpecializedRecordTable<0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12>;+ /** @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);+template <class RecordTableT, std::size_t Arity>+RamDomain pack(RecordTableT&& recordTab, Tuple<RamDomain, Arity> const& tuple) {+    return recordTab.pack(tuple.data(), Arity);+}++/** @brief helper to convert tuple to record reference for the synthesiser */+template <class RecordTableT, std::size_t Arity>+RamDomain pack(RecordTableT&& recordTab, span<const RamDomain, Arity> tuple) {+    return recordTab.pack(tuple.data(), Arity); }  }  // namespace souffle
cbits/souffle/SignalHandler.h view
@@ -21,9 +21,11 @@ #include <cstdio> #include <cstdlib> #include <cstring>+#include <initializer_list> #include <iostream> #include <mutex> #include <string>+#include <unistd.h>  namespace souffle { @@ -135,6 +137,7 @@ private:     // signal context information     std::atomic<const char*> msg;+    static_assert(decltype(msg)::is_always_lock_free, "cannot safely use in signal handler");      // state of signal handler     bool isSet = false;@@ -150,20 +153,35 @@      * Signal handler for various types of signals.      */     static void handler(int signal) {-        const char* msg = instance()->msg;-        std::string error;+        // Signal handlers have extreme restrictions on what stdlib/OS facilities are available.+        // This is b/c signals are async on most platforms.+        // See: https://en.cppreference.com/w/cpp/utility/program/signal+        // See: `man 7 signal`++        const char* error;         switch (signal) {-            case SIGINT: error = "Interrupt"; break;+            case SIGABRT: error = "Abort"; break;             case SIGFPE: error = "Floating-point arithmetic exception"; break;+            case SIGILL: error = "Illegal instruction"; break;+            case SIGINT: error = "Interrupt"; break;             case SIGSEGV: error = "Segmentation violation"; break;+            case SIGTERM: error = "Terminate"; break;             default: error = "Unknown"; break;         }-        if (msg != nullptr) {-            std::cerr << error << " signal in rule:\n" << msg << std::endl;-        } else {-            std::cerr << error << " signal." << std::endl;-        }-        exit(1);++        auto write = [](std::initializer_list<char const*> const& msgs) {+            for (auto&& msg : msgs) {+                [[maybe_unused]] auto _ = ::write(STDERR_FILENO, msg, ::strlen(msg));+            }+        };++        // `instance()` is okay. Static `singleton` must already be constructed if we got here.+        if (const char* msg = instance()->msg)+            write({error, " signal in rule:\n", msg, "\n"});+        else+            write({error, " signal.\n"});++        std::_Exit(EXIT_FAILURE);     }      SignalHandler() : msg(nullptr) {}
cbits/souffle/SouffleInterface.h view
@@ -8,7 +8,7 @@  /************************************************************************  *- * @file CompiledSouffle.h+ * @file SouffleInterface.h  *  * Main include file for generated C++ classes of Souffle  *@@ -17,6 +17,7 @@ #pragma once  #include "souffle/RamTypes.h"+#include "souffle/RecordTable.h" #include "souffle/SymbolTable.h" #include "souffle/utility/MiscUtil.h" #include <algorithm>@@ -27,6 +28,7 @@ #include <iostream> #include <map> #include <memory>+#include <optional> #include <string> #include <tuple> #include <utility>@@ -40,6 +42,9 @@  * Object-oriented wrapper class for Souffle's templatized relations.  */ class Relation {+public:+    using arity_type = uint32_t;+ protected:     /**      * Abstract iterator class.@@ -57,6 +62,9 @@          * Required for identifying type of iterator          * (NB: LLVM has no typeinfo).          *+         * Note: The above statement is not true anymore - should this be made to work the same+         * as Node::operator==?+         *          * TODO (Honghyw) : Provide a clear documentation of what id is used for.          */         uint32_t id;@@ -151,7 +159,7 @@          * iterator_base class pointer.          *          */-        Own<iterator_base> iter = nullptr;+        std::unique_ptr<iterator_base> iter = nullptr;      public:         /**@@ -177,7 +185,7 @@          *          * @param arg An iterator_base class pointer          */-        iterator(iterator_base* arg) : iter(arg) {}+        iterator(std::unique_ptr<iterator_base> it) : iter(std::move(it)) {}          /**          * Destructor.@@ -224,6 +232,20 @@         }          /**+         * Overload the "++" operator.+         *+         * Copies the iterator, increments itself, and returns the (pre-increment) copy.+         * WARNING: Expensive due to copy! Included for API compatibility.+         *+         * @return Pre-increment copy of `this`.+         */+        iterator operator++(int) {+            auto cpy = *this;+            ++(*this);+            return cpy;+        }++        /**          * Overload the "*" operator.          *          * This will return the tuple that the iterator is pointing to.@@ -314,39 +336,52 @@      * which are the primitive types in Souffle.      * <type name> is the name given by the user in the Souffle program      *-     * @param The index of the column starting starting from 0 (size_t)+     * @param The index of the column starting starting from 0 (std::size_t)      * @return The constant string of the attribute type      */-    virtual const char* getAttrType(size_t) const = 0;+    virtual const char* getAttrType(std::size_t) const = 0;      /**      * Get the attribute name of a relation at the column specified by the parameter.      * The attribute name is the name given to the type by the user in the .decl statement. For example, for      * ".decl edge (node1:Node, node2:Node)", the attribute names are node1 and node2.      *-     * @param The index of the column starting starting from 0 (size_t)+     * @param The index of the column starting starting from 0 (std::size_t)      * @return The constant string of the attribute name      */-    virtual const char* getAttrName(size_t) const = 0;+    virtual const char* getAttrName(std::size_t) const = 0;      /**      * Return the arity of a relation.      * For example for a tuple (1 2) the arity is 2 and for a tuple (1 2 3) the arity is 3.      *-     * @return Arity of a relation (size_t)+     * @return Arity of a relation (`arity_type`)      */-    virtual size_t getArity() const = 0;+    virtual arity_type getArity() const = 0;      /**      * Return the number of auxiliary attributes. Auxiliary attributes      * are used for provenance and and other alternative evaluation      * strategies. They are stored as the last attributes of a tuple.      *-     * @return Number of auxiliary attributes of a relation (size_t)+     * @return Number of auxiliary attributes of a relation (`arity_type`)      */-    virtual size_t getAuxiliaryArity() const = 0;+    virtual arity_type getAuxiliaryArity() const = 0;      /**+     * Return the number of non-auxiliary attributes.+     * Auxiliary attributes are used for provenance and and other alternative+     * evaluation strategies.+     * They are stored as the last attributes of a tuple.+     *+     * @return Number of non-auxiliary attributes of a relation (`arity_type`)+     */+    arity_type getPrimaryArity() const {+        assert(getAuxiliaryArity() <= getArity());+        return getArity() - getAuxiliaryArity();+    }++    /**      * Get the symbol table of a relation.      * The symbols in a tuple to be stored into a relation are stored and assigned with a number in a table      * called symbol table. For example, to insert ("John","Student") to a relation, "John" and "Student" are@@ -374,7 +409,7 @@         }          std::string signature = "<" + std::string(getAttrType(0));-        for (size_t i = 1; i < getArity(); i++) {+        for (arity_type i = 1; i < getArity(); i++) {             signature += "," + std::string(getAttrType(i));         }         signature += ">";@@ -423,7 +458,7 @@      * helps to make sure we access an insert a tuple within the bound by making sure pos never exceeds the      * arity of the relation.      */-    size_t pos;+    std::size_t pos;  public:     /**@@ -472,10 +507,11 @@     /**      * Return the number of elements in the tuple.      *-     * @return the number of elements in the tuple (size_t).+     * @return the number of elements in the tuple (std::size_t).      */-    size_t size() const {-        return array.size();+    Relation::arity_type size() const {+        assert(array.size() <= std::numeric_limits<Relation::arity_type>::max());+        return Relation::arity_type(array.size());     }      /**@@ -488,9 +524,9 @@      * only be used by friendly classes such as      * iterators; users should not use this interface.      *-     * @param idx This is the idx of element in a tuple (size_t).+     * @param idx This is the idx of element in a tuple (std::size_t).      */-    RamDomain& operator[](size_t idx) {+    RamDomain& operator[](std::size_t idx) {         return array[idx];     } @@ -504,9 +540,9 @@      * only be used by friendly classes such as      * iterators; users should not use this interface.      *-     * @param idx This is the idx of element in a tuple (size_t).+     * @param idx This is the idx of element in a tuple (std::size_t).      */-    const RamDomain& operator[](size_t idx) const {+    const RamDomain& operator[](std::size_t idx) const {         return array[idx];     } @@ -527,7 +563,7 @@     tuple& operator<<(const std::string& str) {         assert(pos < size() && "exceeded tuple's size");         assert(*relation.getAttrType(pos) == 's' && "wrong element type");-        array[pos++] = relation.getSymbolTable().lookup(str);+        array[pos++] = relation.getSymbolTable().encode(str);         return *this;     } @@ -584,7 +620,7 @@     tuple& operator>>(std::string& str) {         assert(pos < size() && "exceeded tuple's size");         assert(*relation.getAttrType(pos) == 's' && "wrong element type");-        str = relation.getSymbolTable().resolve(array[pos++]);+        str = relation.getSymbolTable().decode(array[pos++]);         return *this;     } @@ -694,24 +730,30 @@      * otherwise will add to internalRelations. (a relation could be both input and output at the same time.)      *      * @param name the name of the relation (std::string)-     * @param rel a pointer to the relation (std::string)+     * @param rel a reference to the relation      * @param isInput a bool argument, true if the relation is a input relation, else false (bool)      * @param isOnput a bool argument, true if the relation is a ouput relation, else false (bool)      */-    void addRelation(const std::string& name, Relation* rel, bool isInput, bool isOutput) {-        relationMap[name] = rel;-        allRelations.push_back(rel);+    void addRelation(const std::string& name, Relation& rel, bool isInput, bool isOutput) {+        relationMap[name] = &rel;+        allRelations.push_back(&rel);         if (isInput) {-            inputRelations.push_back(rel);+            inputRelations.push_back(&rel);         }         if (isOutput) {-            outputRelations.push_back(rel);+            outputRelations.push_back(&rel);         }         if (!isInput && !isOutput) {-            internalRelations.push_back(rel);+            internalRelations.push_back(&rel);         }     } +    [[deprecated("pass `rel` by reference; `rel` may not be null"), maybe_unused]] void addRelation(+            const std::string& name, Relation* rel, bool isInput, bool isOutput) {+        assert(rel && "`rel` may not be null");+        addRelation(name, *rel, isInput, isOutput);+    }+ public:     /**      * Destructor.@@ -763,7 +805,7 @@     /**      * Set the number of threads to be used      */-    void setNumThreads(std::size_t numThreadsValue) {+    virtual void setNumThreads(std::size_t numThreadsValue) {         this->numThreads = numThreadsValue;     } @@ -795,8 +837,12 @@      * @param name The name of the target relation (const std::string)      * @return The size of the target relation (std::size_t)      */-    std::size_t getRelationSize(const std::string& name) const {-        return getRelation(name)->size();+    std::optional<std::size_t> getRelationSize(const std::string& name) const {+        if (auto* rel = getRelation(name)) {+            return rel->size();+        }++        return std::nullopt;     }      /**@@ -805,8 +851,12 @@      * @param name The name of the target relation (const std::string)      * @return The name of the target relation (std::string)      */-    std::string getRelationName(const std::string& name) const {-        return getRelation(name)->getName();+    std::optional<std::string> getRelationName(const std::string& name) const {+        if (auto* rel = getRelation(name)) {+            return rel->getName();+        }++        return std::nullopt;     }      /**@@ -867,6 +917,11 @@     virtual SymbolTable& getSymbolTable() = 0;      /**+     * Get the record table of the program.+     */+    virtual RecordTable& getRecordTable() = 0;++    /**      * Remove all the tuples from the outputRelations, calling the purge method of each.      *      * @see Relation::purge()@@ -902,7 +957,7 @@     /**      * Helper function for the wrapper function Relation::insert() and Relation::contains().      */-    template <typename Tuple, size_t N>+    template <typename Tuple, std::size_t N>     struct tuple_insert {         static void add(const Tuple& t, souffle::tuple& t1) {             tuple_insert<Tuple, N - 1>::add(t, t1);
cbits/souffle/SymbolTable.h view
@@ -1,6 +1,6 @@ /*  * Souffle - A Datalog Compiler- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved+ * Copyright (c) 2013, 2014, 2015 Oracle and/or its affiliates. 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@@ -10,13 +10,14 @@  *  * @file SymbolTable.h  *- * Data container to store symbols of the Datalog program.+ * Encodes/decodes symbols to numbers (and vice versa).  *  ***********************************************************************/  #pragma once  #include "souffle/RamTypes.h"+#include "souffle/datastructure/ConcurrentFlyweight.h" #include "souffle/utility/MiscUtil.h" #include "souffle/utility/ParallelUtil.h" #include "souffle/utility/StreamUtil.h"@@ -35,208 +36,86 @@ /**  * @class SymbolTable  *- * Global pool of re-usable strings- *- * SymbolTable stores Datalog symbols and converts them to numbers and vice versa.+ * SymbolTable encodes symbols to numbers and decodes numbers to symbols.  */-class SymbolTable {+class SymbolTable : protected FlyweightImpl<std::string> { private:-    /** A lock to synchronize parallel accesses */-    mutable Lock access;--    /** Map indices to strings. */-    std::deque<std::string> numToStr;--    /** Map strings to indices. */-    std::unordered_map<std::string, size_t> strToNum;--    /** Convenience method to place a new symbol in the table, if it does not exist, and return the index of-     * it. */-    inline size_t newSymbolOfIndex(const std::string& symbol) {-        size_t index;-        auto it = strToNum.find(symbol);-        if (it == strToNum.end()) {-            index = numToStr.size();-            strToNum[symbol] = index;-            numToStr.push_back(symbol);-        } else {-            index = it->second;-        }-        return index;-    }--    /** Convenience method to place a new symbol in the table, if it does not exist. */-    inline void newSymbol(const std::string& symbol) {-        if (strToNum.find(symbol) == strToNum.end()) {-            strToNum[symbol] = numToStr.size();-            numToStr.push_back(symbol);-        }-    }+    using Base = FlyweightImpl<std::string>;  public:-    /** Empty constructor. */-    SymbolTable() = default;--    /** Copy constructor, performs a deep copy. */-    SymbolTable(const SymbolTable& other) : numToStr(other.numToStr), strToNum(other.strToNum) {}+    using iterator = typename Base::iterator; -    /** Copy constructor for r-value reference. */-    SymbolTable(SymbolTable&& other) noexcept {-        numToStr.swap(other.numToStr);-        strToNum.swap(other.strToNum);-    }+    /** @brief Construct a symbol table with the given number of concurrent access lanes. */+    SymbolTable(const std::size_t LaneCount = 1) : Base(LaneCount) {} -    SymbolTable(std::initializer_list<std::string> symbols) {-        strToNum.reserve(symbols.size());+    /** @brief Construct a symbol table with the given initial symbols. */+    SymbolTable(std::initializer_list<std::string> symbols) : Base(1, symbols.size()) {         for (const auto& symbol : symbols) {-            newSymbol(symbol);-        }-    }--    /** Destructor, frees memory allocated for all strings. */-    virtual ~SymbolTable() = default;--    /** Assignment operator, performs a deep copy and frees memory allocated for all strings. */-    SymbolTable& operator=(const SymbolTable& other) {-        if (this == &other) {-            return *this;-        }-        numToStr = other.numToStr;-        strToNum = other.strToNum;-        return *this;-    }--    /** Assignment operator for r-value references. */-    SymbolTable& operator=(SymbolTable&& other) noexcept {-        numToStr.swap(other.numToStr);-        strToNum.swap(other.strToNum);-        return *this;-    }--    /** Find the index of a symbol in the table, inserting a new symbol if it does not exist there-     * already. */-    RamDomain lookup(const std::string& symbol) {-        {-            auto lease = access.acquire();-            (void)lease;  // avoid warning;-            return static_cast<RamDomain>(newSymbolOfIndex(symbol));-        }-    }--    /** Finds the index of a symbol in the table, giving an error if it's not found */-    RamDomain lookupExisting(const std::string& symbol) const {-        {-            auto lease = access.acquire();-            (void)lease;  // avoid warning;-            auto result = strToNum.find(symbol);-            if (result == strToNum.end()) {-                fatal("Error string not found in call to `SymbolTable::lookupExisting`: `%s`", symbol);-            }-            return static_cast<RamDomain>(result->second);+            findOrInsert(symbol);         }     } -    /** Find the index of a symbol in the table, inserting a new symbol if it does not exist there-     * already. */-    RamDomain unsafeLookup(const std::string& symbol) {-        return static_cast<RamDomain>(newSymbolOfIndex(symbol));-    }--    /** Find a symbol in the table by its index, note that this gives an error if the index is out of-     * bounds.+    /** @brief Construct a symbol table with the given number of concurrent access lanes and initial symbols.      */-    const std::string& resolve(const RamDomain index) const {-        {-            auto lease = access.acquire();-            (void)lease;  // avoid warning;-            auto pos = static_cast<size_t>(index);-            if (pos >= size()) {-                // TODO: use different error reporting here!!-                fatal("Error index out of bounds in call to `SymbolTable::resolve`. index = `%d`", index);-            }-            return numToStr[pos];+    SymbolTable(const std::size_t LaneCount, std::initializer_list<std::string> symbols)+            : Base(LaneCount, symbols.size()) {+        for (const auto& symbol : symbols) {+            findOrInsert(symbol);         }     } -    const std::string& unsafeResolve(const RamDomain index) const {-        return numToStr[static_cast<size_t>(index)];+    /**+     * @brief Set the number of concurrent access lanes.+     * This function is not thread-safe, do not call when other threads are using the datastructure.+     */+    void setNumLanes(const std::size_t NumLanes) {+        Base::setNumLanes(NumLanes);     } -    /* Return the size of the symbol table, being the number of symbols it currently holds. */-    size_t size() const {-        return numToStr.size();+    /** @brief Return an iterator on the first symbol. */+    iterator begin() const {+        return Base::begin();     } -    /** Bulk insert symbols into the table, note that this operation is more efficient than repeated-     * inserts-     * of single symbols. */-    void insert(const std::vector<std::string>& symbols) {-        {-            auto lease = access.acquire();-            (void)lease;  // avoid warning;-            strToNum.reserve(size() + symbols.size());-            for (auto& symbol : symbols) {-                newSymbol(symbol);-            }-        }+    /** @brief Return an iterator past the last symbol. */+    iterator end() const {+        return Base::end();     } -    /** Insert a single symbol into the table, not that this operation should not be used if inserting-     * symbols-     * in bulk. */-    void insert(const std::string& symbol) {-        {-            auto lease = access.acquire();-            (void)lease;  // avoid warning;-            newSymbol(symbol);-        }+    /** @brief Check if the given symbol exist. */+    bool weakContains(const std::string& symbol) const {+        return Base::weakContains(symbol);     } -    /** Print the symbol table to the given stream. */-    void print(std::ostream& out) const {-        {-            out << "SymbolTable: {\n\t";-            out << join(strToNum, "\n\t",-                           [](std::ostream& out, const std::pair<std::string, std::size_t>& entry) {-                               out << entry.first << "\t => " << entry.second;-                           })-                << "\n";-            out << "}\n";-        }+    /** @brief Encode a symbol to a symbol index. */+    RamDomain encode(const std::string& symbol) {+        return Base::findOrInsert(symbol).first;     } -    /** Check if the symbol table contains a string */-    bool contains(const std::string& symbol) const {-        auto lease = access.acquire();-        (void)lease;  // avoid warning;-        auto result = strToNum.find(symbol);-        if (result == strToNum.end()) {-            return false;-        } else {-            return true;-        }+    /** @brief Decode a symbol index to a symbol. */+    const std::string& decode(const RamDomain index) const {+        return Base::fetch(index);     } -    /** Check if the symbol table contains an index */-    bool contains(const RamDomain index) const {-        auto lease = access.acquire();-        (void)lease;  // avoid warning;-        auto pos = static_cast<size_t>(index);-        if (pos >= size()) {-            return false;-        } else {-            return true;-        }+    /** @brief Encode a symbol to a symbol index; aliases encode. */+    RamDomain unsafeEncode(const std::string& symbol) {+        return encode(symbol);     } -    Lock::Lease acquireLock() const {-        return access.acquire();+    /** @brief Decode a symbol index to a symbol; aliases decode. */+    const std::string& unsafeDecode(const RamDomain index) const {+        return decode(index);     } -    /** Stream operator, used as a convenience for print. */-    friend std::ostream& operator<<(std::ostream& out, const SymbolTable& table) {-        table.print(out);-        return out;+    /**+     * @brief Encode the symbol, it is inserted if it does not exist.+     *+     * @return the symbol index and a boolean indicating if an insertion+     * happened.+     */+    std::pair<RamDomain, bool> findOrInsert(const std::string& symbol) {+        auto Res = Base::findOrInsert(symbol);+        return std::make_pair(Res.first, Res.second);     } }; 
cbits/souffle/datastructure/BTree.h view
@@ -19,6 +19,7 @@  #include "souffle/utility/CacheUtil.h" #include "souffle/utility/ContainerUtil.h"+#include "souffle/utility/MiscUtil.h" #include "souffle/utility/ParallelUtil.h" #include <algorithm> #include <cassert>@@ -367,13 +368,13 @@         /**          * The number of keys/node desired by the user.          */-        static constexpr size_t desiredNumKeys =+        static constexpr std::size_t desiredNumKeys =                 ((blockSize > sizeof(base)) ? blockSize - sizeof(base) : 0) / sizeof(Key);          /**          * The actual number of keys/node corrected by functional requirements.          */-        static constexpr size_t maxKeys = (desiredNumKeys > 3) ? desiredNumKeys : 3;+        static constexpr std::size_t maxKeys = (desiredNumKeys > 3) ? desiredNumKeys : 3;          // the keys stored in this node         Key keys[maxKeys];@@ -1105,11 +1106,11 @@         field_index_type pos = 0;      public:-        typedef std::forward_iterator_tag iterator_category;-        typedef Key value_type;-        typedef ptrdiff_t difference_type;-        typedef value_type* pointer;-        typedef value_type& reference;+        using iterator_category = std::forward_iterator_tag;+        using value_type = Key;+        using difference_type = ptrdiff_t;+        using pointer = value_type*;+        using reference = value_type&;          // default constructor -- creating an end-iterator         iterator() : cur(nullptr) {}@@ -1256,7 +1257,7 @@  public:     // the maximum number of keys stored per node-    static constexpr size_t max_keys_per_node = node::maxKeys;+    static constexpr std::size_t max_keys_per_node = node::maxKeys;      // -- ctors / dtors -- 
cbits/souffle/datastructure/Brie.h view
@@ -26,3151 +26,3048 @@  #pragma once -#include "souffle/CompiledTuple.h"-#include "souffle/RamTypes.h"-#include "souffle/utility/CacheUtil.h"-#include "souffle/utility/ContainerUtil.h"-#include "souffle/utility/StreamUtil.h"-#include <algorithm>-#include <atomic>-#include <bitset>-#include <cassert>-#include <cstdint>-#include <cstring>-#include <iostream>-#include <iterator>-#include <limits>-#include <utility>-#include <vector>--#ifdef _WIN32-/**- * When compiling for windows, redefine the gcc builtins which are used to- * their equivalents on the windows platform.- */-#define __sync_synchronize MemoryBarrier-#define __sync_bool_compare_and_swap(ptr, oldval, newval) \-    (InterlockedCompareExchangePointer((void* volatile*)ptr, (void*)newval, (void*)oldval) == (void*)oldval)-#endif  // _WIN32--namespace souffle {--namespace detail {--/**- * A templated functor to obtain default values for- * unspecified elements of sparse array instances.- */-template <typename T>-struct default_factory {-    T operator()() const {-        return T();  // just use the default constructor-    }-};--/**- * A functor representing the identity function.- */-template <typename T>-struct identity {-    T operator()(T v) const {-        return v;-    }-};--/**- * A operation to be utilized by the sparse map when merging- * elements associated to different values.- */-template <typename T>-struct default_merge {-    /**-     * Merges two values a and b when merging spase maps.-     */-    T operator()(T a, T b) const {-        default_factory<T> def;-        // if a is the default => us b, else stick to a-        return (a != def()) ? a : b;-    }-};--}  // end namespace detail--/**- * A sparse array simulates an array associating to every element- * of uint32_t an element of a generic type T. Any non-defined element- * will be default-initialized utilizing the detail::default_factory- * functor.- *- * Internally the array is organized as a balanced tree. The leaf- * level of the tree corresponds to the elements of the represented- * array. Inner nodes utilize individual bits of the indices to reference- * sub-trees. For efficiency reasons, only the minimal sub-tree required- * to cover all non-null / non-default values stored in the array is- * maintained. Furthermore, several levels of nodes are aggreated in a- * B-tree like fashion to inprove cache utilization and reduce the number- * of steps required for lookup and insert operations.- *- * @tparam T the type of the stored elements- * @tparam BITS the number of bits consumed per node-level- *              e.g. if it is set to 3, the resulting tree will be of a degree of- *              2^3=8, and thus 8 child-pointers will be stored in each inner node- *              and as many values will be stored in each leaf node.- * @tparam merge_op the functor to be utilized when merging the content of two- *              instances of this type.- * @tparam copy_op a functor to be applied to each stored value when copying an- *              instance of this array. For instance, this is utilized by the- *              trie implementation to create a clone of each sub-tree instead- *              of preserving the original pointer.- */-template <typename T, unsigned BITS = 6, typename merge_op = detail::default_merge<T>,-        typename copy_op = detail::identity<T>>-class SparseArray {-    using key_type = uint64_t;--    // some internal constants-    static constexpr int BIT_PER_STEP = BITS;-    static constexpr int NUM_CELLS = 1 << BIT_PER_STEP;-    static constexpr key_type INDEX_MASK = NUM_CELLS - 1;--public:-    // the type utilized for indexing contained elements-    using index_type = key_type;--    // the type of value stored in this array-    using value_type = T;--    // the atomic view on stored values-    using atomic_value_type = std::atomic<value_type>;--private:-    struct Node;--    /**-     * The value stored in a single cell of a inner-     * or leaf node.-     */-    union Cell {-        // an atomic view on the pointer referencing a nested level-        std::atomic<Node*> aptr;--        // a pointer to the nested level (unsynchronized operations)-        Node* ptr{nullptr};--        // an atomic view on the value stored in this cell (leaf node)-        atomic_value_type avalue;--        // the value stored in this cell (unsynchronized access, leaf node)-        value_type value;-    };--    /**-     * The node type of the internally maintained tree.-     */-    struct Node {-        // a pointer to the parent node (for efficient iteration)-        const Node* parent;-        // the pointers to the child nodes (inner nodes) or the stored values (leaf nodes)-        Cell cell[NUM_CELLS];-    };--    /**-     * A struct describing all the information required by the container-     * class to manage the wrapped up tree.-     */-    struct RootInfo {-        // the root node of the tree-        Node* root;-        // the number of levels of the tree-        uint32_t levels;-        // the absolute offset of the theoretical first element in the tree-        index_type offset;--        // the first leaf node in the tree-        Node* first;-        // the absolute offset of the first element in the first leaf node-        index_type firstOffset;-    };--    union {-        RootInfo unsynced;         // for sequential operations-        volatile RootInfo synced;  // for synchronized operations-    };--public:-    /**-     * A default constructor creating an empty sparse array.-     */-    SparseArray() : unsynced(RootInfo{nullptr, 0, 0, nullptr, std::numeric_limits<index_type>::max()}) {}--    /**-     * A copy constructor for sparse arrays. It creates a deep-     * copy of the data structure maintained by the handed in-     * array instance.-     */-    SparseArray(const SparseArray& other)-            : unsynced(RootInfo{clone(other.unsynced.root, other.unsynced.levels), other.unsynced.levels,-                      other.unsynced.offset, nullptr, other.unsynced.firstOffset}) {-        if (unsynced.root) {-            unsynced.root->parent = nullptr;-            unsynced.first = findFirst(unsynced.root, unsynced.levels);-        }-    }--    /**-     * A r-value based copy constructor for sparse arrays. It-     * takes over ownership of the structure maintained by the-     * handed in array.-     */-    SparseArray(SparseArray&& other)-            : unsynced(RootInfo{other.unsynced.root, other.unsynced.levels, other.unsynced.offset,-                      other.unsynced.first, other.unsynced.firstOffset}) {-        other.unsynced.root = nullptr;-        other.unsynced.levels = 0;-        other.unsynced.first = nullptr;-    }--    /**-     * A destructor for sparse arrays clearing up the internally-     * maintained data structure.-     */-    ~SparseArray() {-        clean();-    }--    /**-     * An assignment creating a deep copy of the handed in-     * array structure (utilizing the copy functor provided-     * as a template parameter).-     */-    SparseArray& operator=(const SparseArray& other) {-        if (this == &other) return *this;--        // clean this one-        clean();--        // copy content-        unsynced.levels = other.unsynced.levels;-        unsynced.root = clone(other.unsynced.root, unsynced.levels);-        if (unsynced.root) {-            unsynced.root->parent = nullptr;-        }-        unsynced.offset = other.unsynced.offset;-        unsynced.first = (unsynced.root) ? findFirst(unsynced.root, unsynced.levels) : nullptr;-        unsynced.firstOffset = other.unsynced.firstOffset;--        // done-        return *this;-    }--    /**-     * An assignment operation taking over ownership-     * from a r-value reference to a sparse array.-     */-    SparseArray& operator=(SparseArray&& other) {-        // clean this one-        clean();--        // harvest content-        unsynced.root = other.unsynced.root;-        unsynced.levels = other.unsynced.levels;-        unsynced.offset = other.unsynced.offset;-        unsynced.first = other.unsynced.first;-        unsynced.firstOffset = other.unsynced.firstOffset;--        // reset other-        other.unsynced.root = nullptr;-        other.unsynced.levels = 0;-        other.unsynced.first = nullptr;--        // done-        return *this;-    }--    /**-     * Tests whether this sparse array is empty, thus it only-     * contains default-values, or not.-     */-    bool empty() const {-        return unsynced.root == nullptr;-    }--    /**-     * Computes the number of non-empty elements within this-     * sparse array.-     */-    std::size_t size() const {-        // quick one for the empty map-        if (empty()) return 0;--        // count elements -- since maintaining is making inserts more expensive-        std::size_t res = 0;-        for (auto it = begin(); it != end(); ++it) {-            ++res;-        }-        return res;-    }--private:-    /**-     * Computes the memory usage of the given sub-tree.-     */-    static std::size_t getMemoryUsage(const Node* node, int level) {-        // support null-nodes-        if (!node) return 0;--        // add size of current node-        std::size_t res = sizeof(Node);--        // sum up memory usage of child nodes-        if (level > 0) {-            for (int i = 0; i < NUM_CELLS; i++) {-                res += getMemoryUsage(node->cell[i].ptr, level - 1);-            }-        }--        // done-        return res;-    }--public:-    /**-     * Computes the total memory usage of this data structure.-     */-    std::size_t getMemoryUsage() const {-        // the memory of the wrapper class-        std::size_t res = sizeof(*this);--        // add nodes-        if (unsynced.root) {-            res += getMemoryUsage(unsynced.root, unsynced.levels);-        }--        // done-        return res;-    }--    /**-     * Resets the content of this array to default values for each contained-     * element.-     */-    void clear() {-        clean();-        unsynced.root = nullptr;-        unsynced.levels = 0;-        unsynced.first = nullptr;-        unsynced.firstOffset = std::numeric_limits<index_type>::max();-    }--    /**-     * A struct to be utilized as a local, temporal context by client code-     * to speed up the execution of various operations (optional parameter).-     */-    struct op_context {-        index_type lastIndex{0};-        Node* lastNode{nullptr};-        op_context() = default;-    };--private:-    // ----------------------------------------------------------------------    //              Optimistic Locking of Root-Level Infos-    // -----------------------------------------------------------------------    /**-     * A struct to cover a snapshot of the root node state.-     */-    struct RootInfoSnapshot {-        // the current pointer to a root node-        Node* root;-        // the current number of levels-        uint32_t levels;-        // the current offset of the first theoretical element-        index_type offset;-        // a version number for the optimistic locking-        uintptr_t version;-    };--    /**-     * Obtains the current version of the root.-     */-    uint64_t getRootVersion() const {-        // here it is assumed that the load of a 64-bit word is atomic-        return (uint64_t)synced.root;-    }--    /**-     * Obtains a snapshot of the current root information.-     */-    RootInfoSnapshot getRootInfo() const {-        RootInfoSnapshot res{};-        do {-            // first take the mod counter-            do {-                // if res.mod % 2 == 1 .. there is an update in progress-                res.version = getRootVersion();-            } while (res.version % 2);--            // then the rest-            res.root = synced.root;-            res.levels = synced.levels;-            res.offset = synced.offset;--            // check consistency of obtained data (optimistic locking)-        } while (res.version != getRootVersion());--        // got a consistent snapshot-        return res;-    }--    /**-     * Updates the current root information based on the handed in modified-     * snapshot instance if the version number of the snapshot still corresponds-     * to the current version. Otherwise a concurrent update took place and the-     * operation is aborted.-     *-     * @param info the updated information to be assigned to the active root-info data-     * @return true if successfully updated, false if aborted-     */-    bool tryUpdateRootInfo(const RootInfoSnapshot& info) {-        // check mod counter-        uintptr_t version = info.version;--        // update root to invalid pointer (ending with 1)-        if (!__sync_bool_compare_and_swap(&synced.root, (Node*)version, (Node*)(version + 1))) {-            return false;-        }--        // conduct update-        synced.levels = info.levels;-        synced.offset = info.offset;--        // update root (and thus the version to enable future retrievals)-        __sync_synchronize();-        synced.root = info.root;--        // done-        return true;-    }--    /**-     * A struct summarizing the state of the first node reference.-     */-    struct FirstInfoSnapshot {-        // the pointer to the first node-        Node* node;-        // the offset of the first node-        index_type offset;-        // the version number of the first node (for the optimistic locking)-        uintptr_t version;-    };--    /**-     * Obtains the current version number of the first node information.-     */-    uint64_t getFirstVersion() const {-        // here it is assumed that the load of a 64-bit word is atomic-        return (uint64_t)synced.first;-    }--    /**-     * Obtains a snapshot of the current first-node information.-     */-    FirstInfoSnapshot getFirstInfo() const {-        FirstInfoSnapshot res{};-        do {-            // first take the version-            do {-                res.version = getFirstVersion();-            } while (res.version % 2);--            // collect the values-            res.node = synced.first;-            res.offset = synced.firstOffset;--        } while (res.version != getFirstVersion());--        // we got a consistent snapshot-        return res;-    }--    /**-     * Updates the information stored regarding the first node in a-     * concurrent setting utilizing a optimistic locking approach.-     * This is identical to the approach utilized for the root info.-     */-    bool tryUpdateFirstInfo(const FirstInfoSnapshot& info) {-        // check mod counter-        uintptr_t version = info.version;--        // temporary update first pointer to point to uneven value (lock-out)-        if (!__sync_bool_compare_and_swap(&synced.first, (Node*)version, (Node*)(version + 1))) {-            return false;-        }--        // conduct update-        synced.firstOffset = info.offset;--        // update node pointer (and thus the version number)-        __sync_synchronize();-        synced.first = info.node;  // must be last (and atomic)--        // done-        return true;-    }--public:-    /**-     * Obtains a mutable reference to the value addressed by the given index.-     *-     * @param i the index of the element to be addressed-     * @return a mutable reference to the corresponding element-     */-    value_type& get(index_type i) {-        op_context ctxt;-        return get(i, ctxt);-    }--    /**-     * Obtains a mutable reference to the value addressed by the given index.-     *-     * @param i the index of the element to be addressed-     * @param ctxt a operation context to exploit state-less temporal locality-     * @return a mutable reference to the corresponding element-     */-    value_type& get(index_type i, op_context& ctxt) {-        return getLeaf(i, ctxt).value;-    }--    /**-     * Obtains a mutable reference to the atomic value addressed by the given index.-     *-     * @param i the index of the element to be addressed-     * @return a mutable reference to the corresponding element-     */-    atomic_value_type& getAtomic(index_type i) {-        op_context ctxt;-        return getAtomic(i, ctxt);-    }--    /**-     * Obtains a mutable reference to the atomic value addressed by the given index.-     *-     * @param i the index of the element to be addressed-     * @param ctxt a operation context to exploit state-less temporal locality-     * @return a mutable reference to the corresponding element-     */-    atomic_value_type& getAtomic(index_type i, op_context& ctxt) {-        return getLeaf(i, ctxt).avalue;-    }--private:-    /**-     * An internal function capable of navigating to a given leaf node entry.-     * If the cell does not exist yet it will be created as a side-effect.-     *-     * @param i the index of the requested cell-     * @param ctxt a operation context to exploit state-less temporal locality-     * @return a reference to the requested cell-     */-    inline Cell& getLeaf(index_type i, op_context& ctxt) {-        // check context-        if (ctxt.lastNode && (ctxt.lastIndex == (i & ~INDEX_MASK))) {-            // return reference to referenced-            return ctxt.lastNode->cell[i & INDEX_MASK];-        }--        // get snapshot of root-        auto info = getRootInfo();--        // check for emptiness-        if (info.root == nullptr) {-            // build new root node-            info.root = newNode();--            // initialize the new node-            info.root->parent = nullptr;-            info.offset = i & ~(INDEX_MASK);--            // try updating root information atomically-            if (tryUpdateRootInfo(info)) {-                // success -- finish get call--                // update first-                auto firstInfo = getFirstInfo();-                while (info.offset < firstInfo.offset) {-                    firstInfo.node = info.root;-                    firstInfo.offset = info.offset;-                    if (!tryUpdateFirstInfo(firstInfo)) {-                        // there was some concurrent update => check again-                        firstInfo = getFirstInfo();-                    }-                }--                // return reference to proper cell-                return info.root->cell[i & INDEX_MASK];-            }--            // somebody else was faster => use standard insertion procedure-            delete info.root;--            // retrieve new root info-            info = getRootInfo();--            // make sure there is a root-            assert(info.root);-        }--        // for all other inserts-        //   - check boundary-        //   - navigate to node-        //   - insert value--        // check boundaries-        while (!inBoundaries(i, info.levels, info.offset)) {-            // boundaries need to be expanded by growing upwards-            raiseLevel(info);  // try raising level unless someone else did already-            // update root info-            info = getRootInfo();-        }--        // navigate to node-        Node* node = info.root;-        unsigned level = info.levels;-        while (level != 0) {-            // get X coordinate-            auto x = getIndex(static_cast<RamDomain>(i), level);--            // decrease level counter-            --level;--            // check next node-            std::atomic<Node*>& aNext = node->cell[x].aptr;-            Node* next = aNext;-            if (!next) {-                // create new sub-tree-                Node* newNext = newNode();-                newNext->parent = node;--                // try to update next-                if (!aNext.compare_exchange_strong(next, newNext)) {-                    // some other thread was faster => use updated next-                    delete newNext;-                } else {-                    // the locally created next is the new next-                    next = newNext;--                    // update first-                    if (level == 0) {-                        // compute offset of this node-                        auto off = i & ~INDEX_MASK;--                        // fast over-approximation of whether a update is necessary-                        if (off < unsynced.firstOffset) {-                            // update first reference if this one is the smallest-                            auto first_info = getFirstInfo();-                            while (off < first_info.offset) {-                                first_info.node = next;-                                first_info.offset = off;-                                if (!tryUpdateFirstInfo(first_info)) {-                                    // there was some concurrent update => check again-                                    first_info = getFirstInfo();-                                }-                            }-                        }-                    }-                }--                // now next should be defined-                assert(next);-            }--            // continue one level below-            node = next;-        }--        // update context-        ctxt.lastIndex = (i & ~INDEX_MASK);-        ctxt.lastNode = node;--        // return reference to cell-        return node->cell[i & INDEX_MASK];-    }--public:-    /**-     * Updates the value stored in cell i by the given value.-     */-    void update(index_type i, const value_type& val) {-        op_context ctxt;-        update(i, val, ctxt);-    }--    /**-     * Updates the value stored in cell i by the given value. A operation-     * context can be provided for exploiting temporal locality.-     */-    void update(index_type i, const value_type& val, op_context& ctxt) {-        get(i, ctxt) = val;-    }--    /**-     * Obtains the value associated to index i -- which might be-     * the default value of the covered type if the value hasn't been-     * defined previously.-     */-    value_type operator[](index_type i) const {-        return lookup(i);-    }--    /**-     * Obtains the value associated to index i -- which might be-     * the default value of the covered type if the value hasn't been-     * defined previously.-     */-    value_type lookup(index_type i) const {-        op_context ctxt;-        return lookup(i, ctxt);-    }--    /**-     * Obtains the value associated to index i -- which might be-     * the default value of the covered type if the value hasn't been-     * defined previously. A operation context can be provided for-     * exploiting temporal locality.-     */-    value_type lookup(index_type i, op_context& ctxt) const {-        // check whether it is empty-        if (!unsynced.root) return souffle::detail::default_factory<value_type>()();--        // check boundaries-        if (!inBoundaries(i)) return souffle::detail::default_factory<value_type>()();--        // check context-        if (ctxt.lastNode && ctxt.lastIndex == (i & ~INDEX_MASK)) {-            return ctxt.lastNode->cell[i & INDEX_MASK].value;-        }--        // navigate to value-        Node* node = unsynced.root;-        unsigned level = unsynced.levels;-        while (level != 0) {-            // get X coordinate-            auto x = getIndex(static_cast<RamDomain>(i), level);--            // decrease level counter-            --level;--            // check next node-            Node* next = node->cell[x].ptr;--            // check next step-            if (!next) return souffle::detail::default_factory<value_type>()();--            // continue one level below-            node = next;-        }--        // remember context-        ctxt.lastIndex = (i & ~INDEX_MASK);-        ctxt.lastNode = node;--        // return reference to cell-        return node->cell[i & INDEX_MASK].value;-    }--private:-    /**-     * A static operation utilized internally for merging sub-trees recursively.-     *-     * @param parent the parent node of the current merge operation-     * @param trg a reference to the pointer the cloned node should be stored to-     * @param src the node to be cloned-     * @param levels the height of the cloned node-     */-    static void merge(const Node* parent, Node*& trg, const Node* src, int levels) {-        // if other side is null => done-        if (src == nullptr) {-            return;-        }--        // if the trg sub-tree is empty, clone the corresponding branch-        if (trg == nullptr) {-            trg = clone(src, levels);-            if (trg != nullptr) {-                trg->parent = parent;-            }-            return;  // done-        }--        // otherwise merge recursively--        // the leaf-node step-        if (levels == 0) {-            merge_op merg;-            for (int i = 0; i < NUM_CELLS; ++i) {-                trg->cell[i].value = merg(trg->cell[i].value, src->cell[i].value);-            }-            return;-        }--        // the recursive step-        for (int i = 0; i < NUM_CELLS; ++i) {-            merge(trg, trg->cell[i].ptr, src->cell[i].ptr, levels - 1);-        }-    }--public:-    /**-     * Adds all the values stored in the given array to this array.-     */-    void addAll(const SparseArray& other) {-        // skip if other is empty-        if (other.empty()) {-            return;-        }--        // special case: emptiness-        if (empty()) {-            // use assignment operator-            *this = other;-            return;-        }--        // adjust levels-        while (unsynced.levels < other.unsynced.levels || !inBoundaries(other.unsynced.offset)) {-            raiseLevel();-        }--        // navigate to root node equivalent of the other node in this tree-        auto level = unsynced.levels;-        Node** node = &unsynced.root;-        while (level > other.unsynced.levels) {-            // get X coordinate-            auto x = getIndex(static_cast<RamDomain>(other.unsynced.offset), level);--            // decrease level counter-            --level;--            // check next node-            Node*& next = (*node)->cell[x].ptr;-            if (!next) {-                // create new sub-tree-                next = newNode();-                next->parent = *node;-            }--            // continue one level below-            node = &next;-        }--        // merge sub-branches from here-        merge((*node)->parent, *node, other.unsynced.root, level);--        // update first-        if (unsynced.firstOffset > other.unsynced.firstOffset) {-            unsynced.first = findFirst(*node, level);-            unsynced.firstOffset = other.unsynced.firstOffset;-        }-    }--    // ----------------------------------------------------------------------    //                           Iterator-    // -----------------------------------------------------------------------    /**-     * The iterator type to be utilized to iterate over the non-default elements of this array.-     */-    class iterator {-        using pair_type = std::pair<index_type, value_type>;--        // a pointer to the leaf node currently processed or null (end)-        const Node* node;--        // the value currently pointed to-        pair_type value;--    public:-        // default constructor -- creating an end-iterator-        iterator() : node(nullptr) {}--        iterator(const Node* node, pair_type value) : node(node), value(std::move(value)) {}--        iterator(const Node* first, index_type firstOffset) : node(first), value(firstOffset, 0) {-            // if the start is the end => we are done-            if (!first) return;--            // load the value-            if (first->cell[0].value == value_type()) {-                ++(*this);  // walk to first element-            } else {-                value.second = first->cell[0].value;-            }-        }--        // a copy constructor-        iterator(const iterator& other) = default;--        // an assignment operator-        iterator& operator=(const iterator& other) = default;--        // the equality operator as required by the iterator concept-        bool operator==(const iterator& other) const {-            // only equivalent if pointing to the end-            return (node == nullptr && other.node == nullptr) ||-                   (node == other.node && value.first == other.value.first);-        }--        // the not-equality operator as required by the iterator concept-        bool operator!=(const iterator& other) const {-            return !(*this == other);-        }--        // the deref operator as required by the iterator concept-        const pair_type& operator*() const {-            return value;-        }--        // support for the pointer operator-        const pair_type* operator->() const {-            return &value;-        }--        // the increment operator as required by the iterator concept-        iterator& operator++() {-            // get current offset-            index_type x = value.first & INDEX_MASK;--            // go to next non-empty value in current node-            do {-                x++;-            } while (x < NUM_CELLS && node->cell[x].value == value_type());--            // check whether one has been found-            if (x < NUM_CELLS) {-                // update value and be done-                value.first = (value.first & ~INDEX_MASK) | x;-                value.second = node->cell[x].value;-                return *this;  // done-            }--            // go to parent-            node = node->parent;-            int level = 1;--            // get current index on this level-            x = getIndex(static_cast<RamDomain>(value.first), level);-            x++;--            while (level > 0 && node) {-                // search for next child-                while (x < NUM_CELLS) {-                    if (node->cell[x].ptr != nullptr) {-                        break;-                    }-                    x++;-                }--                // pick next step-                if (x < NUM_CELLS) {-                    // going down-                    node = node->cell[x].ptr;-                    value.first &= getLevelMask(level + 1);-                    value.first |= x << (BIT_PER_STEP * level);-                    level--;-                    x = 0;-                } else {-                    // going up-                    node = node->parent;-                    level++;--                    // get current index on this level-                    x = getIndex(static_cast<RamDomain>(value.first), level);-                    x++;  // go one step further-                }-            }--            // check whether it is the end of range-            if (node == nullptr) {-                return *this;-            }--            // search the first value in this node-            x = 0;-            while (node->cell[x].value == value_type()) {-                x++;-            }--            // update value-            value.first |= x;-            value.second = node->cell[x].value;--            // done-            return *this;-        }--        // True if this iterator is passed the last element.-        bool isEnd() const {-            return node == nullptr;-        }--        // enables this iterator core to be printed (for debugging)-        void print(std::ostream& out) const {-            out << "SparseArrayIter(" << node << " @ " << value << ")";-        }--        friend std::ostream& operator<<(std::ostream& out, const iterator& iter) {-            iter.print(out);-            return out;-        }-    };--    /**-     * Obtains an iterator referencing the first non-default element or end in-     * case there are no such elements.-     */-    iterator begin() const {-        return iterator(unsynced.first, unsynced.firstOffset);-    }--    /**-     * An iterator referencing the position after the last non-default element.-     */-    iterator end() const {-        return iterator();-    }--    /**-     * An operation to obtain an iterator referencing an element addressed by the-     * given index. If the corresponding element is a non-default value, a corresponding-     * iterator will be returned. Otherwise end() will be returned.-     */-    iterator find(index_type i) const {-        op_context ctxt;-        return find(i, ctxt);-    }--    /**-     * An operation to obtain an iterator referencing an element addressed by the-     * given index. If the corresponding element is a non-default value, a corresponding-     * iterator will be returned. Otherwise end() will be returned. A operation context-     * can be provided for exploiting temporal locality.-     */-    iterator find(index_type i, op_context& ctxt) const {-        // check whether it is empty-        if (!unsynced.root) return end();--        // check boundaries-        if (!inBoundaries(i)) return end();--        // check context-        if (ctxt.lastNode && ctxt.lastIndex == (i & ~INDEX_MASK)) {-            Node* node = ctxt.lastNode;--            // check whether there is a proper entry-            value_type value = node->cell[i & INDEX_MASK].value;-            if (value == value_type{}) {-                return end();-            }-            // return iterator pointing to value-            return iterator(node, std::make_pair(i, value));-        }--        // navigate to value-        Node* node = unsynced.root;-        unsigned level = unsynced.levels;-        while (level != 0) {-            // get X coordinate-            auto x = getIndex(i, level);--            // decrease level counter-            --level;--            // check next node-            Node* next = node->cell[x].ptr;--            // check next step-            if (!next) return end();--            // continue one level below-            node = next;-        }--        // register in context-        ctxt.lastNode = node;-        ctxt.lastIndex = (i & ~INDEX_MASK);--        // check whether there is a proper entry-        value_type value = node->cell[i & INDEX_MASK].value;-        if (value == value_type{}) {-            return end();-        }--        // return iterator pointing to cell-        return iterator(node, std::make_pair(i, value));-    }--    /**-     * An operation obtaining the smallest non-default element such that it's index is >=-     * the given index.-     */-    iterator lowerBound(index_type i) const {-        op_context ctxt;-        return lowerBound(i, ctxt);-    }--    /**-     * An operation obtaining the smallest non-default element such that it's index is >=-     * the given index. A operation context can be provided for exploiting temporal locality.-     */-    iterator lowerBound(index_type i, op_context&) const {-        // check whether it is empty-        if (!unsynced.root) return end();--        // check boundaries-        if (!inBoundaries(i)) {-            // if it is on the lower end, return minimum result-            if (i < unsynced.offset) {-                const auto& value = unsynced.first->cell[0].value;-                auto res = iterator(unsynced.first, std::make_pair(unsynced.offset, value));-                if (value == value_type()) {-                    ++res;-                }-                return res;-            }-            // otherwise it is on the high end, return end iterator-            return end();-        }--        // navigate to value-        Node* node = unsynced.root;-        unsigned level = unsynced.levels;-        while (true) {-            // get X coordinate-            auto x = getIndex(static_cast<RamDomain>(i), level);--            // check next node-            Node* next = node->cell[x].ptr;--            // check next step-            if (!next) {-                if (x == NUM_CELLS - 1) {-                    ++level;-                    node = const_cast<Node*>(node->parent);-                    if (!node) return end();-                }--                // continue search-                i = i & getLevelMask(level);--                // find next higher value-                i += 1ull << (BITS * level);--            } else {-                if (level == 0) {-                    // found boundary-                    return iterator(node, std::make_pair(i, node->cell[x].value));-                }--                // decrease level counter-                --level;--                // continue one level below-                node = next;-            }-        }-    }--    /**-     * An operation obtaining the smallest non-default element such that it's index is greater-     * the given index.-     */-    iterator upperBound(index_type i) const {-        op_context ctxt;-        return upperBound(i, ctxt);-    }--    /**-     * An operation obtaining the smallest non-default element such that it's index is greater-     * the given index. A operation context can be provided for exploiting temporal locality.-     */-    iterator upperBound(index_type i, op_context& ctxt) const {-        if (i == std::numeric_limits<index_type>::max()) {-            return end();-        }-        return lowerBound(i + 1, ctxt);-    }--private:-    /**-     * An internal debug utility printing the internal structure of this sparse array to the given output-     * stream.-     */-    void dump(bool detailed, std::ostream& out, const Node& node, int level, index_type offset,-            int indent = 0) const {-        auto x = getIndex(offset, level + 1);-        out << times("\t", indent) << x << ": Node " << &node << " on level " << level-            << " parent: " << node.parent << " -- range: " << offset << " - "-            << (offset + ~getLevelMask(level + 1)) << "\n";--        if (level == 0) {-            for (int i = 0; i < NUM_CELLS; i++) {-                if (detailed || node.cell[i].value != value_type()) {-                    out << times("\t", indent + 1) << i << ": [" << (offset + i) << "] " << node.cell[i].value-                        << "\n";-                }-            }-        } else {-            for (int i = 0; i < NUM_CELLS; i++) {-                if (node.cell[i].ptr) {-                    dump(detailed, out, *node.cell[i].ptr, level - 1,-                            offset + (i * (index_type(1) << (level * BIT_PER_STEP))), indent + 1);-                } else if (detailed) {-                    auto low = offset + (i * (1 << (level * BIT_PER_STEP)));-                    auto hig = low + ~getLevelMask(level);-                    out << times("\t", indent + 1) << i << ": empty range " << low << " - " << hig << "\n";-                }-            }-        }-        out << "\n";-    }--public:-    /**-     * A debug utility printing the internal structure of this sparse array to the given output stream.-     */-    void dump(bool detail = false, std::ostream& out = std::cout) const {-        if (!unsynced.root) {-            out << " - empty - \n";-            return;-        }-        out << "root:  " << unsynced.root << "\n";-        out << "offset: " << unsynced.offset << "\n";-        out << "first: " << unsynced.first << "\n";-        out << "fist offset: " << unsynced.firstOffset << "\n";-        dump(detail, out, *unsynced.root, unsynced.levels, unsynced.offset);-    }--private:-    // ---------------------------------------------------------------------------    //                                 Utilities-    // ----------------------------------------------------------------------------    /**-     * Creates new nodes and initializes them with 0.-     */-    static Node* newNode() {-        return new Node();-    }--    /**-     * Destroys a node and all its sub-nodes recursively.-     */-    static void freeNodes(Node* node, int level) {-        if (!node) return;-        if (level != 0) {-            for (int i = 0; i < NUM_CELLS; i++) {-                freeNodes(node->cell[i].ptr, level - 1);-            }-        }-        delete node;-    }--    /**-     * Conducts a cleanup of the internal tree structure.-     */-    void clean() {-        freeNodes(unsynced.root, unsynced.levels);-        unsynced.root = nullptr;-        unsynced.levels = 0;-    }--    /**-     * Clones the given node and all its sub-nodes.-     */-    static Node* clone(const Node* node, int level) {-        // support null-pointers-        if (node == nullptr) {-            return nullptr;-        }--        // create a clone-        auto* res = new Node();--        // handle leaf level-        if (level == 0) {-            copy_op copy;-            for (int i = 0; i < NUM_CELLS; i++) {-                res->cell[i].value = copy(node->cell[i].value);-            }-            return res;-        }--        // for inner nodes clone each child-        for (int i = 0; i < NUM_CELLS; i++) {-            auto cur = clone(node->cell[i].ptr, level - 1);-            if (cur != nullptr) {-                cur->parent = res;-            }-            res->cell[i].ptr = cur;-        }--        // done-        return res;-    }--    /**-     * Obtains the left-most leaf-node of the tree rooted by the given node-     * with the given level.-     */-    static Node* findFirst(Node* node, int level) {-        while (level > 0) {-            bool found = false;-            for (int i = 0; i < NUM_CELLS; i++) {-                Node* cur = node->cell[i].ptr;-                if (cur) {-                    node = cur;-                    --level;-                    found = true;-                    break;-                }-            }-            assert(found && "No first node!");-        }--        return node;-    }--    /**-     * Raises the level of this tree by one level. It does so by introducing-     * a new root node and inserting the current root node as a child node.-     */-    void raiseLevel() {-        // something went wrong when we pass that line-        assert(unsynced.levels < (sizeof(index_type) * 8 / BITS) + 1);--        // create new root-        Node* node = newNode();-        node->parent = nullptr;--        // insert existing root as child-        auto x = getIndex(static_cast<RamDomain>(unsynced.offset), unsynced.levels + 1);-        node->cell[x].ptr = unsynced.root;--        // swap the root-        unsynced.root->parent = node;--        // update root-        unsynced.root = node;-        ++unsynced.levels;--        // update offset be removing additional bits-        unsynced.offset &= getLevelMask(unsynced.levels + 1);-    }--    /**-     * Attempts to raise the height of this tree based on the given root node-     * information and updates the root-info snapshot correspondingly.-     */-    void raiseLevel(RootInfoSnapshot& info) {-        // something went wrong when we pass that line-        assert(info.levels < (sizeof(index_type) * 8 / BITS) + 1);--        // create new root-        Node* newRoot = newNode();-        newRoot->parent = nullptr;--        // insert existing root as child-        auto x = getIndex(static_cast<RamDomain>(info.offset), info.levels + 1);-        newRoot->cell[x].ptr = info.root;--        // exchange the root in the info struct-        auto oldRoot = info.root;-        info.root = newRoot;--        // update level counter-        ++info.levels;--        // update offset-        info.offset &= getLevelMask(info.levels + 1);--        // try exchanging root info-        if (tryUpdateRootInfo(info)) {-            // success => final step, update parent of old root-            oldRoot->parent = info.root;-        } else {-            // throw away temporary new node-            delete newRoot;-        }-    }--    /**-     * Tests whether the given index is covered by the boundaries defined-     * by the hight and offset of the internally maintained tree.-     */-    bool inBoundaries(index_type a) const {-        return inBoundaries(a, unsynced.levels, unsynced.offset);-    }--    /**-     * Tests whether the given index is within the boundaries defined by the-     * given tree hight and offset.-     */-    static bool inBoundaries(index_type a, uint32_t levels, index_type offset) {-        auto mask = getLevelMask(levels + 1);-        return (a & mask) == offset;-    }--    /**-     * Obtains the index within the arrays of cells of a given index on a given-     * level of the internally maintained tree.-     */-    static index_type getIndex(RamDomain a, unsigned level) {-        return (a & (INDEX_MASK << (level * BIT_PER_STEP))) >> (level * BIT_PER_STEP);-    }--    /**-     * Computes the bit-mask to be applicable to obtain the offset of a node on a-     * given tree level.-     */-    static index_type getLevelMask(unsigned level) {-        if (level > (sizeof(index_type) * 8 / BITS)) return 0;-        return (~(index_type(0)) << (level * BIT_PER_STEP));-    }-};--/**- * A sparse bit-map is a bit map virtually assigning a bit value to every value if the- * uint32_t domain. However, only 1-bits are stored utilizing a nested sparse array- * structure.- *- * @tparam BITS similar to the BITS parameter of the sparse array type- */-template <unsigned BITS = 4>-class SparseBitMap {-    // the element type stored in the nested sparse array-    using value_t = uint64_t;--    // define the bit-level merge operation-    struct merge_op {-        value_t operator()(value_t a, value_t b) const {-            return a | b;  // merging bit masks => bitwise or operation-        }-    };--    // the type of the internal data store-    using data_store_t = SparseArray<value_t, BITS, merge_op>;-    using atomic_value_t = typename data_store_t::atomic_value_type;--    // some constants for manipulating stored values-    static constexpr short BITS_PER_ENTRY = sizeof(value_t) * 8;-    static constexpr short LEAF_INDEX_WIDTH = static_cast<short>(__builtin_ctz(BITS_PER_ENTRY));-    static constexpr uint64_t LEAF_INDEX_MASK = BITS_PER_ENTRY - 1;--public:-    // the type to address individual entries-    using index_type = typename data_store_t::index_type;--private:-    // it utilizes a sparse map to store its data-    data_store_t store;--public:-    // a simple default constructor-    SparseBitMap() = default;--    // a default copy constructor-    SparseBitMap(const SparseBitMap&) = default;--    // a default r-value copy constructor-    SparseBitMap(SparseBitMap&&) = default;--    // a default assignment operator-    SparseBitMap& operator=(const SparseBitMap&) = default;--    // a default r-value assignment operator-    SparseBitMap& operator=(SparseBitMap&&) = default;--    // checks whether this bit-map is empty -- thus it does not have any 1-entries-    bool empty() const {-        return store.empty();-    }--    // the type utilized for recording context information for exploiting temporal locality-    using op_context = typename data_store_t::op_context;--    /**-     * Sets the bit addressed by i to 1.-     */-    bool set(index_type i) {-        op_context ctxt;-        return set(i, ctxt);-    }--    /**-     * Sets the bit addressed by i to 1. A context for exploiting temporal locality-     * can be provided.-     */-    bool set(index_type i, op_context& ctxt) {-        atomic_value_t& val = store.getAtomic(i >> LEAF_INDEX_WIDTH, ctxt);-        value_t bit = (1ull << (i & LEAF_INDEX_MASK));--#ifdef __GNUC__-#if __GNUC__ >= 7-        // In GCC >= 7 the usage of fetch_or causes a bug that needs further investigation-        // For now, this two-instruction based implementation provides a fix that does-        // not sacrifice too much performance.--        while (true) {-            auto order = std::memory_order::memory_order_relaxed;--            // load current value-            value_t old = val.load(order);--            // if bit is already set => we are done-            if (old & bit) return false;--            // set the bit, if failed, repeat-            if (!val.compare_exchange_strong(old, old | bit, order, order)) continue;--            // it worked, new bit added-            return true;-        }--#endif-#endif--        value_t old = val.fetch_or(bit, std::memory_order::memory_order_relaxed);-        return (old & bit) == 0u;-    }--    /**-     * Determines the whether the bit addressed by i is set or not.-     */-    bool test(index_type i) const {-        op_context ctxt;-        return test(i, ctxt);-    }--    /**-     * Determines the whether the bit addressed by i is set or not. A context for-     * exploiting temporal locality can be provided.-     */-    bool test(index_type i, op_context& ctxt) const {-        value_t bit = (1ull << (i & LEAF_INDEX_MASK));-        return store.lookup(i >> LEAF_INDEX_WIDTH, ctxt) & bit;-    }--    /**-     * Determines the whether the bit addressed by i is set or not.-     */-    bool operator[](index_type i) const {-        return test(i);-    }--    /**-     * Resets all contained bits to 0.-     */-    void clear() {-        store.clear();-    }--    /**-     * Determines the number of bits set.-     */-    std::size_t size() const {-        // this is computed on demand to keep the set operation simple.-        std::size_t res = 0;-        for (const auto& cur : store) {-            res += __builtin_popcountll(cur.second);-        }-        return res;-    }--    /**-     * Computes the total memory usage of this data structure.-     */-    std::size_t getMemoryUsage() const {-        // compute the total memory usage-        return sizeof(*this) - sizeof(data_store_t) + store.getMemoryUsage();-    }--    /**-     * Sets all bits set in other to 1 within this bit map.-     */-    void addAll(const SparseBitMap& other) {-        // nothing to do if it is a self-assignment-        if (this == &other) return;--        // merge the sparse store-        store.addAll(other.store);-    }--    // ----------------------------------------------------------------------    //                           Iterator-    // -----------------------------------------------------------------------    /**-     * An iterator iterating over all indices set to 1.-     */-    class iterator {-        using nested_iterator = typename data_store_t::iterator;--        // the iterator through the underlying sparse data structure-        nested_iterator iter;--        // the currently consumed mask-        uint64_t mask = 0;--        // the value currently pointed to-        index_type value{};--    public:-        typedef std::forward_iterator_tag iterator_category;-        typedef index_type value_type;-        typedef ptrdiff_t difference_type;-        typedef value_type* pointer;-        typedef value_type& reference;--        // default constructor -- creating an end-iterator-        iterator() = default;--        iterator(const nested_iterator& iter)-                : iter(iter), mask(toMask(iter->second)), value(iter->first << LEAF_INDEX_WIDTH) {-            moveToNextInMask();-        }--        iterator(const nested_iterator& iter, uint64_t m, index_type value)-                : iter(iter), mask(m), value(value) {}--        // a copy constructor-        iterator(const iterator& other) = default;--        // an assignment operator-        iterator& operator=(const iterator& other) = default;--        // the equality operator as required by the iterator concept-        bool operator==(const iterator& other) const {-            // only equivalent if pointing to the end-            return iter == other.iter && mask == other.mask;-        }--        // the not-equality operator as required by the iterator concept-        bool operator!=(const iterator& other) const {-            return !(*this == other);-        }--        // the deref operator as required by the iterator concept-        const index_type& operator*() const {-            return value;-        }--        // support for the pointer operator-        const index_type* operator->() const {-            return &value;-        }--        // the increment operator as required by the iterator concept-        iterator& operator++() {-            // progress in current mask-            if (moveToNextInMask()) return *this;--            // go to next entry-            ++iter;--            // update value-            if (!iter.isEnd()) {-                value = iter->first << LEAF_INDEX_WIDTH;-                mask = toMask(iter->second);-                moveToNextInMask();-            }--            // done-            return *this;-        }--        bool isEnd() const {-            return iter.isEnd();-        }--        void print(std::ostream& out) const {-            out << "SparseBitMapIter(" << iter << " -> " << std::bitset<64>(mask) << " @ " << value << ")";-        }--        // enables this iterator core to be printed (for debugging)-        friend std::ostream& operator<<(std::ostream& out, const iterator& iter) {-            iter.print(out);-            return out;-        }--        static uint64_t toMask(const value_t& value) {-            static_assert(sizeof(value_t) == sizeof(uint64_t), "Fixed for 64-bit compiler.");-            return reinterpret_cast<const uint64_t&>(value);-        }--    private:-        bool moveToNextInMask() {-            // check if there is something left-            if (mask == 0) return false;--            // get position of leading 1-            auto pos = __builtin_ctzll(mask);--            // consume this bit-            mask &= ~(1llu << pos);--            // update value-            value &= ~LEAF_INDEX_MASK;-            value |= pos;--            // done-            return true;-        }-    };--    /**-     * Obtains an iterator pointing to the first index set to 1. If there-     * is no such bit, end() will be returned.-     */-    iterator begin() const {-        auto it = store.begin();-        if (it.isEnd()) return end();-        return iterator(it);-    }--    /**-     * Returns an iterator referencing the position after the last set bit.-     */-    iterator end() const {-        return iterator();-    }--    /**-     * Obtains an iterator referencing the position i if the corresponding-     * bit is set, end() otherwise.-     */-    iterator find(index_type i) const {-        op_context ctxt;-        return find(i, ctxt);-    }--    /**-     * Obtains an iterator referencing the position i if the corresponding-     * bit is set, end() otherwise. An operation context can be provided-     * to exploit temporal locality.-     */-    iterator find(index_type i, op_context& ctxt) const {-        // check prefix part-        auto it = store.find(i >> LEAF_INDEX_WIDTH, ctxt);-        if (it.isEnd()) return end();--        // check bit-set part-        uint64_t mask = iterator::toMask(it->second);-        if (!(mask & (1llu << (i & LEAF_INDEX_MASK)))) return end();--        // OK, it is there => create iterator-        mask &= ((1ull << (i & LEAF_INDEX_MASK)) - 1);  // remove all bits before pos i-        return iterator(it, mask, i);-    }--    /**-     * Locates an iterator to the first element in this sparse bit map not less-     * than the given index.-     */-    iterator lower_bound(index_type i) const {-        auto it = store.lowerBound(i >> LEAF_INDEX_WIDTH);-        if (it.isEnd()) return end();--        // check bit-set part-        uint64_t mask = iterator::toMask(it->second);--        // if there is no bit remaining in this mask, check next mask.-        if (!(mask & ((~uint64_t(0)) << (i & LEAF_INDEX_MASK)))) {-            index_type next = ((i >> LEAF_INDEX_WIDTH) + 1) << LEAF_INDEX_WIDTH;-            if (next < i) return end();-            return lower_bound(next);-        }--        // there are bits left, use least significant bit of those-        if (it->first == i >> LEAF_INDEX_WIDTH) {-            mask &= ((~uint64_t(0)) << (i & LEAF_INDEX_MASK));  // remove all bits before pos i-        }--        // compute value represented by least significant bit-        index_type pos = __builtin_ctzll(mask);--        // remove this bit as well-        mask = mask & ~(1ull << pos);--        // construct value of this located bit-        index_type val = (it->first << LEAF_INDEX_WIDTH) | pos;-        return iterator(it, mask, val);-    }--    /**-     * Locates an iterator to the first element in this sparse bit map than is greater-     * than the given index.-     */-    iterator upper_bound(index_type i) const {-        if (i == std::numeric_limits<index_type>::max()) {-            return end();-        }-        return lower_bound(i + 1);-    }--    /**-     * A debugging utility printing the internal structure of this map to the-     * given output stream.-     */-    void dump(bool detail = false, std::ostream& out = std::cout) const {-        store.dump(detail, out);-    }--    /**-     * Provides write-protected access to the internal store for running-     * analysis on the data structure.-     */-    const data_store_t& getStore() const {-        return store;-    }-};--// ----------------------------------------------------------------------//                              TRIE-// -----------------------------------------------------------------------namespace detail {--/**- * A base class for the Trie implementation allowing various- * specializations of the Trie template to inherit common functionality.- *- * @tparam Dim the number of dimensions / arity of the stored tuples- * @tparam Derived the type derived from this base class- */-template <unsigned Dim, typename Derived>-class TrieBase {-public:-    /**-     * The type of the stored entries / tuples.-     */-    using entry_type = typename souffle::Tuple<RamDomain, Dim>;--    // -- operation wrappers ----    /**-     * A generic function enabling the insertion of tuple values in a user-friendly way.-     */-    template <typename... Values>-    bool insert(Values... values) {-        return static_cast<Derived&>(*this).insert(entry_type{{RamDomain(values)...}});-    }--    /**-     * A generic function enabling the convenient conduction of a membership check.-     */-    template <typename... Values>-    bool contains(Values... values) const {-        return static_cast<const Derived&>(*this).contains(entry_type{{RamDomain(values)...}});-    }--    // ----------------------------------------------------------------------    //                           Iterator-    // -----------------------------------------------------------------------    /**-     * An iterator over the stored entries.-     *-     * Iterators for tries consist of a top-level iterator maintaining the-     * master copy of a materialized tuple and a recursively nested iterator-     * core -- one for each nested trie level.-     */-    template <template <unsigned D> class IterCore>-    class iterator {-        template <unsigned Len, unsigned Pos, unsigned Dimensions>-        friend struct fix_binding;--        template <unsigned Pos, unsigned Dimensions>-        friend struct fix_lower_bound;--        template <unsigned Pos, unsigned Dimensions>-        friend struct fix_upper_bound;--        template <unsigned Pos, unsigned Dimensions>-        friend struct fix_first;--        // the iterator core of this level-        using iter_core_t = IterCore<0>;--        // the wrapped iterator-        iter_core_t iter_core;--        // the value currently pointed to-        entry_type value;--    public:-        typedef std::forward_iterator_tag iterator_category;-        typedef entry_type value_type;-        typedef ptrdiff_t difference_type;-        typedef value_type* pointer;-        typedef value_type& reference;--        // default constructor -- creating an end-iterator-        iterator() = default;--        // a copy constructor-        iterator(const iterator& other) = default;--        iterator(iterator&& other) = default;--        template <typename Param>-        explicit iterator(const Param& param) : iter_core(param, value) {}--        // an assignment operator-        iterator& operator=(const iterator& other) = default;--        // the equality operator as required by the iterator concept-        bool operator==(const iterator& other) const {-            // equivalent if pointing to the same value-            return iter_core == other.iter_core;-        }--        // the not-equality operator as required by the iterator concept-        bool operator!=(const iterator& other) const {-            return !(*this == other);-        }--        // the deref operator as required by the iterator concept-        const entry_type& operator*() const {-            return value;-        }--        // support for the pointer operator-        const entry_type* operator->() const {-            return &value;-        }--        // the increment operator as required by the iterator concept-        iterator& operator++() {-            iter_core.inc(value);-            return *this;-        }--        // enables this iterator to be printed (for debugging)-        void print(std::ostream& out) const {-            out << "iter(" << iter_core << " -> " << value << ")";-        }--        friend std::ostream& operator<<(std::ostream& out, const iterator& iter) {-            iter.print(out);-            return out;-        }-    };--    /* -------------- operator hint statistics ----------------- */--    // an aggregation of statistical values of the hint utilization-    struct hint_statistics {-        // the counter for insertion operations-        CacheAccessCounter inserts;--        // the counter for contains operations-        CacheAccessCounter contains;--        // the counter for get_boundaries operations-        CacheAccessCounter get_boundaries;-    };--protected:-    // the hint statistic of this b-tree instance-    mutable hint_statistics hint_stats;--public:-    void printStats(std::ostream& out) const {-        out << "---------------------------------\n";-        out << "  insert-hint (hits/misses/total): " << hint_stats.inserts.getHits() << "/"-            << hint_stats.inserts.getMisses() << "/" << hint_stats.inserts.getAccesses() << "\n";-        out << "  contains-hint (hits/misses/total):" << hint_stats.contains.getHits() << "/"-            << hint_stats.contains.getMisses() << "/" << hint_stats.contains.getAccesses() << "\n";-        out << "  get-boundaries-hint (hits/misses/total):" << hint_stats.get_boundaries.getHits() << "/"-            << hint_stats.get_boundaries.getMisses() << "/" << hint_stats.get_boundaries.getAccesses()-            << "\n";-        out << "---------------------------------\n";-    }-};--/**- * A functor extracting a reference to a nested iterator core from an enclosing- * iterator core.- */-template <unsigned Level>-struct get_nested_iter_core {-    template <typename IterCore>-    auto operator()(IterCore& core) -> decltype(get_nested_iter_core<Level - 1>()(core.getNested())) {-        return get_nested_iter_core<Level - 1>()(core.getNested());-    }-};--template <>-struct get_nested_iter_core<0> {-    template <typename IterCore>-    IterCore& operator()(IterCore& core) {-        return core;-    }-};--/**- * A functor initializing an iterator upon creation to reference the first- * element in the associated Trie.- */-template <unsigned Pos, unsigned Dim>-struct fix_first {-    template <unsigned bits, typename iterator>-    void operator()(const SparseBitMap<bits>& store, iterator& iter) const {-        // set iterator to first in store-        auto first = store.begin();-        get_nested_iter_core<Pos>()(iter.iter_core).setIterator(first);-        iter.value[Pos] = *first;-    }--    template <typename Store, typename iterator>-    void operator()(const Store& store, iterator& iter) const {-        // set iterator to first in store-        auto first = store.begin();-        get_nested_iter_core<Pos>()(iter.iter_core).setIterator(first);-        iter.value[Pos] = first->first;-        // and continue recursively-        fix_first<Pos + 1, Dim>()(first->second->getStore(), iter);-    }-};--template <unsigned Dim>-struct fix_first<Dim, Dim> {-    template <typename Store, typename iterator>-    void operator()(const Store&, iterator&) const {-        // terminal case => nothing to do-    }-};--/**- * A functor initializing an iterator upon creation to reference the first element- * exhibiting a given prefix within a given Trie.- */-template <unsigned Len, unsigned Pos, unsigned Dim>-struct fix_binding {-    template <unsigned bits, typename iterator, typename entry_type>-    bool operator()(-            const SparseBitMap<bits>& store, iterator& begin, iterator& end, const entry_type& entry) const {-        // search in current level-        auto cur = store.find(entry[Pos]);--        // if not present => fail-        if (cur == store.end()) return false;--        // take current value-        get_nested_iter_core<Pos>()(begin.iter_core).setIterator(cur);-        ++cur;-        get_nested_iter_core<Pos>()(end.iter_core).setIterator(cur);--        // update iterator value-        begin.value[Pos] = entry[Pos];--        // no more remaining levels to fix-        return true;-    }--    template <typename Store, typename iterator, typename entry_type>-    bool operator()(const Store& store, iterator& begin, iterator& end, const entry_type& entry) const {-        // search in current level-        auto cur = store.find(entry[Pos]);--        // if not present => fail-        if (cur == store.end()) return false;--        // take current value as start-        get_nested_iter_core<Pos>()(begin.iter_core).setIterator(cur);--        // update iterator value-        begin.value[Pos] = entry[Pos];--        // fix remaining nested iterators-        auto res = fix_binding<Len - 1, Pos + 1, Dim>()(cur->second->getStore(), begin, end, entry);--        // update end of iterator-        if (get_nested_iter_core<Pos + 1>()(end.iter_core).getIterator() == cur->second->getStore().end()) {-            ++cur;-            if (cur != store.end()) {-                fix_first<Pos + 1, Dim>()(cur->second->getStore(), end);-            }-        }-        get_nested_iter_core<Pos>()(end.iter_core).setIterator(cur);--        // done-        return res;-    }-};--template <unsigned Pos, unsigned Dim>-struct fix_binding<0, Pos, Dim> {-    template <unsigned bits, typename iterator, typename entry_type>-    bool operator()(const SparseBitMap<bits>& store, iterator& begin, iterator& /* end */,-            const entry_type& /* entry */) const {-        // move begin to begin of store-        auto a = store.begin();-        get_nested_iter_core<Pos>()(begin.iter_core).setIterator(a);-        begin.value[Pos] = *a;--        return true;-    }--    template <typename Store, typename iterator, typename entry_type>-    bool operator()(const Store& store, iterator& begin, iterator& end, const entry_type& entry) const {-        // move begin to begin of store-        auto a = store.begin();-        get_nested_iter_core<Pos>()(begin.iter_core).setIterator(a);-        begin.value[Pos] = a->first;--        // continue recursively-        fix_binding<0, Pos + 1, Dim>()(a->second->getStore(), begin, end, entry);-        return true;-    }-};--template <unsigned Dim>-struct fix_binding<0, Dim, Dim> {-    template <typename Store, typename iterator, typename entry_type>-    bool operator()(const Store& /* store */, iterator& /* begin */, iterator& /* end */,-            const entry_type& /* entry */) const {-        // nothing more to do-        return true;-    }-};--/**- * A functor initializing an iterator upon creation to reference the first element- * within a given Trie being not less than a given value .- */-template <unsigned Pos, unsigned Dim>-struct fix_lower_bound {-    template <unsigned bits, typename iterator, typename entry_type>-    bool operator()(const SparseBitMap<bits>& store, iterator& iter, const entry_type& entry) const {-        // search in current level-        auto cur = store.lower_bound(entry[Pos]);--        if (cur == store.end()) return false;--        get_nested_iter_core<Pos>()(iter.iter_core).setIterator(cur);--        assert(entry[Pos] <= RamDomain(*cur));-        iter.value[Pos] = *cur;--        // no more remaining levels to fix-        return true;-    }--    template <typename Store, typename iterator, typename entry_type>-    bool operator()(const Store& store, iterator& iter, const entry_type& entry) const {-        // search in current level-        auto cur = store.lowerBound(entry[Pos]);--        // if no lower boundary is found, be done-        if (cur == store.end()) return false;-        assert(RamDomain(cur->first) >= entry[Pos]);--        // if the lower bound is higher than the requested value, go to first in subtree-        if (RamDomain(cur->first) > entry[Pos]) {-            get_nested_iter_core<Pos>()(iter.iter_core).setIterator(cur);-            iter.value[Pos] = cur->first;-            fix_first<Pos + 1, Dim>()(cur->second->getStore(), iter);-            return true;-        }--        // attempt to fix the rest-        if (!fix_lower_bound<Pos + 1, Dim>()(cur->second->getStore(), iter, entry)) {-            // if it does not work, since there are no matching elements in this branch, go to next-            entry_type sub = entry;-            sub[Pos] += 1;-            for (size_t i = Pos + 1; i < Dim; ++i) {-                sub[i] = 0;-            }-            return (*this)(store, iter, sub);-        }--        // remember result-        get_nested_iter_core<Pos>()(iter.iter_core).setIterator(cur);--        // update iterator value-        iter.value[Pos] = cur->first;--        // done!-        return true;-    }-};--/**- * A functor initializing an iterator upon creation to reference the first element- * within a given Trie being greater than a given value .- */-template <unsigned Pos, unsigned Dim>-struct fix_upper_bound {-    template <unsigned bits, typename iterator, typename entry_type>-    bool operator()(const SparseBitMap<bits>& store, iterator& iter, const entry_type& entry) const {-        // search in current level-        auto cur = store.upper_bound(entry[Pos]);--        if (cur == store.end()) {-            return false;-        }--        get_nested_iter_core<Pos>()(iter.iter_core).setIterator(cur);--        assert(entry[Pos] <= RamDomain(*cur));-        iter.value[Pos] = *cur;--        // no more remaining levels to fix-        return true;-    }--    template <typename Store, typename iterator, typename entry_type>-    bool operator()(const Store& store, iterator& iter, const entry_type& entry) const {-        // search in current level (if it is not the last level, we need a lower bound)-        auto cur = store.lowerBound(entry[Pos]);--        // if no lower boundary is found, be done-        if (cur == store.end()) {-            return false;-        }-        assert(RamDomain(cur->first) >= entry[Pos]);--        // if the lower bound is higher than the requested value, go to first in subtree-        if (RamDomain(cur->first) > entry[Pos]) {-            get_nested_iter_core<Pos>()(iter.iter_core).setIterator(cur);-            iter.value[Pos] = cur->first;-            fix_first<Pos + 1, Dim>()(cur->second->getStore(), iter);-            return true;-        }--        // attempt to fix the rest-        if (!fix_upper_bound<Pos + 1, Dim>()(cur->second->getStore(), iter, entry)) {-            // if it does not work, since there are no matching elements in this branch, go to next-            entry_type sub = entry;-            sub[Pos] += 1;-            for (size_t i = Pos + 1; i < Dim; ++i) {-                sub[i] = 0;-            }-            return (*this)(store, iter, sub);-        }--        // remember result-        get_nested_iter_core<Pos>()(iter.iter_core).setIterator(cur);--        // update iterator value-        iter.value[Pos] = cur->first;--        // done!-        return true;-    }-};--}  // namespace detail--/**- * The most generic implementation of a Trie forming the top-level of any- * Trie storing tuples of arity > 1.- */-template <unsigned Dim>-class Trie : public souffle::detail::TrieBase<Dim, Trie<Dim>> {-    template <unsigned D>-    friend class Trie;--    template <unsigned D, typename Derived>-    friend class TrieBase;--    // a shortcut for the common base class type-    using base = typename souffle::detail::TrieBase<Dim, Trie<Dim>>;--    // the type of the nested tries (1 dimension less)-    using nested_trie_type = Trie<Dim - 1>;--    // the merge operation capable of merging two nested tries-    struct nested_trie_merger {-        nested_trie_type* operator()(nested_trie_type* a, const nested_trie_type* b) const {-            if (!b) return a;-            if (!a) return new nested_trie_type(*b);-            a->insertAll(*b);-            return a;-        }-    };--    // the operation capable of cloning a nested trie-    struct nested_trie_cloner {-        nested_trie_type* operator()(nested_trie_type* a) const {-            if (!a) return a;-            return new nested_trie_type(*a);-        }-    };--    // the data structure utilized for indexing nested tries-    using store_type = SparseArray<nested_trie_type*,-            6,  // = 2^6 entries per block-            nested_trie_merger, nested_trie_cloner>;--    // the actual data store-    store_type store;--public:-    using entry_type = typename souffle::Tuple<RamDomain, Dim>;-    using element_type = entry_type;--    // ----------------------------------------------------------------------    //                           Iterator-    // -----------------------------------------------------------------------    /**-     * The iterator core for trie iterators involving this level.-     */-    template <unsigned I = 0>-    class iterator_core {-        // the iterator for the current level-        using store_iter_t = typename store_type::iterator;--        // the type of the nested iterator-        using nested_iter_core = typename Trie<Dim - 1>::template iterator_core<I + 1>;--        store_iter_t iter;--        nested_iter_core nested;--    public:-        /** default end-iterator constructor */-        iterator_core() = default;--        template <typename Tuple>-        iterator_core(const store_iter_t& iter, Tuple& entry) : iter(iter) {-            entry[I] = iter->first;-            nested = iter->second->template getBeginCoreIterator<I + 1>(entry);-        }--        void setIterator(const store_iter_t& iter) {-            this->iter = iter;-        }--        store_iter_t& getIterator() {-            return this->iter;-        }--        nested_iter_core& getNested() {-            return nested;-        }--        template <typename Tuple>-        bool inc(Tuple& entry) {-            // increment nested iterator-            if (nested.inc(entry)) return true;--            // increment the iterator on this level-            ++iter;--            // check whether the end has been reached-            if (iter.isEnd()) return false;--            // otherwise update entry value-            entry[I] = iter->first;--            // and restart nested-            nested = iter->second->template getBeginCoreIterator<I + 1>(entry);-            return true;-        }--        bool operator==(const iterator_core& other) const {-            return nested == other.nested && iter == other.iter;-        }--        bool operator!=(const iterator_core& other) const {-            return !(*this == other);-        }--        // enables this iterator core to be printed (for debugging)-        void print(std::ostream& out) const {-            out << iter << " | " << nested;-        }--        friend std::ostream& operator<<(std::ostream& out, const iterator_core& iter) {-            iter.print(out);-            return out;-        }-    };--    // the type of iterator to be utilized when iterating of instances of this trie-    using iterator = typename base::template iterator<iterator_core>;--    // the operation context aggregating all operation contexts of nested structures-    struct op_context {-        using local_ctxt = typename store_type::op_context;-        using nested_ctxt = typename nested_trie_type::op_context;--        // for insert and contain-        local_ctxt local{};-        RamDomain lastQuery{};-        nested_trie_type* lastNested{nullptr};-        nested_ctxt nestedCtxt{};--        // for boundaries-        unsigned lastBoundaryLevels{Dim + 1};-        entry_type lastBoundaryRequest{};-        range<iterator> lastBoundaries{iterator(), iterator()};--        op_context() = default;-    };--    using operation_hints = op_context;--    using base::contains;-    using base::insert;--    /**-     * A simple destructore.-     */-    ~Trie() {-        for (auto& cur : store) {-            delete cur.second;  // clears all nested tries-        }-    }--    /**-     * Determines whether this trie is empty or not.-     */-    bool empty() const {-        return store.empty();-    }--    /**-     * Determines the number of entries in this trie.-     */-    std::size_t size() const {-        // the number of elements is lazy-evaluated-        std::size_t res = 0;-        for (const auto& cur : store) {-            res += cur.second->size();-        }-        return res;-    }--    /**-     * Computes the total memory usage of this data structure.-     */-    std::size_t getMemoryUsage() const {-        // compute the total memory usage of this level-        std::size_t res = sizeof(*this) - sizeof(store) + store.getMemoryUsage();--        // add the memory usage of sub-levels-        for (const auto& cur : store) {-            res += cur.second->getMemoryUsage();-        }--        // done-        return res;-    }--    /**-     * Removes all entries within this trie.-     */-    void clear() {-        // delete lower levels-        for (auto& cur : store) {-            delete cur.second;-        }--        // clear store-        store.clear();-    }--    /**-     * Inserts a new entry.-     *-     * @param tuple the entry to be added-     * @return true if the same tuple hasn't been present before, false otherwise-     */-    bool insert(const entry_type& tuple) {-        op_context ctxt;-        return insert(tuple, ctxt);-    }--    /**-     * Inserts a new entry. A operation context may be provided to exploit temporal-     * locality.-     *-     * @param tuple the entry to be added-     * @param ctxt the operation context to be utilized-     * @return true if the same tuple hasn't been present before, false otherwise-     */-    bool insert(const entry_type& tuple, op_context& ctxt) {-        return insert_internal<0>(tuple, ctxt);-    }--    /**-     * Determines whether a given tuple is present within the set specified-     * by this trie.-     *-     * @param tuple the tuple to be tested-     * @return true if present, false otherwise-     */-    bool contains(const entry_type& tuple) const {-        op_context ctxt;-        return contains(tuple, ctxt);-    }--    /**-     * Determines whether a given tuple is present within the set specified-     * by this trie. A operation context may be provided to exploit temporal-     * locality.-     *-     * @param tuple the entry to be added-     * @param ctxt the operation context to be utilized-     * @return true if the same tuple hasn't been present before, false otherwise-     */-    bool contains(const entry_type& tuple, op_context& ctxt) const {-        return contains_internal<0>(tuple, ctxt);-    }--    /**-     * Inserts all elements stored within the given trie into this trie.-     *-     * @param other the elements to be inserted into this trie-     */-    void insertAll(const Trie& other) {-        store.addAll(other.store);-    }--    /**-     * Obtains an iterator referencing the first element stored within this trie.-     */-    iterator begin() const {-        auto it = store.begin();-        if (it.isEnd()) return end();-        return iterator(it);-    }--    /**-     * Obtains an iterator referencing the position after the last element stored-     * within this trie.-     */-    iterator end() const {-        return iterator();-    }--    iterator find(const entry_type& entry) const {-        op_context ctxt;-        return find(entry, ctxt);-    }--    iterator find(const entry_type& entry, op_context& ctxt) const {-        auto range = getBoundaries<Dim>(entry, ctxt);-        return (!range.empty()) ? range.begin() : end();-    }--    /**-     * Obtains a range of elements matching the prefix of the given entry up to-     * levels elements.-     *-     * @tparam levels the length of the requested matching prefix-     * @param entry the entry to be looking for-     * @return the corresponding range of matching elements-     */-    template <unsigned levels>-    range<iterator> getBoundaries(const entry_type& entry) const {-        op_context ctxt;-        return getBoundaries<levels>(entry, ctxt);-    }--    /**-     * Obtains a range of elements matching the prefix of the given entry up to-     * levels elements. A operation context may be provided to exploit temporal-     * locality.-     *-     * @tparam levels the length of the requested matching prefix-     * @param entry the entry to be looking for-     * @param ctxt the operation context to be utilized-     * @return the corresponding range of matching elements-     */-    template <unsigned levels>-    range<iterator> getBoundaries(const entry_type& entry, op_context& ctxt) const {-        // if nothing is bound => just use begin and end-        if (levels == 0) return make_range(begin(), end());--        // check context-        if (ctxt.lastBoundaryLevels == levels) {-            bool fit = true;-            for (unsigned i = 0; i < levels; ++i) {-                fit = fit && (entry[i] == ctxt.lastBoundaryRequest[i]);-            }--            // if it fits => take it-            if (fit) {-                base::hint_stats.get_boundaries.addHit();-                return ctxt.lastBoundaries;-            }-        }--        // the hint has not been a hit-        base::hint_stats.get_boundaries.addMiss();--        // start with two end iterators-        iterator begin{};-        iterator end{};--        // adapt them level by level-        auto found = souffle::detail::fix_binding<levels, 0, Dim>()(store, begin, end, entry);-        if (!found) return make_range(iterator(), iterator());--        // update context-        ctxt.lastBoundaryLevels = levels;-        ctxt.lastBoundaryRequest = entry;-        ctxt.lastBoundaries = make_range(begin, end);--        // use the result-        return ctxt.lastBoundaries;-    }--    /**-     * Obtains an iterator to the first element not less than the given entry value.-     *-     * @param entry the lower bound for this search-     * @param ctxt the operation context to be utilized-     * @return an iterator addressing the first element in this structure not less than the given value-     */-    iterator lower_bound(const entry_type& entry, op_context& /* ctxt */) const {-        // start with a default-initialized iterator-        iterator res;--        // adapt it level by level-        bool found = detail::fix_lower_bound<0, Dim>()(store, res, entry);--        // use the result-        return found ? res : end();-    }--    /**-     * Obtains an iterator to the first element not less than the given entry value.-     *-     * @param entry the lower bound for this search-     * @return an iterator addressing the first element in this structure not less than the given value-     */-    iterator lower_bound(const entry_type& entry) const {-        op_context ctxt;-        return lower_bound(entry, ctxt);-    }--    /**-     * Obtains an iterator to the first element greater than the given entry value, or end if there is no such-     * element.-     *-     * @param entry the upper bound for this search-     * @param ctxt the operation context to be utilized-     * @return an iterator addressing the first element in this structure greater than the given value-     */-    iterator upper_bound(const entry_type& entry, op_context& /* ctxt */) const {-        // start with a default-initialized iterator-        iterator res;--        // adapt it level by level-        bool found = detail::fix_upper_bound<0, Dim>()(store, res, entry);--        // use the result-        return found ? res : end();-    }--    /**-     * Obtains an iterator to the first element greater than the given entry value, or end if there is no such-     * element.-     *-     * @param entry the upper bound for this search-     * @return an iterator addressing the first element in this structure greater than the given value-     */-    iterator upper_bound(const entry_type& entry) const {-        op_context ctxt;-        return upper_bound(entry, ctxt);-    }--    /**-     * Computes a partition of an approximate number of chunks of the content-     * of this trie. Thus, the union of the resulting set of disjoint ranges is-     * equivalent to the content of this trie.-     *-     * @param chunks the number of chunks requested-     * @return a list of sub-ranges forming a partition of the content of this trie-     */-    std::vector<range<iterator>> partition(unsigned chunks = 500) const {-        std::vector<range<iterator>> res;--        // shortcut for empty trie-        if (this->empty()) return res;--        // use top-level elements for partitioning-        int step = std::max(store.size() / chunks, size_t(1));--        int c = 1;-        auto priv = begin();-        for (auto it = store.begin(); it != store.end(); ++it, c++) {-            if (c % step != 0 || c == 1) {-                continue;-            }-            auto cur = iterator(it);-            res.push_back(make_range(priv, cur));-            priv = cur;-        }-        // add final chunk-        res.push_back(make_range(priv, end()));-        return res;-    }--    /**-     * Provides a protected access to the internally maintained store.-     */-    const store_type& getStore() const {-        return store;-    }--private:-    /**-     * Creates a core iterator for this trie level and updates component-     * I of the given entry to exhibit the corresponding first value.-     *-     * @tparam I the index of the tuple to be processed by the resulting iterator core-     * @tparam Tuple the type of the tuple to be processed by the resulting iterator core-     * @param entry a reference to the tuple to be updated to the first value-     * @return the requested iterator core instance-     */-    template <unsigned I, typename Tuple>-    iterator_core<I> getBeginCoreIterator(Tuple& entry) const {-        return iterator_core<I>(store.begin(), entry);-    }--    /**-     * The internally utilized implementation of the insert operation inserting-     * a given tuple into this sub-trie.-     *-     * @tparam I the component index associated to this level-     * @tparam Tuple the tuple type to be inserted-     * @param tuple the tuple to be inserted-     * @param ctxt a operation context to exploit temporal locality-     * @return true if this tuple wasn't contained before, false otherwise-     */-    template <unsigned I, typename Tuple>-    bool insert_internal(const Tuple& tuple, op_context& ctxt) {-        using value_t = typename store_type::value_type;-        using atomic_value_t = typename store_type::atomic_value_type;--        // check context-        if (ctxt.lastNested && ctxt.lastQuery == tuple[I]) {-            base::hint_stats.inserts.addHit();-            return ctxt.lastNested->template insert_internal<I + 1>(tuple, ctxt.nestedCtxt);-        } else {-            base::hint_stats.inserts.addMiss();-        }--        // lookup nested-        atomic_value_t& next = store.getAtomic(tuple[I], ctxt.local);--        // get pure pointer to next level-        value_t nextPtr = next;--        // conduct a lock-free lazy-creation of nested trees-        if (!nextPtr) {-            // create a new sub-tree-            auto newNested = new nested_trie_type();--            // register new sub-tree atomically-            if (next.compare_exchange_weak(nextPtr, newNested)) {-                nextPtr = newNested;  // worked-            } else {-                delete newNested;  // some other thread was faster => use its version-            }-        }--        // make sure a next has been established-        assert(nextPtr);--        // clear context if necessary-        if (nextPtr != ctxt.lastNested) {-            ctxt.lastQuery = tuple[I];-            ctxt.lastNested = nextPtr;-            ctxt.nestedCtxt = typename op_context::nested_ctxt();-        }--        // conduct recursive step-        return nextPtr->template insert_internal<I + 1>(tuple, ctxt.nestedCtxt);-    }--    /**-     * An internal implementation of the contains member function determining-     * whether a given tuple is present within this sub-trie or not.-     *-     * @tparam I the component index associated to this level-     * @tparam Tuple the tuple type to be checked-     * @param tuple the tuple to be checked-     * @param ctxt a operation context to exploit temporal locality-     * @return true if this tuple is present, false otherwise-     */-    template <unsigned I, typename Tuple>-    bool contains_internal(const Tuple& tuple, op_context& ctxt) const {-        // check context-        if (ctxt.lastNested && ctxt.lastQuery == tuple[I]) {-            base::hint_stats.contains.addHit();-            return ctxt.lastNested->template contains_internal<I + 1>(tuple, ctxt.nestedCtxt);-        } else {-            base::hint_stats.contains.addMiss();-        }--        // lookup next step-        auto next = store.lookup(tuple[I], ctxt.local);--        // clear context if necessary-        if (next != ctxt.lastNested) {-            ctxt.lastQuery = tuple[I];-            ctxt.lastNested = next;-            ctxt.nestedCtxt = typename op_context::nested_ctxt();-        }--        // conduct recursive step-        return next && next->template contains_internal<I + 1>(tuple, ctxt.nestedCtxt);-    }-};--/**- * A template specialization for tries representing a set.- * For improved memory efficiency, this level is the leaf-node level- * of all tries exhibiting an arity >= 1. Internally, values are stored utilizing- * sparse bit maps.- */-template <>-class Trie<1u> : public detail::TrieBase<1u, Trie<1u>> {-    template <unsigned Dim>-    friend class Trie;--    template <unsigned Dim, typename Derived>-    friend class detail::TrieBase;--    // a shortcut for the base type-    using base = typename detail::TrieBase<1u, Trie<1u>>;--    // the map type utilized internally-    using map_type = SparseBitMap<>;--    // the internal data store-    map_type map;--public:-    using element_type = entry_type;-    using op_context = typename map_type::op_context;-    using operation_hints = op_context;--    using base::contains;-    using base::insert;--    /**-     * Determines whether this trie is empty or not.-     */-    bool empty() const {-        return map.empty();-    }--    /**-     * Determines the number of elements stored in this trie.-     */-    std::size_t size() const {-        return map.size();-    }--    /**-     * Computes the total memory usage of this data structure.-     */-    std::size_t getMemoryUsage() const {-        // compute the total memory usage-        return sizeof(*this) - sizeof(map_type) + map.getMemoryUsage();-    }--    /**-     * Removes all elements form this trie.-     */-    void clear() {-        map.clear();-    }--    /**-     * Inserts the given tuple into this trie.-     *-     * @param tuple the tuple to be inserted-     * @return true if the tuple has not been present before, false otherwise-     */-    bool insert(const entry_type& tuple) {-        op_context ctxt;-        return insert(tuple, ctxt);-    }--    /**-     * Inserts the given tuple into this trie.-     * An operation context can be provided to exploit temporal locality.-     *-     * @param tuple the tuple to be inserted-     * @param ctxt an operation context for exploiting temporal locality-     * @return true if the tuple has not been present before, false otherwise-     */-    bool insert(const entry_type& tuple, op_context& ctxt) {-        return insert_internal<0>(tuple, ctxt);-    }--    /**-     * Determines whether the given tuple is present in this trie or not.-     *-     * @param tuple the tuple to be tested-     * @return true if present, false otherwise-     */-    bool contains(const entry_type& tuple) const {-        op_context ctxt;-        return contains(tuple, ctxt);-    }--    /**-     * Determines whether the given tuple is present in this trie or not.-     * An operation context can be provided to exploit temporal locality.-     *-     * @param tuple the tuple to be tested-     * @param ctxt an operation context for exploiting temporal locality-     * @return true if present, false otherwise-     */-    bool contains(const entry_type& tuple, op_context& ctxt) const {-        return contains_internal<0>(tuple, ctxt);-    }--    /**-     * Inserts all tuples stored within the given trie into this trie.-     * This operation is considerably more efficient than the consecutive-     * insertion of the elements in other into this trie.-     */-    void insertAll(const Trie& other) {-        map.addAll(other.map);-    }--    // ----------------------------------------------------------------------    //                           Iterator-    // -----------------------------------------------------------------------    /**-     * The iterator core of this level contributing to the construction of-     * a composed trie iterator.-     */-    template <unsigned I = 0>-    class iterator_core {-        // the iterator for this level-        using iter_type = typename map_type::iterator;--        // the referenced bit-map iterator-        iter_type iter;--    public:-        /** default end-iterator constructor */-        iterator_core() = default;--        template <typename Tuple>-        iterator_core(const iter_type& iter, Tuple& entry) : iter(iter) {-            entry[I] = static_cast<RamDomain>(*iter);-        }--        void setIterator(const iter_type& iter) {-            this->iter = iter;-        }--        iter_type& getIterator() {-            return this->iter;-        }--        template <typename Tuple>-        bool inc(Tuple& entry) {-            // increment the iterator on this level-            ++iter;--            // check whether the end has been reached-            if (iter.isEnd()) return false;--            // otherwise update entry value-            entry[I] = *iter;-            return true;-        }--        bool operator==(const iterator_core& other) const {-            return iter == other.iter;-        }--        bool operator!=(const iterator_core& other) const {-            return !(*this == other);-        }--        // enables this iterator core to be printed (for debugging)-        void print(std::ostream& out) const {-            out << iter;-        }--        friend std::ostream& operator<<(std::ostream& out, const iterator_core& iter) {-            iter.print(out);-            return out;-        }-    };--    // the iterator type utilized by this trie type-    using iterator = typename base::template iterator<iterator_core>;--    /**-     * Obtains an iterator referencing the first element stored within this trie-     * or end() if this trie is empty.-     */-    iterator begin() const {-        if (map.empty()) return end();-        return iterator(map.begin());-    }--    /**-     * Obtains an iterator referencing the first position after the last element-     * within this trie.-     */-    iterator end() const {-        return iterator();-    }--    /**-     * Obtains a partition of this tire such that the resulting list of ranges-     * cover disjoint subsets of the elements stored in this trie. Their union-     * is equivalent to the content of this trie.-     */-    std::vector<range<iterator>> partition(unsigned chunks = 500) const {-        std::vector<range<iterator>> res;--        // shortcut for empty trie-        if (this->empty()) return res;--        // use top-level elements for partitioning-        int step = static_cast<int>(std::max(map.size() / chunks, size_t(1)));--        int c = 1;-        auto priv = begin();-        for (auto it = map.begin(); it != map.end(); ++it, c++) {-            if (c % step != 0 || c == 1) {-                continue;-            }-            auto cur = iterator(it);-            res.push_back(make_range(priv, cur));-            priv = cur;-        }-        // add final chunk-        res.push_back(make_range(priv, end()));-        return res;-    }--    /**-     * Obtains a range of elements matching the prefix of the given entry up to-     * levels elements.-     *-     * @tparam levels the length of the requested matching prefix-     * @param entry the entry to be looking for-     * @return the corresponding range of matching elements-     */-    template <unsigned levels>-    range<iterator> getBoundaries(const entry_type& entry) const {-        op_context ctxt;-        return getBoundaries<levels>(entry, ctxt);-    }--    /**-     * Obtains a range of elements matching the prefix of the given entry up to-     * levels elements. A operation context may be provided to exploit temporal-     * locality.-     *-     * @tparam levels the length of the requested matching prefix-     * @param entry the entry to be looking for-     * @param ctxt the operation context to be utilized-     * @return the corresponding range of matching elements-     */-    template <unsigned levels>-    range<iterator> getBoundaries(const entry_type& entry, op_context& ctxt) const {-        // for levels = 0-        if (levels == 0) return make_range(begin(), end());-        // for levels = 1-        auto pos = map.find(entry[0], ctxt);-        if (pos == map.end()) return make_range(end(), end());-        auto next = pos;-        ++next;-        return make_range(iterator(pos), iterator(next));-    }--    iterator lower_bound(const entry_type& entry, op_context&) const {-        return iterator(map.lower_bound(entry[0]));-    }--    iterator lower_bound(const entry_type& entry) const {-        op_context ctxt;-        return lower_bound(entry, ctxt);-    }--    iterator upper_bound(const entry_type& entry, op_context&) const {-        return iterator(map.upper_bound(entry[0]));-    }--    iterator upper_bound(const entry_type& entry) const {-        op_context ctxt;-        return upper_bound(entry, ctxt);-    }--    /**-     * Provides protected access to the internally maintained store.-     */-    const map_type& getStore() const {-        return map;-    }--private:-    /**-     * Creates a core iterator for this trie level and updates component-     * I of the given entry to exhibit the corresponding first value.-     *-     * @tparam I the index of the tuple to be processed by the resulting iterator core-     * @tparam Tuple the type of the tuple to be processed by the resulting iterator core-     * @param entry a reference to the tuple to be updated to the first value-     * @return the requested iterator core instance-     */-    template <unsigned I, typename Tuple>-    iterator_core<I> getBeginCoreIterator(Tuple& entry) const {-        return iterator_core<I>(map.begin(), entry);-    }--    /**-     * The internally utilized implementation of the insert operation inserting-     * a given tuple into this sub-trie.-     *-     * @tparam I the component index associated to this level-     * @tparam Tuple the tuple type to be inserted-     * @param tuple the tuple to be inserted-     * @param ctxt a operation context to exploit temporal locality-     * @return true if this tuple wasn't contained before, false otherwise-     */-    template <unsigned I, typename Tuple>-    bool insert_internal(const Tuple& tuple, op_context& ctxt) {-        return map.set(tuple[I], ctxt);-    }--    /**-     * An internal implementation of the contains member function determining-     * whether a given tuple is present within this sub-trie or not.-     *-     * @tparam I the component index associated to this level-     * @tparam Tuple the tuple type to be checked-     * @param tuple the tuple to be checked-     * @param ctxt a operation context to exploit temporal locality-     * @return true if this tuple is present, false otherwise-     */-    template <unsigned I, typename Tuple>-    bool contains_internal(const Tuple& tuple, op_context& ctxt) const {-        return map.test(tuple[I], ctxt);-    }-};--}  // end namespace souffle+#include "souffle/RamTypes.h"+#include "souffle/utility/CacheUtil.h"+#include "souffle/utility/ContainerUtil.h"+#include "souffle/utility/MiscUtil.h"+#include "souffle/utility/StreamUtil.h"+#include "souffle/utility/span.h"+#include <algorithm>+#include <atomic>+#include <bitset>+#include <cassert>+#include <climits>+#include <cstdint>+#include <cstring>+#include <iostream>+#include <iterator>+#include <limits>+#include <type_traits>+#include <utility>+#include <vector>++// TODO: replace intrinsics w/ std lib functions?+#ifdef _WIN32+/**+ * When compiling for windows, redefine the gcc builtins which are used to+ * their equivalents on the windows platform.+ */+#define __sync_synchronize MemoryBarrier+#define __sync_bool_compare_and_swap(ptr, oldval, newval) \+    (InterlockedCompareExchangePointer((void* volatile*)ptr, (void*)newval, (void*)oldval) == (void*)oldval)+#endif  // _WIN32++namespace souffle {++template <unsigned Dim>+class Trie;++namespace detail::brie {++// FIXME: These data structs should be parameterised/made agnostic to `RamDomain` type.+using brie_element_type = RamDomain;++using tcb::make_span;++template <typename A>+struct forward_non_output_iterator_traits {+    using value_type = A;+    using difference_type = ptrdiff_t;+    using iterator_category = std::forward_iterator_tag;+    using pointer = const value_type*;+    using reference = const value_type&;+};++template <typename A, std::size_t arity>+auto copy(span<A, arity> s) {+    std::array<std::decay_t<A>, arity> cpy;+    std::copy_n(s.begin(), arity, cpy.begin());+    return cpy;+}++template <std::size_t offset, typename A, std::size_t arity>+auto drop(span<A, arity> s) -> std::enable_if_t<offset <= arity, span<A, arity - offset>> {+    return {s.begin() + offset, s.end()};+}++template <typename C>+auto tail(C& s) {+    return drop<1>(make_span(s));+}++/**+ * A templated functor to obtain default values for+ * unspecified elements of sparse array instances.+ */+template <typename T>+struct default_factory {+    T operator()() const {+        return T();  // just use the default constructor+    }+};++/**+ * A functor representing the identity function.+ */+template <typename T>+struct identity {+    T operator()(T v) const {+        return v;+    }+};++/**+ * A operation to be utilized by the sparse map when merging+ * elements associated to different values.+ */+template <typename T>+struct default_merge {+    /**+     * Merges two values a and b when merging spase maps.+     */+    T operator()(T a, T b) const {+        default_factory<T> def;+        // if a is the default => us b, else stick to a+        return (a != def()) ? a : b;+    }+};++/**+ * Iterator type for `souffle::SparseArray`.+ */+template <typename SparseArray>+struct SparseArrayIter {+    using Node = typename SparseArray::Node;+    using index_type = typename SparseArray::index_type;+    using array_value_type = typename SparseArray::value_type;++    using value_type = std::pair<index_type, array_value_type>;++    SparseArrayIter() = default;  // default constructor -- creating an end-iterator+    SparseArrayIter(const SparseArrayIter&) = default;+    SparseArrayIter& operator=(const SparseArrayIter&) = default;++    SparseArrayIter(const Node* node, value_type value) : node(node), value(std::move(value)) {}++    SparseArrayIter(const Node* first, index_type firstOffset) : node(first), value(firstOffset, 0) {+        // if the start is the end => we are done+        if (!first) return;++        // load the value+        if (first->cell[0].value == array_value_type()) {+            ++(*this);  // walk to first element+        } else {+            value.second = first->cell[0].value;+        }+    }++    // the equality operator as required by the iterator concept+    bool operator==(const SparseArrayIter& other) const {+        // only equivalent if pointing to the end+        return (node == nullptr && other.node == nullptr) ||+               (node == other.node && value.first == other.value.first);+    }++    // the not-equality operator as required by the iterator concept+    bool operator!=(const SparseArrayIter& other) const {+        return !(*this == other);+    }++    // the deref operator as required by the iterator concept+    const value_type& operator*() const {+        return value;+    }++    // support for the pointer operator+    const value_type* operator->() const {+        return &value;+    }++    // the increment operator as required by the iterator concept+    SparseArrayIter& operator++() {+        assert(!isEnd());+        // get current offset+        index_type x = value.first & SparseArray::INDEX_MASK;++        // go to next non-empty value in current node+        do {+            x++;+        } while (x < SparseArray::NUM_CELLS && node->cell[x].value == array_value_type());++        // check whether one has been found+        if (x < SparseArray::NUM_CELLS) {+            // update value and be done+            value.first = (value.first & ~SparseArray::INDEX_MASK) | x;+            value.second = node->cell[x].value;+            return *this;  // done+        }++        // go to parent+        node = node->parent;+        int level = 1;++        // get current index on this level+        x = SparseArray::getIndex(brie_element_type(value.first), level);+        x++;++        while (level > 0 && node) {+            // search for next child+            while (x < SparseArray::NUM_CELLS) {+                if (node->cell[x].ptr != nullptr) {+                    break;+                }+                x++;+            }++            // pick next step+            if (x < SparseArray::NUM_CELLS) {+                // going down+                node = node->cell[x].ptr;+                value.first &= SparseArray::getLevelMask(level + 1);+                value.first |= x << (SparseArray::BIT_PER_STEP * level);+                level--;+                x = 0;+            } else {+                // going up+                node = node->parent;+                level++;++                // get current index on this level+                x = SparseArray::getIndex(brie_element_type(value.first), level);+                x++;  // go one step further+            }+        }++        // check whether it is the end of range+        if (node == nullptr) {+            return *this;+        }++        // search the first value in this node+        x = 0;+        while (node->cell[x].value == array_value_type()) {+            x++;+        }++        // update value+        value.first |= x;+        value.second = node->cell[x].value;++        // done+        return *this;+    }++    SparseArrayIter operator++(int) {+        auto cpy = *this;+        ++(*this);+        return cpy;+    }++    // True if this iterator is passed the last element.+    bool isEnd() const {+        return node == nullptr;+    }++    // enables this iterator core to be printed (for debugging)+    void print(std::ostream& out) const {+        // `StreamUtil.h` defines an overload for `pair`, but we can't rely on it b/c+        // it's disabled if `__EMBEDDED__` is defined.+        out << "SparseArrayIter(" << node << " @ (" << value.first << ", " << value.second << "))";+    }++    friend std::ostream& operator<<(std::ostream& out, const SparseArrayIter& iter) {+        iter.print(out);+        return out;+    }++private:+    // a pointer to the leaf node currently processed or null (end)+    const Node* node{};++    // the value currently pointed to+    value_type value;+};++}  // namespace detail::brie++using namespace detail::brie;++/**+ * A sparse array simulates an array associating to every element+ * of uint32_t an element of a generic type T. Any non-defined element+ * will be default-initialized utilizing the detail::brie::default_factory+ * functor.+ *+ * Internally the array is organized as a balanced tree. The leaf+ * level of the tree corresponds to the elements of the represented+ * array. Inner nodes utilize individual bits of the indices to reference+ * sub-trees. For efficiency reasons, only the minimal sub-tree required+ * to cover all non-null / non-default values stored in the array is+ * maintained. Furthermore, several levels of nodes are aggreated in a+ * B-tree like fashion to inprove cache utilization and reduce the number+ * of steps required for lookup and insert operations.+ *+ * @tparam T the type of the stored elements+ * @tparam BITS the number of bits consumed per node-level+ *              e.g. if it is set to 3, the resulting tree will be of a degree of+ *              2^3=8, and thus 8 child-pointers will be stored in each inner node+ *              and as many values will be stored in each leaf node.+ * @tparam merge_op the functor to be utilized when merging the content of two+ *              instances of this type.+ * @tparam copy_op a functor to be applied to each stored value when copying an+ *              instance of this array. For instance, this is utilized by the+ *              trie implementation to create a clone of each sub-tree instead+ *              of preserving the original pointer.+ */+template <typename T, unsigned BITS = 6, typename merge_op = default_merge<T>, typename copy_op = identity<T>>+class SparseArray {+    template <typename A>+    friend struct detail::brie::SparseArrayIter;++    using this_t = SparseArray<T, BITS, merge_op, copy_op>;+    using key_type = uint64_t;++    // some internal constants+    static constexpr int BIT_PER_STEP = BITS;+    static constexpr int NUM_CELLS = 1 << BIT_PER_STEP;+    static constexpr key_type INDEX_MASK = NUM_CELLS - 1;++public:+    // the type utilized for indexing contained elements+    using index_type = key_type;++    // the type of value stored in this array+    using value_type = T;++    // the atomic view on stored values+    using atomic_value_type = std::atomic<value_type>;++private:+    struct Node;++    /**+     * The value stored in a single cell of a inner+     * or leaf node.+     */+    union Cell {+        // an atomic view on the pointer referencing a nested level+        std::atomic<Node*> aptr;++        // a pointer to the nested level (unsynchronized operations)+        Node* ptr{nullptr};++        // an atomic view on the value stored in this cell (leaf node)+        atomic_value_type avalue;++        // the value stored in this cell (unsynchronized access, leaf node)+        value_type value;+    };++    /**+     * The node type of the internally maintained tree.+     */+    struct Node {+        // a pointer to the parent node (for efficient iteration)+        const Node* parent;+        // the pointers to the child nodes (inner nodes) or the stored values (leaf nodes)+        Cell cell[NUM_CELLS];+    };++    /**+     * A struct describing all the information required by the container+     * class to manage the wrapped up tree.+     */+    struct RootInfo {+        // the root node of the tree+        Node* root;+        // the number of levels of the tree+        uint32_t levels;+        // the absolute offset of the theoretical first element in the tree+        index_type offset;++        // the first leaf node in the tree+        Node* first;+        // the absolute offset of the first element in the first leaf node+        index_type firstOffset;+    };++    union {+        RootInfo unsynced;         // for sequential operations+        volatile RootInfo synced;  // for synchronized operations+    };++public:+    /**+     * A default constructor creating an empty sparse array.+     */+    SparseArray() : unsynced(RootInfo{nullptr, 0, 0, nullptr, std::numeric_limits<index_type>::max()}) {}++    /**+     * A copy constructor for sparse arrays. It creates a deep+     * copy of the data structure maintained by the handed in+     * array instance.+     */+    SparseArray(const SparseArray& other)+            : unsynced(RootInfo{clone(other.unsynced.root, other.unsynced.levels), other.unsynced.levels,+                      other.unsynced.offset, nullptr, other.unsynced.firstOffset}) {+        if (unsynced.root) {+            unsynced.root->parent = nullptr;+            unsynced.first = findFirst(unsynced.root, unsynced.levels);+        }+    }++    /**+     * A r-value based copy constructor for sparse arrays. It+     * takes over ownership of the structure maintained by the+     * handed in array.+     */+    SparseArray(SparseArray&& other)+            : unsynced(RootInfo{other.unsynced.root, other.unsynced.levels, other.unsynced.offset,+                      other.unsynced.first, other.unsynced.firstOffset}) {+        other.unsynced.root = nullptr;+        other.unsynced.levels = 0;+        other.unsynced.first = nullptr;+    }++    /**+     * A destructor for sparse arrays clearing up the internally+     * maintained data structure.+     */+    ~SparseArray() {+        clean();+    }++    /**+     * An assignment creating a deep copy of the handed in+     * array structure (utilizing the copy functor provided+     * as a template parameter).+     */+    SparseArray& operator=(const SparseArray& other) {+        if (this == &other) return *this;++        // clean this one+        clean();++        // copy content+        unsynced.levels = other.unsynced.levels;+        unsynced.root = clone(other.unsynced.root, unsynced.levels);+        if (unsynced.root) {+            unsynced.root->parent = nullptr;+        }+        unsynced.offset = other.unsynced.offset;+        unsynced.first = (unsynced.root) ? findFirst(unsynced.root, unsynced.levels) : nullptr;+        unsynced.firstOffset = other.unsynced.firstOffset;++        // done+        return *this;+    }++    /**+     * An assignment operation taking over ownership+     * from a r-value reference to a sparse array.+     */+    SparseArray& operator=(SparseArray&& other) {+        // clean this one+        clean();++        // harvest content+        unsynced.root = other.unsynced.root;+        unsynced.levels = other.unsynced.levels;+        unsynced.offset = other.unsynced.offset;+        unsynced.first = other.unsynced.first;+        unsynced.firstOffset = other.unsynced.firstOffset;++        // reset other+        other.unsynced.root = nullptr;+        other.unsynced.levels = 0;+        other.unsynced.first = nullptr;++        // done+        return *this;+    }++    /**+     * Tests whether this sparse array is empty, thus it only+     * contains default-values, or not.+     */+    bool empty() const {+        return unsynced.root == nullptr;+    }++    /**+     * Computes the number of non-empty elements within this+     * sparse array.+     */+    std::size_t size() const {+        // quick one for the empty map+        if (empty()) return 0;++        // count elements -- since maintaining is making inserts more expensive+        std::size_t res = 0;+        for (auto it = begin(); it != end(); ++it) {+            ++res;+        }+        return res;+    }++private:+    /**+     * Computes the memory usage of the given sub-tree.+     */+    static std::size_t getMemoryUsage(const Node* node, int level) {+        // support null-nodes+        if (!node) return 0;++        // add size of current node+        std::size_t res = sizeof(Node);++        // sum up memory usage of child nodes+        if (level > 0) {+            for (int i = 0; i < NUM_CELLS; i++) {+                res += getMemoryUsage(node->cell[i].ptr, level - 1);+            }+        }++        // done+        return res;+    }++public:+    /**+     * Computes the total memory usage of this data structure.+     */+    std::size_t getMemoryUsage() const {+        // the memory of the wrapper class+        std::size_t res = sizeof(*this);++        // add nodes+        if (unsynced.root) {+            res += getMemoryUsage(unsynced.root, unsynced.levels);+        }++        // done+        return res;+    }++    /**+     * Resets the content of this array to default values for each contained+     * element.+     */+    void clear() {+        clean();+        unsynced.root = nullptr;+        unsynced.levels = 0;+        unsynced.first = nullptr;+        unsynced.firstOffset = std::numeric_limits<index_type>::max();+    }++    /**+     * A struct to be utilized as a local, temporal context by client code+     * to speed up the execution of various operations (optional parameter).+     */+    struct op_context {+        index_type lastIndex{0};+        Node* lastNode{nullptr};+        op_context() = default;+    };++private:+    // ---------------------------------------------------------------------+    //              Optimistic Locking of Root-Level Infos+    // ---------------------------------------------------------------------++    /**+     * A struct to cover a snapshot of the root node state.+     */+    struct RootInfoSnapshot {+        // the current pointer to a root node+        Node* root;+        // the current number of levels+        uint32_t levels;+        // the current offset of the first theoretical element+        index_type offset;+        // a version number for the optimistic locking+        uintptr_t version;+    };++    /**+     * Obtains the current version of the root.+     */+    uint64_t getRootVersion() const {+        // here it is assumed that the load of a 64-bit word is atomic+        return (uint64_t)synced.root;+    }++    /**+     * Obtains a snapshot of the current root information.+     */+    RootInfoSnapshot getRootInfo() const {+        RootInfoSnapshot res{};+        do {+            // first take the mod counter+            do {+                // if res.mod % 2 == 1 .. there is an update in progress+                res.version = getRootVersion();+            } while (res.version % 2);++            // then the rest+            res.root = synced.root;+            res.levels = synced.levels;+            res.offset = synced.offset;++            // check consistency of obtained data (optimistic locking)+        } while (res.version != getRootVersion());++        // got a consistent snapshot+        return res;+    }++    /**+     * Updates the current root information based on the handed in modified+     * snapshot instance if the version number of the snapshot still corresponds+     * to the current version. Otherwise a concurrent update took place and the+     * operation is aborted.+     *+     * @param info the updated information to be assigned to the active root-info data+     * @return true if successfully updated, false if aborted+     */+    bool tryUpdateRootInfo(const RootInfoSnapshot& info) {+        // check mod counter+        uintptr_t version = info.version;++        // update root to invalid pointer (ending with 1)+        if (!__sync_bool_compare_and_swap(&synced.root, (Node*)version, (Node*)(version + 1))) {+            return false;+        }++        // conduct update+        synced.levels = info.levels;+        synced.offset = info.offset;++        // update root (and thus the version to enable future retrievals)+        __sync_synchronize();+        synced.root = info.root;++        // done+        return true;+    }++    /**+     * A struct summarizing the state of the first node reference.+     */+    struct FirstInfoSnapshot {+        // the pointer to the first node+        Node* node;+        // the offset of the first node+        index_type offset;+        // the version number of the first node (for the optimistic locking)+        uintptr_t version;+    };++    /**+     * Obtains the current version number of the first node information.+     */+    uint64_t getFirstVersion() const {+        // here it is assumed that the load of a 64-bit word is atomic+        return (uint64_t)synced.first;+    }++    /**+     * Obtains a snapshot of the current first-node information.+     */+    FirstInfoSnapshot getFirstInfo() const {+        FirstInfoSnapshot res{};+        do {+            // first take the version+            do {+                res.version = getFirstVersion();+            } while (res.version % 2);++            // collect the values+            res.node = synced.first;+            res.offset = synced.firstOffset;++        } while (res.version != getFirstVersion());++        // we got a consistent snapshot+        return res;+    }++    /**+     * Updates the information stored regarding the first node in a+     * concurrent setting utilizing a optimistic locking approach.+     * This is identical to the approach utilized for the root info.+     */+    bool tryUpdateFirstInfo(const FirstInfoSnapshot& info) {+        // check mod counter+        uintptr_t version = info.version;++        // temporary update first pointer to point to uneven value (lock-out)+        if (!__sync_bool_compare_and_swap(&synced.first, (Node*)version, (Node*)(version + 1))) {+            return false;+        }++        // conduct update+        synced.firstOffset = info.offset;++        // update node pointer (and thus the version number)+        __sync_synchronize();+        synced.first = info.node;  // must be last (and atomic)++        // done+        return true;+    }++public:+    /**+     * Obtains a mutable reference to the value addressed by the given index.+     *+     * @param i the index of the element to be addressed+     * @return a mutable reference to the corresponding element+     */+    value_type& get(index_type i) {+        op_context ctxt;+        return get(i, ctxt);+    }++    /**+     * Obtains a mutable reference to the value addressed by the given index.+     *+     * @param i the index of the element to be addressed+     * @param ctxt a operation context to exploit state-less temporal locality+     * @return a mutable reference to the corresponding element+     */+    value_type& get(index_type i, op_context& ctxt) {+        return getLeaf(i, ctxt).value;+    }++    /**+     * Obtains a mutable reference to the atomic value addressed by the given index.+     *+     * @param i the index of the element to be addressed+     * @return a mutable reference to the corresponding element+     */+    atomic_value_type& getAtomic(index_type i) {+        op_context ctxt;+        return getAtomic(i, ctxt);+    }++    /**+     * Obtains a mutable reference to the atomic value addressed by the given index.+     *+     * @param i the index of the element to be addressed+     * @param ctxt a operation context to exploit state-less temporal locality+     * @return a mutable reference to the corresponding element+     */+    atomic_value_type& getAtomic(index_type i, op_context& ctxt) {+        return getLeaf(i, ctxt).avalue;+    }++private:+    /**+     * An internal function capable of navigating to a given leaf node entry.+     * If the cell does not exist yet it will be created as a side-effect.+     *+     * @param i the index of the requested cell+     * @param ctxt a operation context to exploit state-less temporal locality+     * @return a reference to the requested cell+     */+    inline Cell& getLeaf(index_type i, op_context& ctxt) {+        // check context+        if (ctxt.lastNode && (ctxt.lastIndex == (i & ~INDEX_MASK))) {+            // return reference to referenced+            return ctxt.lastNode->cell[i & INDEX_MASK];+        }++        // get snapshot of root+        auto info = getRootInfo();++        // check for emptiness+        if (info.root == nullptr) {+            // build new root node+            info.root = newNode();++            // initialize the new node+            info.root->parent = nullptr;+            info.offset = i & ~(INDEX_MASK);++            // try updating root information atomically+            if (tryUpdateRootInfo(info)) {+                // success -- finish get call++                // update first+                auto firstInfo = getFirstInfo();+                while (info.offset < firstInfo.offset) {+                    firstInfo.node = info.root;+                    firstInfo.offset = info.offset;+                    if (!tryUpdateFirstInfo(firstInfo)) {+                        // there was some concurrent update => check again+                        firstInfo = getFirstInfo();+                    }+                }++                // return reference to proper cell+                return info.root->cell[i & INDEX_MASK];+            }++            // somebody else was faster => use standard insertion procedure+            delete info.root;++            // retrieve new root info+            info = getRootInfo();++            // make sure there is a root+            assert(info.root);+        }++        // for all other inserts+        //   - check boundary+        //   - navigate to node+        //   - insert value++        // check boundaries+        while (!inBoundaries(i, info.levels, info.offset)) {+            // boundaries need to be expanded by growing upwards+            raiseLevel(info);  // try raising level unless someone else did already+            // update root info+            info = getRootInfo();+        }++        // navigate to node+        Node* node = info.root;+        unsigned level = info.levels;+        while (level != 0) {+            // get X coordinate+            auto x = getIndex(brie_element_type(i), level);++            // decrease level counter+            --level;++            // check next node+            std::atomic<Node*>& aNext = node->cell[x].aptr;+            Node* next = aNext;+            if (!next) {+                // create new sub-tree+                Node* newNext = newNode();+                newNext->parent = node;++                // try to update next+                if (!aNext.compare_exchange_strong(next, newNext)) {+                    // some other thread was faster => use updated next+                    delete newNext;+                } else {+                    // the locally created next is the new next+                    next = newNext;++                    // update first+                    if (level == 0) {+                        // compute offset of this node+                        auto off = i & ~INDEX_MASK;++                        // fast over-approximation of whether a update is necessary+                        if (off < unsynced.firstOffset) {+                            // update first reference if this one is the smallest+                            auto first_info = getFirstInfo();+                            while (off < first_info.offset) {+                                first_info.node = next;+                                first_info.offset = off;+                                if (!tryUpdateFirstInfo(first_info)) {+                                    // there was some concurrent update => check again+                                    first_info = getFirstInfo();+                                }+                            }+                        }+                    }+                }++                // now next should be defined+                assert(next);+            }++            // continue one level below+            node = next;+        }++        // update context+        ctxt.lastIndex = (i & ~INDEX_MASK);+        ctxt.lastNode = node;++        // return reference to cell+        return node->cell[i & INDEX_MASK];+    }++public:+    /**+     * Updates the value stored in cell i by the given value.+     */+    void update(index_type i, const value_type& val) {+        op_context ctxt;+        update(i, val, ctxt);+    }++    /**+     * Updates the value stored in cell i by the given value. A operation+     * context can be provided for exploiting temporal locality.+     */+    void update(index_type i, const value_type& val, op_context& ctxt) {+        get(i, ctxt) = val;+    }++    /**+     * Obtains the value associated to index i -- which might be+     * the default value of the covered type if the value hasn't been+     * defined previously.+     */+    value_type operator[](index_type i) const {+        return lookup(i);+    }++    /**+     * Obtains the value associated to index i -- which might be+     * the default value of the covered type if the value hasn't been+     * defined previously.+     */+    value_type lookup(index_type i) const {+        op_context ctxt;+        return lookup(i, ctxt);+    }++    /**+     * Obtains the value associated to index i -- which might be+     * the default value of the covered type if the value hasn't been+     * defined previously. A operation context can be provided for+     * exploiting temporal locality.+     */+    value_type lookup(index_type i, op_context& ctxt) const {+        // check whether it is empty+        if (!unsynced.root) return default_factory<value_type>()();++        // check boundaries+        if (!inBoundaries(i)) return default_factory<value_type>()();++        // check context+        if (ctxt.lastNode && ctxt.lastIndex == (i & ~INDEX_MASK)) {+            return ctxt.lastNode->cell[i & INDEX_MASK].value;+        }++        // navigate to value+        Node* node = unsynced.root;+        unsigned level = unsynced.levels;+        while (level != 0) {+            // get X coordinate+            auto x = getIndex(brie_element_type(i), level);++            // decrease level counter+            --level;++            // check next node+            Node* next = node->cell[x].ptr;++            // check next step+            if (!next) return default_factory<value_type>()();++            // continue one level below+            node = next;+        }++        // remember context+        ctxt.lastIndex = (i & ~INDEX_MASK);+        ctxt.lastNode = node;++        // return reference to cell+        return node->cell[i & INDEX_MASK].value;+    }++private:+    /**+     * A static operation utilized internally for merging sub-trees recursively.+     *+     * @param parent the parent node of the current merge operation+     * @param trg a reference to the pointer the cloned node should be stored to+     * @param src the node to be cloned+     * @param levels the height of the cloned node+     */+    static void merge(const Node* parent, Node*& trg, const Node* src, int levels) {+        // if other side is null => done+        if (src == nullptr) {+            return;+        }++        // if the trg sub-tree is empty, clone the corresponding branch+        if (trg == nullptr) {+            trg = clone(src, levels);+            if (trg != nullptr) {+                trg->parent = parent;+            }+            return;  // done+        }++        // otherwise merge recursively++        // the leaf-node step+        if (levels == 0) {+            merge_op merg;+            for (int i = 0; i < NUM_CELLS; ++i) {+                trg->cell[i].value = merg(trg->cell[i].value, src->cell[i].value);+            }+            return;+        }++        // the recursive step+        for (int i = 0; i < NUM_CELLS; ++i) {+            merge(trg, trg->cell[i].ptr, src->cell[i].ptr, levels - 1);+        }+    }++public:+    /**+     * Adds all the values stored in the given array to this array.+     */+    void addAll(const SparseArray& other) {+        // skip if other is empty+        if (other.empty()) {+            return;+        }++        // special case: emptiness+        if (empty()) {+            // use assignment operator+            *this = other;+            return;+        }++        // adjust levels+        while (unsynced.levels < other.unsynced.levels || !inBoundaries(other.unsynced.offset)) {+            raiseLevel();+        }++        // navigate to root node equivalent of the other node in this tree+        auto level = unsynced.levels;+        Node** node = &unsynced.root;+        while (level > other.unsynced.levels) {+            // get X coordinate+            auto x = getIndex(brie_element_type(other.unsynced.offset), level);++            // decrease level counter+            --level;++            // check next node+            Node*& next = (*node)->cell[x].ptr;+            if (!next) {+                // create new sub-tree+                next = newNode();+                next->parent = *node;+            }++            // continue one level below+            node = &next;+        }++        // merge sub-branches from here+        merge((*node)->parent, *node, other.unsynced.root, level);++        // update first+        if (unsynced.firstOffset > other.unsynced.firstOffset) {+            unsynced.first = findFirst(*node, level);+            unsynced.firstOffset = other.unsynced.firstOffset;+        }+    }++    // ---------------------------------------------------------------------+    //                           Iterator+    // ---------------------------------------------------------------------++    using iterator = SparseArrayIter<this_t>;++    /**+     * Obtains an iterator referencing the first non-default element or end in+     * case there are no such elements.+     */+    iterator begin() const {+        return iterator(unsynced.first, unsynced.firstOffset);+    }++    /**+     * An iterator referencing the position after the last non-default element.+     */+    iterator end() const {+        return iterator();+    }++    /**+     * An operation to obtain an iterator referencing an element addressed by the+     * given index. If the corresponding element is a non-default value, a corresponding+     * iterator will be returned. Otherwise end() will be returned.+     */+    iterator find(index_type i) const {+        op_context ctxt;+        return find(i, ctxt);+    }++    /**+     * An operation to obtain an iterator referencing an element addressed by the+     * given index. If the corresponding element is a non-default value, a corresponding+     * iterator will be returned. Otherwise end() will be returned. A operation context+     * can be provided for exploiting temporal locality.+     */+    iterator find(index_type i, op_context& ctxt) const {+        // check whether it is empty+        if (!unsynced.root) return end();++        // check boundaries+        if (!inBoundaries(i)) return end();++        // check context+        if (ctxt.lastNode && ctxt.lastIndex == (i & ~INDEX_MASK)) {+            Node* node = ctxt.lastNode;++            // check whether there is a proper entry+            value_type value = node->cell[i & INDEX_MASK].value;+            if (value == value_type{}) {+                return end();+            }+            // return iterator pointing to value+            return iterator(node, std::make_pair(i, value));+        }++        // navigate to value+        Node* node = unsynced.root;+        unsigned level = unsynced.levels;+        while (level != 0) {+            // get X coordinate+            auto x = getIndex(i, level);++            // decrease level counter+            --level;++            // check next node+            Node* next = node->cell[x].ptr;++            // check next step+            if (!next) return end();++            // continue one level below+            node = next;+        }++        // register in context+        ctxt.lastNode = node;+        ctxt.lastIndex = (i & ~INDEX_MASK);++        // check whether there is a proper entry+        value_type value = node->cell[i & INDEX_MASK].value;+        if (value == value_type{}) {+            return end();+        }++        // return iterator pointing to cell+        return iterator(node, std::make_pair(i, value));+    }++    /**+     * An operation obtaining the smallest non-default element such that it's index is >=+     * the given index.+     */+    iterator lowerBound(index_type i) const {+        op_context ctxt;+        return lowerBound(i, ctxt);+    }++    /**+     * An operation obtaining the smallest non-default element such that it's index is >=+     * the given index. A operation context can be provided for exploiting temporal locality.+     */+    iterator lowerBound(index_type i, op_context&) const {+        // check whether it is empty+        if (!unsynced.root) return end();++        // check boundaries+        if (!inBoundaries(i)) {+            // if it is on the lower end, return minimum result+            if (i < unsynced.offset) {+                const auto& value = unsynced.first->cell[0].value;+                auto res = iterator(unsynced.first, std::make_pair(unsynced.offset, value));+                if (value == value_type()) {+                    ++res;+                }+                return res;+            }+            // otherwise it is on the high end, return end iterator+            return end();+        }++        // navigate to value+        Node* node = unsynced.root;+        unsigned level = unsynced.levels;+        while (true) {+            // get X coordinate+            auto x = getIndex(brie_element_type(i), level);++            // check next node+            Node* next = node->cell[x].ptr;++            // check next step+            if (!next) {+                if (x == NUM_CELLS - 1) {+                    ++level;+                    node = const_cast<Node*>(node->parent);+                    if (!node) return end();+                }++                // continue search+                i = i & getLevelMask(level);++                // find next higher value+                i += 1ull << (BITS * level);++            } else {+                if (level == 0) {+                    // found boundary+                    return iterator(node, std::make_pair(i, node->cell[x].value));+                }++                // decrease level counter+                --level;++                // continue one level below+                node = next;+            }+        }+    }++    /**+     * An operation obtaining the smallest non-default element such that it's index is greater+     * the given index.+     */+    iterator upperBound(index_type i) const {+        op_context ctxt;+        return upperBound(i, ctxt);+    }++    /**+     * An operation obtaining the smallest non-default element such that it's index is greater+     * the given index. A operation context can be provided for exploiting temporal locality.+     */+    iterator upperBound(index_type i, op_context& ctxt) const {+        if (i == std::numeric_limits<index_type>::max()) {+            return end();+        }+        return lowerBound(i + 1, ctxt);+    }++private:+    /**+     * An internal debug utility printing the internal structure of this sparse array to the given output+     * stream.+     */+    void dump(bool detailed, std::ostream& out, const Node& node, int level, index_type offset,+            int indent = 0) const {+        auto x = getIndex(offset, level + 1);+        out << times("\t", indent) << x << ": Node " << &node << " on level " << level+            << " parent: " << node.parent << " -- range: " << offset << " - "+            << (offset + ~getLevelMask(level + 1)) << "\n";++        if (level == 0) {+            for (int i = 0; i < NUM_CELLS; i++) {+                if (detailed || node.cell[i].value != value_type()) {+                    out << times("\t", indent + 1) << i << ": [" << (offset + i) << "] " << node.cell[i].value+                        << "\n";+                }+            }+        } else {+            for (int i = 0; i < NUM_CELLS; i++) {+                if (node.cell[i].ptr) {+                    dump(detailed, out, *node.cell[i].ptr, level - 1,+                            offset + (i * (index_type(1) << (level * BIT_PER_STEP))), indent + 1);+                } else if (detailed) {+                    auto low = offset + (i * (1 << (level * BIT_PER_STEP)));+                    auto hig = low + ~getLevelMask(level);+                    out << times("\t", indent + 1) << i << ": empty range " << low << " - " << hig << "\n";+                }+            }+        }+        out << "\n";+    }++public:+    /**+     * A debug utility printing the internal structure of this sparse array to the given output stream.+     */+    void dump(bool detail = false, std::ostream& out = std::cout) const {+        if (!unsynced.root) {+            out << " - empty - \n";+            return;+        }+        out << "root:  " << unsynced.root << "\n";+        out << "offset: " << unsynced.offset << "\n";+        out << "first: " << unsynced.first << "\n";+        out << "fist offset: " << unsynced.firstOffset << "\n";+        dump(detail, out, *unsynced.root, unsynced.levels, unsynced.offset);+    }++private:+    // --------------------------------------------------------------------------+    //                                 Utilities+    // --------------------------------------------------------------------------++    /**+     * Creates new nodes and initializes them with 0.+     */+    static Node* newNode() {+        return new Node();+    }++    /**+     * Destroys a node and all its sub-nodes recursively.+     */+    static void freeNodes(Node* node, int level) {+        if (!node) return;+        if (level != 0) {+            for (int i = 0; i < NUM_CELLS; i++) {+                freeNodes(node->cell[i].ptr, level - 1);+            }+        }+        delete node;+    }++    /**+     * Conducts a cleanup of the internal tree structure.+     */+    void clean() {+        freeNodes(unsynced.root, unsynced.levels);+        unsynced.root = nullptr;+        unsynced.levels = 0;+    }++    /**+     * Clones the given node and all its sub-nodes.+     */+    static Node* clone(const Node* node, int level) {+        // support null-pointers+        if (node == nullptr) {+            return nullptr;+        }++        // create a clone+        auto* res = new Node();++        // handle leaf level+        if (level == 0) {+            copy_op copy;+            for (int i = 0; i < NUM_CELLS; i++) {+                res->cell[i].value = copy(node->cell[i].value);+            }+            return res;+        }++        // for inner nodes clone each child+        for (int i = 0; i < NUM_CELLS; i++) {+            auto cur = clone(node->cell[i].ptr, level - 1);+            if (cur != nullptr) {+                cur->parent = res;+            }+            res->cell[i].ptr = cur;+        }++        // done+        return res;+    }++    /**+     * Obtains the left-most leaf-node of the tree rooted by the given node+     * with the given level.+     */+    static Node* findFirst(Node* node, int level) {+        while (level > 0) {+            bool found = false;+            for (int i = 0; i < NUM_CELLS; i++) {+                Node* cur = node->cell[i].ptr;+                if (cur) {+                    node = cur;+                    --level;+                    found = true;+                    break;+                }+            }+            assert(found && "No first node!");+        }++        return node;+    }++    /**+     * Raises the level of this tree by one level. It does so by introducing+     * a new root node and inserting the current root node as a child node.+     */+    void raiseLevel() {+        // something went wrong when we pass that line+        assert(unsynced.levels < (sizeof(index_type) * 8 / BITS) + 1);++        // create new root+        Node* node = newNode();+        node->parent = nullptr;++        // insert existing root as child+        auto x = getIndex(brie_element_type(unsynced.offset), unsynced.levels + 1);+        node->cell[x].ptr = unsynced.root;++        // swap the root+        unsynced.root->parent = node;++        // update root+        unsynced.root = node;+        ++unsynced.levels;++        // update offset be removing additional bits+        unsynced.offset &= getLevelMask(unsynced.levels + 1);+    }++    /**+     * Attempts to raise the height of this tree based on the given root node+     * information and updates the root-info snapshot correspondingly.+     */+    void raiseLevel(RootInfoSnapshot& info) {+        // something went wrong when we pass that line+        assert(info.levels < (sizeof(index_type) * 8 / BITS) + 1);++        // create new root+        Node* newRoot = newNode();+        newRoot->parent = nullptr;++        // insert existing root as child+        auto x = getIndex(brie_element_type(info.offset), info.levels + 1);+        newRoot->cell[x].ptr = info.root;++        // exchange the root in the info struct+        auto oldRoot = info.root;+        info.root = newRoot;++        // update level counter+        ++info.levels;++        // update offset+        info.offset &= getLevelMask(info.levels + 1);++        // try exchanging root info+        if (tryUpdateRootInfo(info)) {+            // success => final step, update parent of old root+            oldRoot->parent = info.root;+        } else {+            // throw away temporary new node+            delete newRoot;+        }+    }++    /**+     * Tests whether the given index is covered by the boundaries defined+     * by the hight and offset of the internally maintained tree.+     */+    bool inBoundaries(index_type a) const {+        return inBoundaries(a, unsynced.levels, unsynced.offset);+    }++    /**+     * Tests whether the given index is within the boundaries defined by the+     * given tree hight and offset.+     */+    static bool inBoundaries(index_type a, uint32_t levels, index_type offset) {+        auto mask = getLevelMask(levels + 1);+        return (a & mask) == offset;+    }++    /**+     * Obtains the index within the arrays of cells of a given index on a given+     * level of the internally maintained tree.+     */+    static index_type getIndex(brie_element_type a, unsigned level) {+        return (a & (INDEX_MASK << (level * BIT_PER_STEP))) >> (level * BIT_PER_STEP);+    }++    /**+     * Computes the bit-mask to be applicable to obtain the offset of a node on a+     * given tree level.+     */+    static index_type getLevelMask(unsigned level) {+        if (level > (sizeof(index_type) * 8 / BITS)) return 0;+        return (~(index_type(0)) << (level * BIT_PER_STEP));+    }+};++namespace detail::brie {++/**+ * Iterator type for `souffle::SparseArray`. It enumerates the indices set to 1.+ */+template <typename SparseBitMap>+class SparseBitMapIter {+    using value_t = typename SparseBitMap::value_t;+    using value_type = typename SparseBitMap::index_type;+    using data_store_t = typename SparseBitMap::data_store_t;+    using nested_iterator = typename data_store_t::iterator;++    // the iterator through the underlying sparse data structure+    nested_iterator iter;++    // the currently consumed mask+    uint64_t mask = 0;++    // the value currently pointed to+    value_type value{};++public:+    SparseBitMapIter() = default;  // default constructor -- creating an end-iterator+    SparseBitMapIter(const SparseBitMapIter&) = default;+    SparseBitMapIter& operator=(const SparseBitMapIter&) = default;++    SparseBitMapIter(const nested_iterator& iter)+            : iter(iter), mask(SparseBitMap::toMask(iter->second)),+              value(iter->first << SparseBitMap::LEAF_INDEX_WIDTH) {+        moveToNextInMask();+    }++    SparseBitMapIter(const nested_iterator& iter, uint64_t m, value_type value)+            : iter(iter), mask(m), value(value) {}++    // the equality operator as required by the iterator concept+    bool operator==(const SparseBitMapIter& other) const {+        // only equivalent if pointing to the end+        return iter == other.iter && mask == other.mask;+    }++    // the not-equality operator as required by the iterator concept+    bool operator!=(const SparseBitMapIter& other) const {+        return !(*this == other);+    }++    // the deref operator as required by the iterator concept+    const value_type& operator*() const {+        return value;+    }++    // support for the pointer operator+    const value_type* operator->() const {+        return &value;+    }++    // the increment operator as required by the iterator concept+    SparseBitMapIter& operator++() {+        // progress in current mask+        if (moveToNextInMask()) return *this;++        // go to next entry+        ++iter;++        // update value+        if (!iter.isEnd()) {+            value = iter->first << SparseBitMap::LEAF_INDEX_WIDTH;+            mask = SparseBitMap::toMask(iter->second);+            moveToNextInMask();+        }++        // done+        return *this;+    }++    SparseBitMapIter operator++(int) {+        auto cpy = *this;+        ++(*this);+        return cpy;+    }++    bool isEnd() const {+        return iter.isEnd();+    }++    void print(std::ostream& out) const {+        out << "SparseBitMapIter(" << iter << " -> " << std::bitset<64>(mask) << " @ " << value << ")";+    }++    // enables this iterator core to be printed (for debugging)+    friend std::ostream& operator<<(std::ostream& out, const SparseBitMapIter& iter) {+        iter.print(out);+        return out;+    }++private:+    bool moveToNextInMask() {+        // check if there is something left+        if (mask == 0) return false;++        // get position of leading 1+        auto pos = __builtin_ctzll(mask);++        // consume this bit+        mask &= ~(1llu << pos);++        // update value+        value &= ~SparseBitMap::LEAF_INDEX_MASK;+        value |= pos;++        // done+        return true;+    }+};++}  // namespace detail::brie++/**+ * A sparse bit-map is a bit map virtually assigning a bit value to every value if the+ * uint32_t domain. However, only 1-bits are stored utilizing a nested sparse array+ * structure.+ *+ * @tparam BITS similar to the BITS parameter of the sparse array type+ */+template <unsigned BITS = 4>+class SparseBitMap {+    template <typename A>+    friend class detail::brie::SparseBitMapIter;++    using this_t = SparseBitMap<BITS>;++    // the element type stored in the nested sparse array+    using value_t = uint64_t;++    // define the bit-level merge operation+    struct merge_op {+        value_t operator()(value_t a, value_t b) const {+            return a | b;  // merging bit masks => bitwise or operation+        }+    };++    // the type of the internal data store+    using data_store_t = SparseArray<value_t, BITS, merge_op>;+    using atomic_value_t = typename data_store_t::atomic_value_type;++    // some constants for manipulating stored values+    static constexpr std::size_t BITS_PER_ENTRY = sizeof(value_t) * CHAR_BIT;+    static constexpr std::size_t LEAF_INDEX_WIDTH = __builtin_ctz(BITS_PER_ENTRY);+    static constexpr uint64_t LEAF_INDEX_MASK = BITS_PER_ENTRY - 1;++    static uint64_t toMask(const value_t& value) {+        static_assert(sizeof(value_t) == sizeof(uint64_t), "Fixed for 64-bit compiler.");+        return reinterpret_cast<const uint64_t&>(value);+    }++public:+    // the type to address individual entries+    using index_type = typename data_store_t::index_type;++private:+    // it utilizes a sparse map to store its data+    data_store_t store;++public:+    // a simple default constructor+    SparseBitMap() = default;++    // a default copy constructor+    SparseBitMap(const SparseBitMap&) = default;++    // a default r-value copy constructor+    SparseBitMap(SparseBitMap&&) = default;++    // a default assignment operator+    SparseBitMap& operator=(const SparseBitMap&) = default;++    // a default r-value assignment operator+    SparseBitMap& operator=(SparseBitMap&&) = default;++    // checks whether this bit-map is empty -- thus it does not have any 1-entries+    bool empty() const {+        return store.empty();+    }++    // the type utilized for recording context information for exploiting temporal locality+    using op_context = typename data_store_t::op_context;++    /**+     * Sets the bit addressed by i to 1.+     */+    bool set(index_type i) {+        op_context ctxt;+        return set(i, ctxt);+    }++    /**+     * Sets the bit addressed by i to 1. A context for exploiting temporal locality+     * can be provided.+     */+    bool set(index_type i, op_context& ctxt) {+        atomic_value_t& val = store.getAtomic(i >> LEAF_INDEX_WIDTH, ctxt);+        value_t bit = (1ull << (i & LEAF_INDEX_MASK));++#ifdef __GNUC__+#if __GNUC__ >= 7+        // In GCC >= 7 the usage of fetch_or causes a bug that needs further investigation+        // For now, this two-instruction based implementation provides a fix that does+        // not sacrifice too much performance.++        while (true) {+            auto order = std::memory_order::memory_order_relaxed;++            // load current value+            value_t old = val.load(order);++            // if bit is already set => we are done+            if (old & bit) return false;++            // set the bit, if failed, repeat+            if (!val.compare_exchange_strong(old, old | bit, order, order)) continue;++            // it worked, new bit added+            return true;+        }++#endif+#endif++        value_t old = val.fetch_or(bit, std::memory_order::memory_order_relaxed);+        return (old & bit) == 0u;+    }++    /**+     * Determines the whether the bit addressed by i is set or not.+     */+    bool test(index_type i) const {+        op_context ctxt;+        return test(i, ctxt);+    }++    /**+     * Determines the whether the bit addressed by i is set or not. A context for+     * exploiting temporal locality can be provided.+     */+    bool test(index_type i, op_context& ctxt) const {+        value_t bit = (1ull << (i & LEAF_INDEX_MASK));+        return store.lookup(i >> LEAF_INDEX_WIDTH, ctxt) & bit;+    }++    /**+     * Determines the whether the bit addressed by i is set or not.+     */+    bool operator[](index_type i) const {+        return test(i);+    }++    /**+     * Resets all contained bits to 0.+     */+    void clear() {+        store.clear();+    }++    /**+     * Determines the number of bits set.+     */+    std::size_t size() const {+        // this is computed on demand to keep the set operation simple.+        std::size_t res = 0;+        for (const auto& cur : store) {+            res += __builtin_popcountll(cur.second);+        }+        return res;+    }++    /**+     * Computes the total memory usage of this data structure.+     */+    std::size_t getMemoryUsage() const {+        // compute the total memory usage+        return sizeof(*this) - sizeof(data_store_t) + store.getMemoryUsage();+    }++    /**+     * Sets all bits set in other to 1 within this bit map.+     */+    void addAll(const SparseBitMap& other) {+        // nothing to do if it is a self-assignment+        if (this == &other) return;++        // merge the sparse store+        store.addAll(other.store);+    }++    // ---------------------------------------------------------------------+    //                           Iterator+    // ---------------------------------------------------------------------++    using iterator = SparseBitMapIter<this_t>;++    /**+     * Obtains an iterator pointing to the first index set to 1. If there+     * is no such bit, end() will be returned.+     */+    iterator begin() const {+        auto it = store.begin();+        if (it.isEnd()) return end();+        return iterator(it);+    }++    /**+     * Returns an iterator referencing the position after the last set bit.+     */+    iterator end() const {+        return iterator();+    }++    /**+     * Obtains an iterator referencing the position i if the corresponding+     * bit is set, end() otherwise.+     */+    iterator find(index_type i) const {+        op_context ctxt;+        return find(i, ctxt);+    }++    /**+     * Obtains an iterator referencing the position i if the corresponding+     * bit is set, end() otherwise. An operation context can be provided+     * to exploit temporal locality.+     */+    iterator find(index_type i, op_context& ctxt) const {+        // check prefix part+        auto it = store.find(i >> LEAF_INDEX_WIDTH, ctxt);+        if (it.isEnd()) return end();++        // check bit-set part+        uint64_t mask = toMask(it->second);+        if (!(mask & (1llu << (i & LEAF_INDEX_MASK)))) return end();++        // OK, it is there => create iterator+        mask &= ((1ull << (i & LEAF_INDEX_MASK)) - 1);  // remove all bits before pos i+        return iterator(it, mask, i);+    }++    /**+     * Locates an iterator to the first element in this sparse bit map not less+     * than the given index.+     */+    iterator lower_bound(index_type i) const {+        auto it = store.lowerBound(i >> LEAF_INDEX_WIDTH);+        if (it.isEnd()) return end();++        // check bit-set part+        uint64_t mask = toMask(it->second);++        // if there is no bit remaining in this mask, check next mask.+        if (!(mask & ((~uint64_t(0)) << (i & LEAF_INDEX_MASK)))) {+            index_type next = ((i >> LEAF_INDEX_WIDTH) + 1) << LEAF_INDEX_WIDTH;+            if (next < i) return end();+            return lower_bound(next);+        }++        // there are bits left, use least significant bit of those+        if (it->first == i >> LEAF_INDEX_WIDTH) {+            mask &= ((~uint64_t(0)) << (i & LEAF_INDEX_MASK));  // remove all bits before pos i+        }++        // compute value represented by least significant bit+        index_type pos = __builtin_ctzll(mask);++        // remove this bit as well+        mask = mask & ~(1ull << pos);++        // construct value of this located bit+        index_type val = (it->first << LEAF_INDEX_WIDTH) | pos;+        return iterator(it, mask, val);+    }++    /**+     * Locates an iterator to the first element in this sparse bit map than is greater+     * than the given index.+     */+    iterator upper_bound(index_type i) const {+        if (i == std::numeric_limits<index_type>::max()) {+            return end();+        }+        return lower_bound(i + 1);+    }++    /**+     * A debugging utility printing the internal structure of this map to the+     * given output stream.+     */+    void dump(bool detail = false, std::ostream& out = std::cout) const {+        store.dump(detail, out);+    }++    /**+     * Provides write-protected access to the internal store for running+     * analysis on the data structure.+     */+    const data_store_t& getStore() const {+        return store;+    }+};++// ---------------------------------------------------------------------+//                              TRIE+// ---------------------------------------------------------------------++namespace detail::brie {++/**+ * An iterator over the stored entries.+ *+ * Iterators for tries consist of a top-level iterator maintaining the+ * master copy of a materialized tuple and a recursively nested iterator+ * core -- one for each nested trie level.+ */+template <typename Value, typename IterCore>+class TrieIterator {+    template <unsigned Len, unsigned Pos, unsigned Dimensions>+    friend struct fix_binding;++    template <unsigned Dimensions>+    friend struct fix_lower_bound;++    template <unsigned Dimensions>+    friend struct fix_upper_bound;++    template <unsigned Pos, unsigned Dimensions>+    friend struct fix_first;++    template <unsigned Dimensions>+    friend struct fix_first_nested;++    template <typename A, typename B>+    friend class TrieIterator;++    // remove ref-qual (if any); this can happen if we're a iterator-view+    using iter_core_arg_type = typename std::remove_reference_t<IterCore>::store_iter;++    Value value;         // the value currently pointed to+    IterCore iter_core;  // the wrapped iterator++    // return an ephemeral nested iterator-view (view -> mutating us mutates our parent)+    // NB: be careful that the lifetime of this iterator-view doesn't exceed that of its parent.+    auto getNestedView() {+        auto& nested_iter_ref = iter_core.getNested();  // by ref (this is critical, we're a view, not a copy)+        auto nested_val = tail(value);+        return TrieIterator<decltype(nested_val), decltype(nested_iter_ref)>(+                std::move(nested_val), nested_iter_ref);+    }++    // special constructor for iterator-views (see `getNestedView`)+    explicit TrieIterator(Value value, IterCore iter_core) : value(std::move(value)), iter_core(iter_core) {}++public:+    TrieIterator() = default;  // default constructor -- creating an end-iterator+    TrieIterator(const TrieIterator&) = default;+    TrieIterator(TrieIterator&&) = default;+    TrieIterator& operator=(const TrieIterator&) = default;+    TrieIterator& operator=(TrieIterator&&) = default;++    explicit TrieIterator(iter_core_arg_type param) : iter_core(std::move(param), value) {}++    // the equality operator as required by the iterator concept+    bool operator==(const TrieIterator& other) const {+        // equivalent if pointing to the same value+        return iter_core == other.iter_core;+    }++    // the not-equality operator as required by the iterator concept+    bool operator!=(const TrieIterator& other) const {+        return !(*this == other);+    }++    const Value& operator*() const {+        return value;+    }++    const Value* operator->() const {+        return &value;+    }++    TrieIterator& operator++() {+        iter_core.inc(value);+        return *this;+    }++    TrieIterator operator++(int) {+        auto cpy = *this;+        ++(*this);+        return cpy;+    }++    // enables this iterator to be printed (for debugging)+    void print(std::ostream& out) const {+        out << "iter(" << iter_core << " -> " << value << ")";+    }++    friend std::ostream& operator<<(std::ostream& out, const TrieIterator& iter) {+        iter.print(out);+        return out;+    }+};++template <unsigned Dim>+struct TrieTypes;++/**+ * A base class for the Trie implementation allowing various+ * specializations of the Trie template to inherit common functionality.+ *+ * @tparam Dim the number of dimensions / arity of the stored tuples+ * @tparam Derived the type derived from this base class+ */+template <unsigned Dim, typename Derived>+class TrieBase {+    Derived& impl() {+        return static_cast<Derived&>(*this);+    }++    const Derived& impl() const {+        return static_cast<const Derived&>(*this);+    }++protected:+    using types = TrieTypes<Dim>;+    using store_type = typename types::store_type;++    store_type store;++public:+    using const_entry_span_type = typename types::const_entry_span_type;+    using entry_span_type = typename types::entry_span_type;+    using entry_type = typename types::entry_type;+    using iterator = typename types::iterator;+    using iterator_core = typename types::iterator_core;+    using op_context = typename types::op_context;++    /**+     * Inserts all tuples stored within the given trie into this trie.+     * This operation is considerably more efficient than the consecutive+     * insertion of the elements in other into this trie.+     *+     * @param other the elements to be inserted into this trie+     */+    void insertAll(const TrieBase& other) {+        store.addAll(other.store);+    }++    /**+     * Provides protected access to the internally maintained store.+     */+    const store_type& getStore() const {+        return store;+    }++    /**+     * Determines whether this trie is empty or not.+     */+    bool empty() const {+        return store.empty();+    }++    /**+     * Obtains an iterator referencing the first element stored within this trie.+     */+    iterator begin() const {+        return empty() ? end() : iterator(store.begin());+    }++    /**+     * Obtains an iterator referencing the position after the last element stored+     * within this trie.+     */+    iterator end() const {+        return iterator();+    }++    iterator find(const_entry_span_type entry, op_context& ctxt) const {+        auto range = impl().template getBoundaries<Dim>(entry, ctxt);+        return range.empty() ? range.end() : range.begin();+    }++    // implemented by `Derived`:+    //      bool insert(const entry_type& tuple, op_context& ctxt);+    //      bool contains(const_entry_span_type tuple, op_context& ctxt) const;+    //      bool lower_bound(const_entry_span_type tuple, op_context& ctxt) const;+    //      bool upper_bound(const_entry_span_type tuple, op_context& ctxt) const;+    //      template <unsigned levels>+    //      range<iterator> getBoundaries(const_entry_span_type, op_context&) const;++    // -- operation wrappers --++    template <unsigned levels>+    range<iterator> getBoundaries(const_entry_span_type entry) const {+        op_context ctxt;+        return impl().template getBoundaries<levels>(entry, ctxt);+    }++    template <unsigned levels>+    range<iterator> getBoundaries(const entry_type& entry, op_context& ctxt) const {+        return impl().template getBoundaries<levels>(const_entry_span_type(entry), ctxt);+    }++    template <unsigned levels>+    range<iterator> getBoundaries(const entry_type& entry) const {+        return impl().template getBoundaries<levels>(const_entry_span_type(entry));+    }++    template <unsigned levels, typename... Values, typename = std::enable_if_t<(isRamType<Values> && ...)>>+    range<iterator> getBoundaries(Values... values) const {+        return impl().template getBoundaries<levels>(entry_type{ramBitCast(values)...});+    }++// declare a initialiser-list compatible overload for a given function+#define BRIE_OVERLOAD_INIT_LIST(fn, constness)                     \+    auto fn(const_entry_span_type entry) constness {               \+        op_context ctxt;                                           \+        return impl().fn(entry, ctxt);                             \+    }                                                              \+    auto fn(const entry_type& entry, op_context& ctxt) constness { \+        return impl().fn(const_entry_span_type(entry), ctxt);      \+    }                                                              \+    auto fn(const entry_type& entry) constness {                   \+        return impl().fn(const_entry_span_type(entry));            \+    }++    BRIE_OVERLOAD_INIT_LIST(insert, )+    BRIE_OVERLOAD_INIT_LIST(find, const)+    BRIE_OVERLOAD_INIT_LIST(contains, const)+    BRIE_OVERLOAD_INIT_LIST(lower_bound, const)+    BRIE_OVERLOAD_INIT_LIST(upper_bound, const)++#undef BRIE_OVERLOAD_INIT_LIST++    /* -------------- operator hint statistics ----------------- */++    // an aggregation of statistical values of the hint utilization+    struct hint_statistics {+        // the counter for insertion operations+        CacheAccessCounter inserts;++        // the counter for contains operations+        CacheAccessCounter contains;++        // the counter for get_boundaries operations+        CacheAccessCounter get_boundaries;+    };++protected:+    // the hint statistic of this b-tree instance+    mutable hint_statistics hint_stats;++public:+    void printStats(std::ostream& out) const {+        out << "---------------------------------\n";+        out << "  insert-hint (hits/misses/total): " << hint_stats.inserts.getHits() << "/"+            << hint_stats.inserts.getMisses() << "/" << hint_stats.inserts.getAccesses() << "\n";+        out << "  contains-hint (hits/misses/total):" << hint_stats.contains.getHits() << "/"+            << hint_stats.contains.getMisses() << "/" << hint_stats.contains.getAccesses() << "\n";+        out << "  get-boundaries-hint (hits/misses/total):" << hint_stats.get_boundaries.getHits() << "/"+            << hint_stats.get_boundaries.getMisses() << "/" << hint_stats.get_boundaries.getAccesses()+            << "\n";+        out << "---------------------------------\n";+    }+};++template <unsigned Dim>+struct TrieTypes;++// FIXME: THIS KILLS COMPILE PERF - O(n^2)+/**+ * A functor extracting a reference to a nested iterator core from an enclosing+ * iterator core.+ */+template <unsigned Level>+struct get_nested_iter_core {+    template <typename IterCore>+    auto operator()(IterCore& core) -> decltype(get_nested_iter_core<Level - 1>()(core.getNested())) {+        return get_nested_iter_core<Level - 1>()(core.getNested());+    }+};++template <>+struct get_nested_iter_core<0> {+    template <typename IterCore>+    IterCore& operator()(IterCore& core) {+        return core;+    }+};++// FIXME: THIS KILLS COMPILE PERF - O(n^2)+/**+ * A functor initializing an iterator upon creation to reference the first+ * element in the associated Trie.+ */+template <unsigned Pos, unsigned Dim>+struct fix_first {+    template <unsigned bits, typename iterator>+    void operator()(const SparseBitMap<bits>& store, iterator& iter) const {+        // set iterator to first in store+        auto first = store.begin();+        get_nested_iter_core<Pos>()(iter.iter_core).setIterator(first);+        iter.value[Pos] = *first;+    }++    template <typename Store, typename iterator>+    void operator()(const Store& store, iterator& iter) const {+        // set iterator to first in store+        auto first = store.begin();+        get_nested_iter_core<Pos>()(iter.iter_core).setIterator(first);+        iter.value[Pos] = first->first;+        // and continue recursively+        fix_first<Pos + 1, Dim>()(first->second->getStore(), iter);+    }+};++template <unsigned Dim>+struct fix_first<Dim, Dim> {+    template <typename Store, typename iterator>+    void operator()(const Store&, iterator&) const {+        // terminal case => nothing to do+    }+};++template <unsigned Dim>+struct fix_first_nested {+    template <unsigned bits, typename iterator>+    void operator()(const SparseBitMap<bits>& store, iterator&& iter) const {+        // set iterator to first in store+        auto first = store.begin();+        iter.value[0] = *first;+        iter.iter_core.setIterator(std::move(first));+    }++    template <typename Store, typename iterator>+    void operator()(const Store& store, iterator&& iter) const {+        // set iterator to first in store+        auto first = store.begin();+        iter.value[0] = first->first;+        iter.iter_core.setIterator(std::move(first));+        // and continue recursively+        fix_first_nested<Dim - 1>()(first->second->getStore(), iter.getNestedView());+    }+};++// TODO: rewrite to erase `Pos` and `Len` arguments. this can cause a template instance explosion+/**+ * A functor initializing an iterator upon creation to reference the first element+ * exhibiting a given prefix within a given Trie.+ */+template <unsigned Len, unsigned Pos, unsigned Dim>+struct fix_binding {+    template <unsigned bits, typename iterator, typename entry_type>+    bool operator()(+            const SparseBitMap<bits>& store, iterator& begin, iterator& end, const entry_type& entry) const {+        // search in current level+        auto cur = store.find(entry[Pos]);++        // if not present => fail+        if (cur == store.end()) return false;++        // take current value+        get_nested_iter_core<Pos>()(begin.iter_core).setIterator(cur);+        ++cur;+        get_nested_iter_core<Pos>()(end.iter_core).setIterator(cur);++        // update iterator value+        begin.value[Pos] = entry[Pos];++        // no more remaining levels to fix+        return true;+    }++    template <typename Store, typename iterator, typename entry_type>+    bool operator()(const Store& store, iterator& begin, iterator& end, const entry_type& entry) const {+        // search in current level+        auto cur = store.find(entry[Pos]);++        // if not present => fail+        if (cur == store.end()) return false;++        // take current value as start+        get_nested_iter_core<Pos>()(begin.iter_core).setIterator(cur);++        // update iterator value+        begin.value[Pos] = entry[Pos];++        // fix remaining nested iterators+        auto res = fix_binding<Len - 1, Pos + 1, Dim>()(cur->second->getStore(), begin, end, entry);++        // update end of iterator+        if (get_nested_iter_core<Pos + 1>()(end.iter_core).getIterator() == cur->second->getStore().end()) {+            ++cur;+            if (cur != store.end()) {+                fix_first<Pos + 1, Dim>()(cur->second->getStore(), end);+            }+        }+        get_nested_iter_core<Pos>()(end.iter_core).setIterator(cur);++        // done+        return res;+    }+};++template <unsigned Pos, unsigned Dim>+struct fix_binding<0, Pos, Dim> {+    template <unsigned bits, typename iterator, typename entry_type>+    bool operator()(const SparseBitMap<bits>& store, iterator& begin, iterator& /* end */,+            const entry_type& /* entry */) const {+        // move begin to begin of store+        auto a = store.begin();+        get_nested_iter_core<Pos>()(begin.iter_core).setIterator(a);+        begin.value[Pos] = *a;++        return true;+    }++    template <typename Store, typename iterator, typename entry_type>+    bool operator()(const Store& store, iterator& begin, iterator& end, const entry_type& entry) const {+        // move begin to begin of store+        auto a = store.begin();+        get_nested_iter_core<Pos>()(begin.iter_core).setIterator(a);+        begin.value[Pos] = a->first;++        // continue recursively+        fix_binding<0, Pos + 1, Dim>()(a->second->getStore(), begin, end, entry);+        return true;+    }+};++template <unsigned Dim>+struct fix_binding<0, Dim, Dim> {+    template <typename Store, typename iterator, typename entry_type>+    bool operator()(const Store& /* store */, iterator& /* begin */, iterator& /* end */,+            const entry_type& /* entry */) const {+        // nothing more to do+        return true;+    }+};++/**+ * A functor initializing an iterator upon creation to reference the first element+ * within a given Trie being not less than a given value .+ */+template <unsigned Dim>+struct fix_lower_bound {+    using types = TrieTypes<Dim>;+    using const_entry_span_type = typename types::const_entry_span_type;++    template <unsigned bits, typename iterator>+    bool operator()(const SparseBitMap<bits>& store, iterator&& iter, const_entry_span_type entry) const {+        auto cur = store.lower_bound(entry[0]);+        if (cur == store.end()) return false;+        assert(entry[0] <= brie_element_type(*cur));++        iter.iter_core.setIterator(cur);+        iter.value[0] = *cur;+        return true;+    }++    template <typename Store, typename iterator>+    bool operator()(const Store& store, iterator&& iter, const_entry_span_type entry) const {+        auto cur = store.lowerBound(entry[0]);  // search in current level+        if (cur == store.end()) return false;   // if no lower boundary is found, be done+        assert(brie_element_type(cur->first) >= entry[0]);++        // if the lower bound is higher than the requested value, go to first in subtree+        if (brie_element_type(cur->first) > entry[0]) {+            iter.iter_core.setIterator(cur);+            iter.value[0] = cur->first;+            fix_first_nested<Dim - 1>()(cur->second->getStore(), iter.getNestedView());+            return true;+        }++        // attempt to fix the rest+        if (!fix_lower_bound<Dim - 1>()(cur->second->getStore(), iter.getNestedView(), tail(entry))) {+            // if it does not work, since there are no matching elements in this branch, go to next+            auto sub = copy(entry);+            sub[0] += 1;+            for (std::size_t i = 1; i < Dim; ++i)+                sub[i] = 0;++            return (*this)(store, iter, sub);+        }++        iter.iter_core.setIterator(cur);  // remember result+        iter.value[0] = cur->first;       // update iterator value+        return true;+    }+};++/**+ * A functor initializing an iterator upon creation to reference the first element+ * within a given Trie being greater than a given value .+ */+template <unsigned Dim>+struct fix_upper_bound {+    using types = TrieTypes<Dim>;+    using const_entry_span_type = typename types::const_entry_span_type;++    template <unsigned bits, typename iterator>+    bool operator()(const SparseBitMap<bits>& store, iterator&& iter, const_entry_span_type entry) const {+        auto cur = store.upper_bound(entry[0]);+        if (cur == store.end()) return false;+        assert(entry[0] <= brie_element_type(*cur));++        iter.iter_core.setIterator(cur);+        iter.value[0] = *cur;+        return true;  // no more remaining levels to fix+    }++    template <typename Store, typename iterator>+    bool operator()(const Store& store, iterator&& iter, const_entry_span_type entry) const {+        auto cur = store.lowerBound(entry[0]);  // search in current level+        if (cur == store.end()) return false;   // if no upper boundary is found, be done+        assert(brie_element_type(cur->first) >= entry[0]);++        // if the lower bound is higher than the requested value, go to first in subtree+        if (brie_element_type(cur->first) > entry[0]) {+            iter.iter_core.setIterator(cur);+            iter.value[0] = cur->first;+            fix_first_nested<Dim - 1>()(cur->second->getStore(), iter.getNestedView());+            return true;+        }++        // attempt to fix the rest+        if (!fix_upper_bound<Dim - 1>()(cur->second->getStore(), iter.getNestedView(), tail(entry))) {+            // if it does not work, since there are no matching elements in this branch, go to next+            auto sub = copy(entry);+            sub[0] += 1;+            for (std::size_t i = 1; i < Dim; ++i)+                sub[i] = 0;++            return (*this)(store, iter, sub);+        }++        iter.iter_core.setIterator(cur);  // remember result+        iter.value[0] = cur->first;       // update iterator value+        return true;+    }+};++template <unsigned Dim>+struct TrieTypes {+    using entry_type = std::array<brie_element_type, Dim>;+    using entry_span_type = span<brie_element_type, Dim>;+    using const_entry_span_type = span<const brie_element_type, Dim>;++    // the type of the nested tries (1 dimension less)+    using nested_trie_type = Trie<Dim - 1>;++    // the merge operation capable of merging two nested tries+    struct nested_trie_merger {+        nested_trie_type* operator()(nested_trie_type* a, const nested_trie_type* b) const {+            if (!b) return a;+            if (!a) return new nested_trie_type(*b);+            a->insertAll(*b);+            return a;+        }+    };++    // the operation capable of cloning a nested trie+    struct nested_trie_cloner {+        nested_trie_type* operator()(nested_trie_type* a) const {+            if (!a) return a;+            return new nested_trie_type(*a);+        }+    };++    // the data structure utilized for indexing nested tries+    using store_type = SparseArray<nested_trie_type*,+            6,  // = 2^6 entries per block+            nested_trie_merger, nested_trie_cloner>;++    // The iterator core for trie iterators involving this level.+    struct iterator_core {+        using store_iter = typename store_type::iterator;  // the iterator for the current level+        using nested_core_iter = typename nested_trie_type::iterator_core;  // the type of the nested iterator++    private:+        store_iter iter;+        nested_core_iter nested;++    public:+        iterator_core() = default;  // default -> end iterator++        iterator_core(store_iter store_iter, entry_span_type entry) : iter(std::move(store_iter)) {+            entry[0] = iter->first;+            nested = {iter->second->getStore().begin(), tail(entry)};+        }++        void setIterator(store_iter store_iter) {+            iter = std::move(store_iter);+        }++        store_iter& getIterator() {+            return iter;+        }++        nested_core_iter& getNested() {+            return nested;+        }++        bool inc(entry_span_type entry) {+            // increment nested iterator+            auto nested_entry = tail(entry);+            if (nested.inc(nested_entry)) return true;++            // increment the iterator on this level+            ++iter;++            // check whether the end has been reached+            if (iter.isEnd()) return false;++            // otherwise update entry value+            entry[0] = iter->first;++            // and restart nested+            nested = {iter->second->getStore().begin(), nested_entry};+            return true;+        }++        bool operator==(const iterator_core& other) const {+            return nested == other.nested && iter == other.iter;+        }++        bool operator!=(const iterator_core& other) const {+            return !(*this == other);+        }++        // enables this iterator core to be printed (for debugging)+        void print(std::ostream& out) const {+            out << iter << " | " << nested;+        }++        friend std::ostream& operator<<(std::ostream& out, const iterator_core& iter) {+            iter.print(out);+            return out;+        }+    };++    using iterator = TrieIterator<entry_type, iterator_core>;++    // the operation context aggregating all operation contexts of nested structures+    struct op_context {+        using local_ctxt = typename store_type::op_context;+        using nested_ctxt = typename nested_trie_type::op_context;++        // for insert and contain+        local_ctxt local{};+        brie_element_type lastQuery{};+        nested_trie_type* lastNested{nullptr};+        nested_ctxt nestedCtxt{};++        // for boundaries+        unsigned lastBoundaryLevels{Dim + 1};+        entry_type lastBoundaryRequest{};+        range<iterator> lastBoundaries{iterator(), iterator()};+    };+};++template <>+struct TrieTypes<1u> {+    using entry_type = std::array<brie_element_type, 1>;+    using entry_span_type = span<brie_element_type, 1>;+    using const_entry_span_type = span<const brie_element_type, 1>;++    // the map type utilized internally+    using store_type = SparseBitMap<>;+    using op_context = store_type::op_context;++    /**+     * The iterator core of this level contributing to the construction of+     * a composed trie iterator.+     */+    struct iterator_core {+        using store_iter = typename store_type::iterator;++    private:+        store_iter iter;++    public:+        iterator_core() = default;  // default end-iterator constructor++        iterator_core(store_iter store_iter, entry_span_type entry)+                : iter(std::move(store_iter))  // NOLINT : mistaken warning -`store_iter` is not const-qual+        {+            entry[0] = brie_element_type(*iter);+        }++        void setIterator(store_iter store_iter) {+            iter = std::move(store_iter);  // NOLINT : mistaken warning - `store_iter` is not const-qual+        }++        store_iter& getIterator() {+            return iter;+        }++        bool inc(entry_span_type entry) {+            // increment the iterator on this level+            ++iter;++            // check whether the end has been reached+            if (iter.isEnd()) return false;++            // otherwise update entry value+            entry[0] = brie_element_type(*iter);+            return true;+        }++        bool operator==(const iterator_core& other) const {+            return iter == other.iter;+        }++        bool operator!=(const iterator_core& other) const {+            return !(*this == other);+        }++        // enables this iterator core to be printed (for debugging)+        void print(std::ostream& out) const {+            out << iter;+        }++        friend std::ostream& operator<<(std::ostream& out, const iterator_core& iter) {+            iter.print(out);+            return out;+        }+    };++    using iterator = TrieIterator<entry_type, iterator_core>;+};++}  // namespace detail::brie++// use an inner class so `TrieN` is fully defined before the recursion, allowing us to use+// `op_context` in `TrieBase`+template <unsigned Dim>+class Trie : public TrieBase<Dim, Trie<Dim>> {+    template <unsigned N>+    friend class Trie;++    // a shortcut for the common base class type+    using base = TrieBase<Dim, Trie<Dim>>;+    using types = TrieTypes<Dim>;+    using nested_trie_type = typename types::nested_trie_type;+    using store_type = typename types::store_type;++    using base::store;++public:+    using const_entry_span_type = typename types::const_entry_span_type;+    using entry_span_type = typename types::entry_span_type;+    using entry_type = typename types::entry_type;+    using iterator = typename types::iterator;+    using iterator_core = typename types::iterator_core;+    using op_context = typename types::op_context;+    // type aliases for compatibility with `BTree` and others+    using operation_hints = op_context;+    using element_type = entry_type;++    using base::begin;+    using base::contains;+    using base::empty;+    using base::end;+    using base::find;+    using base::getBoundaries;+    using base::insert;+    using base::lower_bound;+    using base::upper_bound;++    ~Trie() {+        clear();+    }++    /**+     * Determines the number of entries in this trie.+     */+    std::size_t size() const {+        // the number of elements is lazy-evaluated+        std::size_t res = 0;+        for (auto&& [_, v] : store)+            res += v->size();++        return res;+    }++    /**+     * Computes the total memory usage of this data structure.+     */+    std::size_t getMemoryUsage() const {+        // compute the total memory usage of this level+        auto res = sizeof(*this) - sizeof(store) + store.getMemoryUsage();+        for (auto&& [_, v] : store)+            res += v->getMemoryUsage();  // add the memory usage of sub-levels++        return res;+    }++    /**+     * Removes all entries within this trie.+     */+    void clear() {+        // delete lower levels manually+        // (can't use `Own` b/c we need `atomic` instances and those require trivial assignment)+        for (auto& cur : store)+            delete cur.second;++        // clear store+        store.clear();+    }++    /**+     * Inserts a new entry. A operation context may be provided to exploit temporal+     * locality.+     *+     * @param tuple the entry to be added+     * @param ctxt the operation context to be utilized+     * @return true if the same tuple hasn't been present before, false otherwise+     */+    bool insert(const_entry_span_type tuple, op_context& ctxt) {+        using value_t = typename store_type::value_type;+        using atomic_value_t = typename store_type::atomic_value_type;++        // check context+        if (ctxt.lastNested && ctxt.lastQuery == tuple[0]) {+            base::hint_stats.inserts.addHit();+            return ctxt.lastNested->insert(tail(tuple), ctxt.nestedCtxt);+        }++        base::hint_stats.inserts.addMiss();++        // lookup nested+        atomic_value_t& next = store.getAtomic(tuple[0], ctxt.local);++        // get pure pointer to next level+        value_t nextPtr = next;++        // conduct a lock-free lazy-creation of nested trees+        if (!nextPtr) {+            // create a sub-tree && register it atomically+            auto newNested = mk<nested_trie_type>();+            if (next.compare_exchange_weak(nextPtr, newNested.get())) {+                nextPtr = newNested.release();  // worked, ownership is acquired by `store`+            }+            // otherwise some other thread was faster => use its version+        }++        // make sure a next has been established+        assert(nextPtr);++        // clear context if necessary+        if (nextPtr != ctxt.lastNested) {+            ctxt.lastQuery = tuple[0];+            ctxt.lastNested = nextPtr;+            ctxt.nestedCtxt = {};+        }++        // conduct recursive step+        return nextPtr->insert(tail(tuple), ctxt.nestedCtxt);+    }++    bool contains(const_entry_span_type tuple, op_context& ctxt) const {+        // check context+        if (ctxt.lastNested && ctxt.lastQuery == tuple[0]) {+            base::hint_stats.contains.addHit();+            return ctxt.lastNested->contains(tail(tuple), ctxt.nestedCtxt);+        }++        base::hint_stats.contains.addMiss();++        // lookup next step+        auto next = store.lookup(tuple[0], ctxt.local);++        // clear context if necessary+        if (next != ctxt.lastNested) {+            ctxt.lastQuery = tuple[0];+            ctxt.lastNested = next;+            ctxt.nestedCtxt = {};+        }++        // conduct recursive step+        return next && next->contains(tail(tuple), ctxt.nestedCtxt);+    }++    /**+     * Obtains a range of elements matching the prefix of the given entry up to+     * levels elements. A operation context may be provided to exploit temporal+     * locality.+     *+     * @tparam levels the length of the requested matching prefix+     * @param entry the entry to be looking for+     * @param ctxt the operation context to be utilized+     * @return the corresponding range of matching elements+     */+    template <unsigned levels>+    range<iterator> getBoundaries(const_entry_span_type entry, op_context& ctxt) const {+        // if nothing is bound => just use begin and end+        if constexpr (levels == 0) return make_range(begin(), end());++        // check context+        if (ctxt.lastBoundaryLevels == levels) {+            bool fit = true;+            for (unsigned i = 0; i < levels; ++i) {+                fit = fit && (entry[i] == ctxt.lastBoundaryRequest[i]);+            }++            // if it fits => take it+            if (fit) {+                base::hint_stats.get_boundaries.addHit();+                return ctxt.lastBoundaries;+            }+        }++        // the hint has not been a hit+        base::hint_stats.get_boundaries.addMiss();++        // start with two end iterators+        iterator begin{};+        iterator end{};++        // adapt them level by level+        auto found = fix_binding<levels, 0, Dim>()(store, begin, end, entry);+        if (!found) return make_range(iterator(), iterator());++        // update context+        static_assert(std::tuple_size_v<decltype(ctxt.lastBoundaryRequest)> == Dim);+        static_assert(std::tuple_size_v<decltype(entry)> == Dim);+        ctxt.lastBoundaryLevels = levels;+        std::copy_n(entry.begin(), Dim, ctxt.lastBoundaryRequest.begin());+        ctxt.lastBoundaries = make_range(begin, end);++        // use the result+        return ctxt.lastBoundaries;+    }++    /**+     * Obtains an iterator to the first element not less than the given entry value.+     *+     * @param entry the lower bound for this search+     * @param ctxt the operation context to be utilized+     * @return an iterator addressing the first element in this structure not less than the given value+     */+    iterator lower_bound(const_entry_span_type entry, op_context& /* ctxt */) const {+        // start with a default-initialized iterator+        iterator res;++        // adapt it level by level+        bool found = fix_lower_bound<Dim>()(store, res, entry);++        // use the result+        return found ? res : end();+    }++    /**+     * Obtains an iterator to the first element greater than the given entry value, or end if there is no+     * such element.+     *+     * @param entry the upper bound for this search+     * @param ctxt the operation context to be utilized+     * @return an iterator addressing the first element in this structure greater than the given value+     */+    iterator upper_bound(const_entry_span_type entry, op_context& /* ctxt */) const {+        // start with a default-initialized iterator+        iterator res;++        // adapt it level by level+        bool found = fix_upper_bound<Dim>()(store, res, entry);++        // use the result+        return found ? res : end();+    }++    /**+     * Computes a partition of an approximate number of chunks of the content+     * of this trie. Thus, the union of the resulting set of disjoint ranges is+     * equivalent to the content of this trie.+     *+     * @param chunks the number of chunks requested+     * @return a list of sub-ranges forming a partition of the content of this trie+     */+    std::vector<range<iterator>> partition(unsigned chunks = 500) const {+        std::vector<range<iterator>> res;++        // shortcut for empty trie+        if (this->empty()) return res;++        // use top-level elements for partitioning+        int step = std::max(store.size() / chunks, std::size_t(1));++        int c = 1;+        auto priv = begin();+        for (auto it = store.begin(); it != store.end(); ++it, c++) {+            if (c % step != 0 || c == 1) {+                continue;+            }+            auto cur = iterator(it);+            res.push_back(make_range(priv, cur));+            priv = cur;+        }+        // add final chunk+        res.push_back(make_range(priv, end()));+        return res;+    }+};++/**+ * A template specialization for tries representing a set.+ * For improved memory efficiency, this level is the leaf-node level+ * of all tries exhibiting an arity >= 1. Internally, values are stored utilizing+ * sparse bit maps.+ */+template <>+class Trie<1u> : public TrieBase<1u, Trie<1u>> {+    using base = TrieBase<1u, Trie<1u>>;+    using types = TrieTypes<1u>;+    using store_type = typename types::store_type;++    using base::store;++public:+    using const_entry_span_type = typename types::const_entry_span_type;+    using entry_span_type = typename types::entry_span_type;+    using entry_type = typename types::entry_type;+    using iterator = typename types::iterator;+    using iterator_core = typename types::iterator_core;+    using op_context = typename types::op_context;+    // type aliases for compatibility with `BTree` and others+    using operation_hints = op_context;+    using element_type = entry_type;++    using base::begin;+    using base::contains;+    using base::empty;+    using base::end;+    using base::find;+    using base::getBoundaries;+    using base::insert;+    using base::lower_bound;+    using base::upper_bound;++    /**+     * Determines the number of entries in this trie.+     */+    std::size_t size() const {+        return store.size();+    }++    /**+     * Computes the total memory usage of this data structure.+     */+    std::size_t getMemoryUsage() const {+        // compute the total memory usage+        return sizeof(*this) - sizeof(store) + store.getMemoryUsage();+    }++    /**+     * Removes all elements form this trie.+     */+    void clear() {+        store.clear();+    }++    /**+     * Inserts the given tuple into this trie.+     * An operation context can be provided to exploit temporal locality.+     *+     * @param tuple the tuple to be inserted+     * @param ctxt an operation context for exploiting temporal locality+     * @return true if the tuple has not been present before, false otherwise+     */+    bool insert(const_entry_span_type tuple, op_context& ctxt) {+        return store.set(tuple[0], ctxt);+    }++    /**+     * Determines whether the given tuple is present in this trie or not.+     * An operation context can be provided to exploit temporal locality.+     *+     * @param tuple the tuple to be tested+     * @param ctxt an operation context for exploiting temporal locality+     * @return true if present, false otherwise+     */+    bool contains(const_entry_span_type tuple, op_context& ctxt) const {+        return store.test(tuple[0], ctxt);+    }++    // ---------------------------------------------------------------------+    //                           Iterator+    // ---------------------------------------------------------------------++    /**+     * Obtains a partition of this tire such that the resulting list of ranges+     * cover disjoint subsets of the elements stored in this trie. Their union+     * is equivalent to the content of this trie.+     */+    std::vector<range<iterator>> partition(unsigned chunks = 500) const {+        std::vector<range<iterator>> res;++        // shortcut for empty trie+        if (this->empty()) return res;++        // use top-level elements for partitioning+        int step = static_cast<int>(std::max(store.size() / chunks, std::size_t(1)));++        int c = 1;+        auto priv = begin();+        for (auto it = store.begin(); it != store.end(); ++it, c++) {+            if (c % step != 0 || c == 1) {+                continue;+            }+            auto cur = iterator(it);+            res.push_back(make_range(priv, cur));+            priv = cur;+        }+        // add final chunk+        res.push_back(make_range(priv, end()));+        return res;+    }++    /**+     * Obtains a range of elements matching the prefix of the given entry up to+     * levels elements. A operation context may be provided to exploit temporal+     * locality.+     *+     * @tparam levels the length of the requested matching prefix+     * @param entry the entry to be looking for+     * @param ctxt the operation context to be utilized+     * @return the corresponding range of matching elements+     */+    template <unsigned levels>+    range<iterator> getBoundaries(const_entry_span_type entry, op_context& ctxt) const {+        // for levels = 0+        if (levels == 0) return make_range(begin(), end());+        // for levels = 1+        auto pos = store.find(entry[0], ctxt);+        if (pos == store.end()) return make_range(end(), end());+        auto next = pos;+        ++next;+        return make_range(iterator(pos), iterator(next));+    }++    iterator lower_bound(const_entry_span_type entry, op_context&) const {+        return iterator(store.lower_bound(entry[0]));+    }++    iterator upper_bound(const_entry_span_type entry, op_context&) const {+        return iterator(store.upper_bound(entry[0]));+    }+};++}  // end namespace souffle++namespace std {++using namespace ::souffle::detail::brie;++template <typename A>+struct iterator_traits<SparseArrayIter<A>>+        : forward_non_output_iterator_traits<typename SparseArrayIter<A>::value_type> {};++template <typename A>+struct iterator_traits<SparseBitMapIter<A>>+        : forward_non_output_iterator_traits<typename SparseBitMapIter<A>::value_type> {};++template <typename A, typename IterCore>+struct iterator_traits<TrieIterator<A, IterCore>> : forward_non_output_iterator_traits<A> {};++}  // namespace std++#ifdef _WIN32+#undef __sync_synchronize+#undef __sync_bool_compare_and_swap+#endif
+ cbits/souffle/datastructure/ConcurrentFlyweight.h view
@@ -0,0 +1,477 @@+/*+ * Souffle - A Datalog Compiler+ * Copyright (c) 2021, 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+ */+#pragma once++#include "ConcurrentInsertOnlyHashMap.h"+#include "souffle/utility/ParallelUtil.h"+#include <cassert>+#include <cstring>++namespace souffle {++/**+ * A concurrent, almost lock-free associative datastructure that implements the+ * Flyweight pattern.  Assigns a unique index to each inserted key. Elements+ * cannot be removed, the datastructure can only grow.+ *+ * The datastructure enables a configurable number of concurrent access lanes.+ * Access to the datastructure is lock-free between different lanes.+ * Concurrent accesses through the same lane is sequential.+ *+ * Growing the datastructure requires to temporarily lock all lanes to let a+ * single lane perform the growing operation. The global lock is amortized+ * thanks to an exponential growth strategy.+ *+ */+template <class LanesPolicy, class Key, class Hash = std::hash<Key>, class KeyEqual = std::equal_to<Key>,+        class KeyFactory = details::Factory<Key>>+class ConcurrentFlyweight {+public:+    using lane_id = typename LanesPolicy::lane_id;+    using index_type = std::size_t;+    using key_type = Key;+    using value_type = std::pair<const Key, const index_type>;+    using pointer = const value_type*;+    using reference = const value_type&;++    /// Iterator with concurrent access to the datastructure.+    struct Iterator {+        using iterator_category = std::input_iterator_tag;+        using value_type = ConcurrentFlyweight::value_type;+        using pointer = ConcurrentFlyweight::pointer;+        using reference = ConcurrentFlyweight::reference;++    private:+        using slot_type = int64_t;++        const ConcurrentFlyweight* This;++        /// Access lane to the datastructure.+        lane_id Lane;++        /// Current slot.+        slot_type Slot;++        /// Next slot that might be unassigned.+        slot_type NextMaybeUnassignedSlot;++        /// Handle that owns the next slot that might be unassigned.+        int64_t NextMaybeUnassignedHandle;++        static constexpr int64_t End = std::numeric_limits<slot_type>::max();+        static constexpr int64_t None = -1;++        /// Converts from index to slot.+        static slot_type slot(const index_type I) {+            assert(I >= 0 && I <= std::numeric_limits<slot_type>::max());+            return static_cast<int64_t>(I);+        }++        /// Converts from slot to index.+        static index_type index(const slot_type S) {+            assert(S >= 0 && S <= std::numeric_limits<index_type>::max());+            return static_cast<index_type>(S);+        }++    public:+        // The 'begin' iterator+        Iterator(const ConcurrentFlyweight* This, const lane_id H)+                : This(This), Lane(H), Slot(None), NextMaybeUnassignedSlot(0),+                  NextMaybeUnassignedHandle(None) {+            FindNextMaybeUnassignedSlot();+            MoveToNextAssignedSlot();+        }++        // The 'end' iterator+        Iterator(const ConcurrentFlyweight* This)+                : This(This), Lane(0), Slot(End), NextMaybeUnassignedSlot(End),+                  NextMaybeUnassignedHandle(None) {}++        // The iterator starting at slot I, using access lane H.+        Iterator(const ConcurrentFlyweight* This, const lane_id H, const index_type I)+                : This(This), Lane(H), Slot(slot(I)), NextMaybeUnassignedSlot(slot(I)),+                  NextMaybeUnassignedHandle(None) {+            FindNextMaybeUnassignedSlot();+            MoveToNextAssignedSlot();+        }++        Iterator(const Iterator& That)+                : This(That.This), Lane(That.Lane), Slot(That.Slot),+                  NextMaybeUnassignedSlot(That.NextMaybeUnassignedSlot),+                  NextMaybeUnassignedHandle(That.NextMaybeUnassignedHandle) {}++        Iterator(Iterator&& That)+                : This(That.This), Lane(That.Lane), Slot(That.Slot),+                  NextMaybeUnassignedSlot(That.NextMaybeUnassignedSlot),+                  NextMaybeUnassignedHandle(That.NextMaybeUnassignedHandle) {}++        Iterator& operator=(const Iterator& That) {+            This = That.This;+            Lane = That.Lane;+            Slot = That.Slot;+            NextMaybeUnassignedSlot = That.NextMaybeUnassignedSlot;+            NextMaybeUnassignedHandle = That.NextMaybeUnassignedHandle;+        }++        Iterator& operator=(Iterator&& That) {+            This = That.This;+            Lane = That.Lane;+            Slot = That.Slot;+            NextMaybeUnassignedSlot = That.NextMaybeUnassignedSlot;+            NextMaybeUnassignedHandle = That.NextMaybeUnassignedHandle;+        }++        reference operator*() const {+            const auto Guard = This->Lanes.guard(Lane);+            return *This->Slots[index(Slot)];+        }++        pointer operator->() const {+            const auto Guard = This->Lanes.guard(Lane);+            return This->Slots[index(Slot)];+        }++        Iterator& operator++() {+            MoveToNextAssignedSlot();+            return *this;+        }++        Iterator operator++(int) {+            Iterator Tmp = *this;+            ++(*this);+            return Tmp;+        }++        bool operator==(const Iterator& That) const {+            return (&This == &That.This) && (Slot == That.Slot);+        }++        bool operator!=(const Iterator& That) const {+            return (This != That.This) || (Slot != That.Slot);+        }++    private:+        /** Find next slot after Slot that is maybe unassigned. */+        void FindNextMaybeUnassignedSlot() {+            NextMaybeUnassignedSlot = End;+            for (lane_id I = 0; I < This->Lanes.lanes(); ++I) {+                const auto Lane = This->Lanes.guard(I);+                if (This->Handles[I].NextSlot > Slot && This->Handles[I].NextSlot < NextMaybeUnassignedSlot) {+                    NextMaybeUnassignedSlot = This->Handles[I].NextSlot;+                    NextMaybeUnassignedHandle = I;+                }+            }+            if (NextMaybeUnassignedSlot == End) {+                NextMaybeUnassignedSlot = This->NextSlot;+                NextMaybeUnassignedHandle = None;+            }+        }++        /**+         * Move Slot to next assigned slot and return true.+         * Otherwise the end is reached and Slot is assigned int64_t::max and return false.+         */+        bool MoveToNextAssignedSlot() {+            while (Slot != End) {+                if (Slot + 1 < NextMaybeUnassignedSlot) {  // next unassigned slot not reached+                    Slot = Slot + 1;+                    return true;+                }++                if (NextMaybeUnassignedHandle == None) {  // reaching end+                    Slot = End;+                    NextMaybeUnassignedSlot = End;+                    NextMaybeUnassignedHandle = None;+                    return false;+                }++                if (NextMaybeUnassignedHandle != None) {  // maybe reaching the next unassigned slot+                    This->Lanes.lock(NextMaybeUnassignedHandle);+                    const bool IsAssigned = (Slot + 1 < This->Handles[NextMaybeUnassignedHandle].NextSlot);+                    This->Lanes.unlock(NextMaybeUnassignedHandle);+                    if (IsAssigned) {+                        Slot = Slot + 1;+                    }+                    FindNextMaybeUnassignedSlot();+                    if (IsAssigned) {+                        return true;+                    }+                }+            }+            return false;+        }+    };++    using iterator = Iterator;++    /// Initialize the datastructure with the given capacity.+    ConcurrentFlyweight(const std::size_t LaneCount, const std::size_t InitialCapacity,+            const bool ReserveFirst, const Hash& hash = Hash(), const KeyEqual& key_equal = KeyEqual(),+            const KeyFactory& key_factory = KeyFactory())+            : Lanes(LaneCount), HandleCount(LaneCount),+              Mapping(LaneCount, InitialCapacity, hash, key_equal, key_factory) {+        Slots = std::make_unique<const value_type*[]>(InitialCapacity);+        Handles = std::make_unique<Handle[]>(HandleCount);+        NextSlot = (ReserveFirst ? 1 : 0);+        MaxSlotBeforeGrow = InitialCapacity - 1;+    }++    /// Initialize the datastructure with a capacity of 8 elements.+    ConcurrentFlyweight(const std::size_t LaneCount, const bool ReserveFirst, const Hash& hash = Hash(),+            const KeyEqual& key_equal = KeyEqual(), const KeyFactory& key_factory = KeyFactory())++            : ConcurrentFlyweight(LaneCount, 8, ReserveFirst, hash, key_equal, key_factory) {}++    /// Initialize the datastructure with a capacity of 8 elements.+    ConcurrentFlyweight(const std::size_t LaneCount, const Hash& hash = Hash(),+            const KeyEqual& key_equal = KeyEqual(), const KeyFactory& key_factory = KeyFactory())+            : ConcurrentFlyweight(LaneCount, 8, false, hash, key_equal, key_factory) {}++    virtual ~ConcurrentFlyweight() {+        for (lane_id I = 0; I < HandleCount; ++I) {+            if (Handles[I].NextNode) {+                delete Handles[I].NextNode;+            }+        }+    }++    /**+     * Change the number of lanes and possibly grow the number of handles.+     * Do not use while threads are using this datastructure.+     */+    void setNumLanes(const std::size_t NumLanes) {+        if (NumLanes > HandleCount) {+            std::unique_ptr<Handle[]> NextHandles = std::make_unique<Handle[]>(NumLanes);+            std::copy(Handles.get(), Handles.get() + HandleCount, NextHandles.get());+            Handles.swap(NextHandles);+            HandleCount = NumLanes;+        }+        Mapping.setNumLanes(NumLanes);+        Lanes.setNumLanes(NumLanes);+    }++    /** Return a concurrent iterator on the first element. */+    Iterator begin(const lane_id H) const {+        return Iterator(this, H);+    }++    /** Return an iterator past the last element. */+    Iterator end() const {+        return Iterator(this);+    }++    /// Return true if the value is in the map.+    template <typename K>+    bool weakContains(const lane_id H, const K& X) const {+        return Mapping.weakContains(H, X);+    }++    /// Return the value associated with the given index.+    /// Assumption: the index is mapped in the datastructure.+    const Key& fetch(const lane_id H, const index_type Idx) const {+        const auto Lane = Lanes.guard(H);+        return Slots[Idx]->first;+    }++    /// Return the pair of the index for the given value and a boolean+    /// indicating if the value was already present (false) or inserted by this handle (true).+    /// Insert the value and return a fresh index if the value is not+    /// yet indexed.+    template <class... Args>+    std::pair<index_type, bool> findOrInsert(const lane_id H, Args&&... Xs) {+        const auto Lane = Lanes.guard(H);+        int64_t Slot = Handles[H].NextSlot;+        node_type Node;++        if (Slot == -1) {+            // reserve a slot in the index, be it for now or later usage.+            Slot = NextSlot++;+            Node = Mapping.node(static_cast<index_type>(Slot));++            Handles[H].NextSlot = Slot;+            Handles[H].NextNode = Node;++            if (Slot > MaxSlotBeforeGrow) {+                tryGrow(H);+            }+        } else {+            Node = Handles[H].NextNode;+        }++        // insert key in the index in advance+        Slots[Slot] = &Node->value();++        auto Res = Mapping.get(H, Node, std::forward<Args>(Xs)...);+        if (Res.second) {+            // inserted by self+            Handles[H].NextSlot = -1;+            Handles[H].NextNode = node_type{};+            return std::make_pair(static_cast<index_type>(Slot), true);+        } else {+            // inserted concurrently by another handle,+            return std::make_pair(Res.first->second, false);+        }+    }++private:+    using map_type = ConcurrentInsertOnlyHashMap<LanesPolicy, Key, index_type, Hash, KeyEqual, KeyFactory>;+    using node_type = typename map_type::node_type;++    struct Handle {+        /// Slot where this handle will store its next value+        int64_t NextSlot = -1;+        node_type NextNode = nullptr;+    };++protected:+    // The concurrency manager.+    LanesPolicy Lanes;++private:+    // Number of handles+    std::size_t HandleCount;++    // Handle for each concurrent lane.+    std::unique_ptr<Handle[]> Handles;++    // Slots[I] points to the value associated with index I.+    std::unique_ptr<const value_type*[]> Slots;++    // The map from keys to index.+    map_type Mapping;++    // Next available slot.+    std::atomic<std::int64_t> NextSlot;++    // Maximum allowed slot index before growing+    std::int64_t MaxSlotBeforeGrow;++    bool tryGrow(const lane_id H) {+        Lanes.beforeLockAllBut(H);++        if (NextSlot <= MaxSlotBeforeGrow) {+            // Current size is fine+            Lanes.beforeUnlockAllBut(H);+            return false;+        }++        Lanes.lockAllBut(H);++        {  // safe section+            const std::size_t CurrentSize = MaxSlotBeforeGrow + 1;+            const std::size_t NewSize = (CurrentSize << 1);  // double size policy+            std::unique_ptr<const value_type*[]> NewSlots = std::make_unique<const value_type*[]>(NewSize);+            std::memcpy(NewSlots.get(), Slots.get(), sizeof(const value_type*) * CurrentSize);+            Slots = std::move(NewSlots);+            MaxSlotBeforeGrow = NewSize - 1;+        }++        Lanes.beforeUnlockAllBut(H);+        Lanes.unlockAllBut(H);++        return true;+    }+};++#ifdef _OPENMP+/** A Flyweight datastructure with concurrent access specialized for OpenMP. */+template <class Key, class Hash = std::hash<Key>, class KeyEqual = std::equal_to<Key>,+        class KeyFactory = details::Factory<Key>>+class OmpFlyweight : protected ConcurrentFlyweight<ConcurrentLanes, Key, Hash, KeyEqual, KeyFactory> {+public:+    using Base = ConcurrentFlyweight<ConcurrentLanes, Key, Hash, KeyEqual, KeyFactory>;+    using index_type = typename Base::index_type;+    using lane_id = typename Base::lane_id;+    using iterator = typename Base::iterator;++    explicit OmpFlyweight(const std::size_t LaneCount, const std::size_t InitialCapacity = 8,+            const bool ReserveFirst = false, const Hash& hash = Hash(),+            const KeyEqual& key_equal = KeyEqual(), const KeyFactory& key_factory = KeyFactory())+            : Base(LaneCount, InitialCapacity, ReserveFirst, hash, key_equal, key_factory) {}++    ~OmpFlyweight() {}++    iterator begin() const {+        return Base::begin(Base::Lanes.threadLane());+    }++    iterator end() const {+        return Base::end();+    }++    template <typename K>+    bool weakContains(const K& X) const {+        return Base::weakContains(Base::Lanes.threadLane(), X);+    }++    const Key& fetch(const index_type Idx) const {+        return Base::fetch(Base::Lanes.threadLane(), Idx);+    }++    template <class... Args>+    std::pair<index_type, bool> findOrInsert(Args&&... Xs) {+        return Base::findOrInsert(Base::Lanes.threadLane(), std::forward<Args>(Xs)...);+    }+};+#endif++/**+ * A Flyweight datastructure with sequential access.+ *+ * Reuse the concurrent flyweight with a single access handle.+ */+template <class Key, class Hash = std::hash<Key>, class KeyEqual = std::equal_to<Key>,+        class KeyFactory = details::Factory<Key>>+class SeqFlyweight : protected ConcurrentFlyweight<SeqConcurrentLanes, Key, Hash, KeyEqual, KeyFactory> {+public:+    using Base = ConcurrentFlyweight<SeqConcurrentLanes, Key, Hash, KeyEqual, KeyFactory>;+    using index_type = typename Base::index_type;+    using lane_id = typename Base::lane_id;+    using iterator = typename Base::iterator;++    explicit SeqFlyweight(const std::size_t NumLanes, const std::size_t InitialCapacity = 8,+            const bool ReserveFirst = false, const Hash& hash = Hash(),+            const KeyEqual& key_equal = KeyEqual(), const KeyFactory& key_factory = KeyFactory())+            : Base(NumLanes, InitialCapacity, ReserveFirst, hash, key_equal, key_factory) {}++    ~SeqFlyweight() {}++    iterator begin() const {+        return Base::begin(0);+    }++    iterator end() const {+        return Base::end();+    }++    template <typename K>+    bool weakContains(const K& X) const {+        return Base::weakContains(0, X);+    }++    const Key& fetch(const index_type Idx) const {+        return Base::fetch(0, Idx);+    }++    template <class... Args>+    std::pair<index_type, bool> findOrInsert(Args&&... Xs) {+        return Base::findOrInsert(0, std::forward<Args>(Xs)...);+    }+};++#ifdef _OPENMP+template <class Key, class Hash = std::hash<Key>, class KeyEqual = std::equal_to<Key>,+        class KeyFactory = details::Factory<Key>>+using FlyweightImpl = OmpFlyweight<Key, Hash, KeyEqual, KeyFactory>;+#else+template <class Key, class Hash = std::hash<Key>, class KeyEqual = std::equal_to<Key>,+        class KeyFactory = details::Factory<Key>>+using FlyweightImpl = SeqFlyweight<Key, Hash, KeyEqual, KeyFactory>;+#endif++}  // namespace souffle
+ cbits/souffle/datastructure/ConcurrentInsertOnlyHashMap.h view
@@ -0,0 +1,458 @@+/*+ * Souffle - A Datalog Compiler+ * Copyright (c) 2021, 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+ */+#pragma once++#include "souffle/utility/ParallelUtil.h"++#include <array>+#include <atomic>+#include <cassert>+#include <cmath>+#include <memory>+#include <mutex>+#include <vector>++namespace souffle {+namespace details {++static const std::vector<std::pair<unsigned, unsigned>> ToPrime = {+        // https://primes.utm.edu/lists/2small/0bit.html+        // ((2^n) - k) is prime+        // {n, k}+        {4, 3},  // 2^4 - 3 = 13+        {8, 5},  // 8^5 - 5 = 251+        {9, 3}, {10, 3}, {11, 9}, {12, 3}, {13, 1}, {14, 3}, {15, 19}, {16, 15}, {17, 1}, {18, 5}, {19, 1},+        {20, 3}, {21, 9}, {22, 3}, {23, 15}, {24, 3}, {25, 39}, {26, 5}, {27, 39}, {28, 57}, {29, 3},+        {30, 35}, {31, 1}, {32, 5}, {33, 9}, {34, 41}, {35, 31}, {36, 5}, {37, 25}, {38, 45}, {39, 7},+        {40, 87}, {41, 21}, {42, 11}, {43, 57}, {44, 17}, {45, 55}, {46, 21}, {47, 115}, {48, 59}, {49, 81},+        {50, 27}, {51, 129}, {52, 47}, {53, 111}, {54, 33}, {55, 55}, {56, 5}, {57, 13}, {58, 27}, {59, 55},+        {60, 93}, {61, 1}, {62, 57}, {63, 25}};++// (2^64)-59 is the largest prime that fits in uint64_t+static constexpr uint64_t LargestPrime64 = 18446744073709551557UL;++// Return a prime greater or equal to the lower bound.+// Return 0 if the next prime would not fit in 64 bits.+static uint64_t GreaterOrEqualPrime(const uint64_t LowerBound) {+    if (LowerBound > LargestPrime64) {+        return 0;+    }++    for (std::size_t I = 0; I < ToPrime.size(); ++I) {+        const uint64_t N = ToPrime[I].first;+        const uint64_t K = ToPrime[I].second;+        const uint64_t Prime = (1UL << N) - K;+        if (Prime >= LowerBound) {+            return Prime;+        }+    }+    return LargestPrime64;+}++template <typename T>+struct Factory {+    template <class... Args>+    T& replace(T& Place, Args&&... Xs) {+        Place = T{std::forward<Args>(Xs)...};+        return Place;+    }+};++}  // namespace details++/**+ * A concurrent, almost lock-free associative hash-map that can only grow.+ * Elements cannot be removed, the hash-map can only grow.+ *+ * The datastructures enables a configurable number of concurrent access lanes.+ * Access to the datastructure is lock-free between different lanes.+ * Concurrent accesses through the same lane is sequential.+ *+ * Growing the datastructure requires to temporarily lock all lanes to let a+ * single lane perform the growing operation. The global lock is amortized+ * thanks to an exponential growth strategy.+ */+template <class LanesPolicy, class Key, class T, class Hash = std::hash<Key>,+        class KeyEqual = std::equal_to<Key>, class KeyFactory = details::Factory<Key>>+class ConcurrentInsertOnlyHashMap {+public:+    class Node;++    using key_type = Key;+    using mapped_type = T;+    using node_type = Node*;+    using value_type = std::pair<const Key, const T>;+    using size_type = std::size_t;+    using hasher = Hash;+    using key_equal = KeyEqual;+    using self_type = ConcurrentInsertOnlyHashMap<Key, T, Hash, KeyEqual, KeyFactory>;+    using lane_id = typename LanesPolicy::lane_id;++    class Node {+    public:+        virtual ~Node() {}+        virtual const value_type& value() const = 0;+        virtual const key_type& key() const = 0;+        virtual const mapped_type& mapped() const = 0;+    };++private:+    // Each bucket of the hash-map is a linked list.+    struct BucketList : Node {+        virtual ~BucketList() {}++        BucketList(const Key& K, const T& V, BucketList* N) : Value(K, V), Next(N) {}++        const value_type& value() const {+            return Value;+        }++        const key_type& key() const {+            return Value.first;+        }++        const mapped_type& mapped() const {+            return Value.second;+        }++        // Stores the couple of a key and its associated value.+        value_type Value;++        // Points to next element of the map that falls into the same bucket.+        BucketList* Next;+    };++public:+    /**+     * @brief Construct a hash-map with at least the given number of buckets.+     *+     * Load-factor is initialized to 1.0.+     */+    ConcurrentInsertOnlyHashMap(const std::size_t LaneCount, const std::size_t Bucket_Count,+            const Hash& hash = Hash(), const KeyEqual& key_equal = KeyEqual(),+            const KeyFactory& key_factory = KeyFactory())+            : Lanes(LaneCount), Hasher(hash), EqualTo(key_equal), Factory(key_factory) {+        Size = 0;+        BucketCount = details::GreaterOrEqualPrime(Bucket_Count);+        if (BucketCount == 0) {+            // Hopefuly this number of buckets is never reached.+            BucketCount = std::numeric_limits<std::size_t>::max();+        }+        LoadFactor = 1.0;+        Buckets = std::make_unique<std::atomic<BucketList*>[]>(BucketCount);+        MaxSizeBeforeGrow = std::ceil(LoadFactor * BucketCount);+    }++    ConcurrentInsertOnlyHashMap(const Hash& hash = Hash(), const KeyEqual& key_equal = KeyEqual(),+            const KeyFactory& key_factory = KeyFactory())+            : ConcurrentInsertOnlyHashMap(8, hash, key_equal, key_factory) {}++    ~ConcurrentInsertOnlyHashMap() {+        for (std::size_t Bucket = 0; Bucket < BucketCount; ++Bucket) {+            BucketList* L = Buckets[Bucket].load(std::memory_order_relaxed);+            while (L != nullptr) {+                BucketList* BL = L;+                L = L->Next;+                delete (BL);+            }+        }+    }++    void setNumLanes(const std::size_t NumLanes) {+        Lanes.setNumLanes(NumLanes);+    }++    /** @brief Create a fresh node initialized with the given value and a+     * default-constructed key.+     *+     * The ownership of the returned node given to the caller.+     */+    node_type node(const T& V) {+        BucketList* BL = new BucketList(Key{}, V, nullptr);+        return static_cast<node_type>(BL);+    }++    /** @brief Checks if the map contains an element with the given key.+     *+     * The search is done concurrently with possible insertion of the+     * searched key. If return true, then there is definitely an element+     * with the specified key, if return false then there was no such+     * element when the search began.+     */+    template <class K>+    bool weakContains(const lane_id H, const K& X) const {+        const size_t HashValue = Hasher(X);+        const auto Guard = Lanes.guard(H);+        const size_t Bucket = HashValue % BucketCount;++        BucketList* L = Buckets[Bucket].load(std::memory_order_consume);+        while (L != nullptr) {+            if (EqualTo(L->Value.first, X)) {+                // found the key+                return true;+            }+            L = L->Next;+        }+        return false;+    }++    /**+     * @brief Inserts in-place if the key is not mapped, does nothing if the key already exists.+     *+     * @param H is the access lane.+     *+     * @param N is a node initialized with the mapped value to insert.+     *+     * @param Xs are arguments to forward to the hasher, the comparator and and+     * the constructor of the key.+     *+     *+     * Be Careful: the inserted node becomes available to concurrent lanes as+     * soon as it is inserted, thus concurrent lanes may access the inserted+     * value even before the inserting lane returns from this function.+     * This is the reason why the inserting lane must prepare the inserted+     * node's mapped value prior to calling this function.+     *+     * Be Careful: the given node remains the ownership of the caller unless+     * the returned couple second member is true.+     *+     * Be Careful: the given node may not be inserted if the key already+     * exists.  The caller is in charge of handling that case and either+     * dispose of the node or save it for the next insertion operation.+     *+     * Be Careful: Once the given node is actually inserted, its ownership is+     * transfered to the hash-map. However it remains valid.+     *+     * If the key that compares equal to arguments Xs exists, then nothing is+     * inserted. The returned value is the couple of the pointer to the+     * existing value and the false boolean value.+     *+     * If the key that compares equal to arguments Xs does not exist, then the+     * node N is updated with the key constructed from Xs, and inserted in the+     * hash-map. The returned value is the couple of the pointer to the+     * inserted value and the true boolean value.+     *+     */+    template <class... Args>+    std::pair<const value_type*, bool> get(const lane_id H, node_type N, Args&&... Xs) {+        // At any time a concurrent lane may insert the key before this lane.+        //+        // The synchronisation point is the atomic compare-and-exchange of the+        // head of the bucket list that must contain the inserted node.+        //+        // The insertion algorithm is as follow:+        //+        // 1) Compute the key hash from Xs.+        //+        // 2) Lock the lane, that also prevent concurrent lanes from growing of+        // the datastructure.+        //+        // 3) Determine the bucket where the element must be inserted.+        //+        // 4) Read the "last known head" of the bucket list. Other lanes+        // inserting in the same bucket may update the bucket head+        // concurrently.+        //+        // 5) Search the bucket list for the key by comparing with Xs starting+        // from the last known head. If it is not the first round of search,+        // then stop searching where the previous round of search started.+        //+        // 6) If the key is found return the couple of the value pointer and+        // false (to indicate that this lane did not insert the node N).+        //+        // 7) It the key is not found prepare N for insertion by updating its+        // key with Xs and chaining the last known head.+        //+        // 8) Try to exchange to last known head with N at the bucket head. The+        // atomic compare and exchange operation guarantees that it only+        // succeed if not other node was inserted in the bucket since we+        // searched it, otherwise it fails when another lane has concurrently+        // inserted a node in the same bucket.+        //+        // 9) If the atomic compare and exchange succeeded, the node has just+        // been inserted by this lane. From now-on other lanes can also see+        // the node. Return the couple of a pointer to the inserted value and+        // the true boolean.+        //+        // 10) If the atomic compare and exchange failed, another node has been+        // inserted by a concurrent lane in the same bucket. A new round of+        // search is required -> restart from step 4.+        //+        //+        // The datastructure is optionaly grown after step 9) before returning.++        const value_type* Value = nullptr;+        bool Inserted = false;++        size_t NewSize;++        // 1)+        const size_t HashValue = Hasher(std::forward<Args>(Xs)...);++        // 2)+        Lanes.lock(H);  // prevent the datastructure from growing++        // 3)+        const size_t Bucket = HashValue % BucketCount;++        // 4)+        // the head of the bucket's list last time we checked+        BucketList* LastKnownHead = Buckets[Bucket].load(std::memory_order_relaxed);+        // the head of the bucket's list we already searched from+        BucketList* SearchedFrom = nullptr;+        // the node we want to insert+        BucketList* Node = static_cast<BucketList*>(N);++        // Loop until either the node is inserted or the key is found in the bucket.+        // Assuming bucket collisions are rare this loop is not executed more than once.+        while (true) {+            // 5)+            // search the key in the bucket, stop where we already search at a+            // previous iteration.+            BucketList* L = LastKnownHead;+            while (L != SearchedFrom) {+                if (EqualTo(L->Value.first, std::forward<Args>(Xs)...)) {+                    // 6)+                    // found the key+                    Value = &(L->Value);+                    goto Done;+                }+                L = L->Next;+            }+            SearchedFrom = LastKnownHead;++            // 7)+            // Not found in bucket, prepare node chaining.+            Node->Next = LastKnownHead;+            // The factory step could be done only once, but assuming bucket collisions are+            // rare this whole loop is not executed more than once.+            Factory.replace(const_cast<key_type&>(Node->Value.first), std::forward<Args>(Xs)...);++            // 8)+            // Try to insert the key in front of the bucket's list.+            // This operation also performs step 4) because LastKnownHead is+            // updated in the process.+            if (Buckets[Bucket].compare_exchange_strong(+                        LastKnownHead, Node, std::memory_order_release, std::memory_order_relaxed)) {+                // 9)+                Inserted = true;+                NewSize = ++Size;+                Value = &(Node->Value);+                Node = nullptr;+                goto AfterInserted;+            }++            // 10) concurrent insertion detected in this bucket, new round required.+        }++    AfterInserted : {+        if (NewSize > MaxSizeBeforeGrow) {+            tryGrow(H);+        }+    }++    Done:++        Lanes.unlock(H);++        // 6,9)+        return std::make_pair(Value, Inserted);+    }++private:+    // The concurrent lanes manager.+    LanesPolicy Lanes;++    /// Hash function.+    Hash Hasher;++    /// Current number of buckets.+    std::size_t BucketCount;++    /// Atomic pointer to head bucket linked-list head.+    std::unique_ptr<std::atomic<BucketList*>[]> Buckets;++    /// The Equal-to function.+    KeyEqual EqualTo;++    KeyFactory Factory;++    /// Current number of elements stored in the map.+    std::atomic<std::size_t> Size;++    /// Maximum size before the map should grow.+    std::size_t MaxSizeBeforeGrow;++    /// The load-factor of the map.+    double LoadFactor;++    // Grow the datastructure.+    // Must be called while owning lane H.+    bool tryGrow(const lane_id H) {+        Lanes.beforeLockAllBut(H);++        if (Size <= MaxSizeBeforeGrow) {+            // Current size is fine+            Lanes.beforeUnlockAllBut(H);+            return false;+        }++        Lanes.lockAllBut(H);++        {  // safe section++            // Compute the new number of buckets:+            // Chose a prime number of buckets that ensures the desired load factor+            // given the current number of elements in the map.+            const std::size_t CurrentSize = Size;+            const std::size_t NeededBucketCount = std::ceil(CurrentSize / LoadFactor);+            std::size_t NewBucketCount = NeededBucketCount;+            for (std::size_t I = 0; I < details::ToPrime.size(); ++I) {+                const uint64_t N = details::ToPrime[I].first;+                const uint64_t K = details::ToPrime[I].second;+                const uint64_t Prime = (1UL << N) - K;+                if (Prime >= NeededBucketCount) {+                    NewBucketCount = Prime;+                    break;+                }+            }++            std::unique_ptr<std::atomic<BucketList*>[]> NewBuckets =+                    std::make_unique<std::atomic<BucketList*>[]>(NewBucketCount);++            // Rehash, this operation is costly because it requires to scan+            // the existing elements, compute its hash to find its new bucket+            // and insert in the new bucket.+            //+            // Maybe concurrent lanes could help using some job-stealing algorithm.+            for (std::size_t B = 0; B < BucketCount; ++B) {+                BucketList* L = Buckets[B].load(std::memory_order_relaxed);+                while (L) {+                    BucketList* const Elem = L;+                    L = L->Next;++                    const auto& Value = Elem->Value;+                    std::size_t NewHash = Hasher(Value.first);+                    const std::size_t NewBucket = NewHash % NewBucketCount;+                    Elem->Next = NewBuckets[NewBucket].load(std::memory_order_relaxed);+                    NewBuckets[NewBucket].store(Elem, std::memory_order_relaxed);+                }+            }++            Buckets = std::move(NewBuckets);+            BucketCount = NewBucketCount;+            MaxSizeBeforeGrow = (NewBucketCount * LoadFactor);+        }++        Lanes.beforeUnlockAllBut(H);+        Lanes.unlockAllBut(H);+        return true;+    }+};++}  // namespace souffle
cbits/souffle/datastructure/EquivalenceRelation.h view
@@ -117,8 +117,8 @@             for (auto& p : it) {                 value_type rep = p.first;                 StatesList& pl = *p.second;-                const size_t ksize = pl.size();-                for (size_t i = 0; i < ksize; ++i) {+                const std::size_t ksize = pl.size();+                for (std::size_t i = 0; i < ksize; ++i) {                     this->sds.unionNodes(rep, pl.get(i));                 }             }@@ -192,6 +192,10 @@         return contains(tuple[0], tuple[1]);     }; +    bool contains(const TupleType& tuple) const {+        return contains(tuple[0], tuple[1]);+    };+     void emptyPartition() const {         // delete the beautiful values inside (they're raw ptrs, so they need to be.)         for (auto& pair : equivalencePartition) {@@ -219,14 +223,14 @@      * Size of relation      * @return the sum of the number of pairs per disjoint set      */-    size_t size() const {+    std::size_t size() const {         genAllDisjointSetLists();          statesLock.lock_shared(); -        size_t retVal = 0;+        std::size_t retVal = 0;         for (auto& e : this->equivalencePartition) {-            const size_t s = e.second->size();+            const std::size_t s = e.second->size();             retVal += s * s;         } @@ -241,10 +245,10 @@     class iterator {     public:         typedef std::forward_iterator_tag iterator_category;-        typedef TupleType value_type;-        typedef ptrdiff_t difference_type;-        typedef value_type* pointer;-        typedef value_type& reference;+        using value_type = TupleType;+        using difference_type = ptrdiff_t;+        using pointer = value_type*;+        using reference = value_type&;          // one iterator for signalling the end (simplifies)         explicit iterator(const EquivalenceRelation* br, bool /* signalIsEndIterator */)@@ -447,9 +451,9 @@         typename StatesMap::iterator djSetMapListEnd;          // used for ALL, and POSTERIOR (just a current index in the cList)-        size_t cAnteriorIndex = 0;+        std::size_t cAnteriorIndex = 0;         // used for ALL, and ANTERIOR (just a current index in the cList)-        size_t cPosteriorIndex = 0;+        std::size_t cPosteriorIndex = 0;     };  public:@@ -561,6 +565,11 @@         return end();     } +    iterator lower_bound(const TupleType& entry) const {+        operation_hints hints;+        return lower_bound(entry, hints);+    }+     /**      * This function is only here in order to unify interfaces in InterpreterIndex.      * Unlike the name suggestes, it omit the arguments and simply return the end@@ -573,6 +582,11 @@         return end();     } +    iterator upper_bound(const TupleType& entry) const {+        operation_hints hints;+        return upper_bound(entry, hints);+    }+     /**      * Check emptiness.      */@@ -639,11 +653,11 @@      * @param chunks the number of requested partitions      * @return a list of the iterators as ranges      */-    std::vector<souffle::range<iterator>> partition(size_t chunks) const {+    std::vector<souffle::range<iterator>> partition(std::size_t chunks) const {         // generate all reps         genAllDisjointSetLists(); -        size_t numPairs = this->size();+        std::size_t numPairs = this->size();         if (numPairs == 0) return {};         if (numPairs == 1 || chunks <= 1) return {souffle::make_range(begin(), end())}; @@ -659,9 +673,9 @@         // keep it simple stupid         // just go through and if the size of the binrel is > numpairs/chunks, then generate an anteriorIt for         // each-        const size_t perchunk = numPairs / chunks;+        const std::size_t perchunk = numPairs / chunks;         for (const auto& itp : equivalencePartition) {-            const size_t s = itp.second->size();+            const std::size_t s = itp.second->size();             if (s * s > perchunk) {                 for (const auto& i : *itp.second) {                     ret.push_back(souffle::make_range(anteriorIt(i), end()));@@ -717,8 +731,8 @@         // btree version         emptyPartition(); -        size_t dSetSize = this->sds.ds.a_blocks.size();-        for (size_t i = 0; i < dSetSize; ++i) {+        std::size_t dSetSize = this->sds.ds.a_blocks.size();+        for (std::size_t i = 0; i < dSetSize; ++i) {             typename TupleType::value_type sparseVal = this->sds.toSparse(i);             parent_t rep = this->sds.findNode(sparseVal); 
cbits/souffle/datastructure/PiggyList.h view
@@ -3,6 +3,7 @@ #include "souffle/utility/ParallelUtil.h" #include <array> #include <atomic>+#include <cassert> #include <cstring> #include <iostream> #include <iterator>@@ -12,7 +13,7 @@  * Some versions of MSVC do not provide a builtin for counting leading zeroes  * like gcc, so we have to implement it ourselves.  */-#if _MSC_VER < 1924+#if defined(_MSC_VER) unsigned long __inline __builtin_clzll(unsigned long long value) {     unsigned long msb = 0; @@ -21,7 +22,7 @@     else         return 64; }-#endif  // _MSC_VER < 1924+#endif  // _MSC_VER #endif  // _WIN32  using std::size_t;@@ -38,17 +39,17 @@     RandomInsertPiggyList() = default;     // an instance where the initial size is not 65k, and instead is user settable (to a power of     // initialbitsize)-    RandomInsertPiggyList(size_t initialbitsize) : BLOCKBITS(initialbitsize) {}+    RandomInsertPiggyList(std::size_t initialbitsize) : BLOCKBITS(initialbitsize) {}      /** copy constructor */     RandomInsertPiggyList(const RandomInsertPiggyList& other) : BLOCKBITS(other.BLOCKBITS) {         this->numElements.store(other.numElements.load());          // copy blocks from the old lookup table to this one-        for (size_t i = 0; i < maxContainers; ++i) {+        for (std::size_t i = 0; i < maxContainers; ++i) {             if (other.blockLookupTable[i].load() != nullptr) {                 // calculate the size of that block-                const size_t blockSize = INITIALBLOCKSIZE << i;+                const std::size_t blockSize = INITIALBLOCKSIZE << i;                  // allocate that in the new container                 this->blockLookupTable[i].store(new T[blockSize]);@@ -71,25 +72,25 @@         freeList();     } -    inline size_t size() const {+    inline std::size_t size() const {         return numElements.load();     } -    inline T* getBlock(size_t blockNum) const {+    inline T* getBlock(std::size_t blockNum) const {         return blockLookupTable[blockNum];     } -    inline T& get(size_t index) const {-        size_t nindex = index + INITIALBLOCKSIZE;-        size_t blockNum = (63 - __builtin_clzll(nindex));-        size_t blockInd = (nindex) & ((1 << blockNum) - 1);+    inline T& get(std::size_t index) const {+        std::size_t nindex = index + INITIALBLOCKSIZE;+        std::size_t blockNum = (63 - __builtin_clzll(nindex));+        std::size_t blockInd = (nindex) & ((1 << blockNum) - 1);         return this->getBlock(blockNum - BLOCKBITS)[blockInd];     } -    void insertAt(size_t index, T value) {+    void insertAt(std::size_t index, T value) {         // starting with an initial blocksize requires some shifting to transform into a nice powers of two         // series-        size_t blockNum = (63 - __builtin_clzll(index + INITIALBLOCKSIZE)) - BLOCKBITS;+        std::size_t blockNum = (63 - __builtin_clzll(index + INITIALBLOCKSIZE)) - BLOCKBITS;          // allocate the block if not allocated         if (blockLookupTable[blockNum].load() == nullptr) {@@ -110,14 +111,14 @@         freeList();         numElements.store(0);     }-    const size_t BLOCKBITS = 16ul;-    const size_t INITIALBLOCKSIZE = (1ul << BLOCKBITS);+    const std::size_t BLOCKBITS = 16ul;+    const std::size_t INITIALBLOCKSIZE = (1ul << BLOCKBITS);      // number of elements currently stored within-    std::atomic<size_t> numElements{0};+    std::atomic<std::size_t> numElements{0};      // 2^64 - 1 elements can be stored (default initialised to nullptrs)-    static constexpr size_t maxContainers = 64;+    static constexpr std::size_t maxContainers = 64;     std::array<std::atomic<T*>, maxContainers> blockLookupTable = {};      // for parallel node insertions@@ -129,7 +130,7 @@     void freeList() {         slock.lock();         // delete all - deleting a nullptr is a no-op-        for (size_t i = 0; i < maxContainers; ++i) {+        for (std::size_t i = 0; i < maxContainers; ++i) {             delete[] blockLookupTable[i].load();             // reset the container within to be empty.             blockLookupTable[i].store(nullptr);@@ -142,7 +143,7 @@ class PiggyList { public:     PiggyList() : num_containers(0), container_size(0), m_size(0) {}-    PiggyList(size_t initialbitsize)+    PiggyList(std::size_t initialbitsize)             : BLOCKBITS(initialbitsize), num_containers(0), container_size(0), m_size(0) {}      /** copy constructor */@@ -152,8 +153,8 @@         m_size.store(other.m_size.load());         // copy each chunk from other into this         // the size of the next container to allocate-        size_t cSize = BLOCKSIZE;-        for (size_t i = 0; i < other.num_containers; ++i) {+        std::size_t cSize = BLOCKSIZE;+        for (std::size_t i = 0; i < other.num_containers; ++i) {             this->blockLookupTable[i] = new T[cSize];             std::memcpy(this->blockLookupTable[i], other.blockLookupTable[i], cSize * sizeof(T));             cSize <<= 1;@@ -177,16 +178,16 @@      *  that haven't had time to had containers created and updated      * @return the number of nodes exist within the list + number of nodes queued to be inserted      */-    inline size_t size() const {+    inline std::size_t size() const {         return m_size.load();     }; -    inline T* getBlock(size_t blocknum) const {+    inline T* getBlock(std::size_t blocknum) const {         return this->blockLookupTable[blocknum];     } -    size_t append(T element) {-        size_t new_index = m_size.fetch_add(1, std::memory_order_acquire);+    std::size_t append(T element) {+        std::size_t new_index = m_size.fetch_add(1, std::memory_order_acquire);          // will this not fit?         if (container_size < new_index + 1) {@@ -206,8 +207,8 @@         return new_index;     } -    size_t createNode() {-        size_t new_index = m_size.fetch_add(1, std::memory_order_acquire);+    std::size_t createNode() {+        std::size_t new_index = m_size.fetch_add(1, std::memory_order_acquire);          // will this not fit?         if (container_size < new_index + 1) {@@ -231,11 +232,11 @@      * @param index position to search      * @return the value at index      */-    inline T& get(size_t index) const {+    inline T& get(std::size_t index) const {         // supa fast 2^16 size first block-        size_t nindex = index + BLOCKSIZE;-        size_t blockNum = (63 - __builtin_clzll(nindex));-        size_t blockInd = (nindex) & ((1 << blockNum) - 1);+        std::size_t nindex = index + BLOCKSIZE;+        std::size_t blockNum = (63 - __builtin_clzll(nindex));+        std::size_t blockInd = (nindex) & ((1 << blockNum) - 1);         return this->getBlock(blockNum - BLOCKBITS)[blockInd];     } @@ -252,7 +253,7 @@     }      class iterator : std::iterator<std::forward_iterator_tag, T> {-        size_t cIndex = 0;+        std::size_t cIndex = 0;         PiggyList* bl;      public:@@ -262,7 +263,7 @@         /* begin iterator for iterating over all elements */         iterator(PiggyList* bl) : bl(bl){};         /* ender iterator for marking the end of the iteration */-        iterator(PiggyList* bl, size_t beginInd) : cIndex(beginInd), bl(bl){};+        iterator(PiggyList* bl, std::size_t beginInd) : cIndex(beginInd), bl(bl){};          T operator*() {             return bl->get(cIndex);@@ -297,17 +298,17 @@     iterator end() {         return iterator(this, size());     }-    const size_t BLOCKBITS = 16ul;-    const size_t BLOCKSIZE = (1ul << BLOCKBITS);+    const std::size_t BLOCKBITS = 16ul;+    const std::size_t BLOCKSIZE = (1ul << BLOCKBITS);      // number of inserted-    std::atomic<size_t> num_containers = 0;-    size_t allocsize = BLOCKSIZE;-    std::atomic<size_t> container_size = 0;-    std::atomic<size_t> m_size = 0;+    std::atomic<std::size_t> num_containers = 0;+    std::size_t allocsize = BLOCKSIZE;+    std::atomic<std::size_t> container_size = 0;+    std::atomic<std::size_t> m_size = 0;      // > 2^64 elements can be stored (default initialise to nullptrs)-    static constexpr size_t max_conts = 64;+    static constexpr std::size_t max_conts = 64;     std::array<T*, max_conts> blockLookupTable = {};      // for parallel node insertions@@ -319,7 +320,7 @@     void freeList() {         sl.lock();         // we don't know which ones are taken up!-        for (size_t i = 0; i < num_containers; ++i) {+        for (std::size_t i = 0; i < num_containers; ++i) {             delete[] blockLookupTable[i];         }         sl.unlock();
cbits/souffle/datastructure/UnionFind.h view
@@ -18,6 +18,7 @@  #include "souffle/datastructure/LambdaBTree.h" #include "souffle/datastructure/PiggyList.h"+#include "souffle/utility/MiscUtil.h" #include <atomic> #include <cstddef> #include <cstdint>@@ -67,7 +68,7 @@     /**      * Return the number of elements in this disjoint set (not the number of pairs)      */-    inline size_t size() {+    inline std::size_t size() {         auto sz = a_blocks.size();         return sz;     };@@ -191,7 +192,7 @@      */     inline block_t makeNode() {         // make node and find out where we've added it-        size_t nodeDetails = a_blocks.createNode();+        std::size_t nodeDetails = a_blocks.createNode();          a_blocks.get(nodeDetails).store(pr2b(nodeDetails, 0)); 
cbits/souffle/io/IOSystem.h view
@@ -1,6 +1,6 @@ /*  * Souffle - A Datalog Compiler- * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved+ * Copyright (c) 2021, 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@@ -15,6 +15,7 @@ #pragma once  #include "souffle/RamTypes.h"+#include "souffle/RecordTable.h" #include "souffle/SymbolTable.h" #include "souffle/io/ReadStream.h" #include "souffle/io/ReadStreamCSV.h"@@ -34,7 +35,6 @@ #include <string>  namespace souffle {-class RecordTable;  class IOSystem { public:
cbits/souffle/io/ReadStream.h view
@@ -1,6 +1,6 @@ /*  * Souffle - A Datalog Compiler- * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved+ * Copyright (c) 2021, 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@@ -42,8 +42,6 @@ public:     template <typename T>     void readAll(T& relation) {-        auto lease = symbolTable.acquireLock();-        (void)lease;         while (const auto next = readNextTuple()) {             const RamDomain* ramDomain = next.get();             relation.insert(ramDomain);@@ -60,9 +58,9 @@      * @param consumed - if not nullptr: number of characters read.      *      */-    RamDomain readRecord(const std::string& source, const std::string& recordTypeName, size_t pos = 0,-            size_t* charactersRead = nullptr) {-        const size_t initial_position = pos;+    RamDomain readRecord(const std::string& source, const std::string& recordTypeName, std::size_t pos = 0,+            std::size_t* charactersRead = nullptr) {+        const std::size_t initial_position = pos;          // Check if record type information are present         auto&& recordInfo = types["records"][recordTypeName];@@ -80,15 +78,15 @@         }          auto&& recordTypes = recordInfo["types"];-        const size_t recordArity = recordInfo["arity"].long_value();+        const std::size_t recordArity = recordInfo["arity"].long_value();          std::vector<RamDomain> recordValues(recordArity);          consumeChar(source, '[', pos); -        for (size_t i = 0; i < recordArity; ++i) {+        for (std::size_t i = 0; i < recordArity; ++i) {             const std::string& recordType = recordTypes[i].string_value();-            size_t consumed = 0;+            std::size_t consumed = 0;              if (i > 0) {                 consumeChar(source, ',', pos);@@ -96,7 +94,7 @@             consumeWhiteSpace(source, pos);             switch (recordType[0]) {                 case 's': {-                    recordValues[i] = symbolTable.unsafeLookup(readUntil(source, ",]", pos, &consumed));+                    recordValues[i] = symbolTable.encode(readSymbol(source, ",]", pos, &consumed));                     break;                 }                 case 'i': {@@ -132,11 +130,14 @@         return recordTable.pack(recordValues.data(), recordValues.size());     } -    RamDomain readADT(const std::string& source, const std::string& adtName, size_t pos = 0,-            size_t* charactersRead = nullptr) {-        const size_t initial_position = pos;+    RamDomain readADT(const std::string& source, const std::string& adtName, std::size_t pos = 0,+            std::size_t* charactersRead = nullptr) {+        const std::size_t initial_position = pos; -        // Branch will are encoded as [branchIdx, [branchValues...]].+        // Branch will are encoded as one of the:+        // [branchIdx, [branchValues...]]+        // [branchIdx, branchValue]+        // branchIdx         RamDomain branchIdx = -1;          auto&& adtInfo = types["ADTs"][adtName];@@ -148,11 +149,12 @@          // Consume initial character         consumeChar(source, '$', pos);-        std::string constructor = readAlphanumeric(source, pos);+        std::string constructor = readIdentifier(source, pos);          json11::Json branchInfo = [&]() -> json11::Json {             for (auto branch : branches.array_items()) {                 ++branchIdx;+                 if (branch["name"].string_value() == constructor) {                     return branch;                 }@@ -169,19 +171,26 @@             if (charactersRead != nullptr) {                 *charactersRead = pos - initial_position;             }-            RamDomain emptyArgs = recordTable.pack(toVector<RamDomain>().data(), 0);-            return recordTable.pack(toVector<RamDomain>(branchIdx, emptyArgs).data(), 2);++            if (adtInfo["enum"].bool_value()) {+                return branchIdx;+            }++            const RamDomain empty[] = {};+            RamDomain emptyArgs = recordTable.pack(empty, 0);+            const RamDomain record[] = {branchIdx, emptyArgs};+            return recordTable.pack(record, 2);         }          consumeChar(source, '(', pos);          std::vector<RamDomain> branchArgs(branchTypes.size()); -        for (size_t i = 0; i < branchTypes.size(); ++i) {+        for (std::size_t i = 0; i < branchTypes.size(); ++i) {             auto argType = branchTypes[i].string_value();             assert(!argType.empty()); -            size_t consumed = 0;+            std::size_t consumed = 0;              if (i > 0) {                 consumeChar(source, ',', pos);@@ -190,7 +199,7 @@              switch (argType[0]) {                 case 's': {-                    branchArgs[i] = symbolTable.unsafeLookup(readUntil(source, ",)", pos, &consumed));+                    branchArgs[i] = symbolTable.encode(readSymbol(source, ",)", pos, &consumed));                     break;                 }                 case 'i': {@@ -233,31 +242,35 @@             }         }(); -        return recordTable.pack(toVector<RamDomain>(branchIdx, branchValue).data(), 2);+        RamDomain rec[2] = {branchIdx, branchValue};+        return recordTable.pack(rec, 2);     }      /**-     * Read the next alphanumeric sequence (corresponding to IDENT).+     * Read the next alphanumeric + ('_', '?') sequence (corresponding to IDENT).      * Consume preceding whitespace.      * TODO (darth_tytus): use std::string_view?      */-    std::string readAlphanumeric(const std::string& source, size_t& pos) {+    std::string readIdentifier(const std::string& source, std::size_t& pos) {         consumeWhiteSpace(source, pos);         if (pos >= source.length()) {             throw std::invalid_argument("Unexpected end of input");         } -        const size_t bgn = pos;-        while (pos < source.length() && std::isalnum(static_cast<unsigned char>(source[pos]))) {+        const std::size_t bgn = pos;+        while (pos < source.length()) {+            unsigned char ch = static_cast<unsigned char>(source[pos]);+            bool valid = std::isalnum(ch) || ch == '_' || ch == '?';+            if (!valid) break;             ++pos;         }          return source.substr(bgn, pos - bgn);     } -    std::string readUntil(const std::string& source, const std::string stopChars, const size_t pos,-            size_t* charactersRead) {-        size_t endOfSymbol = source.find_first_of(stopChars, pos);+    std::string readUntil(const std::string& source, const std::string& stopChars, const std::size_t pos,+            std::size_t* charactersRead) {+        std::size_t endOfSymbol = source.find_first_of(stopChars, pos);          if (endOfSymbol == std::string::npos) {             throw std::invalid_argument("Unexpected end of input");@@ -268,10 +281,85 @@         return source.substr(pos, *charactersRead);     } +    std::string readQuotedSymbol(const std::string& source, std::size_t pos, std::size_t* charactersRead) {+        const std::size_t start = pos;+        const std::size_t end = source.length();++        const char quoteMark = source[pos];+        ++pos;++        const std::size_t startOfSymbol = pos;+        std::size_t endOfSymbol = std::string::npos;+        bool hasEscaped = false;++        bool escaped = false;+        while (pos < end) {+            if (escaped) {+                hasEscaped = true;+                escaped = false;+                ++pos;+                continue;+            }++            const char c = source[pos];+            if (c == quoteMark) {+                endOfSymbol = pos;+                ++pos;+                break;+            }+            if (c == '\\') {+                escaped = true;+            }+            ++pos;+        }++        if (endOfSymbol == std::string::npos) {+            throw std::invalid_argument("Unexpected end of input");+        }++        *charactersRead = pos - start;++        std::size_t lengthOfSymbol = endOfSymbol - startOfSymbol;++        // fast handling of symbol without escape sequence+        if (!hasEscaped) {+            return source.substr(startOfSymbol, lengthOfSymbol);+        } else {+            // slow handling of symbol with escape sequence+            std::string symbol;+            symbol.reserve(lengthOfSymbol);+            bool escaped = false;+            for (std::size_t pos = startOfSymbol; pos < endOfSymbol; ++pos) {+                char ch = source[pos];+                if (escaped || ch != '\\') {+                    symbol.push_back(ch);+                    escaped = false;+                } else {+                    escaped = true;+                }+            }+            return symbol;+        }+    }+     /**+     * Read the next symbol.+     * It is either a double-quoted symbol with backslash-escaped chars, or the+     * longuest sequence that do not contains any of the given stopChars.+     * */+    std::string readSymbol(const std::string& source, const std::string& stopChars, const std::size_t pos,+            std::size_t* charactersRead) {+        if (source[pos] == '"') {+            return readQuotedSymbol(source, pos, charactersRead);+        } else {+            return readUntil(source, stopChars, pos, charactersRead);+        }+    }++    /**      * Read past given character, consuming any preceding whitespace.      */-    void consumeChar(const std::string& str, char c, size_t& pos) {+    void consumeChar(const std::string& str, char c, std::size_t& pos) {         consumeWhiteSpace(str, pos);         if (pos >= str.length()) {             throw std::invalid_argument("Unexpected end of input");@@ -287,7 +375,7 @@     /**      * Advance position in the string until first non-whitespace character.      */-    void consumeWhiteSpace(const std::string& str, size_t& pos) {+    void consumeWhiteSpace(const std::string& str, std::size_t& pos) {         while (pos < str.length() && std::isspace(static_cast<unsigned char>(str[pos]))) {             ++pos;         }
cbits/souffle/io/ReadStreamCSV.h view
@@ -1,6 +1,6 @@ /*  * Souffle - A Datalog Compiler- * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved+ * Copyright (c) 2021, 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@@ -15,6 +15,7 @@ #pragma once  #include "souffle/RamTypes.h"+#include "souffle/RecordTable.h" #include "souffle/SymbolTable.h" #include "souffle/io/ReadStream.h" #include "souffle/utility/ContainerUtil.h"@@ -40,15 +41,21 @@ #include <vector>  namespace souffle {-class RecordTable;  class ReadStreamCSV : public ReadStream { public:     ReadStreamCSV(std::istream& file, const std::map<std::string, std::string>& rwOperation,             SymbolTable& symbolTable, RecordTable& recordTable)             : ReadStream(rwOperation, symbolTable, recordTable),-              delimiter(getOr(rwOperation, "delimiter", "\t")), file(file), lineNumber(0),+              rfc4180(getOr(rwOperation, "rfc4180", "false") == std::string("true")),+              delimiter(getOr(rwOperation, "delimiter", (rfc4180 ? "," : "\t"))), file(file), lineNumber(0),               inputMap(getInputColumnMap(rwOperation, static_cast<unsigned int>(arity))) {+        if (rfc4180 && delimiter.find('"') != std::string::npos) {+            std::stringstream errorMessage;+            errorMessage << "CSV delimiter cannot contain '\"' character when rfc4180 is enabled.";+            throw std::invalid_argument(errorMessage.str());+        }+         while (inputMap.size() < arity) {             int size = static_cast<int>(inputMap.size());             inputMap[size] = size;@@ -67,7 +74,7 @@             return nullptr;         }         std::string line;-        Own<RamDomain[]> tuple = std::make_unique<RamDomain[]>(typeAttributes.size());+        Own<RamDomain[]> tuple = mk<RamDomain[]>(typeAttributes.size());          if (!getline(file, line)) {             return nullptr;@@ -78,12 +85,11 @@         }         ++lineNumber; -        size_t start = 0;-        size_t end = 0;-        size_t columnsFilled = 0;+        std::size_t start = 0;+        std::size_t columnsFilled = 0;         for (uint32_t column = 0; columnsFilled < arity; column++) {-            size_t charactersRead = 0;-            std::string element = nextElement(line, start, end);+            std::size_t charactersRead = 0;+            std::string element = nextElement(line, start);             if (inputMap.count(column) == 0) {                 continue;             }@@ -93,7 +99,7 @@                 auto&& ty = typeAttributes.at(inputMap[column]);                 switch (ty[0]) {                     case 's': {-                        tuple[inputMap[column]] = symbolTable.unsafeLookup(element);+                        tuple[inputMap[column]] = symbolTable.encode(element);                         charactersRead = element.size();                         break;                     }@@ -139,7 +145,7 @@      * Read an unsigned element. Possible bases are 2, 10, 16      * Base is indicated by the first two chars.      */-    RamUnsigned readRamUnsigned(const std::string& element, size_t& charactersRead) {+    RamUnsigned readRamUnsigned(const std::string& element, std::size_t& charactersRead) {         // Sanity check         assert(element.size() > 0); @@ -156,13 +162,64 @@         return value;     } -    std::string nextElement(const std::string& line, size_t& start, size_t& end) {+    std::string nextElement(const std::string& line, std::size_t& start) {         std::string element; +        if (rfc4180) {+            if (line[start] == '"') {+                // quoted field+                const std::size_t end = line.length();+                std::size_t pos = start + 1;+                bool foundEndQuote = false;+                while (pos < end) {+                    char c = line[pos++];+                    if (c == '"' && (pos < end) && line[pos] == '"') {+                        // two double-quote => one double-quote+                        element.push_back('"');+                        ++pos;+                    } else if (c == '"') {+                        foundEndQuote = true;+                        break;+                    } else {+                        element.push_back(c);+                    }+                }++                if (!foundEndQuote) {+                    // missing closing quote+                    std::stringstream errorMessage;+                    errorMessage << "Unbalanced field quote in line " << lineNumber << "; ";+                    throw std::invalid_argument(errorMessage.str());+                }++                // field must be immediately followed by delimiter or end of line+                if (pos != line.length()) {+                    std::size_t nextDelimiter = line.find(delimiter, pos);+                    if (nextDelimiter != pos) {+                        std::stringstream errorMessage;+                        errorMessage << "Separator expected immediately after quoted field in line "+                                     << lineNumber << "; ";+                        throw std::invalid_argument(errorMessage.str());+                    }+                }++                start = pos + delimiter.size();+                return element;+            } else {+                // non-quoted field, span until next delimiter or end of line+                const std::size_t end = std::min(line.find(delimiter, start), line.length());+                element = line.substr(start, end - start);+                start = end + delimiter.size();++                return element;+            }+        }++        std::size_t end = start;         // Handle record/tuple delimiter coincidence.         if (delimiter.find(',') != std::string::npos) {             int record_parens = 0;-            size_t next_delimiter = line.find(delimiter, start);+            std::size_t next_delimiter = line.find(delimiter, start);              // Find first delimiter after the record.             while (end < std::min(next_delimiter, line.length()) || record_parens != 0) {@@ -190,7 +247,7 @@             // Handle the end-of-the-line case where parenthesis are unbalanced.             if (record_parens != 0) {                 std::stringstream errorMessage;-                errorMessage << "Unbalanced record parenthesis " << lineNumber << "; ";+                errorMessage << "Unbalanced record parenthesis in line " << lineNumber << "; ";                 throw std::invalid_argument(errorMessage.str());             }         } else {@@ -234,9 +291,10 @@         return inputColumnMap;     } +    const bool rfc4180;     const std::string delimiter;     std::istream& file;-    size_t lineNumber;+    std::size_t lineNumber;     std::map<int, int> inputMap; }; @@ -248,7 +306,10 @@               baseName(souffle::baseName(getFileName(rwOperation))),               fileHandle(getFileName(rwOperation), std::ios::in | std::ios::binary) {         if (!fileHandle.is_open()) {-            throw std::invalid_argument("Cannot open fact file " + baseName + "\n");+            // suppress error message in case file cannot be open when flag -w is set+            if (getOr(rwOperation, "no-warn", "false") != "true") {+                throw std::invalid_argument("Cannot open fact file " + baseName + "\n");+            }         }         // Strip headers if we're using them         if (getOr(rwOperation, "headers", "false") == "true") {
cbits/souffle/io/ReadStreamJSON.h view
@@ -15,6 +15,7 @@ #pragma once  #include "souffle/RamTypes.h"+#include "souffle/RecordTable.h" #include "souffle/SymbolTable.h" #include "souffle/io/ReadStream.h" #include "souffle/utility/ContainerUtil.h"@@ -37,8 +38,14 @@ #include <vector>  namespace souffle {-class RecordTable; +template <typename... T>+[[noreturn]] static void throwError(T const&... t) {+    std::ostringstream out;+    (out << ... << t);+    throw std::runtime_error(out.str());+}+ class ReadStreamJSON : public ReadStream { public:     ReadStreamJSON(std::istream& file, const std::map<std::string, std::string>& rwOperation,@@ -47,18 +54,18 @@         std::string err;         params = Json::parse(rwOperation.at("params"), err);         if (err.length() > 0) {-            fatal("cannot get internal params: %s", err);+            throwError("cannot get internal params: ", err);         }     }  protected:     std::istream& file;-    size_t pos;+    std::size_t pos;     Json jsonSource;     Json params;     bool isInitialized;     bool useObjects;-    std::map<const std::string, const size_t> paramIndex;+    std::map<const std::string, const std::size_t> paramIndex;      Own<RamDomain[]> readNextTuple() override {         // for some reasons we cannot initalized our json objects in constructor@@ -71,22 +78,27 @@             jsonSource = Json::parse(source, error);             // it should be wrapped by an extra array             if (error.length() > 0 || !jsonSource.is_array()) {-                fatal("cannot deserialize json because %s:\n%s", error, source);+                throwError("cannot deserialize json because ", error, ":\n", source);             } +            if (jsonSource.array_items().empty()) {+                // No tuples defined+                return nullptr;+            }+             // we only check the first one, since there are extra checks             // in readNextTupleObject/readNextTupleList             if (jsonSource[0].is_array()) {                 useObjects = false;             } else if (jsonSource[0].is_object()) {                 useObjects = true;-                size_t index_pos = 0;+                std::size_t index_pos = 0;                 for (auto param : params["relation"]["params"].array_items()) {                     paramIndex.insert(std::make_pair(param.string_value(), index_pos));                     index_pos++;                 }             } else {-                fatal("the input is neither list nor object format");+                throwError("the input is neither list nor object format");             }         } @@ -102,16 +114,16 @@             return nullptr;         } -        Own<RamDomain[]> tuple = std::make_unique<RamDomain[]>(typeAttributes.size());+        Own<RamDomain[]> tuple = mk<RamDomain[]>(typeAttributes.size());         const Json& jsonObj = jsonSource[pos];         assert(jsonObj.is_array() && "the input is not json array");         pos++;-        for (size_t i = 0; i < typeAttributes.size(); ++i) {+        for (std::size_t i = 0; i < typeAttributes.size(); ++i) {             try {                 auto&& ty = typeAttributes.at(i);                 switch (ty[0]) {                     case 's': {-                        tuple[i] = symbolTable.unsafeLookup(jsonObj[i].string_value());+                        tuple[i] = symbolTable.encode(jsonObj[i].string_value());                         break;                     }                     case 'r': {@@ -130,7 +142,7 @@                         tuple[i] = static_cast<RamDomain>(jsonObj[i].number_value());                         break;                     }-                    default: fatal("invalid type attribute: `%c`", ty[0]);+                    default: throwError("invalid type attribute: '", ty[0], "'");                 }             } catch (...) {                 std::stringstream errorMessage;@@ -160,13 +172,13 @@          assert(source.is_array() && "the input is not json array");         auto&& recordTypes = recordInfo["types"];-        const size_t recordArity = recordInfo["arity"].long_value();+        const std::size_t recordArity = recordInfo["arity"].long_value();         std::vector<RamDomain> recordValues(recordArity);-        for (size_t i = 0; i < recordArity; ++i) {+        for (std::size_t i = 0; i < recordArity; ++i) {             const std::string& recordType = recordTypes[i].string_value();             switch (recordType[0]) {                 case 's': {-                    recordValues[i] = symbolTable.unsafeLookup(source[i].string_value());+                    recordValues[i] = symbolTable.encode(source[i].string_value());                     break;                 }                 case 'r': {@@ -185,7 +197,7 @@                     recordValues[i] = static_cast<RamDomain>(source[i].number_value());                     break;                 }-                default: fatal("invalid type attribute");+                default: throwError("invalid type attribute");             }         } @@ -197,7 +209,7 @@             return nullptr;         } -        Own<RamDomain[]> tuple = std::make_unique<RamDomain[]>(typeAttributes.size());+        Own<RamDomain[]> tuple = mk<RamDomain[]>(typeAttributes.size());         const Json& jsonObj = jsonSource[pos];         assert(jsonObj.is_object() && "the input is not json object");         pos++;@@ -205,13 +217,13 @@             try {                 // get the corresponding position by parameter name                 if (paramIndex.find(p.first) == paramIndex.end()) {-                    fatal("invalid parameter: %s", p.first);+                    throwError("invalid parameter: ", p.first);                 }-                size_t i = paramIndex.at(p.first);+                std::size_t i = paramIndex.at(p.first);                 auto&& ty = typeAttributes.at(i);                 switch (ty[0]) {                     case 's': {-                        tuple[i] = symbolTable.unsafeLookup(p.second.string_value());+                        tuple[i] = symbolTable.encode(p.second.string_value());                         break;                     }                     case 'r': {@@ -230,7 +242,7 @@                         tuple[i] = static_cast<RamDomain>(p.second.number_value());                         break;                     }-                    default: fatal("invalid type attribute: `%c`", ty[0]);+                    default: throwError("invalid type attribute: '", ty[0], "'");                 }             } catch (...) {                 std::stringstream errorMessage;@@ -245,9 +257,9 @@     RamDomain readNextElementObject(const Json& source, const std::string& recordTypeName) {         auto&& recordInfo = types["records"][recordTypeName];         const std::string recordName = recordTypeName.substr(2);-        std::map<const std::string, const size_t> recordIndex;+        std::map<const std::string, const std::size_t> recordIndex; -        size_t index_pos = 0;+        std::size_t index_pos = 0;         for (auto param : params["records"][recordName]["params"].array_items()) {             recordIndex.insert(std::make_pair(param.string_value(), index_pos));             index_pos++;@@ -264,19 +276,19 @@          assert(source.is_object() && "the input is not json object");         auto&& recordTypes = recordInfo["types"];-        const size_t recordArity = recordInfo["arity"].long_value();+        const std::size_t recordArity = recordInfo["arity"].long_value();         std::vector<RamDomain> recordValues(recordArity);         recordValues.reserve(recordIndex.size());         for (auto readParam : source.object_items()) {             // get the corresponding position by parameter name             if (recordIndex.find(readParam.first) == recordIndex.end()) {-                fatal("invalid parameter: %s", readParam.first);+                throwError("invalid parameter: ", readParam.first);             }-            size_t i = recordIndex.at(readParam.first);+            std::size_t i = recordIndex.at(readParam.first);             auto&& type = recordTypes[i].string_value();             switch (type[0]) {                 case 's': {-                    recordValues[i] = symbolTable.unsafeLookup(readParam.second.string_value());+                    recordValues[i] = symbolTable.encode(readParam.second.string_value());                     break;                 }                 case 'r': {@@ -295,7 +307,7 @@                     recordValues[i] = static_cast<RamDomain>(readParam.second.number_value());                     break;                 }-                default: fatal("invalid type attribute: `%c`", type[0]);+                default: throwError("invalid type attribute: '", type[0], "'");             }         } @@ -307,6 +319,8 @@ public:     ReadFileJSON(const std::map<std::string, std::string>& rwOperation, SymbolTable& symbolTable,             RecordTable& recordTable)+            // FIXME: This is bordering on UB - we're passing an unconstructed+            // object (fileHandle) to the base class             : ReadStreamJSON(fileHandle, rwOperation, symbolTable, recordTable),               baseName(souffle::baseName(getFileName(rwOperation))),               fileHandle(getFileName(rwOperation), std::ios::in | std::ios::binary) {
cbits/souffle/io/ReadStreamSQLite.h view
@@ -1,6 +1,6 @@ /*  * Souffle - A Datalog Compiler- * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved+ * Copyright (c) 2021, 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@@ -15,6 +15,7 @@ #pragma once  #include "souffle/RamTypes.h"+#include "souffle/RecordTable.h" #include "souffle/SymbolTable.h" #include "souffle/io/ReadStream.h" #include "souffle/utility/MiscUtil.h"@@ -30,7 +31,6 @@ #include <sqlite3.h>  namespace souffle {-class RecordTable;  class ReadStreamSQLite : public ReadStream { public:@@ -60,7 +60,7 @@             return nullptr;         } -        Own<RamDomain[]> tuple = std::make_unique<RamDomain[]>(arity + auxiliaryArity);+        Own<RamDomain[]> tuple = mk<RamDomain[]>(arity + auxiliaryArity);          uint32_t column;         for (column = 0; column < arity; column++) {@@ -73,7 +73,7 @@             try {                 auto&& ty = typeAttributes.at(column);                 switch (ty[0]) {-                    case 's': tuple[column] = symbolTable.unsafeLookup(element); break;+                    case 's': tuple[column] = symbolTable.encode(element); break;                     case 'i':                     case 'u':                     case 'f':
cbits/souffle/io/SerialisationStream.h view
@@ -18,6 +18,7 @@  #include "souffle/RamTypes.h" +#include "souffle/utility/StringUtil.h" #include "souffle/utility/json11.h" #include <cassert> #include <cstddef>@@ -28,7 +29,7 @@  namespace souffle { -class RecordTable;+class RecordTableInterface; class SymbolTable;  using json11::Json;@@ -42,48 +43,54 @@     template <typename A>     using RO = std::conditional_t<readOnlyTables, const A, A>; -    SerialisationStream(RO<SymbolTable>& symTab, RO<RecordTable>& recTab, Json types,-            std::vector<std::string> relTypes, size_t auxArity = 0)+    SerialisationStream(RO<SymbolTable>& symTab, RO<RecordTableInterface>& recTab, Json types,+            std::vector<std::string> relTypes, std::size_t auxArity = 0)             : symbolTable(symTab), recordTable(recTab), types(std::move(types)),               typeAttributes(std::move(relTypes)), arity(typeAttributes.size() - auxArity),               auxiliaryArity(auxArity) {} -    SerialisationStream(RO<SymbolTable>& symTab, RO<RecordTable>& recTab, Json types)+    SerialisationStream(RO<SymbolTable>& symTab, RO<RecordTableInterface>& recTab, Json types)             : symbolTable(symTab), recordTable(recTab), types(std::move(types)) {         setupFromJson();     } -    SerialisationStream(RO<SymbolTable>& symTab, RO<RecordTable>& recTab,+    SerialisationStream(RO<SymbolTable>& symTab, RO<RecordTableInterface>& recTab,             const std::map<std::string, std::string>& rwOperation)             : symbolTable(symTab), recordTable(recTab) {         std::string parseErrors;         types = Json::parse(rwOperation.at("types"), parseErrors);         assert(parseErrors.size() == 0 && "Internal JSON parsing failed.");++        auxiliaryArity = RamSignedFromString(getOr(rwOperation, "auxArity", "0"));+         setupFromJson();     }      RO<SymbolTable>& symbolTable;-    RO<RecordTable>& recordTable;+    RO<RecordTableInterface>& recordTable;     Json types;     std::vector<std::string> typeAttributes; -    size_t arity = 0;-    size_t auxiliaryArity = 0;+    std::size_t arity = 0;+    std::size_t auxiliaryArity = 0;  private:     void setupFromJson() {         auto&& relInfo = types["relation"];-        arity = static_cast<size_t>(relInfo["arity"].long_value());-        auxiliaryArity = static_cast<size_t>(relInfo["auxArity"].long_value());+        arity = static_cast<std::size_t>(relInfo["arity"].long_value());          assert(relInfo["types"].is_array());         auto&& relTypes = relInfo["types"].array_items();-        assert(relTypes.size() == (arity + auxiliaryArity));+        assert(relTypes.size() == arity); -        for (size_t i = 0; i < arity + auxiliaryArity; ++i) {-            auto&& type = relTypes[i].string_value();-            assert(!type.empty() && "malformed types tag");-            typeAttributes.push_back(type);+        for (const auto& jsonType : relTypes) {+            const auto& typeString = jsonType.string_value();+            assert(!typeString.empty() && "malformed types tag");+            typeAttributes.push_back(typeString);+        }++        for (std::size_t i = 0; i < auxiliaryArity; i++) {+            typeAttributes.push_back("i:number");         }     } };
cbits/souffle/io/WriteStream.h view
@@ -1,6 +1,6 @@ /*  * Souffle - A Datalog Compiler- * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved+ * Copyright (c) 2021, 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@@ -22,6 +22,7 @@ #include "souffle/utility/json11.h" #include <cassert> #include <cstddef>+#include <iomanip> #include <map> #include <memory> #include <ostream>@@ -43,8 +44,6 @@         if (summary) {             return writeSize(relation.size());         }-        auto lease = symbolTable.acquireLock();-        (void)lease;  // silence "unused variable" warning         if (arity == 0) {             if (relation.begin() != relation.end()) {                 writeNullary();@@ -72,9 +71,14 @@      template <typename Tuple>     void writeNext(const Tuple tuple) {-        writeNextTuple(tuple.data);+        using tcb::make_span;+        writeNextTuple(make_span(tuple).data());     } +    virtual void outputSymbol(std::ostream& destination, const std::string& value) {+        destination << value;+    }+     void outputRecord(std::ostream& destination, const RamDomain value, const std::string& name) {         auto&& recordInfo = types["records"][name]; @@ -88,14 +92,14 @@         }          auto&& recordTypes = recordInfo["types"];-        const size_t recordArity = recordInfo["arity"].long_value();+        const std::size_t recordArity = recordInfo["arity"].long_value();          const RamDomain* tuplePtr = recordTable.unpack(value, recordArity);          destination << "[";          // print record's elements-        for (size_t i = 0; i < recordArity; ++i) {+        for (std::size_t i = 0; i < recordArity; ++i) {             if (i > 0) {                 destination << ", ";             }@@ -107,7 +111,7 @@                 case 'i': destination << recordValue; break;                 case 'f': destination << ramBitCast<RamFloat>(recordValue); break;                 case 'u': destination << ramBitCast<RamUnsigned>(recordValue); break;-                case 's': destination << symbolTable.unsafeResolve(recordValue); break;+                case 's': outputSymbol(destination, symbolTable.decode(recordValue)); break;                 case 'r': outputRecord(destination, recordValue, recordType); break;                 case '+': outputADT(destination, recordValue, recordType); break;                 default: fatal("Unsupported type attribute: `%c`", recordType[0]);@@ -121,28 +125,40 @@          assert(!adtInfo.is_null() && "Missing adt type information"); -        const size_t numBranches = adtInfo["arity"].long_value();+        const std::size_t numBranches = adtInfo["arity"].long_value();         assert(numBranches > 0); -        // adt is encoded as [branchID, [branch_args]] when |branch_args| != 1-        // and as [branchID, arg] when a branch takes a single argument.-        const RamDomain* tuplePtr = recordTable.unpack(value, 2);+        // adt is encoded in one of three possible ways:+        // [branchID, [branch_args]] when |branch_args| != 1+        // [branchID, arg] when a branch takes a single argument.+        // branchID when ADT is an enumeration.+        bool isEnum = adtInfo["enum"].bool_value(); -        const RamDomain branchId = tuplePtr[0];-        const RamDomain rawBranchArgs = tuplePtr[1];+        RamDomain branchId = value;+        const RamDomain* branchArgs = nullptr;+        json11::Json branchInfo;+        json11::Json::array branchTypes; -        auto branchInfo = adtInfo["branches"][branchId];-        auto branchTypes = branchInfo["types"].array_items();+        if (!isEnum) {+            const RamDomain* tuplePtr = recordTable.unpack(value, 2); -        // Prepare branch's arguments for output.-        const RamDomain* branchArgs = [&]() -> const RamDomain* {-            if (branchTypes.size() > 1) {-                return recordTable.unpack(rawBranchArgs, branchTypes.size());-            } else {-                return &rawBranchArgs;-            }-        }();+            branchId = tuplePtr[0];+            branchInfo = adtInfo["branches"][branchId];+            branchTypes = branchInfo["types"].array_items(); +            // Prepare branch's arguments for output.+            branchArgs = [&]() -> const RamDomain* {+                if (branchTypes.size() > 1) {+                    return recordTable.unpack(tuplePtr[1], branchTypes.size());+                } else {+                    return &tuplePtr[1];+                }+            }();+        } else {+            branchInfo = adtInfo["branches"][branchId];+            branchTypes = branchInfo["types"].array_items();+        }+         destination << "$" << branchInfo["name"].string_value();          if (branchTypes.size() > 0) {@@ -150,7 +166,7 @@         }          // Print arguments-        for (size_t i = 0; i < branchTypes.size(); ++i) {+        for (std::size_t i = 0; i < branchTypes.size(); ++i) {             if (i > 0) {                 destination << ", ";             }@@ -160,7 +176,7 @@                 case 'i': destination << branchArgs[i]; break;                 case 'f': destination << ramBitCast<RamFloat>(branchArgs[i]); break;                 case 'u': destination << ramBitCast<RamUnsigned>(branchArgs[i]); break;-                case 's': destination << symbolTable.unsafeResolve(branchArgs[i]); break;+                case 's': outputSymbol(destination, symbolTable.decode(branchArgs[i])); break;                 case 'r': outputRecord(destination, branchArgs[i], argType); break;                 case '+': outputADT(destination, branchArgs[i], argType); break;                 default: fatal("Unsupported type attribute: `%c`", argType[0]);
cbits/souffle/io/WriteStreamCSV.h view
@@ -1,6 +1,6 @@ /*  * Souffle - A Datalog Compiler- * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved+ * Copyright (c) 2021, 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@@ -15,6 +15,7 @@ #pragma once  #include "souffle/RamTypes.h"+#include "souffle/RecordTable.h" #include "souffle/SymbolTable.h" #include "souffle/io/WriteStream.h" #include "souffle/utility/ContainerUtil.h"@@ -35,21 +36,28 @@  namespace souffle { -class RecordTable;- class WriteStreamCSV : public WriteStream { protected:     WriteStreamCSV(const std::map<std::string, std::string>& rwOperation, const SymbolTable& symbolTable,             const RecordTable& recordTable)             : WriteStream(rwOperation, symbolTable, recordTable),-              delimiter(getOr(rwOperation, "delimiter", "\t")){};+              rfc4180(getOr(rwOperation, "rfc4180", "false") == std::string("true")),+              delimiter(getOr(rwOperation, "delimiter", (rfc4180 ? "," : "\t"))) {+        if (rfc4180 && delimiter.find('"') != std::string::npos) {+            std::stringstream errorMessage;+            errorMessage << "CSV delimiter cannot contain '\"' character when rfc4180 is enabled.";+            throw std::invalid_argument(errorMessage.str());+        }+    }; +    const bool rfc4180;+     const std::string delimiter;      void writeNextTupleCSV(std::ostream& destination, const RamDomain* tuple) {         writeNextTupleElement(destination, typeAttributes.at(0), tuple[0]); -        for (size_t col = 1; col < arity; ++col) {+        for (std::size_t col = 1; col < arity; ++col) {             destination << delimiter;             writeNextTupleElement(destination, typeAttributes.at(col), tuple[col]);         }@@ -57,14 +65,60 @@         destination << "\n";     } +    virtual void outputSymbol(std::ostream& destination, const std::string& value) {+        outputSymbol(destination, value, false);+    }++    void outputSymbol(std::ostream& destination, const std::string& value, bool fieldValue) {+        if (rfc4180) {+            if (!fieldValue) {+                destination << '"';+            }+            destination << '"';++            const std::size_t end = value.length();+            for (std::size_t pos = 0; pos < end; ++pos) {+                char ch = value[pos];+                if (ch == '"') {+                    destination << '\\';+                    destination << '"';+                }+                destination << ch;+            }++            if (!fieldValue) {+                destination << '"';+            }+            destination << '"';+        } else {+            destination << value;+        }+    }+     void writeNextTupleElement(std::ostream& destination, const std::string& type, RamDomain value) {         switch (type[0]) {-            case 's': destination << symbolTable.unsafeResolve(value); break;+            case 's': outputSymbol(destination, symbolTable.decode(value), true); break;             case 'i': destination << value; break;             case 'u': destination << ramBitCast<RamUnsigned>(value); break;             case 'f': destination << ramBitCast<RamFloat>(value); break;-            case 'r': outputRecord(destination, value, type); break;-            case '+': outputADT(destination, value, type); break;+            case 'r':+                if (rfc4180) {+                    destination << '"';+                }+                outputRecord(destination, value, type);+                if (rfc4180) {+                    destination << '"';+                }+                break;+            case '+':+                if (rfc4180) {+                    destination << '"';+                }+                outputADT(destination, value, type);+                if (rfc4180) {+                    destination << '"';+                }+                break;             default: fatal("unsupported type attribute: `%c`", type[0]);         }     }@@ -183,8 +237,9 @@  class WriteCoutPrintSize : public WriteStream { public:-    explicit WriteCoutPrintSize(const std::map<std::string, std::string>& rwOperation)-            : WriteStream(rwOperation, {}, {}), lease(souffle::getOutputLock().acquire()) {+    WriteCoutPrintSize(const std::map<std::string, std::string>& rwOperation, const SymbolTable& symbolTable,+            const RecordTable& recordTable)+            : WriteStream(rwOperation, symbolTable, recordTable), lease(souffle::getOutputLock().acquire()) {         std::cout << rwOperation.at("name") << "\t";     } @@ -240,9 +295,9 @@  class WriteCoutPrintSizeFactory : public WriteStreamFactory { public:-    Own<WriteStream> getWriter(const std::map<std::string, std::string>& rwOperation, const SymbolTable&,-            const RecordTable&) override {-        return mk<WriteCoutPrintSize>(rwOperation);+    Own<WriteStream> getWriter(const std::map<std::string, std::string>& rwOperation,+            const SymbolTable& symbolTable, const RecordTable& recordTable) override {+        return mk<WriteCoutPrintSize>(rwOperation, symbolTable, recordTable);     }     const std::string& getName() const override {         static const std::string name = "stdoutprintsize";
cbits/souffle/io/WriteStreamJSON.h view
@@ -56,7 +56,7 @@         else             destination << "["; -        for (size_t col = 0; col < arity; ++col) {+        for (std::size_t col = 0; col < arity; ++col) {             if (col > 0) {                 destination << ", ";             }@@ -96,7 +96,7 @@             assert(currType.length() > 2 && "Invalid type length");             switch (currType[0]) {                 // since some strings may need to be escaped, we use dump here-                case 's': destination << Json(symbolTable.unsafeResolve(currValue)).dump(); break;+                case 's': destination << Json(symbolTable.decode(currValue)).dump(); break;                 case 'i': destination << currValue; break;                 case 'u': destination << (int)ramBitCast<RamUnsigned>(currValue); break;                 case 'f': destination << ramBitCast<RamFloat>(currValue); break;@@ -109,7 +109,7 @@                     }                      auto&& recordTypes = recordInfo["types"];-                    const size_t recordArity = recordInfo["arity"].long_value();+                    const std::size_t recordArity = recordInfo["arity"].long_value();                     const RamDomain* tuplePtr = recordTable.unpack(currValue, recordArity);                     worklist.push("]");                     for (auto i = (long long)(recordArity - 1); i >= 0; --i) {@@ -151,7 +151,7 @@             assert(currType.length() > 2 && "Invalid type length");             switch (currType[0]) {                 // since some strings may need to be escaped, we use dump here-                case 's': destination << Json(symbolTable.unsafeResolve(currValue)).dump(); break;+                case 's': destination << Json(symbolTable.decode(currValue)).dump(); break;                 case 'i': destination << currValue; break;                 case 'u': destination << (int)ramBitCast<RamUnsigned>(currValue); break;                 case 'f': destination << ramBitCast<RamFloat>(currValue); break;@@ -164,7 +164,7 @@                     }                      auto&& recordTypes = recordInfo["types"];-                    const size_t recordArity = recordInfo["arity"].long_value();+                    const std::size_t recordArity = recordInfo["arity"].long_value();                     const RamDomain* tuplePtr = recordTable.unpack(currValue, recordArity);                     worklist.push("}");                     for (auto i = (long long)(recordArity - 1); i >= 0; --i) {
cbits/souffle/io/WriteStreamSQLite.h view
@@ -1,6 +1,6 @@ /*  * Souffle - A Datalog Compiler- * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved+ * Copyright (c) 2021, 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@@ -15,6 +15,7 @@ #pragma once  #include "souffle/RamTypes.h"+#include "souffle/RecordTable.h" #include "souffle/SymbolTable.h" #include "souffle/io/WriteStream.h" #include <cassert>@@ -31,8 +32,6 @@  namespace souffle { -class RecordTable;- class WriteStreamSQLite : public WriteStream { public:     WriteStreamSQLite(const std::map<std::string, std::string>& rwOperation, const SymbolTable& symbolTable,@@ -42,10 +41,11 @@         openDB();         createTables();         prepareStatements();-        //        executeSQL("BEGIN TRANSACTION", db);+        executeSQL("BEGIN TRANSACTION", db);     }      ~WriteStreamSQLite() override {+        executeSQL("COMMIT", db);         sqlite3_finalize(insertStatement);         sqlite3_finalize(symbolInsertStatement);         sqlite3_finalize(symbolSelectStatement);@@ -56,7 +56,7 @@     void writeNullary() override {}      void writeNextTuple(const RamDomain* tuple) override {-        for (size_t i = 0; i < arity; i++) {+        for (std::size_t i = 0; i < arity; i++) {             RamDomain value = 0;  // Silence warning              switch (typeAttributes.at(i)[0]) {@@ -103,7 +103,7 @@     }      uint64_t getSymbolTableIDFromDB(int index) {-        if (sqlite3_bind_text(symbolSelectStatement, 1, symbolTable.unsafeResolve(index).c_str(), -1,+        if (sqlite3_bind_text(symbolSelectStatement, 1, symbolTable.decode(index).c_str(), -1,                     SQLITE_TRANSIENT) != SQLITE_OK) {             throwError("SQLite error in sqlite3_bind_text: ");         }@@ -120,7 +120,7 @@             return dbSymbolTable[index];         } -        if (sqlite3_bind_text(symbolInsertStatement, 1, symbolTable.unsafeResolve(index).c_str(), -1,+        if (sqlite3_bind_text(symbolInsertStatement, 1, symbolTable.decode(index).c_str(), -1,                     SQLITE_TRANSIENT) != SQLITE_OK) {             throwError("SQLite error in sqlite3_bind_text: ");         }
cbits/souffle/io/gzfstream.h view
@@ -1,6 +1,6 @@ /*  * Souffle - A Datalog Compiler- * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved+ * Copyright (c) 2021, 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
cbits/souffle/utility/CacheUtil.h view
@@ -1,6 +1,6 @@ /*  * Souffle - A Datalog Compiler- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved+ * Copyright (c) 2021, 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
cbits/souffle/utility/ContainerUtil.h view
@@ -1,6 +1,6 @@ /*  * Souffle - A Datalog Compiler- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved+ * Copyright (c) 2021, 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@@ -16,11 +16,13 @@  #pragma once +#include "souffle/utility/Iteration.h"+#include "souffle/utility/MiscUtil.h"+ #include <algorithm> #include <functional> #include <iterator> #include <map>-#include <memory> #include <set> #include <type_traits> #include <utility>@@ -32,17 +34,6 @@ //                           General Container Utilities // ------------------------------------------------------------------------------- -template <typename A>-using Own = std::unique_ptr<A>;--template <typename A>-using VecOwn = std::vector<Own<A>>;--template <typename A, typename B = A, typename... Args>-Own<A> mk(Args&&... xs) {-    return Own<A>(new B(std::forward<Args>(xs)...));-}- /**  * Use to range-for iterate in reverse.  * Assumes `std::rbegin` and `std::rend` are defined for type `A`.@@ -113,7 +104,28 @@     } } +namespace detail {+inline auto allOfBool = [](bool b) { return b; };+}+ /**+ * Return true if all elements (optionally after applying up)+ * are true+ */+template <typename R, typename UnaryP = decltype(detail::allOfBool) const&>+bool all(R const& range, UnaryP&& up = detail::allOfBool) {+    return std::all_of(range.begin(), range.end(), std::forward<UnaryP>(up));+}++/**+ * Append elements to a container+ */+template <class C, typename R>+void append(C& container, R&& range) {+    container.insert(container.end(), std::begin(range), std::end(range));+}++/**  * A utility function enabling the creation of a vector with a fixed set of  * elements within a single expression. This is the base case covering empty  * vectors.@@ -137,7 +149,7 @@  * A utility function enabling the creation of a vector of pointers.  */ template <typename T>-std::vector<T*> toPtrVector(const std::vector<std::unique_ptr<T>>& v) {+std::vector<T*> toPtrVector(const VecOwn<T>& v) {     std::vector<T*> res;     for (auto& e : v) {         res.push_back(e.get());@@ -150,6 +162,8 @@  */ template <typename A, typename F /* : A -> B */> auto map(const std::vector<A>& xs, F&& f) {+    // FIXME: We can rewrite this using makeTransformRange now,+    // or remove the usage of this completely     std::vector<decltype(f(xs[0]))> ys;     ys.reserve(xs.size());     for (auto&& x : xs) {@@ -159,232 +173,6 @@ }  // --------------------------------------------------------------------------------//                             Cloning Utilities-// ---------------------------------------------------------------------------------template <typename A>-auto clone(const std::vector<A*>& xs) {-    std::vector<std::unique_ptr<A>> ys;-    ys.reserve(xs.size());-    for (auto&& x : xs) {-        ys.emplace_back(x ? std::unique_ptr<A>(x->clone()) : nullptr);-    }-    return ys;-}--template <typename A>-auto clone(const std::vector<std::unique_ptr<A>>& xs) {-    std::vector<std::unique_ptr<A>> ys;-    ys.reserve(xs.size());-    for (auto&& x : xs) {-        ys.emplace_back(x ? std::unique_ptr<A>(x->clone()) : nullptr);-    }-    return ys;-}--// --------------------------------------------------------------//                            Iterators-// ---------------------------------------------------------------/**- * A wrapper for an iterator obtaining pointers of a certain type,- * dereferencing values before forwarding them to the consumer.- *- * @tparam Iter ... the type of wrapped iterator- * @tparam T    ... the value to be accessed by the resulting iterator- */-template <typename Iter, typename T = typename std::remove_pointer<typename Iter::value_type>::type>-struct IterDerefWrapper : public std::iterator<std::forward_iterator_tag, T> {-    /* The nested iterator. */-    Iter iter;--public:-    // some constructors-    IterDerefWrapper() = default;-    IterDerefWrapper(const Iter& iter) : iter(iter) {}--    // defaulted copy and move constructors-    IterDerefWrapper(const IterDerefWrapper&) = default;-    IterDerefWrapper(IterDerefWrapper&&) = default;--    // default assignment operators-    IterDerefWrapper& operator=(const IterDerefWrapper&) = default;-    IterDerefWrapper& operator=(IterDerefWrapper&&) = default;--    /* The equality operator as required by the iterator concept. */-    bool operator==(const IterDerefWrapper& other) const {-        return iter == other.iter;-    }--    /* The not-equality operator as required by the iterator concept. */-    bool operator!=(const IterDerefWrapper& other) const {-        return iter != other.iter;-    }--    /* The deref operator as required by the iterator concept. */-    const T& operator*() const {-        return **iter;-    }--    /* Support for the pointer operator. */-    const T* operator->() const {-        return &(**iter);-    }--    /* The increment operator as required by the iterator concept. */-    IterDerefWrapper& operator++() {-        ++iter;-        return *this;-    }-};--/**- * A factory function enabling the construction of a dereferencing- * iterator utilizing the automated deduction of template parameters.- */-template <typename Iter>-IterDerefWrapper<Iter> derefIter(const Iter& iter) {-    return IterDerefWrapper<Iter>(iter);-}--/**- * An iterator to be utilized if there is only a single element to iterate over.- */-template <typename T>-class SingleValueIterator : public std::iterator<std::forward_iterator_tag, T> {-    T value;--    bool end = true;--public:-    SingleValueIterator() = default;--    SingleValueIterator(const T& value) : value(value), end(false) {}--    // a copy constructor-    SingleValueIterator(const SingleValueIterator& other) = default;--    // an assignment operator-    SingleValueIterator& operator=(const SingleValueIterator& other) = default;--    // the equality operator as required by the iterator concept-    bool operator==(const SingleValueIterator& other) const {-        // only equivalent if pointing to the end-        return end && other.end;-    }--    // the not-equality operator as required by the iterator concept-    bool operator!=(const SingleValueIterator& other) const {-        return !(*this == other);-    }--    // the deref operator as required by the iterator concept-    const T& operator*() const {-        return value;-    }--    // support for the pointer operator-    const T* operator->() const {-        return &value;-    }--    // the increment operator as required by the iterator concept-    SingleValueIterator& operator++() {-        end = true;-        return *this;-    }-};--// --------------------------------------------------------------//                             Ranges-// ---------------------------------------------------------------/**- * A utility class enabling representation of ranges by pairing- * two iterator instances marking lower and upper boundaries.- */-template <typename Iter>-struct range {-    // the lower and upper boundary-    Iter a, b;--    // a constructor accepting a lower and upper boundary-    range(Iter a, Iter b) : a(std::move(a)), b(std::move(b)) {}--    // default copy / move and assignment support-    range(const range&) = default;-    range(range&&) = default;-    range& operator=(const range&) = default;--    // get the lower boundary (for for-all loop)-    Iter& begin() {-        return a;-    }-    const Iter& begin() const {-        return a;-    }--    // get the upper boundary (for for-all loop)-    Iter& end() {-        return b;-    }-    const Iter& end() const {-        return b;-    }--    // emptiness check-    bool empty() const {-        return a == b;-    }--    // splits up this range into the given number of partitions-    std::vector<range> partition(int np = 100) {-        // obtain the size-        int n = 0;-        for (auto i = a; i != b; ++i) {-            n++;-        }--        // split it up-        auto s = n / np;-        auto r = n % np;-        std::vector<range> res;-        res.reserve(np);-        auto cur = a;-        auto last = cur;-        int i = 0;-        int p = 0;-        while (cur != b) {-            ++cur;-            i++;-            if (i >= (s + (p < r ? 1 : 0))) {-                res.push_back({last, cur});-                last = cur;-                p++;-                i = 0;-            }-        }-        if (cur != last) {-            res.push_back({last, cur});-        }-        return res;-    }-};--/**- * A utility function enabling the construction of ranges- * without explicitly specifying the iterator type.- *- * @tparam Iter .. the iterator type- * @param a .. the lower boundary- * @param b .. the upper boundary- */-template <typename Iter>-range<Iter> make_range(const Iter& a, const Iter& b) {-    return range<Iter>(a, b);-}--// ------------------------------------------------------------------------------- //                             Equality Utilities // ------------------------------------------------------------------------------- @@ -396,8 +184,8 @@  */ template <typename toType, typename baseType> bool castEq(const baseType* left, const baseType* right) {-    if (auto castedLeft = dynamic_cast<const toType*>(left)) {-        if (auto castedRight = dynamic_cast<const toType*>(right)) {+    if (auto castedLeft = as<toType>(left)) {+        if (auto castedRight = as<toType>(right)) {             return castedLeft == castedRight;         }     }@@ -453,8 +241,8 @@  * targets.  */ template <typename T, template <typename...> class Container>-bool equal_targets(const Container<std::unique_ptr<T>>& a, const Container<std::unique_ptr<T>>& b) {-    return equal_targets(a, b, comp_deref<std::unique_ptr<T>>());+bool equal_targets(const Container<Own<T>>& a, const Container<Own<T>>& b) {+    return equal_targets(a, b, comp_deref<Own<T>>()); }  /**@@ -462,11 +250,31 @@  * targets.  */ template <typename Key, typename Value>-bool equal_targets(-        const std::map<Key, std::unique_ptr<Value>>& a, const std::map<Key, std::unique_ptr<Value>>& b) {-    auto comp = comp_deref<std::unique_ptr<Value>>();+bool equal_targets(const std::map<Key, Own<Value>>& a, const std::map<Key, Own<Value>>& b) {+    auto comp = comp_deref<Own<Value>>();     return equal_targets(             a, b, [&comp](auto& a, auto& b) { return a.first == b.first && comp(a.second, b.second); }); } +// -------------------------------------------------------------------------------+//                             Checking Utilities+// -------------------------------------------------------------------------------+template <typename R>+bool allValidPtrs(R const& range) {+    return all(makeTransformRange(range, [](auto const& ptr) { return ptr != nullptr; }));+}+ }  // namespace souffle++namespace std {+template <typename Iter, typename F>+struct iterator_traits<souffle::TransformIterator<Iter, F>> {+    using iter_t = std::iterator_traits<Iter>;+    using iter_tag = typename iter_t::iterator_category;+    using difference_type = typename iter_t::difference_type;+    using reference = decltype(std::declval<F&>()(*std::declval<Iter>()));+    using value_type = std::remove_cv_t<std::remove_reference_t<reference>>;+    using iterator_category = std::conditional_t<std::is_base_of_v<std::random_access_iterator_tag, iter_tag>,+            std::random_access_iterator_tag, iter_tag>;+};+}  // namespace std
cbits/souffle/utility/EvaluatorUtil.h view
@@ -16,9 +16,10 @@  #pragma once -#include "souffle/CompiledTuple.h" #include "souffle/RamTypes.h"-#include "tinyformat.h"+#include "souffle/utility/StringUtil.h"+#include "souffle/utility/tinyformat.h"+#include <csignal>  namespace souffle::evaluator { @@ -54,7 +55,10 @@             return RamSignedFromString(src);         } else if constexpr (std::is_same_v<RamUnsigned, A>) {             return RamUnsignedFromString(src);+        } else {+            static_assert(sizeof(A) == 0, "Invalid type specified for symbol2Numeric");         }+     } catch (...) {         tfm::format(std::cerr, "error: wrong string provided by `to_number(\"%s\")` functor.\n", src);         raise(SIGFPE);
cbits/souffle/utility/FileUtil.h view
@@ -1,6 +1,6 @@ /*  * Souffle - A Datalog Compiler- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved+ * Copyright (c) 2021, 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@@ -127,12 +127,12 @@     if (name.empty()) {         return ".";     }-    size_t lastNotSlash = name.find_last_not_of('/');+    std::size_t lastNotSlash = name.find_last_not_of('/');     // All '/'     if (lastNotSlash == std::string::npos) {         return "/";     }-    size_t leadingSlash = name.find_last_of('/', lastNotSlash);+    std::size_t leadingSlash = name.find_last_of('/', lastNotSlash);     // No '/'     if (leadingSlash == std::string::npos) {         return ".";@@ -195,14 +195,14 @@         return ".";     } -    size_t lastNotSlash = filename.find_last_not_of('/');+    std::size_t lastNotSlash = filename.find_last_not_of('/');     if (lastNotSlash == std::string::npos) {         return "/";     } -    size_t lastSlashBeforeBasename = filename.find_last_of('/', lastNotSlash - 1);+    std::size_t lastSlashBeforeBasename = filename.find_last_of('/', lastNotSlash - 1);     if (lastSlashBeforeBasename == std::string::npos) {-        lastSlashBeforeBasename = static_cast<size_t>(-1);+        lastSlashBeforeBasename = static_cast<std::size_t>(-1);     }     return filename.substr(lastSlashBeforeBasename + 1, lastNotSlash - lastSlashBeforeBasename); }@@ -212,12 +212,12 @@  */ inline std::string simpleName(const std::string& path) {     std::string name = baseName(path);-    const size_t lastDot = name.find_last_of('.');+    const std::size_t lastDot = name.find_last_of('.');     // file has no extension     if (lastDot == std::string::npos) {         return name;     }-    const size_t lastSlash = name.find_last_of('/');+    const std::size_t lastSlash = name.find_last_of('/');     // last slash occurs after last dot, so no extension     if (lastSlash != std::string::npos && lastSlash > lastDot) {         return name;@@ -231,12 +231,12 @@  */ inline std::string fileExtension(const std::string& path) {     std::string name = path;-    const size_t lastDot = name.find_last_of('.');+    const std::size_t lastDot = name.find_last_of('.');     // file has no extension     if (lastDot == std::string::npos) {         return std::string();     }-    const size_t lastSlash = name.find_last_of('/');+    const std::size_t lastSlash = name.find_last_of('/');     // last slash occurs after last dot, so no extension     if (lastSlash != std::string::npos && lastSlash > lastDot) {         return std::string();
cbits/souffle/utility/FunctionalUtil.h view
@@ -1,6 +1,6 @@ /*  * Souffle - A Datalog Compiler- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved+ * Copyright (c) 2021, 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@@ -18,6 +18,7 @@  #include <algorithm> #include <functional>+#include <set> #include <utility> #include <vector> @@ -148,6 +149,18 @@ template <typename A, typename F> std::vector<A> filter(std::vector<A> xs, F&& f) {     return filterNot(std::move(xs), [&](auto&& x) { return !f(x); });+}++// -------------------------------------------------------------------------------+//                               Set Utilities+// -------------------------------------------------------------------------------++template <typename A>+std::set<A> operator-(const std::set<A>& lhs, const std::set<A>& rhs) {+    std::set<A> result;+    std::set_difference(+            lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), std::inserter(result, result.begin()));+    return result; }  }  // namespace souffle
+ cbits/souffle/utility/Iteration.h view
@@ -0,0 +1,374 @@+/*+ * 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 Iteration.h+ *+ * @brief Utilities for iterators and ranges+ *+ ***********************************************************************/++#pragma once++#include "souffle/utility/Types.h"++#include <iterator>+#include <type_traits>+#include <utility>+#include <vector>++namespace souffle {++namespace detail {++// This is a helper in the cases when the lambda is stateless+template <typename F>+F const& makeFun() {+    // Even thought the lambda is stateless, it has no default ctor+    // Is this gross?  Yes, yes it is.+    // FIXME: Remove after C++20+    typename std::aligned_storage<sizeof(F)>::type fakeLam;+    return reinterpret_cast<F const&>(fakeLam);+}+}  // namespace detail++// -------------------------------------------------------------+//                            Iterators+// -------------------------------------------------------------+/**+ * A wrapper for an iterator that transforms values returned by+ * the underlying iter.+ *+ * @tparam Iter ... the type of wrapped iterator+ * @tparam F    ... the function to apply+ *+ */+template <typename Iter, typename F>+class TransformIterator {+    using iter_t = std::iterator_traits<Iter>;+    using difference_type = typename iter_t::difference_type;+    using reference = decltype(std::declval<F&>()(*std::declval<Iter>()));+    static_assert(std::is_empty_v<F>, "Function object must be stateless");++public:+    // some constructors+    template <typename It>+    TransformIterator(It iter, std::enable_if_t<std::is_empty_v<F>, void*> = nullptr)+            : iter(std::move(iter)), fun(detail::makeFun<F>()) {}+    TransformIterator(Iter iter, F f) : iter(std::move(iter)), fun(std::move(f)) {}++    // defaulted copy and move constructors+    TransformIterator(const TransformIterator& other) : iter(other.iter), fun(other.fun) {}+    TransformIterator(TransformIterator&& other) : iter(std::move(other.iter)), fun(std::move(other.fun)) {}++    // default assignment operators+    TransformIterator& operator=(const TransformIterator& other) {+        if (this != &other) {+            iter = other.iter;+        }+        return *this;+    }++    TransformIterator& operator=(TransformIterator&& other) {+        if (this != &other) {+            iter = std::move(other.iter);+        }+        return *this;+    }++    /* The equality operator as required by the iterator concept. */+    bool operator==(const TransformIterator& other) const {+        return iter == other.iter;+    }++    /* The not-equality operator as required by the iterator concept. */+    bool operator!=(const TransformIterator& other) const {+        return iter != other.iter;+    }++    bool operator<(TransformIterator const& other) const {+        return iter < other.iter;+    }++    bool operator<=(TransformIterator const& other) const {+        return iter <= other.iter;+    }++    bool operator>(TransformIterator const& other) const {+        return iter > other.iter;+    }++    bool operator>=(TransformIterator const& other) const {+        return iter >= other.iter;+    }++    /* The deref operator as required by the iterator concept. */+    auto operator*() const -> reference {+        return fun(*iter);+    }++    /* Support for the pointer operator. */+    auto operator->() const {+        return &**this;+    }++    /* The increment operator as required by the iterator concept. */+    TransformIterator& operator++() {+        ++iter;+        return *this;+    }++    TransformIterator operator++(int) {+        auto res = *this;+        ++iter;+        return res;+    }++    TransformIterator& operator--() {+        --iter;+        return *this;+    }++    TransformIterator operator--(int) {+        auto res = *this;+        --iter;+        return res;+    }++    TransformIterator& operator+=(difference_type n) {+        iter += n;+        return *this;+    }++    TransformIterator operator+(difference_type n) {+        auto res = *this;+        res += n;+        return res;+    }++    TransformIterator& operator-=(difference_type n) {+        iter -= n;+        return *this;+    }++    TransformIterator operator-(difference_type n) {+        auto res = *this;+        res -= n;+        return res;+    }++    difference_type operator-(TransformIterator const& other) {+        return iter - other.iter;+    }++    auto operator[](difference_type ii) const -> reference {+        return f(iter[ii]);+    }++private:+    /* The nested iterator. */+    Iter iter;+    F fun;+};++template <typename Iter, typename F>+auto operator+(+        typename TransformIterator<Iter, F>::difference_type n, TransformIterator<Iter, F> const& iter) {+    return iter + n;+}++template <typename Iter, typename F>+auto transformIter(Iter&& iter, F&& f) {+    return TransformIterator<remove_cvref_t<Iter>, std::remove_reference_t<F>>(+            std::forward<Iter>(iter), std::forward<F>(f));+}++/**+ * A wrapper for an iterator obtaining pointers of a certain type,+ * dereferencing values before forwarding them to the consumer.+ */+namespace detail {+inline auto iterDeref = [](auto& p) -> decltype(*p) { return *p; };+}++template <typename Iter>+using IterDerefWrapper = TransformIterator<Iter, decltype(detail::iterDeref)>;++/**+ * A factory function enabling the construction of a dereferencing+ * iterator utilizing the automated deduction of template parameters.+ */+template <typename Iter>+auto derefIter(Iter&& iter) {+    return transformIter(std::forward<Iter>(iter), detail::iterDeref);+}++// -------------------------------------------------------------+//                             Ranges+// -------------------------------------------------------------++/**+ * A utility class enabling representation of ranges by pairing+ * two iterator instances marking lower and upper boundaries.+ */+template <typename Iter>+struct range {+    // the lower and upper boundary+    Iter a, b;++    // a constructor accepting a lower and upper boundary+    range(Iter a, Iter b) : a(std::move(a)), b(std::move(b)) {}++    // default copy / move and assignment support+    range(const range&) = default;+    range(range&&) = default;+    range& operator=(const range&) = default;++    // get the lower boundary (for for-all loop)+    Iter& begin() {+        return a;+    }+    const Iter& begin() const {+        return a;+    }++    // get the upper boundary (for for-all loop)+    Iter& end() {+        return b;+    }+    const Iter& end() const {+        return b;+    }++    // emptiness check+    bool empty() const {+        return a == b;+    }++    // splits up this range into the given number of partitions+    std::vector<range> partition(int np = 100) {+        // obtain the size+        int n = 0;+        for (auto i = a; i != b; ++i) {+            n++;+        }++        // split it up+        auto s = n / np;+        auto r = n % np;+        std::vector<range> res;+        res.reserve(np);+        auto cur = a;+        auto last = cur;+        int i = 0;+        int p = 0;+        while (cur != b) {+            ++cur;+            i++;+            if (i >= (s + (p < r ? 1 : 0))) {+                res.push_back({last, cur});+                last = cur;+                p++;+                i = 0;+            }+        }+        if (cur != last) {+            res.push_back({last, cur});+        }+        return res;+    }+};++/**+ * A utility function enabling the construction of ranges+ * without explicitly specifying the iterator type.+ *+ * @tparam Iter .. the iterator type+ * @param a .. the lower boundary+ * @param b .. the upper boundary+ */+template <typename Iter>+range<Iter> make_range(const Iter& a, const Iter& b) {+    return range<Iter>(a, b);+}++template <typename Iter, typename F>+auto makeTransformRange(Iter&& begin, Iter&& end, F const& f) {+    return make_range(transformIter(std::forward<Iter>(begin), f), transformIter(std::forward<Iter>(end), f));+}++template <typename R, typename F>+auto makeTransformRange(R&& range, F const& f) {+    return makeTransformRange(range.begin(), range.end(), f);+}++template <typename Iter>+auto makeDerefRange(Iter&& begin, Iter&& end) {+    return make_range(derefIter(std::forward<Iter>(begin)), derefIter(std::forward<Iter>(end)));+}++/**+ * This wraps the Range container, and const_casts in place.+ */+template <typename Range, typename F>+class OwningTransformRange {+public:+    OwningTransformRange(Range&& range, F f) : range(std::move(range)), f(std::move(f)) {}++    auto begin() {+        return transformIter(std::begin(range), f);+    }++    auto begin() const {+        return transformIter(std::begin(range), f);+    }++    auto cbegin() const {+        return transformIter(std::cbegin(range), f);+    }++    auto end() {+        return transformIter(std::end(range), f);+    }++    auto end() const {+        return transformIter(std::begin(range), f);+    }++    auto cend() const {+        return transformIter(std::cend(range), f);+    }++    auto size() const {+        return range.size();+    }++    auto& operator[](std::size_t ii) {+        return begin()[ii];+    }++    auto& operator[](std::size_t ii) const {+        return cbegin()[ii];+    }++private:+    Range range;+    F f;+};++/**+ * Convert a range of any ptr-like to a range+ * of pointers+ */+template <typename R>+auto makePtrRange(R const& range) {+    return makeTransformRange(range, [](auto const& ptrLike) { return &*ptrLike; });+}++}  // namespace souffle
cbits/souffle/utility/MiscUtil.h view
@@ -1,6 +1,6 @@ /*  * Souffle - A Datalog Compiler- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved+ * Copyright (c) 2021, 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@@ -16,10 +16,11 @@  #pragma once +#include "souffle/utility/Iteration.h"+#include "souffle/utility/Types.h" #include "tinyformat.h" #include <cassert> #include <chrono>-#include <cstdlib> #include <iostream> #include <memory> #include <utility>@@ -48,7 +49,7 @@  */ #define __builtin_popcountll __popcnt64 -#if _MSC_VER < 1924+#if defined(_MSC_VER) constexpr unsigned long __builtin_ctz(unsigned long value) {     unsigned long trailing_zeroes = 0;     while ((value = value >> 1) ^ 1) {@@ -66,8 +67,8 @@         return 64;     } }-#endif  // _MSC_VER < 1924-#endif+#endif  // _MSC_VER+#endif  // _WIN32  // ------------------------------------------------------------------------------- //                               Timing Utils@@ -98,16 +99,61 @@ //                             Cloning Utilities // ------------------------------------------------------------------------------- +namespace detail {+// TODO: This function is still used by ram::Node::clone() because it hasn't been+// converted to return Own<>.  Once converted, remove this.+template <typename D, typename B>+Own<D> downCast(B* ptr) {+    // ensure the clone operation casts to appropriate pointer+    static_assert(std::is_base_of_v<std::remove_const_t<B>, std::remove_const_t<D>>,+            "Needs to be able to downcast");+    return Own<D>(ptr);+}++template <typename D, typename B>+Own<D> downCast(Own<B> ptr) {+    // ensure the clone operation casts to appropriate pointer+    static_assert(std::is_base_of_v<std::remove_const_t<B>, std::remove_const_t<D>>,+            "Needs to be able to downcast");+    return Own<D>(static_cast<D*>(ptr.release()));+}++}  // namespace detail+ template <typename A>-std::unique_ptr<A> clone(const A* node) {-    return node ? std::unique_ptr<A>(node->clone()) : nullptr;+std::enable_if_t<!std::is_pointer_v<A> && !is_range_v<A>, Own<A>> clone(const A& node) {+    return detail::downCast<A>(node.cloneImpl()); }  template <typename A>-std::unique_ptr<A> clone(const std::unique_ptr<A>& node) {-    return node ? std::unique_ptr<A>(node->clone()) : nullptr;+Own<A> clone(const A* node) {+    return node ? clone(*node) : nullptr; } +template <typename A>+Own<A> clone(const Own<A>& node) {+    return clone(node.get());+}++/**+ * Clone a range+ */+template <typename R>+auto cloneRange(R const& range) {+    return makeTransformRange(std::begin(range), std::end(range), [](auto const& x) { return clone(x); });+}++/**+ * Clone a range, optionally allowing up-casting the result to D+ */+template <typename D = void, typename R, std::enable_if_t<is_range_v<R>, void*> = nullptr>+auto clone(R const& range) {+    auto rn = cloneRange(range);+    using ValueType = remove_cvref_t<decltype(**std::begin(range))>;+    using ResType = std::conditional_t<std::is_same_v<D, void>, ValueType, D>;+    return VecOwn<ResType>(rn.begin(), rn.end());+}+ template <typename A, typename B> auto clone(const std::pair<A, B>& p) {     return std::make_pair(clone(p.first), clone(p.second));@@ -136,54 +182,79 @@  * pointers are null is also considered equivalent.  */ template <typename T>-bool equal_ptr(const std::unique_ptr<T>& a, const std::unique_ptr<T>& b) {+bool equal_ptr(const Own<T>& a, const Own<T>& b) {     return equal_ptr(a.get(), b.get()); } -template <typename A, typename B>-using copy_const_t = std::conditional_t<std::is_const_v<A>, const B, B>;+/**+ * This class is used to tell as<> that cross-casting is allowed.+ * I use a named type rather than just a bool to make the code stand out.+ */+class AllowCrossCast {};  /**  * Helpers for `dynamic_cast`ing without having to specify redundant type qualifiers.- * e.g. `as<AstLiteral>(p)` instead of `dynamic_cast<const AstLiteral*>(p.get())`.+ * e.g. `as<AstLiteral>(p)` instead of `as<AstLiteral>(p)`.  */-template <typename B, typename A>+template <typename B, typename CastType = void, typename A> auto as(A* x) {-    static_assert(std::is_base_of_v<A, B>,-            "`as<B, A>` does not allow cross-type dyn casts. "-            "(i.e. `as<B, A>` where `B <: A` is not true.) "-            "Such a cast is likely a mistake or typo.");+    if constexpr (!std::is_same_v<CastType, AllowCrossCast>) {+        static_assert(std::is_base_of_v<std::remove_const_t<A>, std::remove_const_t<B>>,+                "`as<B, A>` does not allow cross-type dyn casts. "+                "(i.e. `as<B, A>` where `B <: A` is not true.) "+                "Such a cast is likely a mistake or typo.");+    }     return dynamic_cast<copy_const_t<A, B>*>(x); } -template <typename B, typename A>-std::enable_if_t<std::is_base_of_v<A, B>, copy_const_t<A, B>*> as(A& x) {-    return as<B>(&x);+template <typename B, typename CastType = void, typename A>+auto as(A& x) {+    return as<B, CastType>(&x); } -template <typename B, typename A>-B* as(const std::unique_ptr<A>& x) {-    return as<B>(x.get());+template <typename B, typename CastType = void, typename A>+auto as(Own<A>& x) {+    return as<B, CastType>(x.get()); } +template <typename B, typename CastType = void, typename A>+auto as(const Own<A>& x) {+    return as<B, CastType>(x.get());+}+ /**+ * Down-casts and checks the cast has succeeded+ */+template <typename B, typename CastType = void, typename A>+auto& asAssert(A&& a) {+    auto* cast = as<B, CastType>(std::forward<A>(a));+    assert(cast && "Invalid cast");+    return *cast;+}++/**  * Checks if the object of type Source can be casted to type Destination.  */ template <typename B, typename A> bool isA(A* x) {+    // Dont't forward onto as<> - need to check cross-casting     return dynamic_cast<copy_const_t<A, B>*>(x) != nullptr; }  template <typename B, typename A>-std::enable_if_t<std::is_base_of_v<A, B>, bool> isA(A& x) {+auto isA(A& x) {     return isA<B>(&x); }  template <typename B, typename A>-bool isA(const std::unique_ptr<A>& x) {+bool isA(const Own<A>& x) {     return isA<B>(x.get()); } +template <typename B, typename A>+bool isA(Own<A>& x) {+    return isA<B>(x.get());+} // ------------------------------------------------------------------------------- //                               Error Utilities // -------------------------------------------------------------------------------
cbits/souffle/utility/ParallelUtil.h view
@@ -18,7 +18,22 @@ #pragma once  #include <atomic>+#include <cassert>+#include <cstddef>+#include <memory>+#include <new> +#if defined(__cpp_lib_hardware_interference_size) && \+        (!defined(__APPLE__))  // https://bugs.llvm.org/show_bug.cgi?id=41423+using std::hardware_constructive_interference_size;+using std::hardware_destructive_interference_size;+#else+// 64 bytes on x86-64 │ L1_CACHE_BYTES │ L1_CACHE_SHIFT │ __cacheline_aligned │+// ...+constexpr std::size_t hardware_constructive_interference_size = 2 * sizeof(max_align_t);+constexpr std::size_t hardware_destructive_interference_size = 2 * sizeof(max_align_t);+#endif+ #ifdef _OPENMP  /**@@ -55,7 +70,7 @@ #define SECTION_END }  // a macro to create an operation context-#define CREATE_OP_CONTEXT(NAME, INIT) auto NAME = INIT;+#define CREATE_OP_CONTEXT(NAME, INIT) [[maybe_unused]] auto NAME = INIT; #define READ_OP_CONTEXT(NAME) NAME  #else@@ -80,7 +95,7 @@ #define SECTION_END }  // a macro to create an operation context-#define CREATE_OP_CONTEXT(NAME, INIT) auto NAME = INIT;+#define CREATE_OP_CONTEXT(NAME, INIT) [[maybe_unused]] auto NAME = INIT; #define READ_OP_CONTEXT(NAME) NAME  // mark es sequential@@ -93,17 +108,66 @@ #endif  #ifdef IS_PARALLEL+#include <mutex>+#include <vector> #define MAX_THREADS (omp_get_max_threads()) #else #define MAX_THREADS (1) #endif -#ifdef IS_PARALLEL+namespace souffle { -#include <mutex>+struct SeqConcurrentLanes {+    struct TrivialLock {+        ~TrivialLock() {}+    }; -namespace souffle {+    using lane_id = std::size_t;+    using unique_lock_type = TrivialLock; +    explicit SeqConcurrentLanes(std::size_t = 1) {}+    SeqConcurrentLanes(const SeqConcurrentLanes&) = delete;+    SeqConcurrentLanes(SeqConcurrentLanes&&) = delete;++    virtual ~SeqConcurrentLanes() {}++    std::size_t lanes() const {+        return 1;+    }++    void setNumLanes(const std::size_t) {}++    unique_lock_type guard(const lane_id) const {+        return TrivialLock();+    }++    void lock(const lane_id) const {+        return;+    }++    void unlock(const lane_id) const {+        return;+    }++    void beforeLockAllBut(const lane_id) const {+        return;+    }++    void beforeUnlockAllBut(const lane_id) const {+        return;+    }++    void lockAllBut(const lane_id) const {+        return;+    }++    void unlockAllBut(const lane_id) const {+        return;+    }+};++#ifdef IS_PARALLEL+ /**  * A small utility class for implementing simple locks.  */@@ -457,10 +521,190 @@     } }; -#else+/** Concurrent tracks locking mechanism. */+struct MutexConcurrentLanes {+    using lane_id = std::size_t;+    using unique_lock_type = std::unique_lock<std::mutex>; -namespace souffle {+    explicit MutexConcurrentLanes(const std::size_t Sz) : Size(Sz), Attribution(attribution(Sz)) {+        Lanes = std::make_unique<Lane[]>(Sz);+    }+    MutexConcurrentLanes(const MutexConcurrentLanes&) = delete;+    MutexConcurrentLanes(MutexConcurrentLanes&&) = delete; +    virtual ~MutexConcurrentLanes() {}++    // Return the number of lanes.+    std::size_t lanes() const {+        return Size;+    }++    // Select a lane+    lane_id getLane(std::size_t I) const {+        if (Attribution == lane_attribution::mod_power_of_2) {+            return I & (Size - 1);+        } else {+            return I % Size;+        }+    }++    /** Change the number of lanes.+     * DO not use while threads are using this object.+     */+    void setNumLanes(const std::size_t NumLanes) {+        Size = (NumLanes == 0 ? 1 : NumLanes);+        Attribution = attribution(Size);+        Lanes = std::make_unique<Lane[]>(Size);+    }++    unique_lock_type guard(const lane_id Lane) const {+        return unique_lock_type(Lanes[Lane].Access);+    }++    // Lock the given track.+    // Must eventually be followed by unlock(Lane).+    void lock(const lane_id Lane) const {+        assert(Lane < Size);+        Lanes[Lane].Access.lock();+    }++    // Unlock the given track.+    // Must already be the owner of the track's lock.+    void unlock(const lane_id Lane) const {+        assert(Lane < Size);+        Lanes[Lane].Access.unlock();+    }++    // Acquire the capability to lock all other tracks than the given one.+    //+    // Must eventually be followed by beforeUnlockAllBut(Lane).+    void beforeLockAllBut(const lane_id Lane) const {+        if (!BeforeLockAll.try_lock()) {+            // If we cannot get the lock immediately, it means it was acquired+            // concurrently by another track that will also try to acquire our+            // track lock.+            // So we release our track lock to let the concurrent operation+            // progress.+            unlock(Lane);+            BeforeLockAll.lock();+            lock(Lane);+        }+    }++    // Release the capability to lock all other tracks than the given one.+    //+    // Must already be the owner of that capability.+    void beforeUnlockAllBut(const lane_id) const {+        BeforeLockAll.unlock();+    }++    // Lock all tracks but the given one.+    //+    // Must already have acquired the capability to lock all other tracks+    // by calling beforeLockAllBut(Lane).+    //+    // Must eventually be followed by unlockAllBut(Lane).+    void lockAllBut(const lane_id Lane) const {+        for (std::size_t I = 0; I < Size; ++I) {+            if (I != Lane) {+                Lanes[I].Access.lock();+            }+        }+    }++    // Unlock all tracks but the given one.+    // Must already be the owner of all the tracks' locks.+    void unlockAllBut(const lane_id Lane) const {+        for (std::size_t I = 0; I < Size; ++I) {+            if (I != Lane) {+                Lanes[I].Access.unlock();+            }+        }+    }++private:+    enum lane_attribution { mod_power_of_2, mod_other };++    struct Lane {+        alignas(hardware_destructive_interference_size) std::mutex Access;+    };++    static constexpr lane_attribution attribution(const std::size_t Sz) {+        assert(Sz > 0);+        if ((Sz & (Sz - 1)) == 0) {+            // Sz is a power of 2+            return lane_attribution::mod_power_of_2;+        } else {+            return lane_attribution::mod_other;+        }+    }++protected:+    std::size_t Size;+    lane_attribution Attribution;++private:+    mutable std::unique_ptr<Lane[]> Lanes;++    alignas(hardware_destructive_interference_size) mutable std::mutex BeforeLockAll;+};++class ConcurrentLanes : public MutexConcurrentLanes {+    using Base = MutexConcurrentLanes;++public:+    using lane_id = Base::lane_id;+    using Base::beforeLockAllBut;+    using Base::beforeUnlockAllBut;+    using Base::guard;+    using Base::lock;+    using Base::lockAllBut;+    using Base::unlock;+    using Base::unlockAllBut;++    explicit ConcurrentLanes(const std::size_t Sz) : MutexConcurrentLanes(Sz) {}+    ConcurrentLanes(const ConcurrentLanes&) = delete;+    ConcurrentLanes(ConcurrentLanes&&) = delete;++    lane_id threadLane() const {+        return getLane(static_cast<std::size_t>(omp_get_thread_num()));+    }++    void setNumLanes(const std::size_t NumLanes) {+        Base::setNumLanes(NumLanes == 0 ? omp_get_max_threads() : NumLanes);+    }++    unique_lock_type guard() const {+        return Base::guard(threadLane());+    }++    void lock() const {+        return Base::lock(threadLane());+    }++    void unlock() const {+        return Base::unlock(threadLane());+    }++    void beforeLockAllBut() const {+        return Base::beforeLockAllBut(threadLane());+    }++    void beforeUnlockAllBut() const {+        return Base::beforeUnlockAllBut(threadLane());+    }++    void lockAllBut() const {+        return Base::lockAllBut(threadLane());+    }++    void unlockAllBut() const {+        return Base::unlockAllBut(threadLane());+    }+};++#else+ /**  * A small utility class for implementing simple locks.  */@@ -560,6 +804,53 @@     } }; +struct ConcurrentLanes : protected SeqConcurrentLanes {+    using Base = SeqConcurrentLanes;+    using lane_id = SeqConcurrentLanes::lane_id;+    using unique_lock_type = SeqConcurrentLanes::unique_lock_type;++    using Base::lanes;+    using Base::setNumLanes;++    explicit ConcurrentLanes(std::size_t Sz = MAX_THREADS) : Base(Sz) {}+    ConcurrentLanes(const ConcurrentLanes&) = delete;+    ConcurrentLanes(ConcurrentLanes&&) = delete;++    virtual ~ConcurrentLanes() {}++    lane_id threadLane() const {+        return 0;+    }++    unique_lock_type guard() const {+        return Base::guard(threadLane());+    }++    void lock() const {+        return Base::lock(threadLane());+    }++    void unlock() const {+        return Base::unlock(threadLane());+    }++    void beforeLockAllBut() const {+        return Base::beforeLockAllBut(threadLane());+    }++    void beforeUnlockAllBut() const {+        return Base::beforeUnlockAllBut(threadLane());+    }++    void lockAllBut() const {+        return Base::lockAllBut(threadLane());+    }++    void unlockAllBut() const {+        return Base::unlockAllBut(threadLane());+    }+};+ #endif  /**@@ -570,4 +861,4 @@     return outputLock; } -}  // end of namespace souffle+}  // namespace souffle
cbits/souffle/utility/StreamUtil.h view
@@ -1,6 +1,6 @@ /*  * Souffle - A Datalog Compiler- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved+ * Copyright (c) 2021, 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@@ -26,6 +26,7 @@ #include <vector>  #include "souffle/utility/ContainerUtil.h"+#include "souffle/utility/span.h"  // ------------------------------------------------------------------------------- //                           General Print Utilities@@ -33,6 +34,25 @@  namespace souffle { +// Usage:       `using namespace stream_write_qualified_char_as_number;`+//              NB: `using` must appear in the same namespace as the `<<` callers.+//                  Putting the `using` in a parent namespace will have no effect.+// Motivation:  Octet sized numeric types are often defined as aliases of a qualified+//              `char`. e.g. `using uint8_t = unsigned char'`+//              `std::ostream` has an overload which converts qualified `char`s to plain `char`.+//              You don't usually want to print a `uint8_t` as an ASCII character.+//+// NOTE:        `char`, `signed char`, and `unsigned char` are distinct types.+namespace stream_write_qualified_char_as_number {+inline std::ostream& operator<<(std::ostream& os, signed char c) {+    return os << int(c);+}++inline std::ostream& operator<<(std::ostream& os, unsigned char c) {+    return os << unsigned(c);+}+}  // namespace stream_write_qualified_char_as_number+ template <typename A> struct IsPtrLike : std::is_pointer<A> {}; template <typename A>@@ -186,14 +206,14 @@  * For use cases see the test case {util_test.cpp}.  */ template <typename Container, typename Iter = typename Container::const_iterator,-        typename T = typename Iter::value_type>+        typename T = typename std::iterator_traits<Iter>::value_type> std::enable_if_t<!JoinShouldDeref<T>, detail::joined_sequence<Iter, detail::print<id<T>>>> join(         const Container& c, const std::string& sep = ",") {     return join(c.begin(), c.end(), sep, detail::print<id<T>>()); }  template <typename Container, typename Iter = typename Container::const_iterator,-        typename T = typename Iter::value_type>+        typename T = typename std::iterator_traits<Iter>::value_type> std::enable_if_t<JoinShouldDeref<T>, detail::joined_sequence<Iter, detail::print<deref<T>>>> join(         const Container& c, const std::string& sep = ",") {     return join(c.begin(), c.end(), sep, detail::print<deref<T>>());@@ -206,6 +226,15 @@ namespace std {  /**+ * Enables the generic printing of `array`s assuming their element types+ * are printable.+ */+template <typename T, std::size_t E>+ostream& operator<<(ostream& out, const array<T, E>& v) {+    return out << "[" << souffle::join(v) << "]";+}++/**  * Introduces support for printing pairs as long as their components can be printed.  */ template <typename A, typename B>@@ -219,6 +248,15 @@  */ template <typename T, typename A> ostream& operator<<(ostream& out, const vector<T, A>& v) {+    return out << "[" << souffle::join(v) << "]";+}++/**+ * Enables the generic printing of `span`s assuming their element types+ * are printable.+ */+template <typename T, std::size_t E>+ostream& operator<<(ostream& out, const souffle::span<T, E>& v) {     return out << "[" << souffle::join(v) << "]"; } 
cbits/souffle/utility/StringUtil.h view
@@ -1,6 +1,6 @@ /*  * Souffle - A Datalog Compiler- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved+ * Copyright (c) 2021, 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@@ -154,7 +154,7 @@  * starts with minus (c++ default semantics).  */ inline bool canBeParsedAsRamSigned(const std::string& string) {-    size_t charactersRead = 0;+    std::size_t charactersRead = 0;      try {         RamSignedFromString(string, &charactersRead, 0);@@ -171,7 +171,7 @@  * Souffle accepts: hex, binary and base 10.  */ inline bool canBeParsedAsRamUnsigned(const std::string& string) {-    size_t charactersRead = 0;+    std::size_t charactersRead = 0;     try {         RamUnsignedFromString(string, &charactersRead, 0);     } catch (...) {@@ -184,7 +184,7 @@  * Can a string be parsed as RamFloat.  */ inline bool canBeParsedAsRamFloat(const std::string& string) {-    size_t charactersRead = 0;+    std::size_t charactersRead = 0;     try {         RamFloatFromString(string, &charactersRead);     } catch (...) {@@ -323,13 +323,20 @@ }  /**+ * Strips the prefix of a given string if it exists. No change otherwise.+ */+inline std::string stripPrefix(const std::string& prefix, const std::string& element) {+    return isPrefix(prefix, element) ? element.substr(prefix.length()) : element;+}++/**  * Stringify a string using escapes for escape, newline, tab, double-quotes and semicolons  */ inline std::string stringify(const std::string& input) {     std::string str(input);      // replace escapes with double escape sequence-    size_t start_pos = 0;+    std::size_t start_pos = 0;     while ((start_pos = str.find('\\', start_pos)) != std::string::npos) {         str.replace(start_pos, 1, "\\\\");         start_pos += 2;@@ -379,7 +386,7 @@  /** Valid C++ identifier, note that this does not ensure the uniqueness of identifiers returned. */ inline std::string identifier(std::string id) {-    for (size_t i = 0; i < id.length(); i++) {+    for (std::size_t i = 0; i < id.length(); i++) {         if (((isalpha(id[i]) == 0) && i == 0) || ((isalnum(id[i]) == 0) && id[i] != '_')) {             id[i] = '_';         }@@ -392,7 +399,7 @@ inline std::string unescape(         const std::string& inputString, const std::string& needle, const std::string& replacement) {     std::string result = inputString;-    size_t pos = 0;+    std::size_t pos = 0;     while ((pos = result.find(needle, pos)) != std::string::npos) {         result = result.replace(pos, needle.length(), replacement);         pos += replacement.length();@@ -411,7 +418,7 @@ inline std::string escape(         const std::string& inputString, const std::string& needle, const std::string& replacement) {     std::string result = inputString;-    size_t pos = 0;+    std::size_t pos = 0;     while ((pos = result.find(needle, pos)) != std::string::npos) {         result = result.replace(pos, needle.length(), replacement);         pos += replacement.length();
+ cbits/souffle/utility/Types.h view
@@ -0,0 +1,88 @@+/*+ * 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 Types.h+ *+ * @brief Shared type definitions+ *+ ***********************************************************************/++#pragma once++#include <memory>+#include <type_traits>+#include <vector>++namespace souffle {+template <typename A>+using Own = std::unique_ptr<A>;++template <typename A, typename B = A, typename... Args>+Own<A> mk(Args&&... xs) {+    return std::make_unique<B>(std::forward<Args>(xs)...);+}++template <typename A>+using VecOwn = std::vector<Own<A>>;++/**+ * Copy the const qualifier of type T onto type U+ */+template <typename A, typename B>+using copy_const = std::conditional<std::is_const_v<A>, const B, B>;++template <typename A, typename B>+using copy_const_t = typename copy_const<A, B>::type;++namespace detail {+template <typename T, typename U = void>+struct is_range_impl : std::false_type {};++template <typename T>+struct is_range_impl<T, std::void_t<decltype(*std::begin(std::declval<T&>()))>> : std::true_type {};++}  // namespace detail++/**+ * A simple test to check if T is a range (i.e. has std::begin())+ */+template <typename T>+struct is_range : detail::is_range_impl<T> {};++template <typename T>+inline constexpr bool is_range_v = is_range<T>::value;++/**+ * Type identity, remove once we have C++20+ */+template <typename T>+struct type_identity {+    using type = T;+};++/**+ * Remove cv ref, remove once we have C++ 20+ */+template <typename T>+using remove_cvref = std::remove_cv<std::remove_reference_t<T>>;++template <class T>+using remove_cvref_t = typename remove_cvref<T>::type;++template <typename T>+struct is_pointer_like : std::is_pointer<T> {};++template <typename T>+struct is_pointer_like<Own<T>> : std::true_type {};++template <typename T>+inline constexpr bool is_pointer_like_v = is_pointer_like<T>::value;++}  // namespace souffle
cbits/souffle/utility/json11.h view
@@ -168,7 +168,7 @@     const object& object_items() const;      // Return a reference to arr[i] if this is an array, Json() otherwise.-    const Json& operator[](size_t i) const;+    const Json& operator[](std::size_t i) const;     // Return a reference to obj[key] if this is an object, Json() otherwise.     const Json& operator[](const std::string& key) const; @@ -257,7 +257,7 @@     virtual bool bool_value() const;     virtual const std::string& string_value() const;     virtual const Json::array& array_items() const;-    virtual const Json& operator[](size_t i) const;+    virtual const Json& operator[](std::size_t i) const;     virtual const Json::object& object_items() const;     virtual const Json& operator[](const std::string& key) const;     virtual ~JsonValue() = default;@@ -308,7 +308,7 @@  static void dump(const std::string& value, std::string& out) {     out += '"';-    for (size_t i = 0; i < value.length(); i++) {+    for (std::size_t i = 0; i < value.length(); i++) {         const char ch = value[i];         if (ch == '\\') {             out += "\\\\";@@ -465,7 +465,7 @@     const Json::array& array_items() const override {         return m_value;     }-    const Json& operator[](size_t i) const override;+    const Json& operator[](std::size_t i) const override;  public:     explicit JsonArray(const Json::array& value) : Value(value) {}@@ -557,7 +557,7 @@ inline const std::map<std::string, Json>& Json::object_items() const {     return m_ptr->object_items(); }-inline const Json& Json::operator[](size_t i) const {+inline const Json& Json::operator[](std::size_t i) const {     return (*m_ptr)[i]; } inline const Json& Json::operator[](const std::string& key) const {@@ -585,7 +585,7 @@ inline const std::map<std::string, Json>& JsonValue::object_items() const {     return statics().empty_map; }-inline const Json& JsonValue::operator[](size_t) const {+inline const Json& JsonValue::operator[](std::size_t) const {     return static_null(); } inline const Json& JsonValue::operator[](const std::string&) const {@@ -596,7 +596,7 @@     auto iter = m_value.find(key);     return (iter == m_value.end()) ? static_null() : iter->second; }-inline const Json& JsonArray::operator[](size_t i) const {+inline const Json& JsonArray::operator[](std::size_t i) const {     if (i >= m_value.size()) {         return static_null();     }@@ -660,7 +660,7 @@     /* State      */     const std::string& str;-    size_t i;+    std::size_t i;     std::string& err;     bool failed;     const JsonParse strategy;@@ -835,7 +835,7 @@                 if (esc.length() < 4) {                     return fail("bad \\u escape: " + esc, "");                 }-                for (size_t j = 0; j < 4; j++) {+                for (std::size_t j = 0; j < 4; j++) {                     if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F') &&                             !in_range(esc[j], '0', '9'))                         return fail("bad \\u escape: " + esc, "");@@ -888,7 +888,7 @@      * Parse a double.      */     Json parse_number() {-        size_t start_pos = i;+        std::size_t start_pos = i;          if (str[i] == '-') {             i++;@@ -910,7 +910,7 @@         }          if (str[i] != '.' && str[i] != 'e' && str[i] != 'E' &&-                (i - start_pos) <= static_cast<size_t>(std::numeric_limits<int>::digits10)) {+                (i - start_pos) <= static_cast<std::size_t>(std::numeric_limits<int>::digits10)) {             return std::atoll(str.c_str() + start_pos);         } 
+ cbits/souffle/utility/span.h view
@@ -0,0 +1,662 @@+#pragma once++#if __cplusplus >= 202000L++#include <span>  // use std lib impl++namespace souffle {+constexpr auto dynamic_extent = std::dynamic_extent;++template <typename A, std::size_t E = std::dynamic_extent>+using span = std::span<A, E>;+}  // namespace souffle++#else++// clang-format off+/*+This is an implementation of C++20's std::span+http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/n4820.pdf+*/++//          Copyright Tristan Brindle 2018.+// Distributed under the Boost Software License, Version 1.0.+//    (See accompanying file ../../LICENSE_1_0.txt or copy at+//          https://www.boost.org/LICENSE_1_0.txt)++#ifndef TCB_SPAN_HPP_INCLUDED+#define TCB_SPAN_HPP_INCLUDED++#include <array>+#include <cstddef>+#include <cstdint>+#include <type_traits>++#ifndef TCB_SPAN_NO_EXCEPTIONS+// Attempt to discover whether we're being compiled with exception support+#if !(defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND))+#define TCB_SPAN_NO_EXCEPTIONS+#endif+#endif++#ifndef TCB_SPAN_NO_EXCEPTIONS+#include <cstdio>+#include <stdexcept>+#endif++// Various feature test macros++#ifndef TCB_SPAN_NAMESPACE_NAME+#define TCB_SPAN_NAMESPACE_NAME tcb+#endif++#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)+#define TCB_SPAN_HAVE_CPP17+#endif++#if __cplusplus >= 201402L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L)+#define TCB_SPAN_HAVE_CPP14+#endif++namespace TCB_SPAN_NAMESPACE_NAME {++// Establish default contract checking behavior+#if !defined(TCB_SPAN_THROW_ON_CONTRACT_VIOLATION) &&                          \+    !defined(TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION) &&                      \+    !defined(TCB_SPAN_NO_CONTRACT_CHECKING)+#if defined(NDEBUG) || !defined(TCB_SPAN_HAVE_CPP14)+#define TCB_SPAN_NO_CONTRACT_CHECKING+#else+#define TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION+#endif+#endif++#if defined(TCB_SPAN_THROW_ON_CONTRACT_VIOLATION)+struct contract_violation_error : std::logic_error {+    explicit contract_violation_error(const char* msg) : std::logic_error(msg)+    {}+};++inline void contract_violation(const char* msg)+{+    throw contract_violation_error(msg);+}++#elif defined(TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION)+[[noreturn]] inline void contract_violation(const char* /*unused*/)+{+    std::terminate();+}+#endif++#if !defined(TCB_SPAN_NO_CONTRACT_CHECKING)+#define TCB_SPAN_STRINGIFY(cond) #cond+#define TCB_SPAN_EXPECT(cond)                                                  \+    cond ? (void) 0 : contract_violation("Expected " TCB_SPAN_STRINGIFY(cond))+#else+#define TCB_SPAN_EXPECT(cond)+#endif++#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_inline_variables)+#define TCB_SPAN_INLINE_VAR inline+#else+#define TCB_SPAN_INLINE_VAR+#endif++#if defined(TCB_SPAN_HAVE_CPP14) ||                                            \+    (defined(__cpp_constexpr) && __cpp_constexpr >= 201304)+#define TCB_SPAN_HAVE_CPP14_CONSTEXPR+#endif++#if defined(TCB_SPAN_HAVE_CPP14_CONSTEXPR)+#define TCB_SPAN_CONSTEXPR14 constexpr+#else+#define TCB_SPAN_CONSTEXPR14+#endif++#if defined(TCB_SPAN_HAVE_CPP14_CONSTEXPR) &&                                  \+    (!defined(_MSC_VER) || _MSC_VER > 1900)+#define TCB_SPAN_CONSTEXPR_ASSIGN constexpr+#else+#define TCB_SPAN_CONSTEXPR_ASSIGN+#endif++#if defined(TCB_SPAN_NO_CONTRACT_CHECKING)+#define TCB_SPAN_CONSTEXPR11 constexpr+#else+#define TCB_SPAN_CONSTEXPR11 TCB_SPAN_CONSTEXPR14+#endif++#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_deduction_guides)+#define TCB_SPAN_HAVE_DEDUCTION_GUIDES+#endif++#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_byte)+#define TCB_SPAN_HAVE_STD_BYTE+#endif++#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_array_constexpr)+#define TCB_SPAN_HAVE_CONSTEXPR_STD_ARRAY_ETC+#endif++#if defined(TCB_SPAN_HAVE_CONSTEXPR_STD_ARRAY_ETC)+#define TCB_SPAN_ARRAY_CONSTEXPR constexpr+#else+#define TCB_SPAN_ARRAY_CONSTEXPR+#endif++#ifdef TCB_SPAN_HAVE_STD_BYTE+using byte = std::byte;+#else+using byte = unsigned char;+#endif++#if defined(TCB_SPAN_HAVE_CPP17)+#define TCB_SPAN_NODISCARD [[nodiscard]]+#else+#define TCB_SPAN_NODISCARD+#endif++TCB_SPAN_INLINE_VAR constexpr std::size_t dynamic_extent = SIZE_MAX;++template <typename ElementType, std::size_t Extent = dynamic_extent>+class span;++namespace detail {++template <typename E, std::size_t S>+struct span_storage {+    constexpr span_storage() noexcept = default;++    constexpr span_storage(E* p_ptr, std::size_t /*unused*/) noexcept+       : ptr(p_ptr)+    {}++    E* ptr = nullptr;+    static constexpr std::size_t size = S;+};++template <typename E>+struct span_storage<E, dynamic_extent> {+    constexpr span_storage() noexcept = default;++    constexpr span_storage(E* p_ptr, std::size_t p_size) noexcept+        : ptr(p_ptr), size(p_size)+    {}++    E* ptr = nullptr;+    std::size_t size = 0;+};++// Reimplementation of C++17 std::size() and std::data()+#if defined(TCB_SPAN_HAVE_CPP17) ||                                            \+    defined(__cpp_lib_nonmember_container_access)+using std::data;+using std::size;+#else+template <class C>+constexpr auto size(const C& c) -> decltype(c.size())+{+    return c.size();+}++template <class T, std::size_t N>+constexpr std::size_t size(const T (&)[N]) noexcept+{+    return N;+}++template <class C>+constexpr auto data(C& c) -> decltype(c.data())+{+    return c.data();+}++template <class C>+constexpr auto data(const C& c) -> decltype(c.data())+{+    return c.data();+}++template <class T, std::size_t N>+constexpr T* data(T (&array)[N]) noexcept+{+    return array;+}++template <class E>+constexpr const E* data(std::initializer_list<E> il) noexcept+{+    return il.begin();+}+#endif // TCB_SPAN_HAVE_CPP17++#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_void_t)+using std::void_t;+#else+template <typename...>+using void_t = void;+#endif++template <typename T>+using uncvref_t =+    typename std::remove_cv<typename std::remove_reference<T>::type>::type;++template <typename>+struct is_span : std::false_type {};++template <typename T, std::size_t S>+struct is_span<span<T, S>> : std::true_type {};++template <typename>+struct is_std_array : std::false_type {};++template <typename T, std::size_t N>+struct is_std_array<std::array<T, N>> : std::true_type {};++template <typename, typename = void>+struct has_size_and_data : std::false_type {};++template <typename T>+struct has_size_and_data<T, void_t<decltype(detail::size(std::declval<T>())),+                                   decltype(detail::data(std::declval<T>()))>>+    : std::true_type {};++template <typename C, typename U = uncvref_t<C>>+struct is_container {+    static constexpr bool value =+        !is_span<U>::value && !is_std_array<U>::value &&+        !std::is_array<U>::value && has_size_and_data<C>::value;+};++template <typename T>+using remove_pointer_t = typename std::remove_pointer<T>::type;++template <typename, typename, typename = void>+struct is_container_element_type_compatible : std::false_type {};++template <typename T, typename E>+struct is_container_element_type_compatible<+    T, E,+    typename std::enable_if<+        !std::is_same<typename std::remove_cv<decltype(+                          detail::data(std::declval<T>()))>::type,+                      void>::value>::type>+    : std::is_convertible<+          remove_pointer_t<decltype(detail::data(std::declval<T>()))> (*)[],+          E (*)[]> {};++template <typename, typename = std::size_t>+struct is_complete : std::false_type {};++template <typename T>+struct is_complete<T, decltype(sizeof(T))> : std::true_type {};++} // namespace detail++template <typename ElementType, std::size_t Extent>+class span {+    static_assert(std::is_object<ElementType>::value,+                  "A span's ElementType must be an object type (not a "+                  "reference type or void)");+    static_assert(detail::is_complete<ElementType>::value,+                  "A span's ElementType must be a complete type (not a forward "+                  "declaration)");+    static_assert(!std::is_abstract<ElementType>::value,+                  "A span's ElementType cannot be an abstract class type");++    using storage_type = detail::span_storage<ElementType, Extent>;++public:+    // constants and types+    using element_type = ElementType;+    using value_type = typename std::remove_cv<ElementType>::type;+    using size_type = std::size_t;+    using difference_type = std::ptrdiff_t;+    using pointer = element_type*;+    using const_pointer = const element_type*;+    using reference = element_type&;+    using const_reference = const element_type&;+    using iterator = pointer;+    using reverse_iterator = std::reverse_iterator<iterator>;++    static constexpr size_type extent = Extent;++    // [span.cons], span constructors, copy, assignment, and destructor+    template <+        std::size_t E = Extent,+        typename std::enable_if<(E == dynamic_extent || E <= 0), int>::type = 0>+    constexpr span() noexcept // NOLINT : clang-tidy is mistaken. one cannot `default` a template ctor+    {}++    TCB_SPAN_CONSTEXPR11 span(pointer ptr, size_type count)+        : storage_(ptr, count)+    {+        TCB_SPAN_EXPECT(extent == dynamic_extent || count == extent);+    }++    TCB_SPAN_CONSTEXPR11 span(pointer first_elem, pointer last_elem)+        : storage_(first_elem, last_elem - first_elem)+    {+        TCB_SPAN_EXPECT(extent == dynamic_extent ||+                        last_elem - first_elem ==+                            static_cast<std::ptrdiff_t>(extent));+    }++    template <std::size_t N, std::size_t E = Extent,+              typename std::enable_if<+                  (E == dynamic_extent || N == E) &&+                      detail::is_container_element_type_compatible<+                          element_type (&)[N], ElementType>::value,+                  int>::type = 0>+    constexpr span(element_type (&arr)[N]) noexcept : storage_(arr, N)+    {}++    template <std::size_t N, std::size_t E = Extent,+              typename std::enable_if<+                  (E == dynamic_extent || N == E) &&+                      detail::is_container_element_type_compatible<+                          std::array<value_type, N>&, ElementType>::value,+                  int>::type = 0>+    TCB_SPAN_ARRAY_CONSTEXPR span(std::array<value_type, N>& arr) noexcept+        : storage_(arr.data(), N)+    {}++    template <std::size_t N, std::size_t E = Extent,+              typename std::enable_if<+                  (E == dynamic_extent || N == E) &&+                      detail::is_container_element_type_compatible<+                          const std::array<value_type, N>&, ElementType>::value,+                  int>::type = 0>+    TCB_SPAN_ARRAY_CONSTEXPR span(const std::array<value_type, N>& arr) noexcept+        : storage_(arr.data(), N)+    {}++    template <+        typename Container, std::size_t E = Extent,+        typename std::enable_if<+            E == dynamic_extent && detail::is_container<Container>::value &&+                detail::is_container_element_type_compatible<+                    Container&, ElementType>::value,+            int>::type = 0>+    constexpr span(Container& cont)+        : storage_(detail::data(cont), detail::size(cont))+    {}++    template <+        typename Container, std::size_t E = Extent,+        typename std::enable_if<+            E == dynamic_extent && detail::is_container<Container>::value &&+                detail::is_container_element_type_compatible<+                    const Container&, ElementType>::value,+            int>::type = 0>+    constexpr span(const Container& cont)+        : storage_(detail::data(cont), detail::size(cont))+    {}++    constexpr span(const span& other) noexcept = default;++    template <typename OtherElementType, std::size_t OtherExtent,+              typename std::enable_if<+                  (Extent == OtherExtent || Extent == dynamic_extent) &&+                      std::is_convertible<OtherElementType (*)[],+                                          ElementType (*)[]>::value,+                  int>::type = 0>+    constexpr span(const span<OtherElementType, OtherExtent>& other) noexcept+        : storage_(other.data(), other.size())+    {}++    ~span() noexcept = default;++    TCB_SPAN_CONSTEXPR_ASSIGN span&+    operator=(const span& other) noexcept = default;++    // [span.sub], span subviews+    template <std::size_t Count>+    TCB_SPAN_CONSTEXPR11 span<element_type, Count> first() const+    {+        TCB_SPAN_EXPECT(Count <= size());+        return {data(), Count};+    }++    template <std::size_t Count>+    TCB_SPAN_CONSTEXPR11 span<element_type, Count> last() const+    {+        TCB_SPAN_EXPECT(Count <= size());+        return {data() + (size() - Count), Count};+    }++    template <std::size_t Offset, std::size_t Count = dynamic_extent>+    using subspan_return_t =+        span<ElementType, Count != dynamic_extent+                              ? Count+                              : (Extent != dynamic_extent ? Extent - Offset+                                                          : dynamic_extent)>;++    template <std::size_t Offset, std::size_t Count = dynamic_extent>+    TCB_SPAN_CONSTEXPR11 subspan_return_t<Offset, Count> subspan() const+    {+        TCB_SPAN_EXPECT(Offset <= size() &&+                        (Count == dynamic_extent || Offset + Count <= size()));+        return {data() + Offset,+                Count != dynamic_extent ? Count : size() - Offset};+    }++    TCB_SPAN_CONSTEXPR11 span<element_type, dynamic_extent>+    first(size_type count) const+    {+        TCB_SPAN_EXPECT(count <= size());+        return {data(), count};+    }++    TCB_SPAN_CONSTEXPR11 span<element_type, dynamic_extent>+    last(size_type count) const+    {+        TCB_SPAN_EXPECT(count <= size());+        return {data() + (size() - count), count};+    }++    TCB_SPAN_CONSTEXPR11 span<element_type, dynamic_extent>+    subspan(size_type offset, size_type count = dynamic_extent) const+    {+        TCB_SPAN_EXPECT(offset <= size() &&+                        (count == dynamic_extent || offset + count <= size()));+        return {data() + offset,+                count == dynamic_extent ? size() - offset : count};+    }++    // [span.obs], span observers+    constexpr size_type size() const noexcept { return storage_.size; }++    constexpr size_type size_bytes() const noexcept+    {+        return size() * sizeof(element_type);+    }++    TCB_SPAN_NODISCARD constexpr bool empty() const noexcept+    {+        return size() == 0;+    }++    // [span.elem], span element access+    TCB_SPAN_CONSTEXPR11 reference operator[](size_type idx) const+    {+        TCB_SPAN_EXPECT(idx < size());+        return *(data() + idx);+    }++    TCB_SPAN_CONSTEXPR11 reference front() const+    {+        TCB_SPAN_EXPECT(!empty());+        return *data();+    }++    TCB_SPAN_CONSTEXPR11 reference back() const+    {+        TCB_SPAN_EXPECT(!empty());+        return *(data() + (size() - 1));+    }++    constexpr pointer data() const noexcept { return storage_.ptr; }++    // [span.iterators], span iterator support+    constexpr iterator begin() const noexcept { return data(); }++    constexpr iterator end() const noexcept { return data() + size(); }++    TCB_SPAN_ARRAY_CONSTEXPR reverse_iterator rbegin() const noexcept+    {+        return reverse_iterator(end());+    }++    TCB_SPAN_ARRAY_CONSTEXPR reverse_iterator rend() const noexcept+    {+        return reverse_iterator(begin());+    }++private:+    storage_type storage_{};+};++#ifdef TCB_SPAN_HAVE_DEDUCTION_GUIDES++/* Deduction Guides */+template <class T, std::size_t N>+span(T (&)[N])->span<T, N>;++template <class T, std::size_t N>+span(std::array<T, N>&)->span<T, N>;++template <class T, std::size_t N>+span(const std::array<T, N>&)->span<const T, N>;++template <class Container>+span(Container&)->span<typename Container::value_type>;++template <class Container>+span(const Container&)->span<const typename Container::value_type>;++#endif // TCB_HAVE_DEDUCTION_GUIDES++template <typename ElementType, std::size_t Extent>+constexpr span<ElementType, Extent>+make_span(span<ElementType, Extent> s) noexcept+{+    return s;+}++template <typename T, std::size_t N>+constexpr span<T, N> make_span(T (&arr)[N]) noexcept+{+    return {arr};+}++template <typename T, std::size_t N>+TCB_SPAN_ARRAY_CONSTEXPR span<T, N> make_span(std::array<T, N>& arr) noexcept+{+    return {arr};+}++template <typename T, std::size_t N>+TCB_SPAN_ARRAY_CONSTEXPR span<const T, N>+make_span(const std::array<T, N>& arr) noexcept+{+    return {arr};+}++template <typename Container>+constexpr span<typename Container::value_type> make_span(Container& cont)+{+    return {cont};+}++template <typename Container>+constexpr span<const typename Container::value_type>+make_span(const Container& cont)+{+    return {cont};+}++template <typename ElementType, std::size_t Extent>+span<const byte, ((Extent == dynamic_extent) ? dynamic_extent+                                             : sizeof(ElementType) * Extent)>+as_bytes(span<ElementType, Extent> s) noexcept+{+    return {reinterpret_cast<const byte*>(s.data()), s.size_bytes()};+}++template <+    class ElementType, std::size_t Extent,+    typename std::enable_if<!std::is_const<ElementType>::value, int>::type = 0>+span<byte, ((Extent == dynamic_extent) ? dynamic_extent+                                       : sizeof(ElementType) * Extent)>+as_writable_bytes(span<ElementType, Extent> s) noexcept+{+    return {reinterpret_cast<byte*>(s.data()), s.size_bytes()};+}++template <std::size_t N, typename E, std::size_t S>+constexpr auto get(span<E, S> s) -> decltype(s[N])+{+    return s[N];+}++} // namespace TCB_SPAN_NAMESPACE_NAME++namespace std {++// see:     https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82716+// libc++:  https://reviews.llvm.org/D55466#1325498+//          `libc++` changed to use `struct`+// spec:    http://eel.is/c++draft/tuple.helper+//          Spec says to use `struct`.+// MSVC:    Has different ABI for `class`/`struct`.+//          Defined `tuple_size` as `class`.+#if defined(_MSC_VER)+    #define TCB_SPAN_TUPLE_SIZE_KIND class+#else+    #define TCB_SPAN_TUPLE_SIZE_KIND struct+#endif++#if defined(__clang__)+    #pragma clang diagnostic push+    #pragma clang diagnostic ignored "-Wmismatched-tags"+#endif++template <typename ElementType, std::size_t Extent>+TCB_SPAN_TUPLE_SIZE_KIND tuple_size<TCB_SPAN_NAMESPACE_NAME::span<ElementType, Extent>>+    : public integral_constant<std::size_t, Extent> {};++template <typename ElementType>+TCB_SPAN_TUPLE_SIZE_KIND tuple_size<TCB_SPAN_NAMESPACE_NAME::span<+    ElementType, TCB_SPAN_NAMESPACE_NAME::dynamic_extent>>; // not defined++template <std::size_t I, typename ElementType, std::size_t Extent>+TCB_SPAN_TUPLE_SIZE_KIND tuple_element<I, TCB_SPAN_NAMESPACE_NAME::span<ElementType, Extent>> {+public:+    static_assert(Extent != TCB_SPAN_NAMESPACE_NAME::dynamic_extent &&+                      I < Extent,+                  "");+    using type = ElementType;+};++#if defined(__clang__)+    #pragma clang diagnostic pop+#endif++#undef TCB_SPAN_TUPLE_SIZE_KIND++} // end namespace std++#endif // TCB_SPAN_HPP_INCLUDED++// clang-format on++namespace souffle {+constexpr auto dynamic_extent = tcb::dynamic_extent;++template <typename A, std::size_t E = tcb::dynamic_extent>+using span = tcb::span<A, E>;+}  // namespace souffle++#endif
cbits/souffle/utility/tinyformat.h view
@@ -50,7 +50,7 @@ // //   std::string weekday = "Wednesday"; //   const char* month = "July";-//   size_t day = 27;+//   std::size_t day = 27; //   long hour = 14; //   int min = 44; //@@ -66,7 +66,7 @@ // // The strange types here emphasize the type safety of the interface; it is // possible to print a std::string using the "%s" conversion, and a-// size_t using the "%d" conversion.  A similar result could be achieved+// std::size_t using the "%d" conversion.  A similar result could be achieved // using either of the tfm::format() functions.  One prints on a user provided // stream: //
lib/Language/Souffle/Compiled.hs view
@@ -29,7 +29,6 @@   ) where  import Prelude hiding ( init )-import Control.Exception import Control.Monad.Except import Control.Monad.State.Strict import Data.Foldable ( traverse_ )@@ -172,11 +171,10 @@       else do         ptr <- gets castPtr         bs <- liftIO $ BSU.unsafePackCStringLen (ptr, fromIntegral byteCount)-        -- NOTE: `evaluate` is needed here to force the text value. A copy needs to-        -- be made (using toShort), before the bytearray is overwritten.-        bss <- liftIO $ evaluate $ BSS.toShort bs         put $ ptr `plusPtr` fromIntegral byteCount-        pure $ TSU.fromShortByteStringUnsafe bss+        -- NOTE: $! is needed here to force the text value. A copy needs to+        -- be made (using toShort), before the bytearray is overwritten.+        pure $! TSU.fromShortByteStringUnsafe $ BSS.toShort bs   {-# INLINABLE popText #-}  
souffle-haskell.cabal view
@@ -3,11 +3,9 @@ -- This file has been generated from package.yaml by hpack version 0.34.2. -- -- see: https://github.com/sol/hpack------ hash: 7e179d1e2ce8ddd1c9a53631a6b4743b754065bf70a2130d654d738b83cec53a  name:           souffle-haskell-version:        3.0.0+version:        3.1.0 synopsis:       Souffle Datalog bindings for Haskell description:    Souffle Datalog bindings for Haskell. category:       Logic Programming, Foreign Binding, Bindings@@ -25,9 +23,10 @@     LICENSE     cbits/souffle.h     cbits/souffle/CompiledSouffle.h-    cbits/souffle/CompiledTuple.h     cbits/souffle/datastructure/Brie.h     cbits/souffle/datastructure/BTree.h+    cbits/souffle/datastructure/ConcurrentFlyweight.h+    cbits/souffle/datastructure/ConcurrentInsertOnlyHashMap.h     cbits/souffle/datastructure/EquivalenceRelation.h     cbits/souffle/datastructure/LambdaBTree.h     cbits/souffle/datastructure/PiggyList.h@@ -54,12 +53,15 @@     cbits/souffle/utility/EvaluatorUtil.h     cbits/souffle/utility/FileUtil.h     cbits/souffle/utility/FunctionalUtil.h+    cbits/souffle/utility/Iteration.h     cbits/souffle/utility/json11.h     cbits/souffle/utility/MiscUtil.h     cbits/souffle/utility/ParallelUtil.h+    cbits/souffle/utility/span.h     cbits/souffle/utility/StreamUtil.h     cbits/souffle/utility/StringUtil.h     cbits/souffle/utility/tinyformat.h+    cbits/souffle/utility/Types.h     cbits/souffle.cpp     cbits/souffle/LICENSE @@ -90,15 +92,19 @@       cbits/souffle   install-includes:       souffle/CompiledSouffle.h-      souffle/CompiledTuple.h       souffle/RamTypes.h       souffle/RecordTable.h+      souffle/datastructure/ConcurrentFlyweight.h+      souffle/datastructure/ConcurrentInsertOnlyHashMap.h+      souffle/utility/ParallelUtil.h+      souffle/utility/span.h       souffle/SignalHandler.h       souffle/SouffleInterface.h       souffle/SymbolTable.h       souffle/utility/MiscUtil.h+      souffle/utility/Iteration.h+      souffle/utility/Types.h       souffle/utility/tinyformat.h-      souffle/utility/ParallelUtil.h       souffle/utility/StreamUtil.h       souffle/utility/ContainerUtil.h       souffle/datastructure/Brie.h@@ -112,8 +118,8 @@       souffle/io/IOSystem.h       souffle/io/ReadStream.h       souffle/io/SerialisationStream.h-      souffle/utility/json11.h       souffle/utility/StringUtil.h+      souffle/utility/json11.h       souffle/io/ReadStreamCSV.h       souffle/utility/FileUtil.h       souffle/io/gzfstream.h@@ -166,15 +172,19 @@       cbits/souffle   install-includes:       souffle/CompiledSouffle.h-      souffle/CompiledTuple.h       souffle/RamTypes.h       souffle/RecordTable.h+      souffle/datastructure/ConcurrentFlyweight.h+      souffle/datastructure/ConcurrentInsertOnlyHashMap.h+      souffle/utility/ParallelUtil.h+      souffle/utility/span.h       souffle/SignalHandler.h       souffle/SouffleInterface.h       souffle/SymbolTable.h       souffle/utility/MiscUtil.h+      souffle/utility/Iteration.h+      souffle/utility/Types.h       souffle/utility/tinyformat.h-      souffle/utility/ParallelUtil.h       souffle/utility/StreamUtil.h       souffle/utility/ContainerUtil.h       souffle/datastructure/Brie.h@@ -188,8 +198,8 @@       souffle/io/IOSystem.h       souffle/io/ReadStream.h       souffle/io/SerialisationStream.h-      souffle/utility/json11.h       souffle/utility/StringUtil.h+      souffle/utility/json11.h       souffle/io/ReadStreamCSV.h       souffle/utility/FileUtil.h       souffle/io/gzfstream.h@@ -246,15 +256,19 @@       cbits/souffle   install-includes:       souffle/CompiledSouffle.h-      souffle/CompiledTuple.h       souffle/RamTypes.h       souffle/RecordTable.h+      souffle/datastructure/ConcurrentFlyweight.h+      souffle/datastructure/ConcurrentInsertOnlyHashMap.h+      souffle/utility/ParallelUtil.h+      souffle/utility/span.h       souffle/SignalHandler.h       souffle/SouffleInterface.h       souffle/SymbolTable.h       souffle/utility/MiscUtil.h+      souffle/utility/Iteration.h+      souffle/utility/Types.h       souffle/utility/tinyformat.h-      souffle/utility/ParallelUtil.h       souffle/utility/StreamUtil.h       souffle/utility/ContainerUtil.h       souffle/datastructure/Brie.h@@ -268,8 +282,8 @@       souffle/io/IOSystem.h       souffle/io/ReadStream.h       souffle/io/SerialisationStream.h-      souffle/utility/json11.h       souffle/utility/StringUtil.h+      souffle/utility/json11.h       souffle/io/ReadStreamCSV.h       souffle/utility/FileUtil.h       souffle/io/gzfstream.h
tests/fixtures/edge_cases.cpp view
@@ -7,6 +7,7 @@ namespace souffle { static const RamDomain RAM_BIT_SHIFT_MASK = RAM_DOMAIN_SIZE - 1; struct t_btree_iii__0_1_2__111 {+static constexpr Relation::arity_type Arity = 3; using t_tuple = Tuple<RamDomain, 3>; struct t_comparator_0{  int operator()(const t_tuple& a, const t_tuple& b) const {@@ -109,6 +110,7 @@ } }; struct t_btree_i__0__1 {+static constexpr Relation::arity_type Arity = 1; using t_tuple = Tuple<RamDomain, 1>; struct t_comparator_0{  int operator()(const t_tuple& a, const t_tuple& b) const {@@ -211,6 +213,7 @@ } }; struct t_btree_uif__0_1_2__111 {+static constexpr Relation::arity_type Arity = 3; using t_tuple = Tuple<RamDomain, 3>; struct t_comparator_0{  int operator()(const t_tuple& a, const t_tuple& b) const {@@ -323,7 +326,7 @@    return result; } private:-static inline std::string substr_wrapper(const std::string& str, size_t idx, size_t len) {+static inline std::string substr_wrapper(const std::string& str, std::size_t idx, std::size_t len) {    std::string result;     try { result = str.substr(idx,len); } catch(...) {       std::cerr << "warning: wrong index position provided by substr(\"";@@ -342,48 +345,53 @@ RecordTable recordTable; // -- Table: empty_strings Own<t_btree_iii__0_1_2__111> rel_1_empty_strings = mk<t_btree_iii__0_1_2__111>();-souffle::RelationWrapper<0,t_btree_iii__0_1_2__111,Tuple<RamDomain,3>,3,0> wrapper_rel_1_empty_strings;+souffle::RelationWrapper<t_btree_iii__0_1_2__111> wrapper_rel_1_empty_strings; // -- Table: long_strings Own<t_btree_i__0__1> rel_2_long_strings = mk<t_btree_i__0__1>();-souffle::RelationWrapper<1,t_btree_i__0__1,Tuple<RamDomain,1>,1,0> wrapper_rel_2_long_strings;-// -- Table: no_strings-Own<t_btree_uif__0_1_2__111> rel_3_no_strings = mk<t_btree_uif__0_1_2__111>();-souffle::RelationWrapper<2,t_btree_uif__0_1_2__111,Tuple<RamDomain,3>,3,0> wrapper_rel_3_no_strings;+souffle::RelationWrapper<t_btree_i__0__1> wrapper_rel_2_long_strings; // -- Table: unicode-Own<t_btree_i__0__1> rel_4_unicode = mk<t_btree_i__0__1>();-souffle::RelationWrapper<3,t_btree_i__0__1,Tuple<RamDomain,1>,1,0> wrapper_rel_4_unicode;+Own<t_btree_i__0__1> rel_3_unicode = mk<t_btree_i__0__1>();+souffle::RelationWrapper<t_btree_i__0__1> wrapper_rel_3_unicode;+// -- Table: no_strings+Own<t_btree_uif__0_1_2__111> rel_4_no_strings = mk<t_btree_uif__0_1_2__111>();+souffle::RelationWrapper<t_btree_uif__0_1_2__111> wrapper_rel_4_no_strings; public:-Sf_edge_cases() : -wrapper_rel_1_empty_strings(*rel_1_empty_strings,symTable,"empty_strings",std::array<const char *,3>{{"s:symbol","s:symbol","i:number"}},std::array<const char *,3>{{"s","s2","n"}}),--wrapper_rel_2_long_strings(*rel_2_long_strings,symTable,"long_strings",std::array<const char *,1>{{"s:symbol"}},std::array<const char *,1>{{"s"}}),--wrapper_rel_3_no_strings(*rel_3_no_strings,symTable,"no_strings",std::array<const char *,3>{{"u:unsigned","i:number","f:float"}},std::array<const char *,3>{{"u","n","f"}}),--wrapper_rel_4_unicode(*rel_4_unicode,symTable,"unicode",std::array<const char *,1>{{"s:symbol"}},std::array<const char *,1>{{"s"}}){-addRelation("empty_strings",&wrapper_rel_1_empty_strings,true,true);-addRelation("long_strings",&wrapper_rel_2_long_strings,true,true);-addRelation("no_strings",&wrapper_rel_3_no_strings,true,true);-addRelation("unicode",&wrapper_rel_4_unicode,true,true);+Sf_edge_cases()+: wrapper_rel_1_empty_strings(0, *rel_1_empty_strings, *this, "empty_strings", std::array<const char *,3>{{"s:symbol","s:symbol","i:number"}}, std::array<const char *,3>{{"s","s2","n"}}, 0)+, wrapper_rel_2_long_strings(1, *rel_2_long_strings, *this, "long_strings", std::array<const char *,1>{{"s:symbol"}}, std::array<const char *,1>{{"s"}}, 0)+, wrapper_rel_3_unicode(2, *rel_3_unicode, *this, "unicode", std::array<const char *,1>{{"s:symbol"}}, std::array<const char *,1>{{"s"}}, 0)+, wrapper_rel_4_no_strings(3, *rel_4_no_strings, *this, "no_strings", std::array<const char *,3>{{"u:unsigned","i:number","f:float"}}, std::array<const char *,3>{{"u","n","f"}}, 0)+{+addRelation("empty_strings", wrapper_rel_1_empty_strings, true, true);+addRelation("long_strings", wrapper_rel_2_long_strings, true, true);+addRelation("unicode", wrapper_rel_3_unicode, true, true);+addRelation("no_strings", wrapper_rel_4_no_strings, true, true); } ~Sf_edge_cases() { }+ private:-std::string inputDirectory;-std::string outputDirectory;-bool performIO;-std::atomic<RamDomain> ctr{};+std::string             inputDirectory;+std::string             outputDirectory;+SignalHandler*          signalHandler {SignalHandler::instance()};+std::atomic<RamDomain>  ctr {};+std::atomic<std::size_t>     iter {};+bool                    performIO = false; -std::atomic<size_t> iter{};-void runFunction(std::string inputDirectoryArg = "", std::string outputDirectoryArg = "", bool performIOArg = false) {-this->inputDirectory = inputDirectoryArg;-this->outputDirectory = outputDirectoryArg;-this->performIO = performIOArg;-SignalHandler::instance()->set();+void runFunction(std::string  inputDirectoryArg   = "",+                 std::string  outputDirectoryArg  = "",+                 bool         performIOArg        = false) {+    this->inputDirectory  = std::move(inputDirectoryArg);+    this->outputDirectory = std::move(outputDirectoryArg);+    this->performIO       = performIOArg;++    // set default threads (in embedded mode)+    // if this is not set, and omp is used, the default omp setting of number of cores is used. #if defined(_OPENMP)-if (getNumThreads() > 0) {omp_set_num_threads(getNumThreads());}+    if (0 < getNumThreads()) { omp_set_num_threads(getNumThreads()); } #endif +    signalHandler->set(); // -- query evaluation -- {  std::vector<RamDomain> args, ret;@@ -403,7 +411,7 @@ }  // -- relation hint statistics ---SignalHandler::instance()->reset();+signalHandler->reset(); } public: void run() override { runFunction("", "", false); }@@ -412,40 +420,40 @@ } public: void printAll(std::string outputDirectoryArg = "") override {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s"},{"name","unicode"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"s\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s\ts2\tn"},{"auxArity","0"},{"name","empty_strings"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 3, \"params\": [\"s\", \"s2\", \"n\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 3, \"types\": [\"s:symbol\", \"s:symbol\", \"i:number\"]}}"}}); if (!outputDirectoryArg.empty()) {directiveMap["output-dir"] = outputDirectoryArg;}-IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_4_unicode);+IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_1_empty_strings); } catch (std::exception& e) {std::cerr << e.what();exit(1);}-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s"},{"name","long_strings"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"s\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s"},{"auxArity","0"},{"name","long_strings"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"s\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"s:symbol\"]}}"}}); if (!outputDirectoryArg.empty()) {directiveMap["output-dir"] = outputDirectoryArg;} IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_2_long_strings); } catch (std::exception& e) {std::cerr << e.what();exit(1);}-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","u\tn\tf"},{"name","no_strings"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 3, \"auxArity\": 0, \"params\": [\"u\", \"n\", \"f\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 3, \"auxArity\": 0, \"types\": [\"u:unsigned\", \"i:number\", \"f:float\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s"},{"auxArity","0"},{"name","unicode"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"s\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"s:symbol\"]}}"}}); if (!outputDirectoryArg.empty()) {directiveMap["output-dir"] = outputDirectoryArg;}-IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_3_no_strings);+IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_3_unicode); } catch (std::exception& e) {std::cerr << e.what();exit(1);}-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s\ts2\tn"},{"name","empty_strings"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 3, \"auxArity\": 0, \"params\": [\"s\", \"s2\", \"n\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 3, \"auxArity\": 0, \"types\": [\"s:symbol\", \"s:symbol\", \"i:number\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","u\tn\tf"},{"auxArity","0"},{"name","no_strings"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 3, \"params\": [\"u\", \"n\", \"f\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 3, \"types\": [\"u:unsigned\", \"i:number\", \"f:float\"]}}"}}); if (!outputDirectoryArg.empty()) {directiveMap["output-dir"] = outputDirectoryArg;}-IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_1_empty_strings);+IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_4_no_strings); } catch (std::exception& e) {std::cerr << e.what();exit(1);} } public: void loadAll(std::string inputDirectoryArg = "") override {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s"},{"fact-dir","."},{"name","long_strings"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"s\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s"},{"auxArity","0"},{"fact-dir","."},{"name","long_strings"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"s\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"s:symbol\"]}}"}}); if (!inputDirectoryArg.empty()) {directiveMap["fact-dir"] = inputDirectoryArg;} IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_2_long_strings); } catch (std::exception& e) {std::cerr << "Error loading data: " << e.what() << '\n';}-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s"},{"fact-dir","."},{"name","unicode"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"s\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s\ts2\tn"},{"auxArity","0"},{"fact-dir","."},{"name","empty_strings"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 3, \"params\": [\"s\", \"s2\", \"n\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 3, \"types\": [\"s:symbol\", \"s:symbol\", \"i:number\"]}}"}}); if (!inputDirectoryArg.empty()) {directiveMap["fact-dir"] = inputDirectoryArg;}-IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_4_unicode);+IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_1_empty_strings); } catch (std::exception& e) {std::cerr << "Error loading data: " << e.what() << '\n';}-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","u\tn\tf"},{"fact-dir","."},{"name","no_strings"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 3, \"auxArity\": 0, \"params\": [\"u\", \"n\", \"f\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 3, \"auxArity\": 0, \"types\": [\"u:unsigned\", \"i:number\", \"f:float\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s"},{"auxArity","0"},{"fact-dir","."},{"name","unicode"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"s\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"s:symbol\"]}}"}}); if (!inputDirectoryArg.empty()) {directiveMap["fact-dir"] = inputDirectoryArg;}-IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_3_no_strings);+IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_3_unicode); } catch (std::exception& e) {std::cerr << "Error loading data: " << e.what() << '\n';}-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s\ts2\tn"},{"fact-dir","."},{"name","empty_strings"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 3, \"auxArity\": 0, \"params\": [\"s\", \"s2\", \"n\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 3, \"auxArity\": 0, \"types\": [\"s:symbol\", \"s:symbol\", \"i:number\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","u\tn\tf"},{"auxArity","0"},{"fact-dir","."},{"name","no_strings"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 3, \"params\": [\"u\", \"n\", \"f\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 3, \"types\": [\"u:unsigned\", \"i:number\", \"f:float\"]}}"}}); if (!inputDirectoryArg.empty()) {directiveMap["fact-dir"] = inputDirectoryArg;}-IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_1_empty_strings);+IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_4_no_strings); } catch (std::exception& e) {std::cerr << "Error loading data: " << e.what() << '\n';} } public:@@ -458,54 +466,62 @@ } catch (std::exception& e) {std::cerr << e.what();exit(1);} try {std::map<std::string, std::string> rwOperation; rwOperation["IO"] = "stdout";+rwOperation["name"] = "empty_strings";+rwOperation["types"] = "{\"relation\": {\"arity\": 3, \"auxArity\": 0, \"types\": [\"s:symbol\", \"s:symbol\", \"i:number\"]}}";+IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_1_empty_strings);+} catch (std::exception& e) {std::cerr << e.what();exit(1);}+try {std::map<std::string, std::string> rwOperation;+rwOperation["IO"] = "stdout"; rwOperation["name"] = "unicode"; rwOperation["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}";-IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_4_unicode);+IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_3_unicode); } catch (std::exception& e) {std::cerr << e.what();exit(1);} try {std::map<std::string, std::string> rwOperation; rwOperation["IO"] = "stdout"; rwOperation["name"] = "no_strings"; rwOperation["types"] = "{\"relation\": {\"arity\": 3, \"auxArity\": 0, \"types\": [\"u:unsigned\", \"i:number\", \"f:float\"]}}";-IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_3_no_strings);+IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_4_no_strings); } catch (std::exception& e) {std::cerr << e.what();exit(1);}+}+public:+void dumpOutputs() override { try {std::map<std::string, std::string> rwOperation; rwOperation["IO"] = "stdout"; rwOperation["name"] = "empty_strings"; rwOperation["types"] = "{\"relation\": {\"arity\": 3, \"auxArity\": 0, \"types\": [\"s:symbol\", \"s:symbol\", \"i:number\"]}}"; IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_1_empty_strings); } catch (std::exception& e) {std::cerr << e.what();exit(1);}-}-public:-void dumpOutputs() override { try {std::map<std::string, std::string> rwOperation; rwOperation["IO"] = "stdout";-rwOperation["name"] = "unicode";+rwOperation["name"] = "long_strings"; rwOperation["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}";-IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_4_unicode);+IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_2_long_strings); } catch (std::exception& e) {std::cerr << e.what();exit(1);} try {std::map<std::string, std::string> rwOperation; rwOperation["IO"] = "stdout";-rwOperation["name"] = "long_strings";+rwOperation["name"] = "unicode"; rwOperation["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}";-IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_2_long_strings);+IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_3_unicode); } catch (std::exception& e) {std::cerr << e.what();exit(1);} try {std::map<std::string, std::string> rwOperation; rwOperation["IO"] = "stdout"; rwOperation["name"] = "no_strings"; rwOperation["types"] = "{\"relation\": {\"arity\": 3, \"auxArity\": 0, \"types\": [\"u:unsigned\", \"i:number\", \"f:float\"]}}";-IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_3_no_strings);-} catch (std::exception& e) {std::cerr << e.what();exit(1);}-try {std::map<std::string, std::string> rwOperation;-rwOperation["IO"] = "stdout";-rwOperation["name"] = "empty_strings";-rwOperation["types"] = "{\"relation\": {\"arity\": 3, \"auxArity\": 0, \"types\": [\"s:symbol\", \"s:symbol\", \"i:number\"]}}";-IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_1_empty_strings);+IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_4_no_strings); } catch (std::exception& e) {std::cerr << e.what();exit(1);} } public: SymbolTable& getSymbolTable() override { return symTable; }+RecordTable& getRecordTable() override {+return recordTable;+}+void setNumThreads(std::size_t numThreadsValue) override {+SouffleProgram::setNumThreads(numThreadsValue);+symTable.setNumLanes(getNumThreads());+recordTable.setNumLanes(getNumThreads());+} void executeSubroutine(std::string name, const std::vector<RamDomain>& args, std::vector<RamDomain>& ret) override { if (name == "stratum_0") { subroutine_0(args, ret);@@ -526,34 +542,34 @@ #endif // _MSC_VER void subroutine_0(const std::vector<RamDomain>& args, std::vector<RamDomain>& ret) { if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s\ts2\tn"},{"fact-dir","."},{"name","empty_strings"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 3, \"auxArity\": 0, \"params\": [\"s\", \"s2\", \"n\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 3, \"auxArity\": 0, \"types\": [\"s:symbol\", \"s:symbol\", \"i:number\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s\ts2\tn"},{"auxArity","0"},{"fact-dir","."},{"name","empty_strings"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 3, \"params\": [\"s\", \"s2\", \"n\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 3, \"types\": [\"s:symbol\", \"s:symbol\", \"i:number\"]}}"}}); if (!inputDirectory.empty()) {directiveMap["fact-dir"] = inputDirectory;} IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_1_empty_strings); } catch (std::exception& e) {std::cerr << "Error loading data: " << e.what() << '\n';} }-SignalHandler::instance()->setMsg(R"_(empty_strings("","",42).-in file /home/luc/souffle-haskell/tests/fixtures/edge_cases.dl [20:1-20:27])_");+signalHandler->setMsg(R"_(empty_strings("","",42).+in file /home/luc/personal/souffle-haskell/tests/fixtures/edge_cases.dl [20:1-20:27])_"); [&](){ CREATE_OP_CONTEXT(rel_1_empty_strings_op_ctxt,rel_1_empty_strings->createContext()); Tuple<RamDomain,3> tuple{{ramBitCast(RamSigned(0)),ramBitCast(RamSigned(0)),ramBitCast(RamSigned(42))}}; rel_1_empty_strings->insert(tuple,READ_OP_CONTEXT(rel_1_empty_strings_op_ctxt)); }-();SignalHandler::instance()->setMsg(R"_(empty_strings("","abc",42).-in file /home/luc/souffle-haskell/tests/fixtures/edge_cases.dl [21:1-21:30])_");+();signalHandler->setMsg(R"_(empty_strings("","abc",42).+in file /home/luc/personal/souffle-haskell/tests/fixtures/edge_cases.dl [21:1-21:30])_"); [&](){ CREATE_OP_CONTEXT(rel_1_empty_strings_op_ctxt,rel_1_empty_strings->createContext()); Tuple<RamDomain,3> tuple{{ramBitCast(RamSigned(0)),ramBitCast(RamSigned(1)),ramBitCast(RamSigned(42))}}; rel_1_empty_strings->insert(tuple,READ_OP_CONTEXT(rel_1_empty_strings_op_ctxt)); }-();SignalHandler::instance()->setMsg(R"_(empty_strings("abc","",42).-in file /home/luc/souffle-haskell/tests/fixtures/edge_cases.dl [22:1-22:30])_");+();signalHandler->setMsg(R"_(empty_strings("abc","",42).+in file /home/luc/personal/souffle-haskell/tests/fixtures/edge_cases.dl [22:1-22:30])_"); [&](){ CREATE_OP_CONTEXT(rel_1_empty_strings_op_ctxt,rel_1_empty_strings->createContext()); Tuple<RamDomain,3> tuple{{ramBitCast(RamSigned(1)),ramBitCast(RamSigned(0)),ramBitCast(RamSigned(42))}}; rel_1_empty_strings->insert(tuple,READ_OP_CONTEXT(rel_1_empty_strings_op_ctxt)); } ();if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s\ts2\tn"},{"name","empty_strings"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 3, \"auxArity\": 0, \"params\": [\"s\", \"s2\", \"n\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 3, \"auxArity\": 0, \"types\": [\"s:symbol\", \"s:symbol\", \"i:number\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s\ts2\tn"},{"auxArity","0"},{"name","empty_strings"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 3, \"params\": [\"s\", \"s2\", \"n\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 3, \"types\": [\"s:symbol\", \"s:symbol\", \"i:number\"]}}"}}); if (!outputDirectory.empty()) {directiveMap["output-dir"] = outputDirectory;} IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_1_empty_strings); } catch (std::exception& e) {std::cerr << e.what();exit(1);}@@ -567,20 +583,20 @@ #endif // _MSC_VER void subroutine_1(const std::vector<RamDomain>& args, std::vector<RamDomain>& ret) { if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s"},{"fact-dir","."},{"name","long_strings"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"s\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s"},{"auxArity","0"},{"fact-dir","."},{"name","long_strings"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"s\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"s:symbol\"]}}"}}); if (!inputDirectory.empty()) {directiveMap["fact-dir"] = inputDirectory;} IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_2_long_strings); } catch (std::exception& e) {std::cerr << "Error loading data: " << e.what() << '\n';} }-SignalHandler::instance()->setMsg(R"_(long_strings("long_string_from_DL:...............................................................................................................................................................................................................................................................................................end").-in file /home/luc/souffle-haskell/tests/fixtures/edge_cases.dl [25:1-25:328])_");+signalHandler->setMsg(R"_(long_strings("long_string_from_DL:...............................................................................................................................................................................................................................................................................................end").+in file /home/luc/personal/souffle-haskell/tests/fixtures/edge_cases.dl [25:1-25:328])_"); [&](){ CREATE_OP_CONTEXT(rel_2_long_strings_op_ctxt,rel_2_long_strings->createContext()); Tuple<RamDomain,1> tuple{{ramBitCast(RamSigned(2))}}; rel_2_long_strings->insert(tuple,READ_OP_CONTEXT(rel_2_long_strings_op_ctxt)); } ();if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s"},{"name","long_strings"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"s\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s"},{"auxArity","0"},{"name","long_strings"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"s\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"s:symbol\"]}}"}}); if (!outputDirectory.empty()) {directiveMap["output-dir"] = outputDirectory;} IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_2_long_strings); } catch (std::exception& e) {std::cerr << e.what();exit(1);}@@ -594,29 +610,29 @@ #endif // _MSC_VER void subroutine_2(const std::vector<RamDomain>& args, std::vector<RamDomain>& ret) { if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s"},{"fact-dir","."},{"name","unicode"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"s\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s"},{"auxArity","0"},{"fact-dir","."},{"name","unicode"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"s\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"s:symbol\"]}}"}}); if (!inputDirectory.empty()) {directiveMap["fact-dir"] = inputDirectory;}-IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_4_unicode);+IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_3_unicode); } catch (std::exception& e) {std::cerr << "Error loading data: " << e.what() << '\n';} }-SignalHandler::instance()->setMsg(R"_(unicode("∀").-in file /home/luc/souffle-haskell/tests/fixtures/edge_cases.dl [30:1-30:16])_");+signalHandler->setMsg(R"_(unicode("∀").+in file /home/luc/personal/souffle-haskell/tests/fixtures/edge_cases.dl [30:1-30:16])_"); [&](){-CREATE_OP_CONTEXT(rel_4_unicode_op_ctxt,rel_4_unicode->createContext());+CREATE_OP_CONTEXT(rel_3_unicode_op_ctxt,rel_3_unicode->createContext()); Tuple<RamDomain,1> tuple{{ramBitCast(RamSigned(3))}};-rel_4_unicode->insert(tuple,READ_OP_CONTEXT(rel_4_unicode_op_ctxt));+rel_3_unicode->insert(tuple,READ_OP_CONTEXT(rel_3_unicode_op_ctxt)); }-();SignalHandler::instance()->setMsg(R"_(unicode("∀∀").-in file /home/luc/souffle-haskell/tests/fixtures/edge_cases.dl [31:1-31:19])_");+();signalHandler->setMsg(R"_(unicode("∀∀").+in file /home/luc/personal/souffle-haskell/tests/fixtures/edge_cases.dl [31:1-31:19])_"); [&](){-CREATE_OP_CONTEXT(rel_4_unicode_op_ctxt,rel_4_unicode->createContext());+CREATE_OP_CONTEXT(rel_3_unicode_op_ctxt,rel_3_unicode->createContext()); Tuple<RamDomain,1> tuple{{ramBitCast(RamSigned(4))}};-rel_4_unicode->insert(tuple,READ_OP_CONTEXT(rel_4_unicode_op_ctxt));+rel_3_unicode->insert(tuple,READ_OP_CONTEXT(rel_3_unicode_op_ctxt)); } ();if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s"},{"name","unicode"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"s\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","s"},{"auxArity","0"},{"name","unicode"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"s\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"s:symbol\"]}}"}}); if (!outputDirectory.empty()) {directiveMap["output-dir"] = outputDirectory;}-IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_4_unicode);+IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_3_unicode); } catch (std::exception& e) {std::cerr << e.what();exit(1);} } }@@ -628,29 +644,29 @@ #endif // _MSC_VER void subroutine_3(const std::vector<RamDomain>& args, std::vector<RamDomain>& ret) { if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","u\tn\tf"},{"fact-dir","."},{"name","no_strings"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 3, \"auxArity\": 0, \"params\": [\"u\", \"n\", \"f\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 3, \"auxArity\": 0, \"types\": [\"u:unsigned\", \"i:number\", \"f:float\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","u\tn\tf"},{"auxArity","0"},{"fact-dir","."},{"name","no_strings"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 3, \"params\": [\"u\", \"n\", \"f\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 3, \"types\": [\"u:unsigned\", \"i:number\", \"f:float\"]}}"}}); if (!inputDirectory.empty()) {directiveMap["fact-dir"] = inputDirectory;}-IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_3_no_strings);+IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_4_no_strings); } catch (std::exception& e) {std::cerr << "Error loading data: " << e.what() << '\n';} }-SignalHandler::instance()->setMsg(R"_(no_strings(42,-100,1.5).-in file /home/luc/souffle-haskell/tests/fixtures/edge_cases.dl [33:1-33:27])_");+signalHandler->setMsg(R"_(no_strings(42,-100,1.5).+in file /home/luc/personal/souffle-haskell/tests/fixtures/edge_cases.dl [33:1-33:27])_"); [&](){-CREATE_OP_CONTEXT(rel_3_no_strings_op_ctxt,rel_3_no_strings->createContext());+CREATE_OP_CONTEXT(rel_4_no_strings_op_ctxt,rel_4_no_strings->createContext()); Tuple<RamDomain,3> tuple{{ramBitCast(RamUnsigned(42)),ramBitCast(RamSigned(-100)),ramBitCast(RamFloat(1.5))}};-rel_3_no_strings->insert(tuple,READ_OP_CONTEXT(rel_3_no_strings_op_ctxt));+rel_4_no_strings->insert(tuple,READ_OP_CONTEXT(rel_4_no_strings_op_ctxt)); }-();SignalHandler::instance()->setMsg(R"_(no_strings(123,-456,3.14).-in file /home/luc/souffle-haskell/tests/fixtures/edge_cases.dl [34:1-34:29])_");+();signalHandler->setMsg(R"_(no_strings(123,-456,3.14).+in file /home/luc/personal/souffle-haskell/tests/fixtures/edge_cases.dl [34:1-34:29])_"); [&](){-CREATE_OP_CONTEXT(rel_3_no_strings_op_ctxt,rel_3_no_strings->createContext());+CREATE_OP_CONTEXT(rel_4_no_strings_op_ctxt,rel_4_no_strings->createContext()); Tuple<RamDomain,3> tuple{{ramBitCast(RamUnsigned(123)),ramBitCast(RamSigned(-456)),ramBitCast(RamFloat(3.1400001))}};-rel_3_no_strings->insert(tuple,READ_OP_CONTEXT(rel_3_no_strings_op_ctxt));+rel_4_no_strings->insert(tuple,READ_OP_CONTEXT(rel_4_no_strings_op_ctxt)); } ();if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","u\tn\tf"},{"name","no_strings"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 3, \"auxArity\": 0, \"params\": [\"u\", \"n\", \"f\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 3, \"auxArity\": 0, \"types\": [\"u:unsigned\", \"i:number\", \"f:float\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","u\tn\tf"},{"auxArity","0"},{"name","no_strings"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 3, \"params\": [\"u\", \"n\", \"f\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 3, \"types\": [\"u:unsigned\", \"i:number\", \"f:float\"]}}"}}); if (!outputDirectory.empty()) {directiveMap["output-dir"] = outputDirectory;}-IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_3_no_strings);+IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_4_no_strings); } catch (std::exception& e) {std::cerr << e.what();exit(1);} } }@@ -659,7 +675,7 @@ #endif // _MSC_VER }; SouffleProgram *newInstance_edge_cases(){return new Sf_edge_cases;}-SymbolTable *getST_edge_cases(SouffleProgram *p){return &reinterpret_cast<Sf_edge_cases*>(p)->symTable;}+SymbolTable *getST_edge_cases(SouffleProgram *p){return &reinterpret_cast<Sf_edge_cases*>(p)->getSymbolTable();}  #ifdef __EMBEDDED_SOUFFLE__ class factory_Sf_edge_cases: public souffle::ProgramFactory {
tests/fixtures/path.cpp view
@@ -6,7 +6,8 @@  namespace souffle { static const RamDomain RAM_BIT_SHIFT_MASK = RAM_DOMAIN_SIZE - 1;-struct t_btree_ii__0_1__11__10 {+struct t_btree_ii__0_1__11 {+static constexpr Relation::arity_type Arity = 2; using t_tuple = Tuple<RamDomain, 2>; struct t_comparator_0{  int operator()(const t_tuple& a, const t_tuple& b) const {@@ -88,18 +89,6 @@ context h; return lowerUpperRange_11(lower,upper,h); }-range<t_ind_0::iterator> lowerUpperRange_10(const t_tuple& lower, const t_tuple& upper, context& h) const {-t_comparator_0 comparator;-int cmp = comparator(lower, upper);-if (cmp > 0) {-    return make_range(ind_0.end(), ind_0.end());-}-return make_range(ind_0.lower_bound(lower, h.hints_0_lower), ind_0.upper_bound(upper, h.hints_0_upper));-}-range<t_ind_0::iterator> lowerUpperRange_10(const t_tuple& lower, const t_tuple& upper) const {-context h;-return lowerUpperRange_10(lower,upper,h);-} bool empty() const { return ind_0.empty(); }@@ -120,7 +109,8 @@ ind_0.printStats(o); } };-struct t_btree_ii__0_1__11 {+struct t_btree_ii__0_1__11__10 {+static constexpr Relation::arity_type Arity = 2; using t_tuple = Tuple<RamDomain, 2>; struct t_comparator_0{  int operator()(const t_tuple& a, const t_tuple& b) const {@@ -202,6 +192,18 @@ context h; return lowerUpperRange_11(lower,upper,h); }+range<t_ind_0::iterator> lowerUpperRange_10(const t_tuple& lower, const t_tuple& upper, context& h) const {+t_comparator_0 comparator;+int cmp = comparator(lower, upper);+if (cmp > 0) {+    return make_range(ind_0.end(), ind_0.end());+}+return make_range(ind_0.lower_bound(lower, h.hints_0_lower), ind_0.upper_bound(upper, h.hints_0_upper));+}+range<t_ind_0::iterator> lowerUpperRange_10(const t_tuple& lower, const t_tuple& upper) const {+context h;+return lowerUpperRange_10(lower,upper,h);+} bool empty() const { return ind_0.empty(); }@@ -233,7 +235,7 @@    return result; } private:-static inline std::string substr_wrapper(const std::string& str, size_t idx, size_t len) {+static inline std::string substr_wrapper(const std::string& str, std::size_t idx, std::size_t len) {    std::string result;     try { result = str.substr(idx,len); } catch(...) {       std::cerr << "warning: wrong index position provided by substr(\"";@@ -248,42 +250,49 @@ 	R"_(c)_", };// -- initialize record table -- RecordTable recordTable;-// -- Table: @delta_reachable-Own<t_btree_ii__0_1__11__10> rel_1_delta_reachable = mk<t_btree_ii__0_1__11__10>();-// -- Table: @new_reachable-Own<t_btree_ii__0_1__11__10> rel_2_new_reachable = mk<t_btree_ii__0_1__11__10>(); // -- Table: edge-Own<t_btree_ii__0_1__11> rel_3_edge = mk<t_btree_ii__0_1__11>();-souffle::RelationWrapper<0,t_btree_ii__0_1__11,Tuple<RamDomain,2>,2,0> wrapper_rel_3_edge;+Own<t_btree_ii__0_1__11> rel_1_edge = mk<t_btree_ii__0_1__11>();+souffle::RelationWrapper<t_btree_ii__0_1__11> wrapper_rel_1_edge; // -- Table: reachable-Own<t_btree_ii__0_1__11> rel_4_reachable = mk<t_btree_ii__0_1__11>();-souffle::RelationWrapper<1,t_btree_ii__0_1__11,Tuple<RamDomain,2>,2,0> wrapper_rel_4_reachable;+Own<t_btree_ii__0_1__11> rel_2_reachable = mk<t_btree_ii__0_1__11>();+souffle::RelationWrapper<t_btree_ii__0_1__11> wrapper_rel_2_reachable;+// -- Table: @delta_reachable+Own<t_btree_ii__0_1__11__10> rel_3_delta_reachable = mk<t_btree_ii__0_1__11__10>();+// -- Table: @new_reachable+Own<t_btree_ii__0_1__11__10> rel_4_new_reachable = mk<t_btree_ii__0_1__11__10>(); public:-Sf_path() : -wrapper_rel_3_edge(*rel_3_edge,symTable,"edge",std::array<const char *,2>{{"s:symbol","s:symbol"}},std::array<const char *,2>{{"n","m"}}),--wrapper_rel_4_reachable(*rel_4_reachable,symTable,"reachable",std::array<const char *,2>{{"s:symbol","s:symbol"}},std::array<const char *,2>{{"n","m"}}){-addRelation("edge",&wrapper_rel_3_edge,true,true);-addRelation("reachable",&wrapper_rel_4_reachable,false,true);+Sf_path()+: wrapper_rel_1_edge(0, *rel_1_edge, *this, "edge", std::array<const char *,2>{{"s:symbol","s:symbol"}}, std::array<const char *,2>{{"n","m"}}, 0)+, wrapper_rel_2_reachable(1, *rel_2_reachable, *this, "reachable", std::array<const char *,2>{{"s:symbol","s:symbol"}}, std::array<const char *,2>{{"n","m"}}, 0)+{+addRelation("edge", wrapper_rel_1_edge, true, true);+addRelation("reachable", wrapper_rel_2_reachable, false, true); } ~Sf_path() { }+ private:-std::string inputDirectory;-std::string outputDirectory;-bool performIO;-std::atomic<RamDomain> ctr{};+std::string             inputDirectory;+std::string             outputDirectory;+SignalHandler*          signalHandler {SignalHandler::instance()};+std::atomic<RamDomain>  ctr {};+std::atomic<std::size_t>     iter {};+bool                    performIO = false; -std::atomic<size_t> iter{};-void runFunction(std::string inputDirectoryArg = "", std::string outputDirectoryArg = "", bool performIOArg = false) {-this->inputDirectory = inputDirectoryArg;-this->outputDirectory = outputDirectoryArg;-this->performIO = performIOArg;-SignalHandler::instance()->set();+void runFunction(std::string  inputDirectoryArg   = "",+                 std::string  outputDirectoryArg  = "",+                 bool         performIOArg        = false) {+    this->inputDirectory  = std::move(inputDirectoryArg);+    this->outputDirectory = std::move(outputDirectoryArg);+    this->performIO       = performIOArg;++    // set default threads (in embedded mode)+    // if this is not set, and omp is used, the default omp setting of number of cores is used. #if defined(_OPENMP)-if (getNumThreads() > 0) {omp_set_num_threads(getNumThreads());}+    if (0 < getNumThreads()) { omp_set_num_threads(getNumThreads()); } #endif +    signalHandler->set(); // -- query evaluation -- {  std::vector<RamDomain> args, ret;@@ -295,7 +304,7 @@ }  // -- relation hint statistics ---SignalHandler::instance()->reset();+signalHandler->reset(); } public: void run() override { runFunction("", "", false); }@@ -304,20 +313,20 @@ } public: void printAll(std::string outputDirectoryArg = "") override {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","n\tm"},{"name","edge"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 2, \"auxArity\": 0, \"params\": [\"n\", \"m\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 2, \"auxArity\": 0, \"types\": [\"s:symbol\", \"s:symbol\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","n\tm"},{"auxArity","0"},{"name","edge"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 2, \"params\": [\"n\", \"m\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 2, \"types\": [\"s:symbol\", \"s:symbol\"]}}"}}); if (!outputDirectoryArg.empty()) {directiveMap["output-dir"] = outputDirectoryArg;}-IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_3_edge);+IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_1_edge); } catch (std::exception& e) {std::cerr << e.what();exit(1);}-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","n\tm"},{"name","reachable"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 2, \"auxArity\": 0, \"params\": [\"n\", \"m\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 2, \"auxArity\": 0, \"types\": [\"s:symbol\", \"s:symbol\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","n\tm"},{"auxArity","0"},{"name","reachable"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 2, \"params\": [\"n\", \"m\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 2, \"types\": [\"s:symbol\", \"s:symbol\"]}}"}}); if (!outputDirectoryArg.empty()) {directiveMap["output-dir"] = outputDirectoryArg;}-IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_4_reachable);+IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_2_reachable); } catch (std::exception& e) {std::cerr << e.what();exit(1);} } public: void loadAll(std::string inputDirectoryArg = "") override {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","n\tm"},{"fact-dir","."},{"name","edge"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 2, \"auxArity\": 0, \"params\": [\"n\", \"m\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 2, \"auxArity\": 0, \"types\": [\"s:symbol\", \"s:symbol\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","n\tm"},{"auxArity","0"},{"fact-dir","."},{"name","edge"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 2, \"params\": [\"n\", \"m\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 2, \"types\": [\"s:symbol\", \"s:symbol\"]}}"}}); if (!inputDirectoryArg.empty()) {directiveMap["fact-dir"] = inputDirectoryArg;}-IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_3_edge);+IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_1_edge); } catch (std::exception& e) {std::cerr << "Error loading data: " << e.what() << '\n';} } public:@@ -326,7 +335,7 @@ rwOperation["IO"] = "stdout"; rwOperation["name"] = "edge"; rwOperation["types"] = "{\"relation\": {\"arity\": 2, \"auxArity\": 0, \"types\": [\"s:symbol\", \"s:symbol\"]}}";-IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_3_edge);+IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_1_edge); } catch (std::exception& e) {std::cerr << e.what();exit(1);} } public:@@ -335,19 +344,27 @@ rwOperation["IO"] = "stdout"; rwOperation["name"] = "edge"; rwOperation["types"] = "{\"relation\": {\"arity\": 2, \"auxArity\": 0, \"types\": [\"s:symbol\", \"s:symbol\"]}}";-IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_3_edge);+IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_1_edge); } catch (std::exception& e) {std::cerr << e.what();exit(1);} try {std::map<std::string, std::string> rwOperation; rwOperation["IO"] = "stdout"; rwOperation["name"] = "reachable"; rwOperation["types"] = "{\"relation\": {\"arity\": 2, \"auxArity\": 0, \"types\": [\"s:symbol\", \"s:symbol\"]}}";-IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_4_reachable);+IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_2_reachable); } catch (std::exception& e) {std::cerr << e.what();exit(1);} } public: SymbolTable& getSymbolTable() override { return symTable; }+RecordTable& getRecordTable() override {+return recordTable;+}+void setNumThreads(std::size_t numThreadsValue) override {+SouffleProgram::setNumThreads(numThreadsValue);+symTable.setNumLanes(getNumThreads());+recordTable.setNumLanes(getNumThreads());+} void executeSubroutine(std::string name, const std::vector<RamDomain>& args, std::vector<RamDomain>& ret) override { if (name == "stratum_0") { subroutine_0(args, ret);@@ -362,29 +379,29 @@ #endif // _MSC_VER void subroutine_0(const std::vector<RamDomain>& args, std::vector<RamDomain>& ret) { if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","n\tm"},{"fact-dir","."},{"name","edge"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 2, \"auxArity\": 0, \"params\": [\"n\", \"m\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 2, \"auxArity\": 0, \"types\": [\"s:symbol\", \"s:symbol\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","n\tm"},{"auxArity","0"},{"fact-dir","."},{"name","edge"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 2, \"params\": [\"n\", \"m\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 2, \"types\": [\"s:symbol\", \"s:symbol\"]}}"}}); if (!inputDirectory.empty()) {directiveMap["fact-dir"] = inputDirectory;}-IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_3_edge);+IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_1_edge); } catch (std::exception& e) {std::cerr << "Error loading data: " << e.what() << '\n';} }-SignalHandler::instance()->setMsg(R"_(edge("a","b").-in file /Users/luc/personal/souffle-hs/tests/fixtures/path.dl [11:1-11:16])_");+signalHandler->setMsg(R"_(edge("a","b").+in file /home/luc/personal/souffle-haskell/tests/fixtures/path.dl [11:1-11:16])_"); [&](){-CREATE_OP_CONTEXT(rel_3_edge_op_ctxt,rel_3_edge->createContext());+CREATE_OP_CONTEXT(rel_1_edge_op_ctxt,rel_1_edge->createContext()); Tuple<RamDomain,2> tuple{{ramBitCast(RamSigned(0)),ramBitCast(RamSigned(1))}};-rel_3_edge->insert(tuple,READ_OP_CONTEXT(rel_3_edge_op_ctxt));+rel_1_edge->insert(tuple,READ_OP_CONTEXT(rel_1_edge_op_ctxt)); }-();SignalHandler::instance()->setMsg(R"_(edge("b","c").-in file /Users/luc/personal/souffle-hs/tests/fixtures/path.dl [12:1-12:16])_");+();signalHandler->setMsg(R"_(edge("b","c").+in file /home/luc/personal/souffle-haskell/tests/fixtures/path.dl [12:1-12:16])_"); [&](){-CREATE_OP_CONTEXT(rel_3_edge_op_ctxt,rel_3_edge->createContext());+CREATE_OP_CONTEXT(rel_1_edge_op_ctxt,rel_1_edge->createContext()); Tuple<RamDomain,2> tuple{{ramBitCast(RamSigned(1)),ramBitCast(RamSigned(2))}};-rel_3_edge->insert(tuple,READ_OP_CONTEXT(rel_3_edge_op_ctxt));+rel_1_edge->insert(tuple,READ_OP_CONTEXT(rel_1_edge_op_ctxt)); } ();if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","n\tm"},{"name","edge"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 2, \"auxArity\": 0, \"params\": [\"n\", \"m\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 2, \"auxArity\": 0, \"types\": [\"s:symbol\", \"s:symbol\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","n\tm"},{"auxArity","0"},{"name","edge"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 2, \"params\": [\"n\", \"m\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 2, \"types\": [\"s:symbol\", \"s:symbol\"]}}"}}); if (!outputDirectory.empty()) {directiveMap["output-dir"] = outputDirectory;}-IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_3_edge);+IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_1_edge); } catch (std::exception& e) {std::cerr << e.what();exit(1);} } }@@ -395,81 +412,81 @@ #pragma warning(disable: 4100) #endif // _MSC_VER void subroutine_1(const std::vector<RamDomain>& args, std::vector<RamDomain>& ret) {-SignalHandler::instance()->setMsg(R"_(reachable(x,y) :- +signalHandler->setMsg(R"_(reachable(x,y) :-     edge(x,y).-in file /Users/luc/personal/souffle-hs/tests/fixtures/path.dl [14:1-14:31])_");-if(!(rel_3_edge->empty())) {+in file /home/luc/personal/souffle-haskell/tests/fixtures/path.dl [14:1-14:31])_");+if(!(rel_1_edge->empty())) { [&](){-CREATE_OP_CONTEXT(rel_3_edge_op_ctxt,rel_3_edge->createContext());-CREATE_OP_CONTEXT(rel_4_reachable_op_ctxt,rel_4_reachable->createContext());-for(const auto& env0 : *rel_3_edge) {+CREATE_OP_CONTEXT(rel_2_reachable_op_ctxt,rel_2_reachable->createContext());+CREATE_OP_CONTEXT(rel_1_edge_op_ctxt,rel_1_edge->createContext());+for(const auto& env0 : *rel_1_edge) { Tuple<RamDomain,2> tuple{{ramBitCast(env0[0]),ramBitCast(env0[1])}};-rel_4_reachable->insert(tuple,READ_OP_CONTEXT(rel_4_reachable_op_ctxt));+rel_2_reachable->insert(tuple,READ_OP_CONTEXT(rel_2_reachable_op_ctxt)); } } ();} [&](){-CREATE_OP_CONTEXT(rel_4_reachable_op_ctxt,rel_4_reachable->createContext());-CREATE_OP_CONTEXT(rel_1_delta_reachable_op_ctxt,rel_1_delta_reachable->createContext());-for(const auto& env0 : *rel_4_reachable) {+CREATE_OP_CONTEXT(rel_3_delta_reachable_op_ctxt,rel_3_delta_reachable->createContext());+CREATE_OP_CONTEXT(rel_2_reachable_op_ctxt,rel_2_reachable->createContext());+for(const auto& env0 : *rel_2_reachable) { Tuple<RamDomain,2> tuple{{ramBitCast(env0[0]),ramBitCast(env0[1])}};-rel_1_delta_reachable->insert(tuple,READ_OP_CONTEXT(rel_1_delta_reachable_op_ctxt));+rel_3_delta_reachable->insert(tuple,READ_OP_CONTEXT(rel_3_delta_reachable_op_ctxt)); } } ();iter = 0; for(;;) {-SignalHandler::instance()->setMsg(R"_(reachable(x,z) :- +signalHandler->setMsg(R"_(reachable(x,z) :-     edge(x,y),    reachable(y,z).-in file /Users/luc/personal/souffle-hs/tests/fixtures/path.dl [15:1-15:48])_");-if(!(rel_3_edge->empty()) && !(rel_1_delta_reachable->empty())) {+in file /home/luc/personal/souffle-haskell/tests/fixtures/path.dl [15:1-15:48])_");+if(!(rel_1_edge->empty()) && !(rel_3_delta_reachable->empty())) { [&](){-CREATE_OP_CONTEXT(rel_3_edge_op_ctxt,rel_3_edge->createContext());-CREATE_OP_CONTEXT(rel_4_reachable_op_ctxt,rel_4_reachable->createContext());-CREATE_OP_CONTEXT(rel_1_delta_reachable_op_ctxt,rel_1_delta_reachable->createContext());-CREATE_OP_CONTEXT(rel_2_new_reachable_op_ctxt,rel_2_new_reachable->createContext());-for(const auto& env0 : *rel_3_edge) {-auto range = rel_1_delta_reachable->lowerUpperRange_10(Tuple<RamDomain,2>{{ramBitCast(env0[1]), ramBitCast<RamDomain>(MIN_RAM_SIGNED)}},Tuple<RamDomain,2>{{ramBitCast(env0[1]), ramBitCast<RamDomain>(MAX_RAM_SIGNED)}},READ_OP_CONTEXT(rel_1_delta_reachable_op_ctxt));+CREATE_OP_CONTEXT(rel_3_delta_reachable_op_ctxt,rel_3_delta_reachable->createContext());+CREATE_OP_CONTEXT(rel_2_reachable_op_ctxt,rel_2_reachable->createContext());+CREATE_OP_CONTEXT(rel_1_edge_op_ctxt,rel_1_edge->createContext());+CREATE_OP_CONTEXT(rel_4_new_reachable_op_ctxt,rel_4_new_reachable->createContext());+for(const auto& env0 : *rel_1_edge) {+auto range = rel_3_delta_reachable->lowerUpperRange_10(Tuple<RamDomain,2>{{ramBitCast(env0[1]), ramBitCast<RamDomain>(MIN_RAM_SIGNED)}},Tuple<RamDomain,2>{{ramBitCast(env0[1]), ramBitCast<RamDomain>(MAX_RAM_SIGNED)}},READ_OP_CONTEXT(rel_3_delta_reachable_op_ctxt)); for(const auto& env1 : range) {-if( !(rel_4_reachable->contains(Tuple<RamDomain,2>{{ramBitCast(env0[0]),ramBitCast(env1[1])}},READ_OP_CONTEXT(rel_4_reachable_op_ctxt)))) {+if( !(rel_2_reachable->contains(Tuple<RamDomain,2>{{ramBitCast(env0[0]),ramBitCast(env1[1])}},READ_OP_CONTEXT(rel_2_reachable_op_ctxt)))) { Tuple<RamDomain,2> tuple{{ramBitCast(env0[0]),ramBitCast(env1[1])}};-rel_2_new_reachable->insert(tuple,READ_OP_CONTEXT(rel_2_new_reachable_op_ctxt));+rel_4_new_reachable->insert(tuple,READ_OP_CONTEXT(rel_4_new_reachable_op_ctxt)); } } } } ();}-if(rel_2_new_reachable->empty()) break;+if(rel_4_new_reachable->empty()) break; [&](){-CREATE_OP_CONTEXT(rel_4_reachable_op_ctxt,rel_4_reachable->createContext());-CREATE_OP_CONTEXT(rel_2_new_reachable_op_ctxt,rel_2_new_reachable->createContext());-for(const auto& env0 : *rel_2_new_reachable) {+CREATE_OP_CONTEXT(rel_2_reachable_op_ctxt,rel_2_reachable->createContext());+CREATE_OP_CONTEXT(rel_4_new_reachable_op_ctxt,rel_4_new_reachable->createContext());+for(const auto& env0 : *rel_4_new_reachable) { Tuple<RamDomain,2> tuple{{ramBitCast(env0[0]),ramBitCast(env0[1])}};-rel_4_reachable->insert(tuple,READ_OP_CONTEXT(rel_4_reachable_op_ctxt));+rel_2_reachable->insert(tuple,READ_OP_CONTEXT(rel_2_reachable_op_ctxt)); } }-();std::swap(rel_1_delta_reachable, rel_2_new_reachable);-rel_2_new_reachable->purge();+();std::swap(rel_3_delta_reachable, rel_4_new_reachable);+rel_4_new_reachable->purge(); iter++; } iter = 0;-rel_1_delta_reachable->purge();-rel_2_new_reachable->purge();+rel_3_delta_reachable->purge();+rel_4_new_reachable->purge(); if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","n\tm"},{"name","reachable"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 2, \"auxArity\": 0, \"params\": [\"n\", \"m\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 2, \"auxArity\": 0, \"types\": [\"s:symbol\", \"s:symbol\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","n\tm"},{"auxArity","0"},{"name","reachable"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 2, \"params\": [\"n\", \"m\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 2, \"types\": [\"s:symbol\", \"s:symbol\"]}}"}}); if (!outputDirectory.empty()) {directiveMap["output-dir"] = outputDirectory;}-IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_4_reachable);+IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_2_reachable); } catch (std::exception& e) {std::cerr << e.what();exit(1);} }-if (performIO) rel_4_reachable->purge();-if (performIO) rel_3_edge->purge();+if (performIO) rel_2_reachable->purge();+if (performIO) rel_1_edge->purge(); } #ifdef _MSC_VER #pragma warning(default: 4100) #endif // _MSC_VER }; SouffleProgram *newInstance_path(){return new Sf_path;}-SymbolTable *getST_path(SouffleProgram *p){return &reinterpret_cast<Sf_path*>(p)->symTable;}+SymbolTable *getST_path(SouffleProgram *p){return &reinterpret_cast<Sf_path*>(p)->getSymbolTable();}  #ifdef __EMBEDDED_SOUFFLE__ class factory_Sf_path: public souffle::ProgramFactory {
tests/fixtures/round_trip.cpp view
@@ -6,17 +6,18 @@  namespace souffle { static const RamDomain RAM_BIT_SHIFT_MASK = RAM_DOMAIN_SIZE - 1;-struct t_btree_f__0__1 {+struct t_btree_i__0__1 {+static constexpr Relation::arity_type Arity = 1; using t_tuple = Tuple<RamDomain, 1>; struct t_comparator_0{  int operator()(const t_tuple& a, const t_tuple& b) const {-  return (ramBitCast<RamFloat>(a[0]) < ramBitCast<RamFloat>(b[0])) ? -1 : (ramBitCast<RamFloat>(a[0]) > ramBitCast<RamFloat>(b[0])) ? 1 :(0);+  return (ramBitCast<RamSigned>(a[0]) < ramBitCast<RamSigned>(b[0])) ? -1 : (ramBitCast<RamSigned>(a[0]) > ramBitCast<RamSigned>(b[0])) ? 1 :(0);  } bool less(const t_tuple& a, const t_tuple& b) const {-  return (ramBitCast<RamFloat>(a[0]) < ramBitCast<RamFloat>(b[0]));+  return (ramBitCast<RamSigned>(a[0]) < ramBitCast<RamSigned>(b[0]));  } bool equal(const t_tuple& a, const t_tuple& b) const {-return (ramBitCast<RamFloat>(a[0]) == ramBitCast<RamFloat>(b[0]));+return (ramBitCast<RamSigned>(a[0]) == ramBitCast<RamSigned>(b[0]));  } }; using t_ind_0 = btree_set<t_tuple,t_comparator_0>;@@ -108,17 +109,18 @@ ind_0.printStats(o); } };-struct t_btree_i__0__1 {+struct t_btree_u__0__1 {+static constexpr Relation::arity_type Arity = 1; using t_tuple = Tuple<RamDomain, 1>; struct t_comparator_0{  int operator()(const t_tuple& a, const t_tuple& b) const {-  return (ramBitCast<RamSigned>(a[0]) < ramBitCast<RamSigned>(b[0])) ? -1 : (ramBitCast<RamSigned>(a[0]) > ramBitCast<RamSigned>(b[0])) ? 1 :(0);+  return (ramBitCast<RamUnsigned>(a[0]) < ramBitCast<RamUnsigned>(b[0])) ? -1 : (ramBitCast<RamUnsigned>(a[0]) > ramBitCast<RamUnsigned>(b[0])) ? 1 :(0);  } bool less(const t_tuple& a, const t_tuple& b) const {-  return (ramBitCast<RamSigned>(a[0]) < ramBitCast<RamSigned>(b[0]));+  return (ramBitCast<RamUnsigned>(a[0]) < ramBitCast<RamUnsigned>(b[0]));  } bool equal(const t_tuple& a, const t_tuple& b) const {-return (ramBitCast<RamSigned>(a[0]) == ramBitCast<RamSigned>(b[0]));+return (ramBitCast<RamUnsigned>(a[0]) == ramBitCast<RamUnsigned>(b[0]));  } }; using t_ind_0 = btree_set<t_tuple,t_comparator_0>;@@ -210,17 +212,18 @@ ind_0.printStats(o); } };-struct t_btree_u__0__1 {+struct t_btree_f__0__1 {+static constexpr Relation::arity_type Arity = 1; using t_tuple = Tuple<RamDomain, 1>; struct t_comparator_0{  int operator()(const t_tuple& a, const t_tuple& b) const {-  return (ramBitCast<RamUnsigned>(a[0]) < ramBitCast<RamUnsigned>(b[0])) ? -1 : (ramBitCast<RamUnsigned>(a[0]) > ramBitCast<RamUnsigned>(b[0])) ? 1 :(0);+  return (ramBitCast<RamFloat>(a[0]) < ramBitCast<RamFloat>(b[0])) ? -1 : (ramBitCast<RamFloat>(a[0]) > ramBitCast<RamFloat>(b[0])) ? 1 :(0);  } bool less(const t_tuple& a, const t_tuple& b) const {-  return (ramBitCast<RamUnsigned>(a[0]) < ramBitCast<RamUnsigned>(b[0]));+  return (ramBitCast<RamFloat>(a[0]) < ramBitCast<RamFloat>(b[0]));  } bool equal(const t_tuple& a, const t_tuple& b) const {-return (ramBitCast<RamUnsigned>(a[0]) == ramBitCast<RamUnsigned>(b[0]));+return (ramBitCast<RamFloat>(a[0]) == ramBitCast<RamFloat>(b[0]));  } }; using t_ind_0 = btree_set<t_tuple,t_comparator_0>;@@ -323,7 +326,7 @@    return result; } private:-static inline std::string substr_wrapper(const std::string& str, size_t idx, size_t len) {+static inline std::string substr_wrapper(const std::string& str, std::size_t idx, std::size_t len) {    std::string result;     try { result = str.substr(idx,len); } catch(...) {       std::cerr << "warning: wrong index position provided by substr(\"";@@ -334,50 +337,55 @@ // -- initialize symbol table -- SymbolTable symTable;// -- initialize record table -- RecordTable recordTable;-// -- Table: float_fact-Own<t_btree_f__0__1> rel_1_float_fact = mk<t_btree_f__0__1>();-souffle::RelationWrapper<0,t_btree_f__0__1,Tuple<RamDomain,1>,1,0> wrapper_rel_1_float_fact;+// -- Table: string_fact+Own<t_btree_i__0__1> rel_1_string_fact = mk<t_btree_i__0__1>();+souffle::RelationWrapper<t_btree_i__0__1> wrapper_rel_1_string_fact; // -- Table: number_fact Own<t_btree_i__0__1> rel_2_number_fact = mk<t_btree_i__0__1>();-souffle::RelationWrapper<1,t_btree_i__0__1,Tuple<RamDomain,1>,1,0> wrapper_rel_2_number_fact;-// -- Table: string_fact-Own<t_btree_i__0__1> rel_3_string_fact = mk<t_btree_i__0__1>();-souffle::RelationWrapper<2,t_btree_i__0__1,Tuple<RamDomain,1>,1,0> wrapper_rel_3_string_fact;+souffle::RelationWrapper<t_btree_i__0__1> wrapper_rel_2_number_fact; // -- Table: unsigned_fact-Own<t_btree_u__0__1> rel_4_unsigned_fact = mk<t_btree_u__0__1>();-souffle::RelationWrapper<3,t_btree_u__0__1,Tuple<RamDomain,1>,1,0> wrapper_rel_4_unsigned_fact;+Own<t_btree_u__0__1> rel_3_unsigned_fact = mk<t_btree_u__0__1>();+souffle::RelationWrapper<t_btree_u__0__1> wrapper_rel_3_unsigned_fact;+// -- Table: float_fact+Own<t_btree_f__0__1> rel_4_float_fact = mk<t_btree_f__0__1>();+souffle::RelationWrapper<t_btree_f__0__1> wrapper_rel_4_float_fact; public:-Sf_round_trip() : -wrapper_rel_1_float_fact(*rel_1_float_fact,symTable,"float_fact",std::array<const char *,1>{{"f:float"}},std::array<const char *,1>{{"x"}}),--wrapper_rel_2_number_fact(*rel_2_number_fact,symTable,"number_fact",std::array<const char *,1>{{"i:number"}},std::array<const char *,1>{{"x"}}),--wrapper_rel_3_string_fact(*rel_3_string_fact,symTable,"string_fact",std::array<const char *,1>{{"s:symbol"}},std::array<const char *,1>{{"x"}}),--wrapper_rel_4_unsigned_fact(*rel_4_unsigned_fact,symTable,"unsigned_fact",std::array<const char *,1>{{"u:unsigned"}},std::array<const char *,1>{{"x"}}){-addRelation("float_fact",&wrapper_rel_1_float_fact,true,true);-addRelation("number_fact",&wrapper_rel_2_number_fact,true,true);-addRelation("string_fact",&wrapper_rel_3_string_fact,true,true);-addRelation("unsigned_fact",&wrapper_rel_4_unsigned_fact,true,true);+Sf_round_trip()+: wrapper_rel_1_string_fact(0, *rel_1_string_fact, *this, "string_fact", std::array<const char *,1>{{"s:symbol"}}, std::array<const char *,1>{{"x"}}, 0)+, wrapper_rel_2_number_fact(1, *rel_2_number_fact, *this, "number_fact", std::array<const char *,1>{{"i:number"}}, std::array<const char *,1>{{"x"}}, 0)+, wrapper_rel_3_unsigned_fact(2, *rel_3_unsigned_fact, *this, "unsigned_fact", std::array<const char *,1>{{"u:unsigned"}}, std::array<const char *,1>{{"x"}}, 0)+, wrapper_rel_4_float_fact(3, *rel_4_float_fact, *this, "float_fact", std::array<const char *,1>{{"f:float"}}, std::array<const char *,1>{{"x"}}, 0)+{+addRelation("string_fact", wrapper_rel_1_string_fact, true, true);+addRelation("number_fact", wrapper_rel_2_number_fact, true, true);+addRelation("unsigned_fact", wrapper_rel_3_unsigned_fact, true, true);+addRelation("float_fact", wrapper_rel_4_float_fact, true, true); } ~Sf_round_trip() { }+ private:-std::string inputDirectory;-std::string outputDirectory;-bool performIO;-std::atomic<RamDomain> ctr{};+std::string             inputDirectory;+std::string             outputDirectory;+SignalHandler*          signalHandler {SignalHandler::instance()};+std::atomic<RamDomain>  ctr {};+std::atomic<std::size_t>     iter {};+bool                    performIO = false; -std::atomic<size_t> iter{};-void runFunction(std::string inputDirectoryArg = "", std::string outputDirectoryArg = "", bool performIOArg = false) {-this->inputDirectory = inputDirectoryArg;-this->outputDirectory = outputDirectoryArg;-this->performIO = performIOArg;-SignalHandler::instance()->set();+void runFunction(std::string  inputDirectoryArg   = "",+                 std::string  outputDirectoryArg  = "",+                 bool         performIOArg        = false) {+    this->inputDirectory  = std::move(inputDirectoryArg);+    this->outputDirectory = std::move(outputDirectoryArg);+    this->performIO       = performIOArg;++    // set default threads (in embedded mode)+    // if this is not set, and omp is used, the default omp setting of number of cores is used. #if defined(_OPENMP)-if (getNumThreads() > 0) {omp_set_num_threads(getNumThreads());}+    if (0 < getNumThreads()) { omp_set_num_threads(getNumThreads()); } #endif +    signalHandler->set(); // -- query evaluation -- {  std::vector<RamDomain> args, ret;@@ -397,7 +405,7 @@ }  // -- relation hint statistics ---SignalHandler::instance()->reset();+signalHandler->reset(); } public: void run() override { runFunction("", "", false); }@@ -406,49 +414,49 @@ } public: void printAll(std::string outputDirectoryArg = "") override {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"name","number_fact"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"i:number\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"auxArity","0"},{"name","string_fact"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"s:symbol\"]}}"}}); if (!outputDirectoryArg.empty()) {directiveMap["output-dir"] = outputDirectoryArg;}-IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_2_number_fact);+IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_1_string_fact); } catch (std::exception& e) {std::cerr << e.what();exit(1);}-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"name","unsigned_fact"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"u:unsigned\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"auxArity","0"},{"name","number_fact"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"i:number\"]}}"}}); if (!outputDirectoryArg.empty()) {directiveMap["output-dir"] = outputDirectoryArg;}-IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_4_unsigned_fact);+IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_2_number_fact); } catch (std::exception& e) {std::cerr << e.what();exit(1);}-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"name","string_fact"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"auxArity","0"},{"name","unsigned_fact"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"u:unsigned\"]}}"}}); if (!outputDirectoryArg.empty()) {directiveMap["output-dir"] = outputDirectoryArg;}-IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_3_string_fact);+IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_3_unsigned_fact); } catch (std::exception& e) {std::cerr << e.what();exit(1);}-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"name","float_fact"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"f:float\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"auxArity","0"},{"name","float_fact"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"f:float\"]}}"}}); if (!outputDirectoryArg.empty()) {directiveMap["output-dir"] = outputDirectoryArg;}-IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_1_float_fact);+IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_4_float_fact); } catch (std::exception& e) {std::cerr << e.what();exit(1);} } public: void loadAll(std::string inputDirectoryArg = "") override {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"fact-dir","."},{"name","float_fact"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"f:float\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"auxArity","0"},{"fact-dir","."},{"name","string_fact"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"s:symbol\"]}}"}}); if (!inputDirectoryArg.empty()) {directiveMap["fact-dir"] = inputDirectoryArg;}-IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_1_float_fact);+IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_1_string_fact); } catch (std::exception& e) {std::cerr << "Error loading data: " << e.what() << '\n';}-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"fact-dir","."},{"name","number_fact"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"i:number\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"auxArity","0"},{"fact-dir","."},{"name","number_fact"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"i:number\"]}}"}}); if (!inputDirectoryArg.empty()) {directiveMap["fact-dir"] = inputDirectoryArg;} IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_2_number_fact); } catch (std::exception& e) {std::cerr << "Error loading data: " << e.what() << '\n';}-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"fact-dir","."},{"name","string_fact"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"auxArity","0"},{"fact-dir","."},{"name","unsigned_fact"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"u:unsigned\"]}}"}}); if (!inputDirectoryArg.empty()) {directiveMap["fact-dir"] = inputDirectoryArg;}-IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_3_string_fact);+IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_3_unsigned_fact); } catch (std::exception& e) {std::cerr << "Error loading data: " << e.what() << '\n';}-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"fact-dir","."},{"name","unsigned_fact"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"u:unsigned\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"auxArity","0"},{"fact-dir","."},{"name","float_fact"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"f:float\"]}}"}}); if (!inputDirectoryArg.empty()) {directiveMap["fact-dir"] = inputDirectoryArg;}-IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_4_unsigned_fact);+IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_4_float_fact); } catch (std::exception& e) {std::cerr << "Error loading data: " << e.what() << '\n';} } public: void dumpInputs() override { try {std::map<std::string, std::string> rwOperation; rwOperation["IO"] = "stdout";-rwOperation["name"] = "float_fact";-rwOperation["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"f:float\"]}}";-IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_1_float_fact);+rwOperation["name"] = "string_fact";+rwOperation["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}";+IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_1_string_fact); } catch (std::exception& e) {std::cerr << e.what();exit(1);} try {std::map<std::string, std::string> rwOperation; rwOperation["IO"] = "stdout";@@ -458,21 +466,27 @@ } catch (std::exception& e) {std::cerr << e.what();exit(1);} try {std::map<std::string, std::string> rwOperation; rwOperation["IO"] = "stdout";-rwOperation["name"] = "string_fact";-rwOperation["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}";-IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_3_string_fact);+rwOperation["name"] = "unsigned_fact";+rwOperation["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"u:unsigned\"]}}";+IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_3_unsigned_fact); } catch (std::exception& e) {std::cerr << e.what();exit(1);} try {std::map<std::string, std::string> rwOperation; rwOperation["IO"] = "stdout";-rwOperation["name"] = "unsigned_fact";-rwOperation["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"u:unsigned\"]}}";-IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_4_unsigned_fact);+rwOperation["name"] = "float_fact";+rwOperation["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"f:float\"]}}";+IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_4_float_fact); } catch (std::exception& e) {std::cerr << e.what();exit(1);} } public: void dumpOutputs() override { try {std::map<std::string, std::string> rwOperation; rwOperation["IO"] = "stdout";+rwOperation["name"] = "string_fact";+rwOperation["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}";+IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_1_string_fact);+} catch (std::exception& e) {std::cerr << e.what();exit(1);}+try {std::map<std::string, std::string> rwOperation;+rwOperation["IO"] = "stdout"; rwOperation["name"] = "number_fact"; rwOperation["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"i:number\"]}}"; IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_2_number_fact);@@ -481,25 +495,27 @@ rwOperation["IO"] = "stdout"; rwOperation["name"] = "unsigned_fact"; rwOperation["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"u:unsigned\"]}}";-IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_4_unsigned_fact);-} catch (std::exception& e) {std::cerr << e.what();exit(1);}-try {std::map<std::string, std::string> rwOperation;-rwOperation["IO"] = "stdout";-rwOperation["name"] = "string_fact";-rwOperation["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}";-IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_3_string_fact);+IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_3_unsigned_fact); } catch (std::exception& e) {std::cerr << e.what();exit(1);} try {std::map<std::string, std::string> rwOperation; rwOperation["IO"] = "stdout"; rwOperation["name"] = "float_fact"; rwOperation["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"f:float\"]}}";-IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_1_float_fact);+IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_4_float_fact); } catch (std::exception& e) {std::cerr << e.what();exit(1);} } public: SymbolTable& getSymbolTable() override { return symTable; }+RecordTable& getRecordTable() override {+return recordTable;+}+void setNumThreads(std::size_t numThreadsValue) override {+SouffleProgram::setNumThreads(numThreadsValue);+symTable.setNumLanes(getNumThreads());+recordTable.setNumLanes(getNumThreads());+} void executeSubroutine(std::string name, const std::vector<RamDomain>& args, std::vector<RamDomain>& ret) override { if (name == "stratum_0") { subroutine_0(args, ret);@@ -520,15 +536,15 @@ #endif // _MSC_VER void subroutine_0(const std::vector<RamDomain>& args, std::vector<RamDomain>& ret) { if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"fact-dir","."},{"name","string_fact"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"auxArity","0"},{"fact-dir","."},{"name","string_fact"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"s:symbol\"]}}"}}); if (!inputDirectory.empty()) {directiveMap["fact-dir"] = inputDirectory;}-IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_3_string_fact);+IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_1_string_fact); } catch (std::exception& e) {std::cerr << "Error loading data: " << e.what() << '\n';} } if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"name","string_fact"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"auxArity","0"},{"name","string_fact"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"s:symbol\"]}}"}}); if (!outputDirectory.empty()) {directiveMap["output-dir"] = outputDirectory;}-IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_3_string_fact);+IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_1_string_fact); } catch (std::exception& e) {std::cerr << e.what();exit(1);} } }@@ -540,13 +556,13 @@ #endif // _MSC_VER void subroutine_1(const std::vector<RamDomain>& args, std::vector<RamDomain>& ret) { if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"fact-dir","."},{"name","number_fact"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"i:number\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"auxArity","0"},{"fact-dir","."},{"name","number_fact"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"i:number\"]}}"}}); if (!inputDirectory.empty()) {directiveMap["fact-dir"] = inputDirectory;} IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_2_number_fact); } catch (std::exception& e) {std::cerr << "Error loading data: " << e.what() << '\n';} } if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"name","number_fact"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"i:number\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"auxArity","0"},{"name","number_fact"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"i:number\"]}}"}}); if (!outputDirectory.empty()) {directiveMap["output-dir"] = outputDirectory;} IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_2_number_fact); } catch (std::exception& e) {std::cerr << e.what();exit(1);}@@ -560,15 +576,15 @@ #endif // _MSC_VER void subroutine_2(const std::vector<RamDomain>& args, std::vector<RamDomain>& ret) { if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"fact-dir","."},{"name","unsigned_fact"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"u:unsigned\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"auxArity","0"},{"fact-dir","."},{"name","unsigned_fact"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"u:unsigned\"]}}"}}); if (!inputDirectory.empty()) {directiveMap["fact-dir"] = inputDirectory;}-IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_4_unsigned_fact);+IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_3_unsigned_fact); } catch (std::exception& e) {std::cerr << "Error loading data: " << e.what() << '\n';} } if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"name","unsigned_fact"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"u:unsigned\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"auxArity","0"},{"name","unsigned_fact"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"u:unsigned\"]}}"}}); if (!outputDirectory.empty()) {directiveMap["output-dir"] = outputDirectory;}-IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_4_unsigned_fact);+IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_3_unsigned_fact); } catch (std::exception& e) {std::cerr << e.what();exit(1);} } }@@ -580,15 +596,15 @@ #endif // _MSC_VER void subroutine_3(const std::vector<RamDomain>& args, std::vector<RamDomain>& ret) { if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"fact-dir","."},{"name","float_fact"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"f:float\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"auxArity","0"},{"fact-dir","."},{"name","float_fact"},{"operation","input"},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"f:float\"]}}"}}); if (!inputDirectory.empty()) {directiveMap["fact-dir"] = inputDirectory;}-IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_1_float_fact);+IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_4_float_fact); } catch (std::exception& e) {std::cerr << "Error loading data: " << e.what() << '\n';} } if (performIO) {-try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"name","float_fact"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"f:float\"]}}"}});+try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","x"},{"auxArity","0"},{"name","float_fact"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 1, \"params\": [\"x\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 1, \"types\": [\"f:float\"]}}"}}); if (!outputDirectory.empty()) {directiveMap["output-dir"] = outputDirectory;}-IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_1_float_fact);+IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_4_float_fact); } catch (std::exception& e) {std::cerr << e.what();exit(1);} } }@@ -597,7 +613,7 @@ #endif // _MSC_VER }; SouffleProgram *newInstance_round_trip(){return new Sf_round_trip;}-SymbolTable *getST_round_trip(SouffleProgram *p){return &reinterpret_cast<Sf_round_trip*>(p)->symTable;}+SymbolTable *getST_round_trip(SouffleProgram *p){return &reinterpret_cast<Sf_round_trip*>(p)->getSymbolTable();}  #ifdef __EMBEDDED_SOUFFLE__ class factory_Sf_round_trip: public souffle::ProgramFactory {