Index: llvm/include/llvm/ADT/MutableRange.h =================================================================== --- /dev/null +++ llvm/include/llvm/ADT/MutableRange.h @@ -0,0 +1,302 @@ +//===-- MutableRange.h ------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements the MutableRange class template which wraps +// over a const container making it effectively mutable. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_ADT_MUTABLERANGE_H +#define LLVM_ADT_MUTABLERANGE_H + +#include "iterator.h" +#include +#include +#include +#include +#include +#include + +namespace llvm { + +template +class is_associative : public std::false_type {}; +template +class is_associative< + T, typename std::enable_if::type> + : public std::true_type {}; + +template class is_map : public std::false_type {}; +template +class is_map< + T, typename std::enable_if::type> + : public std::true_type {}; + +template +class is_unordered : public std::false_type {}; +template +class is_unordered< + T, typename std::enable_if::type> + : public std::true_type {}; + +template struct get_hasher { using type = void; }; +template +struct get_hasher::value>::type> { + using type = typename T::hasher; +}; + +template struct get_mapped_type { + using type = void; +}; +template +struct get_mapped_type::value>::type> { + using type = typename T::mapped_type; +}; + +template static constexpr Value extract_key(Value Val) { + return Val; +} +template +static constexpr First extract_key(std::pair Pair) { + return Pair.first; +} + +template class MutableRange; + +/// Creates a wrapper over a const container effectively making it mutable +/// without modifying the underlying container. +/// +/// Specialization for non associative containers. +/// +/// @tparam T underlying container type. +template +class MutableRange::value>::type> { + using SizeType = size_t; + using Iter = typename T::const_iterator; + using ValueType = typename T::value_type; + using MutableT = typename std::remove_const::type; + + static_assert( + std::is_base_of::value, + "T::iterator does not meet the requirments of a forward iterator"); + + /// Describes where to find the underlying object that is pointed to + /// by Index. + struct MappingType { + enum Operation { + Original = 0, /// Index represents offset from First. + New /// Index represents index into Modified. + /// Is either an addition or replacement. + }; + Operation Type : 1; + SizeType Index : sizeof(void *) * 8 - 1; + + MappingType(const MappingType &) = default; + MappingType(Operation Type, SizeType Index) : Type(Type), Index(Index) {} + + operator SizeType() const { return Index; } + }; + static_assert(sizeof(MappingType) == sizeof(void *), + "MappingType is too large"); + + const Iter UnderlyingBegin; + const Iter UnderlyingEnd; + + std::vector Mappings; + std::vector Modified; + + const ValueType &getValueFromMapping(MappingType Mapping) const { + if (Mapping.Type == MappingType::Original) { + auto Begin = UnderlyingBegin; + std::advance(Begin, Mapping); + return *Begin; + } + + return Modified[Mapping]; + } + + // Iter getFirst() const { return UnderlyingBegin; } + + ValueType &insert(SizeType Index) { + auto Begin = Mappings.begin(); + std::advance(Begin, Index); + Mappings.insert(Begin, MappingType(MappingType::New, Modified.size() - 1)); + return Modified.back(); + } + +public: + MutableRange(const MutableRange &) = default; + MutableRange(MutableRange &&) = default; + explicit MutableRange(const T &Container) + : MutableRange(Container.begin(), Container.end()) {} + MutableRange(Iter Begin, Iter End) + : UnderlyingBegin(Begin), UnderlyingEnd(End) { + SizeType Length = std::distance(Begin, End); + Mappings.reserve(Length); + size_t Count = 0; + auto MapBegin = Mappings.begin(); + std::transform(MapBegin, MapBegin + Length, std::back_inserter(Mappings), + [&Count](const MappingType &) { + return MappingType(MappingType::Original, Count++); + }); + } + + /// Returns an immutable reference to the object at Index. + const ValueType &operator[](SizeType Index) const { + return getValueFromMapping(Mappings[Index]); + } + + /// Emplaces a T::value_type at a specified index. + template ValueType &emplace(SizeType Index, Ts &&... Args) { + Modified.emplace_back(ValueType(std::forward(Args)...)); + return insert(Index); + } + + /// Inserts a T::value_type a specified index. + ValueType &insert(SizeType Index, ValueType Value) { + Modified.push_back(Value); + return insert(Index); + } + + /// Appends a new element to the back. + template ValueType &emplace_back(Ts &&... Args) { + return insert(Mappings.size(), std::forward(Args)...); + } + + /// Removes an element at the specified Index. + void erase(SizeType Index) { + assert(Index < Mappings.size() && "Index larger than number of elements"); + Mappings.erase(Mappings.begin() + Index); + } + + /// Returns a mutable reference to the index at Index. + ValueType &makeMutable(SizeType Index) { + assert(Index < Mappings.size() && "Index larger than number of elements"); + if (Mappings[Index].Type == MappingType::New) + return Modified[Mappings[Index]]; + + auto ToCopy = UnderlyingBegin; + std::advance(ToCopy, Mappings[Index]); + Modified.emplace_back(ValueType(*ToCopy)); + Mappings[Index] = MappingType(MappingType::New, Modified.size() - 1); + return Modified.back(); + } + + /// Returns the MutableRange as the same type as the original collection. + MutableT collect() const { + MutableT Container; + std::transform(Mappings.begin(), Mappings.end(), + std::back_inserter(Container), + [this](const MappingType &Mapping) { + return getValueFromMapping(Mapping); + }); + return Container; + } +}; + +template +class MutableRange::value>::type> { +public: + using value_type = typename T::value_type; + using key_type = typename T::key_type; + +private: + static constexpr bool IsMap = is_map::value; + + // If this is a set use the smallest available type for the internal map. + using MappedType = + typename std::conditional::type, + char>::type; + + using TIter = typename T::iterator; + using ValueType = std::pair; + + struct InternalRep { + enum { IterToOrig, OwnedValueType } Type; + + union { + TIter Iter; + MappedType Value; + }; + + explicit InternalRep(TIter Iter) : Type(IterToOrig), Iter(Iter) {} + explicit InternalRep(ValueType Value) + : Type(OwnedValueType), Value(Value) {} + // For set like T's where their mapped_type is irrelevent. + InternalRep() : Type(OwnedValueType), Value(0) {} + }; + + // It isn't always valid to use an unordered_map because there might not be a + // specialization of std::hash. If T is unordered than it is safe to use an + // unordered_map internally, use its hasher as it might be a user defined + // hasher and not a user specialization of std::hash. + using InternalMapType = typename std::conditional< + is_unordered::value, + std::unordered_map::type>, + std::map>::type; + + InternalMapType Map; + + template + class Iterator : public MapType::iterator { + using Base = typename MapType::iterator; + + public: + Iterator(const Base &B) : Base(B) {} + + template + const typename std::enable_if::type &operator*() const { + const auto &Pair = Base::operator*(); + if (Pair.second.Type == InternalRep::IterToOrig) + return *Pair.second.Iter; + assert(Pair.second.Type == InternalRep::OwnedValueType); + return std::make_pair(Pair.first, Pair.second.Value); + } + + template + const typename std::enable_if::type &operator*() const { + return Base::operator*().first; + } + }; + +public: + MutableRange(const MutableRange &) = default; + MutableRange(MutableRange &&) = default; + explicit MutableRange(const T &Container) + : MutableRange(Container.begin(), Container.end()) {} + MutableRange(TIter First, TIter Last) { + for (TIter I = First; I != Last; ++I) + Map.insert(std::make_pair(extract_key(*I), I)); + } + + using iterator = Iterator; + + iterator begin() { return Map.begin(); } + iterator end() { return Map.end(); } + + template + void insert(const value_type &Value, + typename std::enable_if::type = 0) { + Map.insert(std::make_pair(Value, InternalRep())); + } + +#if 0 + template + void insert(const value_type &Value, typename std::enable_if::type = 0) { + Map.insert(Value.first, InternalRep(Value)); + } +#endif + + iterator find(const key_type &Key) { return Map.find(Key); } +}; + +} // namespace llvm + +#endif // LLVM_ADT_MUTABLERANGE_H Index: llvm/unittests/ADT/CMakeLists.txt =================================================================== --- llvm/unittests/ADT/CMakeLists.txt +++ llvm/unittests/ADT/CMakeLists.txt @@ -39,6 +39,7 @@ MakeUniqueTest.cpp MappedIteratorTest.cpp MapVectorTest.cpp + MutableRangeTest.cpp OptionalTest.cpp PackedVectorTest.cpp PointerEmbeddedIntTest.cpp Index: llvm/unittests/ADT/MutableRangeTest.cpp =================================================================== --- /dev/null +++ llvm/unittests/ADT/MutableRangeTest.cpp @@ -0,0 +1,143 @@ +//===-- MutableRangeTest.cpp - MutableRange unit tests --------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/MutableRange.h" +#include "gtest/gtest.h" +#include +#include +#include + +// These could be static_assert, but it makes more sense to have the static +// assertions in this file than MutableRange.h, at that point it might as +// well be a TEST. +TEST(type_traits, is_associative) { + EXPECT_TRUE(llvm::is_associative>::value) + << "set is associative"; + EXPECT_FALSE(llvm::is_associative>::value) + << "vector not associative"; +} + +class MutableRangeTest : public ::testing::Test { + + void TearDown() override { + ASSERT_EQ(IntVec.size(), 3U) << "container was modified"; + ASSERT_EQ(IntVec[0], 1) << "container was modified"; + ASSERT_EQ(IntVec[1], 2) << "container was modified"; + ASSERT_EQ(IntVec[2], 3) << "container was modified"; + } + +public: + const std::vector IntVec {1, 2, 3}; + + const std::set IntSet {1, 2, 3}; + + llvm::MutableRange getIntRange() { + return llvm::MutableRange(IntVec); + } + + llvm::MutableRange getIntSetRange() { + return llvm::MutableRange(IntSet); + } +}; + +TEST_F(MutableRangeTest, Constructor) { + const auto Range = getIntRange(); + + EXPECT_EQ(Range[0], 1) << "doesn't correctly represent underlying container"; + EXPECT_EQ(Range[1], 2); + EXPECT_EQ(Range[2], 3); + + llvm::MutableRange ConsWithIter(IntVec.begin(), + IntVec.end()); + + EXPECT_EQ(ConsWithIter[0], 1) << "these constructors should be equivalent"; + EXPECT_EQ(ConsWithIter[1], 2); + EXPECT_EQ(ConsWithIter[2], 3); +} + +TEST_F(MutableRangeTest, Insert) { + auto Range = getIntRange(); + + EXPECT_EQ(Range.insert(1, 9), 9) + << "didn't correctly return reference to inserted"; + + EXPECT_EQ(Range[0], 1); + EXPECT_EQ(Range[1], 9) << "inserted in wrong area"; + EXPECT_EQ(Range[2], 2); + EXPECT_EQ(Range[3], 3); + + EXPECT_EQ(Range.emplace_back(4), 4) + << "didn't correctly return reference to appened element"; + + EXPECT_EQ(Range[0], 1); + EXPECT_EQ(Range[1], 9); + EXPECT_EQ(Range[2], 2); + EXPECT_EQ(Range[3], 3); + EXPECT_EQ(Range[4], 4) << "emplace_back didn't push to back"; +} + +TEST_F(MutableRangeTest, Erase) { + auto Range = getIntRange(); + + Range.erase(0); + + EXPECT_EQ(Range[0], 2); + EXPECT_EQ(Range[1], 3) << "erase didn't correctly remove the element"; +} + +TEST_F(MutableRangeTest, MakeMutable) { + auto Range = getIntRange(); + + Range.makeMutable(0) = 10; + + EXPECT_EQ(Range[0], 10) << "makeMutable didn't correctly update"; + EXPECT_EQ(Range[1], 2); + EXPECT_EQ(Range[2], 3); +} + +TEST_F(MutableRangeTest, Collect) { + auto Range = getIntRange(); + + Range.emplace_back(7); + + std::vector NewVec = Range.collect(); + + EXPECT_EQ(NewVec.size(), 4U); + EXPECT_EQ(NewVec[0], 1); + EXPECT_EQ(NewVec[1], 2); + EXPECT_EQ(NewVec[2], 3); + EXPECT_EQ(NewVec[3], 7); +} + +TEST_F(MutableRangeTest, Set) { + auto Range = getIntSetRange(); + + EXPECT_EQ(std::distance(Range.begin(), Range.end()), 3U); + + Range.insert(5); + + EXPECT_EQ(std::distance(Range.begin(), Range.end()), 4U); + + auto Five = Range.begin(); std::advance(Five, 3); + EXPECT_EQ(*Five, 5); + + auto FoundFive = Range.find(5); + EXPECT_EQ(Five, FoundFive); +} + +// Not currently working with map like containers. +#if 0 +TEST_F(MutableRangeTest, Map) { + std::map Map {{1, 2}, {2, 3}, {3, 4}}; + llvm::MutableRange Range(Map); + + auto Found = Range.find(2); + EXPECT_EQ(Found->first, 2); + EXPECT_EQ(Found->second, 3); +} +#endif