diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,12 @@
 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.5.0] - 2022-06-04
+
+### Changed
+
+- souffle-haskell now supports Souffle version 2.3.
+
 ## [3.4.0] - 2022-05-15
 
 ### Changed
diff --git a/cbits/souffle/CompiledSouffle.h b/cbits/souffle/CompiledSouffle.h
--- a/cbits/souffle/CompiledSouffle.h
+++ b/cbits/souffle/CompiledSouffle.h
@@ -24,6 +24,8 @@
 #include "souffle/datastructure/BTreeDelete.h"
 #include "souffle/datastructure/Brie.h"
 #include "souffle/datastructure/EquivalenceRelation.h"
+#include "souffle/datastructure/RecordTableImpl.h"
+#include "souffle/datastructure/SymbolTableImpl.h"
 #include "souffle/datastructure/Table.h"
 #include "souffle/io/IOSystem.h"
 #include "souffle/io/WriteStream.h"
diff --git a/cbits/souffle/RecordTable.h b/cbits/souffle/RecordTable.h
--- a/cbits/souffle/RecordTable.h
+++ b/cbits/souffle/RecordTable.h
@@ -18,469 +18,11 @@
 #pragma once
 
 #include "souffle/RamTypes.h"
-#include "souffle/datastructure/ConcurrentFlyweight.h"
 #include "souffle/utility/span.h"
-#include <cassert>
-#include <cstddef>
-#include <limits>
-#include <memory>
-#include <utility>
-#include <vector>
+#include <initializer_list>
 
 namespace souffle {
 
-namespace details {
-
-// 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;
-    };
-
-    /** @brief converts record to a record reference */
-    RamDomain pack(const RamDomain* Tuple) override {
-        details::GenericRecordView View{Tuple, Arity};
-        return findOrInsert(View).first;
-    }
-
-    /** @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:
-    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 */
-    RamDomain pack(const std::vector<RamDomain>& Vector) override {
-        assert(Vector.size() == Arity);
-        RecordView View{Vector.data()};
-        return Base::findOrInsert(View).first;
-    };
-
-    /** @brief converts record to a record reference */
-    RamDomain pack(const RamDomain* Tuple) override {
-        RecordView View{Tuple};
-        return Base::findOrInsert(View).first;
-    }
-
-    /** @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 override {
-        return Base::fetch(Index).data();
-    }
-};
-
-/** 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:
-    SpecializedRecordMap(const std::size_t /* LaneCount */) {}
-
-    virtual ~SpecializedRecordMap() {}
-
-    void setNumLanes(const std::size_t) override {}
-
-    /** @brief converts record to a record reference */
-    RamDomain pack([[maybe_unused]] 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([[maybe_unused]] const std::initializer_list<RamDomain>& List) override {
-        assert(List.size() == 0);
-        return EmptyRecordIndex;
-    }
-
-    /** @brief convert record reference to a record pointer */
-    const RamDomain* unpack([[maybe_unused]] RamDomain Index) const override {
-        assert(Index == EmptyRecordIndex);
-        return EmptyRecordData;
-    }
-};
-
 /** The interface of any Record Table. */
 class RecordTable {
 public:
@@ -493,121 +35,6 @@
     virtual RamDomain pack(const std::initializer_list<RamDomain>& List) = 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 RecordTable {
-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) {
-            if (Map) {
-                Map->setNumLanes(NumLanes);
-            }
-        }
-    }
-
-    /** @brief convert tuple to record reference */
-    virtual RamDomain pack(const RamDomain* Tuple, const std::size_t Arity) override {
-        auto Guard = Lanes.guard();
-        return lookupMap(Arity).pack(Tuple);
-    }
-
-    /** @brief convert tuple to record reference */
-    virtual RamDomain pack(const std::initializer_list<RamDomain>& List) override {
-        auto Guard = Lanes.guard();
-        return lookupMap(List.size()).pack(std::data(List));
-    }
-
-    /** @brief convert record reference to a record */
-    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& lookupMap(const std::size_t Arity) {
-        if (Arity < Size) {
-            auto* Map = Maps[Arity];
-            if (Map) {
-                return *Map;
-            }
-        }
-
-        createMap(Arity);
-        return *Maps[Arity];
-    }
-
-    /** @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();
-    }
 };
 
 /** @brief helper to convert tuple to record reference for the synthesiser */
diff --git a/cbits/souffle/SignalHandler.h b/cbits/souffle/SignalHandler.h
--- a/cbits/souffle/SignalHandler.h
+++ b/cbits/souffle/SignalHandler.h
@@ -26,12 +26,12 @@
 #include <mutex>
 #include <string>
 
-#ifdef _WIN32
-#include <io.h>
-#define STDERR_FILENO 2 /* Standard error output.  */
-#else
+#ifndef _MSC_VER
 #include <unistd.h>
-#endif  //_WIN32
+#else
+#include <io.h>
+#define STDERR_FILENO 2
+#endif
 
 namespace souffle {
 
@@ -180,7 +180,13 @@
                 // assign to variable to suppress ignored-return-value error.
                 // I don't think we care enough to handle this fringe failure mode.
                 // Worse case we don't get an error message.
-                [[maybe_unused]] auto _ = ::write(STDERR_FILENO, msg, ::strlen(msg));
+#ifdef _MSC_VER
+                [[maybe_unused]] auto _ =
+                        ::_write(STDERR_FILENO, msg, static_cast<unsigned int>(::strlen(msg)));
+#else
+                [[maybe_unused]] auto _ =
+                        ::write(STDERR_FILENO, msg, static_cast<unsigned int>(::strlen(msg)));
+#endif
             }
         };
 
