souffle-haskell 3.0.0 → 3.1.0
raw patch · 46 files changed
+7462/−4531 lines, 46 files
Files
- CHANGELOG.md +12/−0
- cbits/souffle.cpp +19/−14
- cbits/souffle/CompiledSouffle.h +207/−37
- cbits/souffle/CompiledTuple.h +0/−186
- cbits/souffle/RamTypes.h +5/−0
- cbits/souffle/RecordTable.h +554/−88
- cbits/souffle/SignalHandler.h +27/−9
- cbits/souffle/SouffleInterface.h +90/−35
- cbits/souffle/SymbolTable.h +53/−174
- cbits/souffle/datastructure/BTree.h +9/−8
- cbits/souffle/datastructure/Brie.h +3045/−3148
- cbits/souffle/datastructure/ConcurrentFlyweight.h +477/−0
- cbits/souffle/datastructure/ConcurrentInsertOnlyHashMap.h +458/−0
- cbits/souffle/datastructure/EquivalenceRelation.h +31/−17
- cbits/souffle/datastructure/PiggyList.h +42/−41
- cbits/souffle/datastructure/UnionFind.h +3/−2
- cbits/souffle/io/IOSystem.h +2/−2
- cbits/souffle/io/ReadStream.h +118/−30
- cbits/souffle/io/ReadStreamCSV.h +77/−16
- cbits/souffle/io/ReadStreamJSON.h +41/−27
- cbits/souffle/io/ReadStreamSQLite.h +4/−4
- cbits/souffle/io/SerialisationStream.h +22/−15
- cbits/souffle/io/WriteStream.h +41/−25
- cbits/souffle/io/WriteStreamCSV.h +68/−13
- cbits/souffle/io/WriteStreamJSON.h +5/−5
- cbits/souffle/io/WriteStreamSQLite.h +7/−7
- cbits/souffle/io/gzfstream.h +1/−1
- cbits/souffle/utility/CacheUtil.h +1/−1
- cbits/souffle/utility/ContainerUtil.h +55/−247
- cbits/souffle/utility/EvaluatorUtil.h +6/−2
- cbits/souffle/utility/FileUtil.h +10/−10
- cbits/souffle/utility/FunctionalUtil.h +14/−1
- cbits/souffle/utility/Iteration.h +374/−0
- cbits/souffle/utility/MiscUtil.h +97/−26
- cbits/souffle/utility/ParallelUtil.h +299/−8
- cbits/souffle/utility/StreamUtil.h +41/−3
- cbits/souffle/utility/StringUtil.h +15/−8
- cbits/souffle/utility/Types.h +88/−0
- cbits/souffle/utility/json11.h +11/−11
- cbits/souffle/utility/span.h +662/−0
- cbits/souffle/utility/tinyformat.h +2/−2
- lib/Language/Souffle/Compiled.hs +3/−5
- souffle-haskell.cabal +27/−13
- tests/fixtures/edge_cases.cpp +115/−99
- tests/fixtures/path.cpp +115/−98
- tests/fixtures/round_trip.cpp +109/−93
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 {