Skip to content

Commit 75eb8c7

Browse files
committed
[ADT] Make Zippy more iterator-like for lifetime safety
@geoffromer identifier/encountered a lifetime issue when using concat+zip, zip would return by value, concat would take references to that value and use them in its result after they had expired. This is a common problem with range adapters and the lifetime of values. But it's also non-conforming with the C++ iterator requirements, I think - partly because op-> should be supported (which I haven't done here) and that basically has to return by pointer. So the best thing is to stash a value in the iterator and return a pointer/reference to that.
1 parent 23da169 commit 75eb8c7

File tree

3 files changed

+26
-20
lines changed

3 files changed

+26
-20
lines changed

llvm/include/llvm/ADT/STLExtras.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -704,10 +704,12 @@ struct zip_common : public zip_traits<ZipType, ReferenceTupleType, Iters...> {
704704
using value_type = typename Base::value_type;
705705

706706
std::tuple<Iters...> iterators;
707+
mutable std::optional<value_type> value;
707708

708709
protected:
709-
template <size_t... Ns> value_type deref(std::index_sequence<Ns...>) const {
710-
return value_type(*std::get<Ns>(iterators)...);
710+
template <size_t... Ns> const value_type &deref(std::index_sequence<Ns...>) const {
711+
value.emplace(*std::get<Ns>(iterators)...);
712+
return *value;
711713
}
712714

713715
template <size_t... Ns> void tup_inc(std::index_sequence<Ns...>) {
@@ -728,7 +730,7 @@ struct zip_common : public zip_traits<ZipType, ReferenceTupleType, Iters...> {
728730
public:
729731
zip_common(Iters &&... ts) : iterators(std::forward<Iters>(ts)...) {}
730732

731-
value_type operator*() const { return deref(IndexSequence{}); }
733+
const value_type &operator*() const { return deref(IndexSequence{}); }
732734

733735
ZipType &operator++() {
734736
tup_inc(IndexSequence{});

llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9991,7 +9991,7 @@ class BoUpSLP::ShuffleCostEstimator : public BaseShuffleAnalysis {
99919991
if (!E->ReorderIndices.empty() && CommonVF == E->ReorderIndices.size() &&
99929992
CommonVF == CommonMask.size() &&
99939993
any_of(enumerate(CommonMask),
9994-
[](const auto &&P) {
9994+
[](const auto &P) {
99959995
return P.value() != PoisonMaskElem &&
99969996
static_cast<unsigned>(P.value()) != P.index();
99979997
}) &&

llvm/unittests/ADT/IteratorTest.cpp

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -482,39 +482,26 @@ TEST(ZipIteratorTest, ZipEqualConstCorrectness) {
482482
EXPECT_THAT(first, ElementsAre(0, 0, 0));
483483
EXPECT_THAT(second, ElementsAre(true, true, true));
484484

485-
std::vector<bool> nemesis = {true, false, true};
486-
const std::vector<bool> c_nemesis = nemesis;
487-
488-
for (auto &&[a, b, c, d] : zip_equal(first, c_first, nemesis, c_nemesis)) {
485+
for (auto &&[a, b] : zip_equal(first, c_first)) {
489486
a = 2;
490-
c = true;
491487
static_assert(!IsConstRef<decltype(a)>);
492488
static_assert(IsConstRef<decltype(b)>);
493-
static_assert(!IsBoolConstRef<decltype(c)>);
494-
static_assert(IsBoolConstRef<decltype(d)>);
495489
}
496490

497491
EXPECT_THAT(first, ElementsAre(2, 2, 2));
498-
EXPECT_THAT(nemesis, ElementsAre(true, true, true));
499492

500493
unsigned iters = 0;
501-
for (const auto &[a, b, c, d] :
502-
zip_equal(first, c_first, nemesis, c_nemesis)) {
494+
for (const auto &[a, b] : zip_equal(first, c_first)) {
503495
static_assert(!IsConstRef<decltype(a)>);
504496
static_assert(IsConstRef<decltype(b)>);
505-
static_assert(!IsBoolConstRef<decltype(c)>);
506-
static_assert(IsBoolConstRef<decltype(d)>);
507497
++iters;
508498
}
509499
EXPECT_EQ(iters, 3u);
510500
iters = 0;
511501

512-
for (const auto &[a, b, c, d] :
513-
MakeConst(zip_equal(first, c_first, nemesis, c_nemesis))) {
502+
for (const auto &[a, b] : MakeConst(zip_equal(first, c_first))) {
514503
static_assert(!IsConstRef<decltype(a)>);
515504
static_assert(IsConstRef<decltype(b)>);
516-
static_assert(!IsBoolConstRef<decltype(c)>);
517-
static_assert(IsBoolConstRef<decltype(d)>);
518505
++iters;
519506
}
520507
EXPECT_EQ(iters, 3u);
@@ -643,6 +630,23 @@ TEST(ZipIteratorTest, Mutability) {
643630
}
644631
}
645632

633+
TEST(ZipIteratorTest, Lifetime) {
634+
SmallVector<unsigned> v1 = {1, 2, 3, 4};
635+
SmallVector<unsigned> v2 = {5, 6, 7, 8};
636+
637+
auto zipper = zip(v1, v2);
638+
639+
auto zip_iter = zipper.begin();
640+
EXPECT_NE(zip_iter, zipper.end());
641+
642+
auto *elem = &*zip_iter;
643+
EXPECT_EQ(std::get<0>(*elem), 1u);
644+
EXPECT_EQ(std::get<1>(*elem), 5u);
645+
646+
std::get<0>(*elem) = 42;
647+
EXPECT_EQ(v1[0], 42u);
648+
}
649+
646650
TEST(ZipIteratorTest, ZipFirstMutability) {
647651
using namespace std;
648652
vector<unsigned> pi{3, 1, 4, 1, 5, 9};

0 commit comments

Comments
 (0)