diff --git a/cbits/souffle/SouffleInterface.h b/cbits/souffle/SouffleInterface.h
--- a/cbits/souffle/SouffleInterface.h
+++ b/cbits/souffle/SouffleInterface.h
@@ -43,7 +43,7 @@
  */
 class Relation {
 public:
-    using arity_type = uint32_t;
+    using arity_type = std::size_t;
 
 protected:
     /**
@@ -67,15 +67,15 @@
          *
          * TODO (Honghyw) : Provide a clear documentation of what id is used for.
          */
-        uint32_t id;
+        std::size_t id;
 
     public:
         /**
          * Get the ID of the iterator_base object.
          *
-         * @return ID of the iterator_base object (unit32_t)
+         * @return ID of the iterator_base object (std::size_t)
          */
-        virtual uint32_t getId() const {
+        virtual std::size_t getId() const {
             return id;
         }
 
@@ -84,9 +84,9 @@
          *
          * Create an instance of iterator_base and set its ID to be arg_id.
          *
-         * @param arg_id ID of an iterator object (unit32_t)
+         * @param arg_id ID of an iterator object (std::size_t)
          */
-        iterator_base(uint32_t arg_id) : id(arg_id) {}
+        iterator_base(std::size_t arg_id) : id(arg_id) {}
 
         /**
          * Destructor.
@@ -332,7 +332,8 @@
     /**
      * Get the attribute type of a relation at the column specified by the parameter.
      * The attribute type is in the form "<primitive type>:<type name>".
-     * <primitive type> can be s, f, u, or i standing for symbol, float, unsigned, and integer respectively,
+     * <primitive type> can be s, f, u, i, r, or + standing for symbol, float,
+     * unsigned, integer, record, and ADT respectively,
      * which are the primitive types in Souffle.
      * <type name> is the name given by the user in the Souffle program
      *
@@ -576,7 +577,8 @@
      */
     tuple& operator<<(RamSigned integer) {
         assert(pos < size() && "exceeded tuple's size");
-        assert((*relation.getAttrType(pos) == 'i' || *relation.getAttrType(pos) == 'r') &&
+        assert((*relation.getAttrType(pos) == 'i' || *relation.getAttrType(pos) == 'r' ||
+                       *relation.getAttrType(pos) == '+') &&
                 "wrong element type");
         array[pos++] = integer;
         return *this;
@@ -633,7 +635,8 @@
      */
     tuple& operator>>(RamSigned& integer) {
         assert(pos < size() && "exceeded tuple's size");
-        assert((*relation.getAttrType(pos) == 'i' || *relation.getAttrType(pos) == 'r') &&
+        assert((*relation.getAttrType(pos) == 'i' || *relation.getAttrType(pos) == 'r' ||
+                       *relation.getAttrType(pos) == '+') &&
                 "wrong element type");
         integer = ramBitCast<RamSigned>(array[pos++]);
         return *this;
diff --git a/cbits/souffle/SymbolTable.h b/cbits/souffle/SymbolTable.h
--- a/cbits/souffle/SymbolTable.h
+++ b/cbits/souffle/SymbolTable.h
@@ -17,95 +17,105 @@
 #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"
-#include <algorithm>
-#include <cstdlib>
-#include <deque>
-#include <initializer_list>
-#include <iostream>
+
+#include <memory>
 #include <string>
-#include <unordered_map>
-#include <utility>
-#include <vector>
 
 namespace souffle {
 
+/** Interface of a generic SymbolTable iterator. */
+class SymbolTableIteratorInterface {
+public:
+    virtual ~SymbolTableIteratorInterface() {}
+
+    virtual const std::pair<const std::string, const std::size_t>& get() const = 0;
+
+    virtual bool equals(const SymbolTableIteratorInterface& other) = 0;
+
+    virtual SymbolTableIteratorInterface& incr() = 0;
+
+    virtual std::unique_ptr<SymbolTableIteratorInterface> copy() const = 0;
+};
+
 /**
  * @class SymbolTable
  *
  * SymbolTable encodes symbols to numbers and decodes numbers to symbols.
  */
-class SymbolTable : protected FlyweightImpl<std::string> {
-private:
-    using Base = FlyweightImpl<std::string>;
-
+class SymbolTable {
 public:
-    using iterator = typename Base::iterator;
+    virtual ~SymbolTable() {}
 
-    /** @brief Construct a symbol table with the given number of concurrent access lanes. */
-    SymbolTable(const std::size_t LaneCount = 1) : Base(LaneCount) {}
+    /**
+     * @brief Iterator on a symbol table.
+     *
+     * Iterator over pairs of a symbol and its encoding index.
+     */
+    class Iterator {
+    public:
+        using value_type = const std::pair<const std::string, const std::size_t>;
+        using reference = value_type&;
+        using pointer = value_type*;
 
-    /** @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) {
-            findOrInsert(symbol);
+        Iterator(std::unique_ptr<SymbolTableIteratorInterface> ptr) : impl(std::move(ptr)) {}
+
+        Iterator(const Iterator& it) : impl(it.impl->copy()) {}
+
+        Iterator(Iterator&& it) : impl(std::move(it.impl)) {}
+
+        reference operator*() const {
+            return impl->get();
         }
-    }
 
-    /** @brief Construct a symbol table with the given number of concurrent access lanes and initial symbols.
-     */
-    SymbolTable(const std::size_t LaneCount, std::initializer_list<std::string> symbols)
-            : Base(LaneCount, symbols.size()) {
-        for (const auto& symbol : symbols) {
-            findOrInsert(symbol);
+        pointer operator->() const {
+            return &impl->get();
         }
-    }
 
-    /**
-     * @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);
-    }
+        Iterator& operator++() {
+            impl->incr();
+            return *this;
+        }
 
+        Iterator operator++(int) {
+            Iterator prev(impl->copy());
+            impl->incr();
+            return prev;
+        }
+
+        bool operator==(const Iterator& I) const {
+            return impl->equals(*I.impl);
+        }
+
+        bool operator!=(const Iterator& I) const {
+            return !impl->equals(*I.impl);
+        }
+
+    private:
+        std::unique_ptr<SymbolTableIteratorInterface> impl;
+    };
+
+    using iterator = Iterator;
+
     /** @brief Return an iterator on the first symbol. */
-    iterator begin() const {
-        return Base::begin();
-    }
+    virtual iterator begin() const = 0;
 
     /** @brief Return an iterator past the last symbol. */
-    iterator end() const {
-        return Base::end();
-    }
+    virtual iterator end() const = 0;
 
     /** @brief Check if the given symbol exist. */
-    bool weakContains(const std::string& symbol) const {
-        return Base::weakContains(symbol);
-    }
+    virtual bool weakContains(const std::string& symbol) const = 0;
 
     /** @brief Encode a symbol to a symbol index. */
-    RamDomain encode(const std::string& symbol) {
-        return Base::findOrInsert(symbol).first;
-    }
+    virtual RamDomain encode(const std::string& symbol) = 0;
 
     /** @brief Decode a symbol index to a symbol. */
-    const std::string& decode(const RamDomain index) const {
-        return Base::fetch(index);
-    }
+    virtual const std::string& decode(const RamDomain index) const = 0;
 
     /** @brief Encode a symbol to a symbol index; aliases encode. */
-    RamDomain unsafeEncode(const std::string& symbol) {
-        return encode(symbol);
-    }
+    virtual RamDomain unsafeEncode(const std::string& symbol) = 0;
 
     /** @brief Decode a symbol index to a symbol; aliases decode. */
-    const std::string& unsafeDecode(const RamDomain index) const {
-        return decode(index);
-    }
+    virtual const std::string& unsafeDecode(const RamDomain index) const = 0;
 
     /**
      * @brief Encode the symbol, it is inserted if it does not exist.
@@ -113,10 +123,7 @@
      * @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);
-    }
+    virtual std::pair<RamDomain, bool> findOrInsert(const std::string& symbol) = 0;
 };
 
 }  // namespace souffle
diff --git a/cbits/souffle/datastructure/BTree.h b/cbits/souffle/datastructure/BTree.h
--- a/cbits/souffle/datastructure/BTree.h
+++ b/cbits/souffle/datastructure/BTree.h
@@ -624,7 +624,7 @@
                         }
                     }
 
-                    pos = (i > other->numElements) ? 0 : i;
+                    pos = (i > static_cast<unsigned>(other->numElements)) ? 0 : static_cast<unsigned>(i);
                     other->insert_inner(root, root_lock, pos, predecessor, key, newNode, locked_nodes);
 #else
                     other->insert_inner(root, root_lock, pos, predecessor, key, newNode);
@@ -1340,7 +1340,8 @@
 
                 // split this node
                 auto old_root = root;
-                idx -= cur->rebalance_or_split(const_cast<node**>(&root), root_lock, idx, parents);
+                idx -= cur->rebalance_or_split(
+                        const_cast<node**>(&root), root_lock, static_cast<int>(idx), parents);
 
                 // release parent lock
                 for (auto it = parents.rbegin(); it != parents.rend(); ++it) {
@@ -1372,7 +1373,7 @@
             assert(cur->numElements < node::maxKeys && "Split required!");
 
             // move keys
-            for (int j = cur->numElements; j > idx; --j) {
+            for (int j = static_cast<int>(cur->numElements); j > static_cast<int>(idx); --j) {
                 cur->keys[j] = cur->keys[j - 1];
             }
 
@@ -1960,7 +1961,7 @@
         const int N = node::maxKeys;
 
         // divide range in N+1 sub-ranges
-        int length = (b - a) + 1;
+        int64_t length = (b - a) + 1;
 
         // terminal case: length is less then maxKeys
         if (length <= N) {
@@ -1977,7 +1978,7 @@
 
         // recursive case - compute step size
         int numKeys = N;
-        int step = ((length - numKeys) / (numKeys + 1));
+        int64_t step = ((length - numKeys) / (numKeys + 1));
 
         while (numKeys > 1 && (step < N / 2)) {
             numKeys--;
diff --git a/cbits/souffle/datastructure/BTreeDelete.h b/cbits/souffle/datastructure/BTreeDelete.h
--- a/cbits/souffle/datastructure/BTreeDelete.h
+++ b/cbits/souffle/datastructure/BTreeDelete.h
@@ -904,8 +904,10 @@
     /**
      * The iterator type to be utilized for scanning through btree instances.
      */
-    class iterator : public std::iterator<std::bidirectional_iterator_tag, Key> {
-    public:
+    class iterator {
+        friend class souffle::detail::btree_delete<Key, Comparator, Allocator, blockSize, SearchStrategy,
+                true, WeakComparator, Updater>;
+
         // a pointer to the node currently referred to
         // node const* cur;
         node* cur;
@@ -913,6 +915,7 @@
         // the index of the element currently addressed within the referenced node
         field_index_type pos = 0;
 
+    public:
         using iterator_category = std::forward_iterator_tag;
         using value_type = Key;
         using difference_type = ptrdiff_t;
@@ -1578,7 +1581,7 @@
         } else {
             auto lower_iter = internal_lower_bound(k);
             if (lower_iter != end() && equal(*lower_iter, k)) {
-                return distance(lower_iter, internal_upper_bound(k));
+                return std::distance(lower_iter, internal_upper_bound(k));
             } else {
                 return 0;
             }
@@ -1605,7 +1608,7 @@
         } else {
             iterator lower_iter = internal_lower_bound(k);
             if (lower_iter != end() && equal(*lower_iter, k)) {
-                size_type count = distance(lower_iter, internal_upper_bound(k));
+                size_type count = std::distance(lower_iter, internal_upper_bound(k));
                 for (size_type i = 0; i < count; i++) {
                     erase(lower_iter);
                 }
diff --git a/cbits/souffle/datastructure/Brie.h b/cbits/souffle/datastructure/Brie.h
--- a/cbits/souffle/datastructure/Brie.h
+++ b/cbits/souffle/datastructure/Brie.h
@@ -1612,7 +1612,7 @@
 
 /**
  * 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
+ * uint64_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
@@ -1640,7 +1640,7 @@
 
     // 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 std::size_t LEAF_INDEX_WIDTH = __builtin_ctz(static_cast<unsigned long>(BITS_PER_ENTRY));
     static constexpr uint64_t LEAF_INDEX_MASK = BITS_PER_ENTRY - 1;
 
     static uint64_t toMask(const value_t& value) {
@@ -1936,8 +1936,8 @@
     // 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
+    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.
@@ -2887,9 +2887,9 @@
         if (this->empty()) return res;
 
         // use top-level elements for partitioning
-        int step = std::max(store.size() / chunks, std::size_t(1));
+        size_t step = std::max(store.size() / chunks, std::size_t(1));
 
-        int c = 1;
+        size_t c = 1;
         auto priv = begin();
         for (auto it = store.begin(); it != store.end(); ++it, c++) {
             if (c % step != 0 || c == 1) {
diff --git a/cbits/souffle/datastructure/ConcurrentInsertOnlyHashMap.h b/cbits/souffle/datastructure/ConcurrentInsertOnlyHashMap.h
--- a/cbits/souffle/datastructure/ConcurrentInsertOnlyHashMap.h
+++ b/cbits/souffle/datastructure/ConcurrentInsertOnlyHashMap.h
@@ -46,7 +46,7 @@
     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;
+        const uint64_t Prime = (1ULL << N) - K;
         if (Prime >= LowerBound) {
             return Prime;
         }
@@ -145,7 +145,7 @@
         }
         LoadFactor = 1.0;
         Buckets = std::make_unique<std::atomic<BucketList*>[]>(BucketCount);
-        MaxSizeBeforeGrow = std::ceil(LoadFactor * (double)BucketCount);
+        MaxSizeBeforeGrow = static_cast<std::size_t>(std::ceil(LoadFactor * (double)BucketCount));
     }
 
     ConcurrentInsertOnlyHashMap(const Hash& hash = Hash(), const KeyEqual& key_equal = KeyEqual(),
@@ -412,12 +412,14 @@
             // 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((double)CurrentSize / LoadFactor);
+            assert(LoadFactor > 0);
+            const std::size_t NeededBucketCount =
+                    static_cast<std::size_t>(std::ceil(static_cast<double>(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;
+                const uint64_t Prime = (1ULL << N) - K;
                 if (Prime >= NeededBucketCount) {
                     NewBucketCount = Prime;
                     break;
@@ -451,7 +453,8 @@
 
             Buckets = std::move(NewBuckets);
             BucketCount = NewBucketCount;
-            MaxSizeBeforeGrow = ((double)NewBucketCount * LoadFactor);
+            MaxSizeBeforeGrow =
+                    static_cast<std::size_t>(std::ceil(static_cast<double>(NewBucketCount) * LoadFactor));
         }
 
         Lanes.beforeUnlockAllBut(H);
diff --git a/cbits/souffle/datastructure/EquivalenceRelation.h b/cbits/souffle/datastructure/EquivalenceRelation.h
--- a/cbits/souffle/datastructure/EquivalenceRelation.h
+++ b/cbits/souffle/datastructure/EquivalenceRelation.h
@@ -114,14 +114,10 @@
         other.genAllDisjointSetLists();
 
         // iterate over partitions at a time
-        for (typename StatesMap::chunk it : other.equivalencePartition.getChunks(MAX_THREADS)) {
-            for (auto& p : it) {
-                value_type rep = p.first;
-                StatesList& pl = *p.second;
-                const std::size_t ksize = pl.size();
-                for (std::size_t i = 0; i < ksize; ++i) {
-                    this->sds.unionNodes(rep, pl.get(i));
-                }
+        for (auto&& [rep, pl] : other.equivalencePartition) {
+            const std::size_t ksize = pl->size();
+            for (std::size_t i = 0; i < ksize; ++i) {
+                this->sds.unionNodes(rep, pl->get(i));
             }
         }
         // invalidate iterators unconditionally
diff --git a/cbits/souffle/datastructure/LambdaBTree.h b/cbits/souffle/datastructure/LambdaBTree.h
--- a/cbits/souffle/datastructure/LambdaBTree.h
+++ b/cbits/souffle/datastructure/LambdaBTree.h
@@ -313,8 +313,8 @@
 
                 // split this node
                 auto old_root = this->root;
-                idx -= cur->rebalance_or_split(
-                        const_cast<typename parenttype::node**>(&this->root), this->root_lock, idx, parents);
+                idx -= cur->rebalance_or_split(const_cast<typename parenttype::node**>(&this->root),
+                        this->root_lock, static_cast<int>(idx), parents);
 
                 // release parent lock
                 for (auto it = parents.rbegin(); it != parents.rend(); ++it) {
@@ -346,7 +346,7 @@
             assert(cur->numElements < parenttype::node::maxKeys && "Split required!");
 
             // move keys
-            for (int j = cur->numElements; j > idx; --j) {
+            for (int j = static_cast<int>(cur->numElements); j > idx; --j) {
                 cur->keys[j] = cur->keys[j - 1];
             }
 
@@ -444,8 +444,8 @@
 
             if (cur->numElements >= parenttype::node::maxKeys) {
                 // split this node
-                idx -= cur->rebalance_or_split(
-                        const_cast<typename parenttype::node**>(&this->root), this->root_lock, idx);
+                idx -= cur->rebalance_or_split(const_cast<typename parenttype::node**>(&this->root),
+                        this->root_lock, static_cast<int>(idx));
 
                 // insert element in right fragment
                 if (((typename parenttype::size_type)idx) > cur->numElements) {
diff --git a/cbits/souffle/datastructure/PiggyList.h b/cbits/souffle/datastructure/PiggyList.h
--- a/cbits/souffle/datastructure/PiggyList.h
+++ b/cbits/souffle/datastructure/PiggyList.h
@@ -9,18 +9,14 @@
 #include <iterator>
 
 #ifdef _WIN32
+#include <intrin.h>
 /**
  * Some versions of MSVC do not provide a builtin for counting leading zeroes
  * like gcc, so we have to implement it ourselves.
  */
 #if defined(_MSC_VER)
-unsigned long __inline __builtin_clzll(unsigned long long value) {
-    unsigned long msb = 0;
-
-    if (_BitScanReverse64(&msb, value))
-        return 63 - msb;
-    else
-        return 64;
+int __inline __builtin_clzll(unsigned long long value) {
+    return static_cast<int>(__lzcnt64(value));
 }
 #endif  // _MSC_VER
 #endif  // _WIN32
@@ -112,7 +108,7 @@
         numElements.store(0);
     }
     const std::size_t BLOCKBITS = 16ul;
-    const std::size_t INITIALBLOCKSIZE = (1ul << BLOCKBITS);
+    const std::size_t INITIALBLOCKSIZE = (((std::size_t)1ul) << BLOCKBITS);
 
     // number of elements currently stored within
     std::atomic<std::size_t> numElements{0};
@@ -252,11 +248,17 @@
         container_size = 0;
     }
 
-    class iterator : std::iterator<std::forward_iterator_tag, T> {
+    class iterator {
         std::size_t cIndex = 0;
         PiggyList* bl;
 
     public:
+        using iterator_category = std::forward_iterator_tag;
+        using value_type = T;
+        using difference_type = void;
+        using pointer = T*;
+        using reference = T&;
+
         // default ctor, to silence
         iterator() = default;
 
@@ -299,7 +301,7 @@
         return iterator(this, size());
     }
     const std::size_t BLOCKBITS = 16ul;
-    const std::size_t BLOCKSIZE = (1ul << BLOCKBITS);
+    const std::size_t BLOCKSIZE = (((std::size_t)1ul) << BLOCKBITS);
 
     // number of inserted
     std::atomic<std::size_t> num_containers = 0;
diff --git a/cbits/souffle/datastructure/RecordTableImpl.h b/cbits/souffle/datastructure/RecordTableImpl.h
new file mode 100644
--- /dev/null
+++ b/cbits/souffle/datastructure/RecordTableImpl.h
@@ -0,0 +1,599 @@
+/*
+ * Souffle - A Datalog Compiler
+ * Copyright (c) 2022, 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 RecordTableImpl.h
+ *
+ * RecordTable definition
+ */
+
+#pragma once
+
+#include "souffle/RamTypes.h"
+#include "souffle/RecordTable.h"
+#include "souffle/datastructure/ConcurrentFlyweight.h"
+#include "souffle/utility/span.h"
+
+#include <cassert>
+#include <cstddef>
+#include <limits>
+#include <memory>
+#include <utility>
+#include <vector>
+
+namespace souffle {
+
+namespace details {
+
+// 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[(int)I]) + 0x9e3779b9U + (Seed << 6U) + (Seed >> 2U);
+        }
+        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[(int)I]) + 0x9e3779b9U + (Seed << 6U) + (Seed >> 2U);
+        });
+        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;
+    };
+
+    /** @brief converts record to a record reference */
+    RamDomain pack(const RamDomain* Tuple) override {
+        details::GenericRecordView View{Tuple, Arity};
+        return findOrInsert(View).first;
+    }
+
+    /** @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:
+    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 */
+    RamDomain pack(const std::vector<RamDomain>& Vector) override {
+        assert(Vector.size() == Arity);
+        RecordView View{Vector.data()};
+        return Base::findOrInsert(View).first;
+    };
+
+    /** @brief converts record to a record reference */
+    RamDomain pack(const RamDomain* Tuple) override {
+        RecordView View{Tuple};
+        return Base::findOrInsert(View).first;
+    }
+
+    /** @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 override {
+        return Base::fetch(Index).data();
+    }
+};
+
+/** 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:
+    SpecializedRecordMap(const std::size_t /* LaneCount */) {}
+
+    virtual ~SpecializedRecordMap() {}
+
+    void setNumLanes(const std::size_t) override {}
+
+    /** @brief converts record to a record reference */
+    RamDomain pack([[maybe_unused]] 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([[maybe_unused]] const std::initializer_list<RamDomain>& List) override {
+        assert(List.size() == 0);
+        return EmptyRecordIndex;
+    }
+
+    /** @brief convert record reference to a record pointer */
+    const RamDomain* unpack([[maybe_unused]] RamDomain Index) const override {
+        assert(Index == EmptyRecordIndex);
+        return EmptyRecordData;
+    }
+};
+
+/** A concurrent Record Table with some specialized record maps. */
+template <std::size_t... SpecializedArities>
+class SpecializedRecordTable : public RecordTable {
+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) {
+            if (Map) {
+                Map->setNumLanes(NumLanes);
+            }
+        }
+    }
+
+    /** @brief convert tuple to record reference */
+    virtual RamDomain pack(const RamDomain* Tuple, const std::size_t Arity) override {
+        auto Guard = Lanes.guard();
+        return lookupMap(Arity).pack(Tuple);
+    }
+
+    /** @brief convert tuple to record reference */
+    virtual RamDomain pack(const std::initializer_list<RamDomain>& List) override {
+        auto Guard = Lanes.guard();
+        return lookupMap(List.size()).pack(std::data(List));
+    }
+
+    /** @brief convert record reference to a record */
+    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& lookupMap(const std::size_t Arity) {
+        if (Arity < Size) {
+            auto* Map = Maps[Arity];
+            if (Map) {
+                return *Map;
+            }
+        }
+
+        createMap(Arity);
+        return *Maps[Arity];
+    }
+
+    /** @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();
+    }
+};
+
+}  // namespace souffle
diff --git a/cbits/souffle/datastructure/SymbolTableImpl.h b/cbits/souffle/datastructure/SymbolTableImpl.h
new file mode 100644
--- /dev/null
+++ b/cbits/souffle/datastructure/SymbolTableImpl.h
@@ -0,0 +1,133 @@
+/*
+ * Souffle - A Datalog Compiler
+ * Copyright (c) 2022, 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 SymbolTableImpl.h
+ *
+ * SymbolTable definition
+ */
+
+#pragma once
+
+#include "souffle/SymbolTable.h"
+#include "souffle/datastructure/ConcurrentFlyweight.h"
+#include "souffle/utility/MiscUtil.h"
+#include "souffle/utility/ParallelUtil.h"
+#include "souffle/utility/StreamUtil.h"
+
+#include <algorithm>
+#include <cstdlib>
+#include <deque>
+#include <initializer_list>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+namespace souffle {
+
+/**
+ * @class SymbolTableImpl
+ *
+ * Implementation of the symbol table.
+ */
+class SymbolTableImpl : public SymbolTable, protected FlyweightImpl<std::string> {
+private:
+    using Base = FlyweightImpl<std::string>;
+
+public:
+    class IteratorImpl : public SymbolTableIteratorInterface, private Base::iterator {
+    public:
+        IteratorImpl(Base::iterator&& it) : Base::iterator(it) {}
+
+        IteratorImpl(const Base::iterator& it) : Base::iterator(it) {}
+
+        const std::pair<const std::string, const std::size_t>& get() const {
+            return **this;
+        }
+
+        bool equals(const SymbolTableIteratorInterface& other) {
+            return (*this) == static_cast<const IteratorImpl&>(other);
+        }
+
+        SymbolTableIteratorInterface& incr() {
+            ++(*this);
+            return *this;
+        }
+
+        std::unique_ptr<SymbolTableIteratorInterface> copy() const {
+            return std::make_unique<IteratorImpl>(*this);
+        }
+    };
+
+    using iterator = SymbolTable::Iterator;
+
+    /** @brief Construct a symbol table with the given number of concurrent access lanes. */
+    SymbolTableImpl(const std::size_t LaneCount = 1) : Base(LaneCount) {}
+
+    /** @brief Construct a symbol table with the given initial symbols. */
+    SymbolTableImpl(std::initializer_list<std::string> symbols) : Base(1, symbols.size()) {
+        for (const auto& symbol : symbols) {
+            findOrInsert(symbol);
+        }
+    }
+
+    /** @brief Construct a symbol table with the given number of concurrent access lanes and initial symbols.
+     */
+    SymbolTableImpl(const std::size_t LaneCount, std::initializer_list<std::string> symbols)
+            : Base(LaneCount, symbols.size()) {
+        for (const auto& symbol : symbols) {
+            findOrInsert(symbol);
+        }
+    }
+
+    /**
+     * @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);
+    }
+
+    iterator begin() const override {
+        return SymbolTable::Iterator(std::make_unique<IteratorImpl>(Base::begin()));
+    }
+
+    iterator end() const override {
+        return SymbolTable::Iterator(std::make_unique<IteratorImpl>(Base::end()));
+    }
+
+    bool weakContains(const std::string& symbol) const override {
+        return Base::weakContains(symbol);
+    }
+
+    RamDomain encode(const std::string& symbol) override {
+        return Base::findOrInsert(symbol).first;
+    }
+
+    const std::string& decode(const RamDomain index) const override {
+        return Base::fetch(index);
+    }
+
+    RamDomain unsafeEncode(const std::string& symbol) override {
+        return encode(symbol);
+    }
+
+    const std::string& unsafeDecode(const RamDomain index) const override {
+        return decode(index);
+    }
+
+    std::pair<RamDomain, bool> findOrInsert(const std::string& symbol) override {
+        auto Res = Base::findOrInsert(symbol);
+        return std::make_pair(static_cast<RamDomain>(Res.first), Res.second);
+    }
+};
+
+}  // namespace souffle
diff --git a/cbits/souffle/datastructure/Table.h b/cbits/souffle/datastructure/Table.h
--- a/cbits/souffle/datastructure/Table.h
+++ b/cbits/souffle/datastructure/Table.h
@@ -49,11 +49,17 @@
     std::size_t count = 0;
 
 public:
-    class iterator : public std::iterator<std::forward_iterator_tag, T> {
+    class iterator {
         Block* block;
         unsigned pos;
 
     public:
+        using iterator_category = std::forward_iterator_tag;
+        using value_type = T;
+        using difference_type = void;
+        using pointer = T*;
+        using reference = T&;
+
         iterator(Block* block = nullptr, unsigned pos = 0) : block(block), pos(pos) {}
 
         iterator(const iterator&) = default;
diff --git a/cbits/souffle/io/ReadStreamCSV.h b/cbits/souffle/io/ReadStreamCSV.h
--- a/cbits/souffle/io/ReadStreamCSV.h
+++ b/cbits/souffle/io/ReadStreamCSV.h
@@ -347,8 +347,8 @@
      */
     static std::string getFileName(const std::map<std::string, std::string>& rwOperation) {
         auto name = getOr(rwOperation, "filename", rwOperation.at("name") + ".facts");
-        if (name.front() != '/') {
-            name = getOr(rwOperation, "fact-dir", ".") + "/" + name;
+        if (!isAbsolute(name)) {
+            name = getOr(rwOperation, "fact-dir", ".") + pathSeparator + name;
         }
         return name;
     }
diff --git a/cbits/souffle/io/ReadStreamSQLite.h b/cbits/souffle/io/ReadStreamSQLite.h
--- a/cbits/souffle/io/ReadStreamSQLite.h
+++ b/cbits/souffle/io/ReadStreamSQLite.h
@@ -127,6 +127,7 @@
     }
 
     void openDB() {
+        sqlite3_config(SQLITE_CONFIG_URI, 1);
         if (sqlite3_open(dbFilename.c_str(), &db) != SQLITE_OK) {
             throwError("SQLite error in sqlite3_open: ");
         }
@@ -170,6 +171,10 @@
         // convert dbname to filename
         auto name = getOr(rwOperation, "dbname", rwOperation.at("name") + ".sqlite");
         name = getOr(rwOperation, "filename", name);
+
+        if (name.rfind("file:", 0) == 0 || name.rfind(":memory:", 0) == 0) {
+            return name;
+        }
 
         if (name.front() != '/') {
             name = getOr(rwOperation, "fact-dir", ".") + "/" + name;
diff --git a/cbits/souffle/io/WriteStreamSQLite.h b/cbits/souffle/io/WriteStreamSQLite.h
--- a/cbits/souffle/io/WriteStreamSQLite.h
+++ b/cbits/souffle/io/WriteStreamSQLite.h
@@ -65,9 +65,11 @@
             }
 
 #if RAM_DOMAIN_SIZE == 64
-            if (sqlite3_bind_int64(insertStatement, i + 1, value) != SQLITE_OK) {
+            if (sqlite3_bind_int64(insertStatement, static_cast<int>(i + 1),
+                        static_cast<sqlite3_int64>(value)) != SQLITE_OK) {
 #else
-            if (sqlite3_bind_int(insertStatement, i + 1, value) != SQLITE_OK) {
+            if (sqlite3_bind_int(insertStatement, static_cast<int>(i + 1), static_cast<int>(value)) !=
+                    SQLITE_OK) {
 #endif
                 throwError("SQLite error in sqlite3_bind_text: ");
             }
@@ -102,7 +104,7 @@
         throw std::invalid_argument(error.str());
     }
 
-    uint64_t getSymbolTableIDFromDB(int index) {
+    uint64_t getSymbolTableIDFromDB(std::size_t index) {
         if (sqlite3_bind_text(symbolSelectStatement, 1, symbolTable.decode(index).c_str(), -1,
                     SQLITE_TRANSIENT) != SQLITE_OK) {
             throwError("SQLite error in sqlite3_bind_text: ");
@@ -115,7 +117,7 @@
         sqlite3_reset(symbolSelectStatement);
         return rowid;
     }
-    uint64_t getSymbolTableID(int index) {
+    uint64_t getSymbolTableID(std::size_t index) {
         if (dbSymbolTable.count(index) != 0) {
             return dbSymbolTable[index];
         }
@@ -140,8 +142,9 @@
     }
 
     void openDB() {
+        sqlite3_config(SQLITE_CONFIG_URI, 1);
         if (sqlite3_open(dbFilename.c_str(), &db) != SQLITE_OK) {
-            throwError("SQLite error in sqlite3_open");
+            throwError("SQLite error in sqlite3_open: ");
         }
         sqlite3_extended_result_codes(db, 1);
         executeSQL("PRAGMA synchronous = OFF", db);
@@ -269,6 +272,10 @@
         // convert dbname to filename
         auto name = getOr(rwOperation, "dbname", rwOperation.at("name") + ".sqlite");
         name = getOr(rwOperation, "filename", name);
+
+        if (name.rfind("file:", 0) == 0 || name.rfind(":memory:", 0) == 0) {
+            return name;
+        }
 
         if (name.front() != '/') {
             name = getOr(rwOperation, "output-dir", ".") + "/" + name;
diff --git a/cbits/souffle/io/gzfstream.h b/cbits/souffle/io/gzfstream.h
--- a/cbits/souffle/io/gzfstream.h
+++ b/cbits/souffle/io/gzfstream.h
@@ -91,8 +91,8 @@
             *pptr() = c;
             pbump(1);
         }
-        int toWrite = pptr() - pbase();
-        if (gzwrite(fileHandle, pbase(), toWrite) != toWrite) {
+        const int toWrite = static_cast<int>(pptr() - pbase());
+        if (gzwrite(fileHandle, pbase(), static_cast<unsigned int>(toWrite)) != toWrite) {
             return EOF;
         }
         pbump(-toWrite);
@@ -108,13 +108,14 @@
             return traits_type::to_int_type(*gptr());
         }
 
-        unsigned charsPutBack = gptr() - eback();
+        std::size_t charsPutBack = gptr() - eback();
         if (charsPutBack > reserveSize) {
             charsPutBack = reserveSize;
         }
         memcpy(buffer + reserveSize - charsPutBack, gptr() - charsPutBack, charsPutBack);
 
-        int charsRead = gzread(fileHandle, buffer + reserveSize, bufferSize - reserveSize);
+        int charsRead =
+                gzread(fileHandle, buffer + reserveSize, static_cast<unsigned int>(bufferSize - reserveSize));
         if (charsRead <= 0) {
             return EOF;
         }
@@ -126,8 +127,8 @@
 
     int sync() override {
         if ((pptr() != nullptr) && pptr() > pbase()) {
-            int toWrite = pptr() - pbase();
-            if (gzwrite(fileHandle, pbase(), toWrite) != toWrite) {
+            const int toWrite = static_cast<int>(pptr() - pbase());
+            if (gzwrite(fileHandle, pbase(), static_cast<unsigned int>(toWrite)) != toWrite) {
                 return -1;
             }
             pbump(-toWrite);
@@ -136,8 +137,8 @@
     }
 
 private:
-    static constexpr unsigned int bufferSize = 65536;
-    static constexpr unsigned int reserveSize = 16;
+    static constexpr std::size_t bufferSize = 65536;
+    static constexpr std::size_t reserveSize = 16;
 
     char buffer[bufferSize] = {};
     gzFile fileHandle = {};
diff --git a/cbits/souffle/utility/ContainerUtil.h b/cbits/souffle/utility/ContainerUtil.h
--- a/cbits/souffle/utility/ContainerUtil.h
+++ b/cbits/souffle/utility/ContainerUtil.h
@@ -219,6 +219,20 @@
     return equal_targets(a, b, comp_deref<Own<T>>());
 }
 
+#ifdef _MSC_VER
+// issue:
+// https://developercommunity.visualstudio.com/t/c-template-template-not-recognized-as-class-templa/558979
+template <typename T>
+bool equal_targets(const std::vector<Own<T>>& a, const std::vector<Own<T>>& b) {
+    return equal_targets(a, b, comp_deref<Own<T>>());
+}
+
+template <typename T>
+bool equal_targets(const std::vector<T>& a, const std::vector<T>& b) {
+    return equal_targets(a, b, comp_deref<T>());
+}
+#endif
+
 /**
  * A function testing whether two maps of unique pointers are referencing to equivalent
  * targets.
diff --git a/cbits/souffle/utility/EvaluatorUtil.h b/cbits/souffle/utility/EvaluatorUtil.h
--- a/cbits/souffle/utility/EvaluatorUtil.h
+++ b/cbits/souffle/utility/EvaluatorUtil.h
@@ -24,7 +24,7 @@
 namespace souffle::evaluator {
 
 template <typename A, typename F /* Tuple<RamDomain,1> -> void */>
-void runRange(A from, A to, A step, F&& go) {
+void runRange(const A from, const A to, const A step, F&& go) {
 #define GO(x) go(Tuple<RamDomain, 1>{ramBitCast(x)})
     if (0 < step) {
         for (auto x = from; x < to; x += step) {
@@ -42,8 +42,26 @@
 }
 
 template <typename A, typename F /* Tuple<RamDomain,1> -> void */>
-void runRange(A from, A to, F&& go) {
-    return runRange(from, to, A(from <= to ? 1 : -1), std::forward<F>(go));
+void runRangeBackward(const A from, const A to, F&& func) {
+    assert(from > to);
+    if (from > to) {
+        for (auto x = from; x > to; --x) {
+            func(Tuple<RamDomain, 1>{ramBitCast(x)});
+        }
+    }
+}
+
+template <typename A, typename F /* Tuple<RamDomain,1> -> void */>
+void runRange(const A from, const A to, F&& go) {
+    if constexpr (std::is_unsigned<A>()) {
+        if (from <= to) {
+            runRange(from, to, static_cast<A>(1U), std::forward<F>(go));
+        } else {
+            runRangeBackward(from, to, std::forward<F>(go));
+        }
+    } else {
+        return runRange(from, to, A(from <= to ? 1 : -1), std::forward<F>(go));
+    }
 }
 
 template <typename A>
diff --git a/cbits/souffle/utility/FileUtil.h b/cbits/souffle/utility/FileUtil.h
--- a/cbits/souffle/utility/FileUtil.h
+++ b/cbits/souffle/utility/FileUtil.h
@@ -16,36 +16,39 @@
 
 #pragma once
 
+#include <algorithm>
 #include <climits>
 #include <cstdio>
 #include <cstdlib>
+#include <filesystem>
 #include <fstream>
+#include <map>
+#include <optional>
 #include <sstream>
 #include <string>
 #include <utility>
 #include <sys/stat.h>
 
+// -------------------------------------------------------------------------------
+//                               File Utils
+// -------------------------------------------------------------------------------
+
 #ifndef _WIN32
 #include <unistd.h>
 #else
+#define NOMINMAX
+#define NOGDI
 #include <fcntl.h>
 #include <io.h>
 #include <stdlib.h>
 #include <windows.h>
 
 // -------------------------------------------------------------------------------
-//                               File Utils
+//                               Windows
 // -------------------------------------------------------------------------------
 
-#define X_OK 1 /* execute permission - unsupported in windows*/
-
 #define PATH_MAX 260
 
-/**
- * access and realpath are missing on windows, we use their windows equivalents
- * as work-arounds.
- */
-#define access _access
 inline char* realpath(const char* path, char* resolved_path) {
     return _fullpath(resolved_path, path, PATH_MAX);
 }
@@ -57,19 +60,52 @@
 #define pclose _pclose
 #endif
 
+// -------------------------------------------------------------------------------
+//                               All systems
+// -------------------------------------------------------------------------------
+
 namespace souffle {
 
+// The separator in the PATH variable
+#ifdef _MSC_VER
+const char PATHdelimiter = ';';
+const char pathSeparator = '/';
+#else
+const char PATHdelimiter = ':';
+const char pathSeparator = '/';
+#endif
+
+inline std::string& makePreferred(std::string& name) {
+    std::replace(name.begin(), name.end(), '\\', '/');
+    // std::replace(name.begin(), name.end(), '/', pathSeparator);
+    return name;
+}
+
+inline bool isAbsolute(const std::string& path) {
+    std::filesystem::path P(path);
+    return P.is_absolute();
+}
+
 /**
  *  Check whether a file exists in the file system
  */
 inline bool existFile(const std::string& name) {
+    static std::map<std::string, bool> existFileCache{};
+    auto it = existFileCache.find(name);
+    if (it != existFileCache.end()) {
+        return it->second;
+    }
+    std::filesystem::path P(name);
+    bool result = std::filesystem::exists(P);
+    /*bool result = false;
     struct stat buffer = {};
-    if (stat(name.c_str(), &buffer) == 0) {
+    if (stat(P.native().c_str(), &buffer) == 0) {
         if ((buffer.st_mode & S_IFMT) != 0) {
-            return true;
+            result = true;
         }
-    }
-    return false;
+    }*/
+    existFileCache[name] = result;
+    return result;
 }
 
 /**
@@ -88,16 +124,24 @@
 /**
  * Check whether a given file exists and it is an executable
  */
+#ifdef _WIN32
 inline bool isExecutable(const std::string& name) {
+    return existFile(
+            name);  // there is no EXECUTABLE bit on Windows, so theoretically any file may be executable
+}
+#else
+inline bool isExecutable(const std::string& name) {
     return existFile(name) && (access(name.c_str(), X_OK) == 0);
 }
+#endif
 
 /**
  * Simple implementation of a which tool
  */
 inline std::string which(const std::string& name) {
     // Check if name has path components in it and if so return it immediately
-    if (name.find('/') != std::string::npos) {
+    std::filesystem::path P(name);
+    if (P.has_parent_path()) {
         return name;
     }
     // Get PATH from environment, if it exists.
@@ -111,8 +155,8 @@
     std::string sub;
 
     // Check for existence of a binary called 'name' in PATH
-    while (std::getline(sstr, sub, ':')) {
-        std::string path = sub + "/" + name;
+    while (std::getline(sstr, sub, PATHdelimiter)) {
+        std::string path = sub + pathSeparator + name;
         if ((::realpath(path.c_str(), buf) != nullptr) && isExecutable(path) && !existDir(path)) {
             return buf;
         }
@@ -127,19 +171,27 @@
     if (name.empty()) {
         return ".";
     }
-    std::size_t lastNotSlash = name.find_last_not_of('/');
+
+    std::filesystem::path P(name);
+    if (P.has_parent_path()) {
+        return P.parent_path().string();
+    } else {
+        return ".";
+    }
+
+    std::size_t lastNotSlash = name.find_last_not_of(pathSeparator);
     // All '/'
     if (lastNotSlash == std::string::npos) {
         return "/";
     }
-    std::size_t leadingSlash = name.find_last_of('/', lastNotSlash);
+    std::size_t leadingSlash = name.find_last_of(pathSeparator, lastNotSlash);
     // No '/'
     if (leadingSlash == std::string::npos) {
         return ".";
     }
     // dirname is '/'
     if (leadingSlash == 0) {
-        return "/";
+        return std::string(1, pathSeparator);
     }
     return name.substr(0, leadingSlash);
 }
@@ -157,15 +209,17 @@
  *  Join two paths together; note that this does not resolve overlaps or relative paths.
  */
 inline std::string pathJoin(const std::string& first, const std::string& second) {
-    unsigned firstPos = static_cast<unsigned>(first.size()) - 1;
-    while (first.at(firstPos) == '/') {
+    return (std::filesystem::path(first) / std::filesystem::path(second)).string();
+
+    /*unsigned firstPos = static_cast<unsigned>(first.size()) - 1;
+    while (first.at(firstPos) == pathSeparator) {
         firstPos--;
     }
     unsigned secondPos = 0;
-    while (second.at(secondPos) == '/') {
+    while (second.at(secondPos) == pathSeparator) {
         secondPos++;
     }
-    return first.substr(0, firstPos + 1) + '/' + second.substr(secondPos);
+    return first.substr(0, firstPos + 1) + pathSeparator + second.substr(secondPos);*/
 }
 
 /*
@@ -173,18 +227,19 @@
  * relative to the directory given by @ base. A path here refers a
  * colon-separated list of directories.
  */
-inline std::string findTool(const std::string& tool, const std::string& base, const std::string& path) {
-    std::string dir = dirName(base);
+inline std::optional<std::string> findTool(
+        const std::string& tool, const std::string& base, const std::string& path) {
+    std::filesystem::path dir(dirName(base));
     std::stringstream sstr(path);
     std::string sub;
 
     while (std::getline(sstr, sub, ':')) {
-        std::string subpath = dir + "/" + sub + '/' + tool;
-        if (isExecutable(subpath)) {
-            return absPath(subpath);
+        auto subpath = (dir / sub / tool);
+        if (std::filesystem::exists(subpath)) {
+            return absPath(subpath.string());
         }
     }
-    return "";
+    return {};
 }
 
 /*
@@ -195,12 +250,12 @@
         return ".";
     }
 
-    std::size_t lastNotSlash = filename.find_last_not_of('/');
+    std::size_t lastNotSlash = filename.find_last_not_of(pathSeparator);
     if (lastNotSlash == std::string::npos) {
-        return "/";
+        return std::string(1, pathSeparator);
     }
 
-    std::size_t lastSlashBeforeBasename = filename.find_last_of('/', lastNotSlash - 1);
+    std::size_t lastSlashBeforeBasename = filename.find_last_of(pathSeparator, lastNotSlash - 1);
     if (lastSlashBeforeBasename == std::string::npos) {
         lastSlashBeforeBasename = static_cast<std::size_t>(-1);
     }
@@ -217,7 +272,7 @@
     if (lastDot == std::string::npos) {
         return name;
     }
-    const std::size_t lastSlash = name.find_last_of('/');
+    const std::size_t lastSlash = name.find_last_of(pathSeparator);
     // last slash occurs after last dot, so no extension
     if (lastSlash != std::string::npos && lastSlash > lastDot) {
         return name;
@@ -236,7 +291,7 @@
     if (lastDot == std::string::npos) {
         return std::string();
     }
-    const std::size_t lastSlash = name.find_last_of('/');
+    const std::size_t lastSlash = name.find_last_of(pathSeparator);
     // last slash occurs after last dot, so no extension
     if (lastSlash != std::string::npos && lastSlash > lastDot) {
         return std::string();
@@ -250,10 +305,11 @@
  */
 inline std::string tempFile() {
 #ifdef _WIN32
+    char ctempl[L_tmpnam];
     std::string templ;
     std::FILE* f = nullptr;
     while (f == nullptr) {
-        templ = std::tmpnam(nullptr);
+        templ = std::tmpnam(ctempl);
         f = fopen(templ.c_str(), "wx");
     }
     fclose(f);
@@ -268,13 +324,16 @@
 inline std::stringstream execStdOut(char const* cmd) {
     FILE* in = popen(cmd, "r");
     std::stringstream data;
-    while (in != nullptr) {
+
+    if (in == nullptr) {
+        return data;
+    }
+
+    while (!feof(in)) {
         int c = fgetc(in);
-        if (feof(in) != 0) {
-            break;
-        }
         data << static_cast<char>(c);
     }
+
     pclose(in);
     return data;
 }
diff --git a/cbits/souffle/utility/Iteration.h b/cbits/souffle/utility/Iteration.h
--- a/cbits/souffle/utility/Iteration.h
+++ b/cbits/souffle/utility/Iteration.h
@@ -26,7 +26,18 @@
 namespace souffle {
 
 namespace detail {
-
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4172)
+#elif defined(__GNUC__) && (__GNUC__ >= 7)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wreturn-local-addr"
+#elif defined(__has_warning)
+#pragma clang diagnostic push
+#if __has_warning("-Wreturn-stack-address")
+#pragma clang diagnostic ignored "-Wreturn-stack-address"
+#endif
+#endif
 // This is a helper in the cases when the lambda is stateless
 template <typename F>
 F makeFun() {
@@ -34,9 +45,16 @@
     // 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;
+    typename std::aligned_storage<sizeof(F)>::type fakeLam{};
     return reinterpret_cast<F const&>(fakeLam);
 }
+#ifdef _MSC_VER
+#pragma warning(pop)
+#elif defined(__GNUC__) && (__GNUC__ >= 7)
+#pragma GCC diagnostic pop
+#elif defined(__has_warning)
+#pragma clang diagnostic pop
+#endif
 }  // namespace detail
 
 // -------------------------------------------------------------
@@ -101,7 +119,7 @@
     }
 
     /* Support for the pointer operator. */
-    auto operator-> () const {
+    auto operator->() const {
         return &**this;
     }
 
@@ -265,9 +283,9 @@
     }
 
     // splits up this range into the given number of partitions
-    std::vector<range> partition(int np = 100) {
+    std::vector<range> partition(std::size_t np = 100) {
         // obtain the size
-        int n = 0;
+        std::size_t n = 0;
         for (auto i = a; i != b; ++i) {
             n++;
         }
@@ -279,8 +297,8 @@
         res.reserve(np);
         auto cur = a;
         auto last = cur;
-        int i = 0;
-        int p = 0;
+        std::size_t i = 0;
+        std::size_t p = 0;
         while (cur != b) {
             ++cur;
             i++;
diff --git a/cbits/souffle/utility/MiscUtil.h b/cbits/souffle/utility/MiscUtil.h
--- a/cbits/souffle/utility/MiscUtil.h
+++ b/cbits/souffle/utility/MiscUtil.h
@@ -30,19 +30,14 @@
 #include <utility>
 
 #ifdef _WIN32
+#define NOMINMAX
+#define NOGDI
 #include <fcntl.h>
 #include <io.h>
 #include <stdlib.h>
 #include <windows.h>
 
 /**
- * Windows headers define these and they interfere with the standard library
- * functions.
- */
-#undef min
-#undef max
-
-/**
  * On windows, the following gcc builtins are missing.
  *
  * In the case of popcountll, __popcnt64 is the windows equivalent.
@@ -54,21 +49,34 @@
 #define __builtin_popcountll __popcnt64
 
 #if defined(_MSC_VER)
-constexpr unsigned long __builtin_ctz(unsigned long value) {
+// return the number of trailing zeroes in value, or 32 if value is zero.
+inline constexpr unsigned long __builtin_ctz(unsigned long value) {
     unsigned long trailing_zeroes = 0;
+    if (value == 0) return 32;
     while ((value = value >> 1) ^ 1) {
         ++trailing_zeroes;
     }
     return trailing_zeroes;
 }
 
-inline unsigned long __builtin_ctzll(unsigned long long value) {
-    unsigned long trailing_zero = 0;
+// return the number of trailing zeroes in value, or 64 if value is zero.
+inline constexpr int __builtin_ctzll_constexpr(unsigned long long value) {
+    int trailing_zeroes = 0;
 
-    if (_BitScanForward64(&trailing_zero, value)) {
-        return trailing_zero;
+    if (value == 0) return 64;
+    while ((value = value >> 1) ^ 1) {
+        ++trailing_zeroes;
+    }
+    return trailing_zeroes;
+}
+
+inline int __builtin_ctzll(unsigned long long value) {
+    unsigned long trailing_zeroes = 0;
+
+    if (_BitScanForward64(&trailing_zeroes, value)) {
+        return static_cast<int>(trailing_zeroes);
     } else {
-        return 64;
+        return 64;  // return 64 like GCC would when value == 0
     }
 }
 #endif  // _MSC_VER
diff --git a/cbits/souffle/utility/ParallelUtil.h b/cbits/souffle/utility/ParallelUtil.h
--- a/cbits/souffle/utility/ParallelUtil.h
+++ b/cbits/souffle/utility/ParallelUtil.h
@@ -48,14 +48,28 @@
 #include <sched.h>
 // pthread_yield is deprecated and should be replaced by sched_yield
 #define pthread_yield sched_yield
+#elif defined _MSC_VER
+#include <thread>
+#define NOMINMAX
+#include <windows.h>
+#define pthread_yield std::this_thread::yield
 #endif
 
+#ifdef _MSC_VER
 // support for a parallel region
+#define PARALLEL_START __pragma(omp parallel) {
+#define PARALLEL_END }
+
+// support for parallel loops
+#define pfor __pragma(omp for schedule(dynamic)) for
+#else
+// support for a parallel region
 #define PARALLEL_START _Pragma("omp parallel") {
 #define PARALLEL_END }
 
 // support for parallel loops
 #define pfor _Pragma("omp for schedule(dynamic)") for
+#endif
 
 // spawn and sync are processed sequentially (overhead to expensive)
 #define task_spawn
@@ -221,10 +235,14 @@
 namespace detail {
 
 /* Pause instruction to prevent excess processor bus usage */
+#if defined _MSC_VER
+#define cpu_relax() YieldProcessor()
+#else
 #ifdef __x86_64__
 #define cpu_relax() asm volatile("pause\n" : : : "memory")
 #else
 #define cpu_relax() asm volatile("" : : : "memory")
+#endif
 #endif
 
 /**
diff --git a/cbits/souffle/utility/tinyformat.h b/cbits/souffle/utility/tinyformat.h
--- a/cbits/souffle/utility/tinyformat.h
+++ b/cbits/souffle/utility/tinyformat.h
@@ -792,27 +792,29 @@
             break;
         case 'X':
             out.setf(std::ios::uppercase);
-            // Falls through
-        case 'x': case 'p':
+            [[fallthrough]];
+        case 'x':
+            [[fallthrough]]; 
+        case 'p':
             out.setf(std::ios::hex, std::ios::basefield);
             intConversion = true;
             break;
         case 'E':
             out.setf(std::ios::uppercase);
-            // Falls through
+            [[fallthrough]];
         case 'e':
             out.setf(std::ios::scientific, std::ios::floatfield);
             out.setf(std::ios::dec, std::ios::basefield);
             break;
         case 'F':
             out.setf(std::ios::uppercase);
-            // Falls through
+            [[fallthrough]];
         case 'f':
             out.setf(std::ios::fixed, std::ios::floatfield);
             break;
         case 'A':
             out.setf(std::ios::uppercase);
-            // Falls through
+            [[fallthrough]];
         case 'a':
 #           ifdef _MSC_VER
             // Workaround https://developercommunity.visualstudio.com/content/problem/520472/hexfloat-stream-output-does-not-ignore-precision-a.html
@@ -824,7 +826,7 @@
             break;
         case 'G':
             out.setf(std::ios::uppercase);
-            // Falls through
+            [[fallthrough]];
         case 'g':
             out.setf(std::ios::dec, std::ios::basefield);
             // As in boost::format, let stream decide float format.
diff --git a/souffle-haskell.cabal b/souffle-haskell.cabal
--- a/souffle-haskell.cabal
+++ b/souffle-haskell.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           souffle-haskell
-version:        3.4.0
+version:        3.5.0
 synopsis:       Souffle Datalog bindings for Haskell
 description:    Souffle Datalog bindings for Haskell.
 category:       Logic Programming, Foreign Binding, Bindings
@@ -32,6 +32,8 @@
     cbits/souffle/datastructure/EquivalenceRelation.h
     cbits/souffle/datastructure/LambdaBTree.h
     cbits/souffle/datastructure/PiggyList.h
+    cbits/souffle/datastructure/RecordTableImpl.h
+    cbits/souffle/datastructure/SymbolTableImpl.h
     cbits/souffle/datastructure/Table.h
     cbits/souffle/datastructure/UnionFind.h
     cbits/souffle/io/gzfstream.h
@@ -102,9 +104,6 @@
       souffle/CompiledSouffle.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
@@ -114,18 +113,23 @@
       souffle/utility/Iteration.h
       souffle/utility/Types.h
       souffle/utility/tinyformat.h
-      souffle/utility/StreamUtil.h
-      souffle/utility/ContainerUtil.h
-      souffle/utility/DynamicCasting.h
       souffle/datastructure/BTreeDelete.h
       souffle/datastructure/BTreeUtil.h
       souffle/utility/CacheUtil.h
+      souffle/utility/ContainerUtil.h
+      souffle/utility/DynamicCasting.h
+      souffle/utility/ParallelUtil.h
       souffle/datastructure/Brie.h
+      souffle/utility/StreamUtil.h
       souffle/datastructure/EquivalenceRelation.h
       souffle/datastructure/LambdaBTree.h
       souffle/datastructure/BTree.h
       souffle/datastructure/PiggyList.h
       souffle/datastructure/UnionFind.h
+      souffle/datastructure/RecordTableImpl.h
+      souffle/datastructure/ConcurrentFlyweight.h
+      souffle/datastructure/ConcurrentInsertOnlyHashMap.h
+      souffle/datastructure/SymbolTableImpl.h
       souffle/datastructure/Table.h
       souffle/io/IOSystem.h
       souffle/io/ReadStream.h
@@ -194,9 +198,6 @@
       souffle/CompiledSouffle.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
@@ -206,18 +207,23 @@
       souffle/utility/Iteration.h
       souffle/utility/Types.h
       souffle/utility/tinyformat.h
-      souffle/utility/StreamUtil.h
-      souffle/utility/ContainerUtil.h
-      souffle/utility/DynamicCasting.h
       souffle/datastructure/BTreeDelete.h
       souffle/datastructure/BTreeUtil.h
       souffle/utility/CacheUtil.h
+      souffle/utility/ContainerUtil.h
+      souffle/utility/DynamicCasting.h
+      souffle/utility/ParallelUtil.h
       souffle/datastructure/Brie.h
+      souffle/utility/StreamUtil.h
       souffle/datastructure/EquivalenceRelation.h
       souffle/datastructure/LambdaBTree.h
       souffle/datastructure/BTree.h
       souffle/datastructure/PiggyList.h
       souffle/datastructure/UnionFind.h
+      souffle/datastructure/RecordTableImpl.h
+      souffle/datastructure/ConcurrentFlyweight.h
+      souffle/datastructure/ConcurrentInsertOnlyHashMap.h
+      souffle/datastructure/SymbolTableImpl.h
       souffle/datastructure/Table.h
       souffle/io/IOSystem.h
       souffle/io/ReadStream.h
@@ -281,9 +287,6 @@
       souffle/CompiledSouffle.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
@@ -293,18 +296,23 @@
       souffle/utility/Iteration.h
       souffle/utility/Types.h
       souffle/utility/tinyformat.h
-      souffle/utility/StreamUtil.h
-      souffle/utility/ContainerUtil.h
-      souffle/utility/DynamicCasting.h
       souffle/datastructure/BTreeDelete.h
       souffle/datastructure/BTreeUtil.h
       souffle/utility/CacheUtil.h
+      souffle/utility/ContainerUtil.h
+      souffle/utility/DynamicCasting.h
+      souffle/utility/ParallelUtil.h
       souffle/datastructure/Brie.h
+      souffle/utility/StreamUtil.h
       souffle/datastructure/EquivalenceRelation.h
       souffle/datastructure/LambdaBTree.h
       souffle/datastructure/BTree.h
       souffle/datastructure/PiggyList.h
       souffle/datastructure/UnionFind.h
+      souffle/datastructure/RecordTableImpl.h
+      souffle/datastructure/ConcurrentFlyweight.h
+      souffle/datastructure/ConcurrentInsertOnlyHashMap.h
+      souffle/datastructure/SymbolTableImpl.h
       souffle/datastructure/Table.h
       souffle/io/IOSystem.h
       souffle/io/ReadStream.h
diff --git a/tests/fixtures/edge_cases.cpp b/tests/fixtures/edge_cases.cpp
--- a/tests/fixtures/edge_cases.cpp
+++ b/tests/fixtures/edge_cases.cpp
@@ -1,8 +1,10 @@
 
 #include "souffle/CompiledSouffle.h"
 
-extern "C" {
+namespace functors {
+ extern "C" {
 }
+}
 
 namespace souffle {
 static const RamDomain RAM_BIT_SHIFT_MASK = RAM_DOMAIN_SIZE - 1;
@@ -14,7 +16,7 @@
   return (ramBitCast<RamSigned>(a[0]) < ramBitCast<RamSigned>(b[0])) ? -1 : (ramBitCast<RamSigned>(a[0]) > ramBitCast<RamSigned>(b[0])) ? 1 :((ramBitCast<RamSigned>(a[1]) < ramBitCast<RamSigned>(b[1])) ? -1 : (ramBitCast<RamSigned>(a[1]) > ramBitCast<RamSigned>(b[1])) ? 1 :((ramBitCast<RamSigned>(a[2]) < ramBitCast<RamSigned>(b[2])) ? -1 : (ramBitCast<RamSigned>(a[2]) > ramBitCast<RamSigned>(b[2])) ? 1 :(0)));
  }
 bool less(const t_tuple& a, const t_tuple& b) const {
-  return (ramBitCast<RamSigned>(a[0]) < ramBitCast<RamSigned>(b[0]))|| (ramBitCast<RamSigned>(a[0]) == ramBitCast<RamSigned>(b[0])) && ((ramBitCast<RamSigned>(a[1]) < ramBitCast<RamSigned>(b[1]))|| (ramBitCast<RamSigned>(a[1]) == ramBitCast<RamSigned>(b[1])) && ((ramBitCast<RamSigned>(a[2]) < ramBitCast<RamSigned>(b[2]))));
+  return (ramBitCast<RamSigned>(a[0]) < ramBitCast<RamSigned>(b[0]))|| ((ramBitCast<RamSigned>(a[0]) == ramBitCast<RamSigned>(b[0])) && ((ramBitCast<RamSigned>(a[1]) < ramBitCast<RamSigned>(b[1]))|| ((ramBitCast<RamSigned>(a[1]) == ramBitCast<RamSigned>(b[1])) && ((ramBitCast<RamSigned>(a[2]) < ramBitCast<RamSigned>(b[2]))))));
  }
 bool equal(const t_tuple& a, const t_tuple& b) const {
 return (ramBitCast<RamSigned>(a[0]) == ramBitCast<RamSigned>(b[0]))&&(ramBitCast<RamSigned>(a[1]) == ramBitCast<RamSigned>(b[1]))&&(ramBitCast<RamSigned>(a[2]) == ramBitCast<RamSigned>(b[2]));
@@ -220,7 +222,7 @@
   return (ramBitCast<RamUnsigned>(a[0]) < ramBitCast<RamUnsigned>(b[0])) ? -1 : (ramBitCast<RamUnsigned>(a[0]) > ramBitCast<RamUnsigned>(b[0])) ? 1 :((ramBitCast<RamSigned>(a[1]) < ramBitCast<RamSigned>(b[1])) ? -1 : (ramBitCast<RamSigned>(a[1]) > ramBitCast<RamSigned>(b[1])) ? 1 :((ramBitCast<RamFloat>(a[2]) < ramBitCast<RamFloat>(b[2])) ? -1 : (ramBitCast<RamFloat>(a[2]) > ramBitCast<RamFloat>(b[2])) ? 1 :(0)));
  }
 bool less(const t_tuple& a, const t_tuple& b) const {
-  return (ramBitCast<RamUnsigned>(a[0]) < ramBitCast<RamUnsigned>(b[0]))|| (ramBitCast<RamUnsigned>(a[0]) == ramBitCast<RamUnsigned>(b[0])) && ((ramBitCast<RamSigned>(a[1]) < ramBitCast<RamSigned>(b[1]))|| (ramBitCast<RamSigned>(a[1]) == ramBitCast<RamSigned>(b[1])) && ((ramBitCast<RamFloat>(a[2]) < ramBitCast<RamFloat>(b[2]))));
+  return (ramBitCast<RamUnsigned>(a[0]) < ramBitCast<RamUnsigned>(b[0]))|| ((ramBitCast<RamUnsigned>(a[0]) == ramBitCast<RamUnsigned>(b[0])) && ((ramBitCast<RamSigned>(a[1]) < ramBitCast<RamSigned>(b[1]))|| ((ramBitCast<RamSigned>(a[1]) == ramBitCast<RamSigned>(b[1])) && ((ramBitCast<RamFloat>(a[2]) < ramBitCast<RamFloat>(b[2]))))));
  }
 bool equal(const t_tuple& a, const t_tuple& b) const {
 return (ramBitCast<RamUnsigned>(a[0]) == ramBitCast<RamUnsigned>(b[0]))&&(ramBitCast<RamSigned>(a[1]) == ramBitCast<RamSigned>(b[1]))&&(ramBitCast<RamFloat>(a[2]) == ramBitCast<RamFloat>(b[2]));
@@ -327,7 +329,7 @@
 }
 public:
 // -- initialize symbol table --
-SymbolTable symTable{
+SymbolTableImpl symTable{
 	R"_()_",
 	R"_(abc)_",
 	R"_(long_string_from_DL:...............................................................................................................................................................................................................................................................................................end)_",
@@ -381,7 +383,7 @@
     // 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 (0 < getNumThreads()) { omp_set_num_threads(getNumThreads()); }
+    if (0 < getNumThreads()) { omp_set_num_threads(static_cast<int>(getNumThreads())); }
 #endif
 
     signalHandler->set();
@@ -413,29 +415,25 @@
 }
 public:
 void printAll(std::string outputDirectoryArg = "") override {
-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_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"},{"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"},{"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_3_no_strings);
-} catch (std::exception& e) {std::cerr << e.what();exit(1);}
 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_4_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"},{"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_1_empty_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"},{"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_3_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"},{"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 long_strings data: " << e.what() << '\n';}
 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_3_no_strings);
@@ -448,17 +446,15 @@
 if (!inputDirectoryArg.empty()) {directiveMap["fact-dir"] = inputDirectoryArg;}
 IOSystem::getInstance().getReader(directiveMap, symTable, recordTable)->readAll(*rel_4_unicode);
 } catch (std::exception& e) {std::cerr << "Error loading unicode data: " << e.what() << '\n';}
+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 long_strings data: " << e.what() << '\n';}
 }
 public:
 void dumpInputs() override {
 try {std::map<std::string, std::string> rwOperation;
 rwOperation["IO"] = "stdout";
-rwOperation["name"] = "long_strings";
-rwOperation["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}";
-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"] = "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);
@@ -475,32 +471,38 @@
 rwOperation["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}";
 IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_4_unicode);
 } 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["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}";
+IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_2_long_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);
+rwOperation["name"] = "long_strings";
+rwOperation["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"s:symbol\"]}}";
+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_4_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);
+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);
+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);}
 }
 public:
@@ -541,21 +543,21 @@
 } catch (std::exception& e) {std::cerr << "Error loading empty_strings data: " << e.what() << '\n';}
 }
 signalHandler->setMsg(R"_(empty_strings("","",42).
-in file /home/luc/personal/souffle-haskell/tests/fixtures/edge_cases.dl [20:1-20:27])_");
+in file 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->setMsg(R"_(empty_strings("","abc",42).
-in file /home/luc/personal/souffle-haskell/tests/fixtures/edge_cases.dl [21:1-21:30])_");
+in file 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->setMsg(R"_(empty_strings("abc","",42).
-in file /home/luc/personal/souffle-haskell/tests/fixtures/edge_cases.dl [22:1-22:30])_");
+in file 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))}};
@@ -582,7 +584,7 @@
 } catch (std::exception& e) {std::cerr << "Error loading long_strings data: " << e.what() << '\n';}
 }
 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])_");
+in file 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))}};
@@ -609,14 +611,14 @@
 } catch (std::exception& e) {std::cerr << "Error loading no_strings data: " << e.what() << '\n';}
 }
 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])_");
+in file edge_cases.dl [33:1-33:27])_");
 [&](){
 CREATE_OP_CONTEXT(rel_3_no_strings_op_ctxt,rel_3_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));
 }
 ();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])_");
+in file edge_cases.dl [34:1-34:29])_");
 [&](){
 CREATE_OP_CONTEXT(rel_3_no_strings_op_ctxt,rel_3_no_strings->createContext());
 Tuple<RamDomain,3> tuple{{ramBitCast(RamUnsigned(123)),ramBitCast(RamSigned(-456)),ramBitCast(RamFloat(3.1400001))}};
@@ -643,14 +645,14 @@
 } catch (std::exception& e) {std::cerr << "Error loading unicode data: " << e.what() << '\n';}
 }
 signalHandler->setMsg(R"_(unicode("∀").
-in file /home/luc/personal/souffle-haskell/tests/fixtures/edge_cases.dl [30:1-30:16])_");
+in file edge_cases.dl [30:1-30:16])_");
 [&](){
 CREATE_OP_CONTEXT(rel_4_unicode_op_ctxt,rel_4_unicode->createContext());
 Tuple<RamDomain,1> tuple{{ramBitCast(RamSigned(3))}};
 rel_4_unicode->insert(tuple,READ_OP_CONTEXT(rel_4_unicode_op_ctxt));
 }
 ();signalHandler->setMsg(R"_(unicode("∀∀").
-in file /home/luc/personal/souffle-haskell/tests/fixtures/edge_cases.dl [31:1-31:19])_");
+in file edge_cases.dl [31:1-31:19])_");
 [&](){
 CREATE_OP_CONTEXT(rel_4_unicode_op_ctxt,rel_4_unicode->createContext());
 Tuple<RamDomain,1> tuple{{ramBitCast(RamSigned(4))}};
diff --git a/tests/fixtures/path.cpp b/tests/fixtures/path.cpp
--- a/tests/fixtures/path.cpp
+++ b/tests/fixtures/path.cpp
@@ -1,8 +1,10 @@
 
 #include "souffle/CompiledSouffle.h"
 
-extern "C" {
+namespace functors {
+ extern "C" {
 }
+}
 
 namespace souffle {
 static const RamDomain RAM_BIT_SHIFT_MASK = RAM_DOMAIN_SIZE - 1;
@@ -14,7 +16,7 @@
   return (ramBitCast<RamSigned>(a[0]) < ramBitCast<RamSigned>(b[0])) ? -1 : (ramBitCast<RamSigned>(a[0]) > ramBitCast<RamSigned>(b[0])) ? 1 :((ramBitCast<RamSigned>(a[1]) < ramBitCast<RamSigned>(b[1])) ? -1 : (ramBitCast<RamSigned>(a[1]) > ramBitCast<RamSigned>(b[1])) ? 1 :(0));
  }
 bool less(const t_tuple& a, const t_tuple& b) const {
-  return (ramBitCast<RamSigned>(a[0]) < ramBitCast<RamSigned>(b[0]))|| (ramBitCast<RamSigned>(a[0]) == ramBitCast<RamSigned>(b[0])) && ((ramBitCast<RamSigned>(a[1]) < ramBitCast<RamSigned>(b[1])));
+  return (ramBitCast<RamSigned>(a[0]) < ramBitCast<RamSigned>(b[0]))|| ((ramBitCast<RamSigned>(a[0]) == ramBitCast<RamSigned>(b[0])) && ((ramBitCast<RamSigned>(a[1]) < ramBitCast<RamSigned>(b[1]))));
  }
 bool equal(const t_tuple& a, const t_tuple& b) const {
 return (ramBitCast<RamSigned>(a[0]) == ramBitCast<RamSigned>(b[0]))&&(ramBitCast<RamSigned>(a[1]) == ramBitCast<RamSigned>(b[1]));
@@ -117,7 +119,7 @@
   return (ramBitCast<RamSigned>(a[0]) < ramBitCast<RamSigned>(b[0])) ? -1 : (ramBitCast<RamSigned>(a[0]) > ramBitCast<RamSigned>(b[0])) ? 1 :((ramBitCast<RamSigned>(a[1]) < ramBitCast<RamSigned>(b[1])) ? -1 : (ramBitCast<RamSigned>(a[1]) > ramBitCast<RamSigned>(b[1])) ? 1 :(0));
  }
 bool less(const t_tuple& a, const t_tuple& b) const {
-  return (ramBitCast<RamSigned>(a[0]) < ramBitCast<RamSigned>(b[0]))|| (ramBitCast<RamSigned>(a[0]) == ramBitCast<RamSigned>(b[0])) && ((ramBitCast<RamSigned>(a[1]) < ramBitCast<RamSigned>(b[1])));
+  return (ramBitCast<RamSigned>(a[0]) < ramBitCast<RamSigned>(b[0]))|| ((ramBitCast<RamSigned>(a[0]) == ramBitCast<RamSigned>(b[0])) && ((ramBitCast<RamSigned>(a[1]) < ramBitCast<RamSigned>(b[1]))));
  }
 bool equal(const t_tuple& a, const t_tuple& b) const {
 return (ramBitCast<RamSigned>(a[0]) == ramBitCast<RamSigned>(b[0]))&&(ramBitCast<RamSigned>(a[1]) == ramBitCast<RamSigned>(b[1]));
@@ -236,7 +238,7 @@
 }
 public:
 // -- initialize symbol table --
-SymbolTable symTable{
+SymbolTableImpl symTable{
 	R"_(a)_",
 	R"_(b)_",
 	R"_(c)_",
@@ -282,7 +284,7 @@
     // 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 (0 < getNumThreads()) { omp_set_num_threads(getNumThreads()); }
+    if (0 < getNumThreads()) { omp_set_num_threads(static_cast<int>(getNumThreads())); }
 #endif
 
     signalHandler->set();
@@ -378,14 +380,14 @@
 } catch (std::exception& e) {std::cerr << "Error loading edge data: " << e.what() << '\n';}
 }
 signalHandler->setMsg(R"_(edge("a","b").
-in file /home/luc/personal/souffle-haskell/tests/fixtures/path.dl [11:1-11:16])_");
+in file path.dl [11:1-11:16])_");
 [&](){
 CREATE_OP_CONTEXT(rel_1_edge_op_ctxt,rel_1_edge->createContext());
 Tuple<RamDomain,2> tuple{{ramBitCast(RamSigned(0)),ramBitCast(RamSigned(1))}};
 rel_1_edge->insert(tuple,READ_OP_CONTEXT(rel_1_edge_op_ctxt));
 }
 ();signalHandler->setMsg(R"_(edge("b","c").
-in file /home/luc/personal/souffle-haskell/tests/fixtures/path.dl [12:1-12:16])_");
+in file path.dl [12:1-12:16])_");
 [&](){
 CREATE_OP_CONTEXT(rel_1_edge_op_ctxt,rel_1_edge->createContext());
 Tuple<RamDomain,2> tuple{{ramBitCast(RamSigned(1)),ramBitCast(RamSigned(2))}};
@@ -407,11 +409,11 @@
 void subroutine_1(const std::vector<RamDomain>& args, std::vector<RamDomain>& ret) {
 signalHandler->setMsg(R"_(reachable(x,y) :- 
    edge(x,y).
-in file /home/luc/personal/souffle-haskell/tests/fixtures/path.dl [14:1-14:31])_");
+in file path.dl [14:1-14:31])_");
 if(!(rel_1_edge->empty())) {
 [&](){
-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_2_reachable_op_ctxt,rel_2_reachable->createContext());
 for(const auto& env0 : *rel_1_edge) {
 Tuple<RamDomain,2> tuple{{ramBitCast(env0[0]),ramBitCast(env0[1])}};
 rel_2_reachable->insert(tuple,READ_OP_CONTEXT(rel_2_reachable_op_ctxt));
@@ -431,12 +433,12 @@
 signalHandler->setMsg(R"_(reachable(x,z) :- 
    edge(x,y),
    reachable(y,z).
-in file /home/luc/personal/souffle-haskell/tests/fixtures/path.dl [15:1-15:48])_");
+in file path.dl [15:1-15:48])_");
 if(!(rel_1_edge->empty()) && !(rel_3_delta_reachable->empty())) {
 [&](){
-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());
 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());
+CREATE_OP_CONTEXT(rel_2_reachable_op_ctxt,rel_2_reachable->createContext());
 CREATE_OP_CONTEXT(rel_3_delta_reachable_op_ctxt,rel_3_delta_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));
@@ -451,8 +453,8 @@
 ();}
 if(rel_4_new_reachable->empty()) break;
 [&](){
-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());
+CREATE_OP_CONTEXT(rel_2_reachable_op_ctxt,rel_2_reachable->createContext());
 for(const auto& env0 : *rel_4_new_reachable) {
 Tuple<RamDomain,2> tuple{{ramBitCast(env0[0]),ramBitCast(env0[1])}};
 rel_2_reachable->insert(tuple,READ_OP_CONTEXT(rel_2_reachable_op_ctxt));
diff --git a/tests/fixtures/round_trip.cpp b/tests/fixtures/round_trip.cpp
--- a/tests/fixtures/round_trip.cpp
+++ b/tests/fixtures/round_trip.cpp
@@ -1,8 +1,10 @@
 
 #include "souffle/CompiledSouffle.h"
 
-extern "C" {
+namespace functors {
+ extern "C" {
 }
+}
 
 namespace souffle {
 static const RamDomain RAM_BIT_SHIFT_MASK = RAM_DOMAIN_SIZE - 1;
@@ -117,7 +119,7 @@
   return (ramBitCast<RamSigned>(a[0]) < ramBitCast<RamSigned>(b[0])) ? -1 : (ramBitCast<RamSigned>(a[0]) > ramBitCast<RamSigned>(b[0])) ? 1 :((ramBitCast<RamSigned>(a[1]) < ramBitCast<RamSigned>(b[1])) ? -1 : (ramBitCast<RamSigned>(a[1]) > ramBitCast<RamSigned>(b[1])) ? 1 :((ramBitCast<RamSigned>(a[2]) < ramBitCast<RamSigned>(b[2])) ? -1 : (ramBitCast<RamSigned>(a[2]) > ramBitCast<RamSigned>(b[2])) ? 1 :((ramBitCast<RamSigned>(a[3]) < ramBitCast<RamSigned>(b[3])) ? -1 : (ramBitCast<RamSigned>(a[3]) > ramBitCast<RamSigned>(b[3])) ? 1 :(0))));
  }
 bool less(const t_tuple& a, const t_tuple& b) const {
-  return (ramBitCast<RamSigned>(a[0]) < ramBitCast<RamSigned>(b[0]))|| (ramBitCast<RamSigned>(a[0]) == ramBitCast<RamSigned>(b[0])) && ((ramBitCast<RamSigned>(a[1]) < ramBitCast<RamSigned>(b[1]))|| (ramBitCast<RamSigned>(a[1]) == ramBitCast<RamSigned>(b[1])) && ((ramBitCast<RamSigned>(a[2]) < ramBitCast<RamSigned>(b[2]))|| (ramBitCast<RamSigned>(a[2]) == ramBitCast<RamSigned>(b[2])) && ((ramBitCast<RamSigned>(a[3]) < ramBitCast<RamSigned>(b[3])))));
+  return (ramBitCast<RamSigned>(a[0]) < ramBitCast<RamSigned>(b[0]))|| ((ramBitCast<RamSigned>(a[0]) == ramBitCast<RamSigned>(b[0])) && ((ramBitCast<RamSigned>(a[1]) < ramBitCast<RamSigned>(b[1]))|| ((ramBitCast<RamSigned>(a[1]) == ramBitCast<RamSigned>(b[1])) && ((ramBitCast<RamSigned>(a[2]) < ramBitCast<RamSigned>(b[2]))|| ((ramBitCast<RamSigned>(a[2]) == ramBitCast<RamSigned>(b[2])) && ((ramBitCast<RamSigned>(a[3]) < ramBitCast<RamSigned>(b[3]))))))));
  }
 bool equal(const t_tuple& a, const t_tuple& b) const {
 return (ramBitCast<RamSigned>(a[0]) == ramBitCast<RamSigned>(b[0]))&&(ramBitCast<RamSigned>(a[1]) == ramBitCast<RamSigned>(b[1]))&&(ramBitCast<RamSigned>(a[2]) == ramBitCast<RamSigned>(b[2]))&&(ramBitCast<RamSigned>(a[3]) == ramBitCast<RamSigned>(b[3]));
@@ -430,7 +432,7 @@
 }
 public:
 // -- initialize symbol table --
-SymbolTable symTable;// -- initialize record table --
+SymbolTableImpl symTable;// -- initialize record table --
 SpecializedRecordTable<0> recordTable{};
 // -- Table: float_fact
 Own<t_btree_f__0__1> rel_1_float_fact = mk<t_btree_f__0__1>();
@@ -483,7 +485,7 @@
     // 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 (0 < getNumThreads()) { omp_set_num_threads(getNumThreads()); }
+    if (0 < getNumThreads()) { omp_set_num_threads(static_cast<int>(getNumThreads())); }
 #endif
 
     signalHandler->set();
@@ -519,10 +521,6 @@
 }
 public:
 void printAll(std::string outputDirectoryArg = "") override {
-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);
-} catch (std::exception& e) {std::cerr << e.what();exit(1);}
 try {std::map<std::string, std::string> directiveMap({{"IO","file"},{"attributeNames","a\tb\tc\td"},{"auxArity","0"},{"name","large_record"},{"operation","output"},{"output-dir","."},{"params","{\"records\": {}, \"relation\": {\"arity\": 4, \"params\": [\"a\", \"b\", \"c\", \"d\"]}}"},{"types","{\"ADTs\": {}, \"records\": {}, \"relation\": {\"arity\": 4, \"types\": [\"i:number\", \"i:number\", \"i:number\", \"i:number\"]}}"}});
 if (!outputDirectoryArg.empty()) {directiveMap["output-dir"] = outputDirectoryArg;}
 IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_2_large_record);
@@ -539,6 +537,10 @@
 if (!outputDirectoryArg.empty()) {directiveMap["output-dir"] = outputDirectoryArg;}
 IOSystem::getInstance().getWriter(directiveMap, symTable, recordTable)->writeAll(*rel_5_unsigned_fact);
 } catch (std::exception& e) {std::cerr << e.what();exit(1);}
+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);
+} catch (std::exception& e) {std::cerr << e.what();exit(1);}
 }
 public:
 void loadAll(std::string inputDirectoryArg = "") override {
@@ -600,12 +602,6 @@
 void dumpOutputs() 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);
-} catch (std::exception& e) {std::cerr << e.what();exit(1);}
-try {std::map<std::string, std::string> rwOperation;
-rwOperation["IO"] = "stdout";
 rwOperation["name"] = "large_record";
 rwOperation["types"] = "{\"relation\": {\"arity\": 4, \"auxArity\": 0, \"types\": [\"i:number\", \"i:number\", \"i:number\", \"i:number\"]}}";
 IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_2_large_record);
@@ -627,6 +623,12 @@
 rwOperation["name"] = "unsigned_fact";
 rwOperation["types"] = "{\"relation\": {\"arity\": 1, \"auxArity\": 0, \"types\": [\"u:unsigned\"]}}";
 IOSystem::getInstance().getWriter(rwOperation, symTable, recordTable)->writeAll(*rel_5_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);
 } catch (std::exception& e) {std::cerr << e.what();exit(1);}
 }
 public:
