https://gcc.gnu.org/bugzilla/show_bug.cgi?id=112307
Bug ID: 112307 Summary: Segmentation fault with -O1 -fcode-hoisting Product: gcc Version: 14.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: raffael at casagrande dot ch Target Milestone: --- *Affected Versions*: 10.0 - current (14.0.0 20231031). Earlier versions cannot compile the code because it uses c++20. *System*: Linux *Command Line*: g++ main.cc -std=c++20 -O1 -fcode-hoisting *Compiler Output*: <source>:48:87: warning: friend declaration 'bool operator==(const EnumeratorRange<ENUMERATOR>::Sentinel&, const EnumeratorRange<ENUMERATOR>::Iterator&)' declares a non-template function [-Wnon-template-friend] 48 | friend auto operator==(const Sentinel& /*unused*/, const Iterator& i) noexcept -> bool; | ^~~~ <source>:48:87: note: (if this is not what you intended, make sure the function template has already been declared and add '<>' after the function name here) *Runtime Output*: Segmentation fault, "boundary" is printed many times. *Source File*: #include <ranges> #include <iostream> #include <optional> #include <cassert> template <class ENUMERATOR> class EnumeratorRange { public: struct Sentinel { Sentinel() noexcept = default; Sentinel(const Sentinel&) noexcept = default; Sentinel(Sentinel&&) noexcept = default; auto operator=(const Sentinel&) noexcept -> Sentinel& = default; auto operator=(Sentinel&&) noexcept -> Sentinel& = default; ~Sentinel() noexcept = default; }; class Iterator { public: using value_type = typename ENUMERATOR::value_type; using difference_type = std::ptrdiff_t; explicit Iterator(EnumeratorRange* range) : range_(range) {} Iterator() noexcept = default; Iterator(const Iterator&) = delete; Iterator(Iterator&&) noexcept = default; ~Iterator() noexcept = default; auto operator=(const Iterator&) = delete; auto operator=(Iterator&&) noexcept -> Iterator& = default; auto operator*() const noexcept { assert(!range_->end_reached_); return *range_->enumerator_; } auto operator++() noexcept -> Iterator& { assert(!range_->end_reached_); range_->end_reached_ = !range_->enumerator_.Next(); return *this; } auto operator++(int) noexcept -> void { ++*this; } private: EnumeratorRange* range_; friend auto operator==(const Sentinel& /*unused*/, const Iterator& i) noexcept -> bool; }; explicit EnumeratorRange(ENUMERATOR&& e) : enumerator_(std::move(e)), end_reached_(!enumerator_.Next()) {} auto begin() const noexcept { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) return Iterator(const_cast<EnumeratorRange*>(this)); } auto end() const noexcept { return Sentinel(); } private: ENUMERATOR enumerator_; bool end_reached_; friend auto operator==(const Sentinel&, const Iterator& i) noexcept -> bool { return i.range_->end_reached_; } friend auto operator==(const Iterator& i, const Sentinel& s) noexcept -> bool { return s == i; } friend auto operator!=(const Sentinel& s, const Iterator& i) noexcept -> bool { return !(s == i); } friend auto operator!=(const Iterator& i, const Sentinel& s) noexcept -> bool { return !(s == i); } }; class Intersection { public: auto Boundary() const noexcept -> bool { return is_boundary_; } private: bool is_boundary_ = true; }; class CompositeMesh { public: auto Intersections() const noexcept -> std::ranges::input_range auto; }; auto CompositeMesh::Intersections() const noexcept -> std::ranges::input_range auto { class Enumerator { public: using wrapped_range_t = decltype(std::views::single(Intersection())); explicit Enumerator(wrapped_range_t&& range) : range_(std::move(range)), begin_{} {} using value_type = Intersection; auto operator*() const noexcept -> value_type { return *begin_.value(); } auto Next() noexcept -> bool { if (!begin_.has_value()) { auto b = range_.begin(); bool result = (b != range_.end()); begin_ = std::move(b); return result; } else { auto& b = *begin_; if ((*b).Boundary()) { std::cout << "boundary" << std::endl; } ++b; return b != range_.end(); } } private: wrapped_range_t range_; std::optional<std::ranges::iterator_t<wrapped_range_t>> begin_; }; return EnumeratorRange(Enumerator(std::views::single(Intersection()))); } int main() { auto mesh = CompositeMesh(); decltype(auto) intersections = mesh.Intersections(); for (auto intersection : intersections) { } } *Additional Notes* - It works if we use `-O2` (which includes -fcode-hoisting) - Godbolt: https://godbolt.org/z/1PqTKz33Y - One can disable the following compiler options from `-O1` and still reproduce the bug: -fno-auto-inc-dec -fno-branch-count-reg -fno-combine-stack-adjustments -fno-compare-elim -fno-cprop-registers -fno-dce -fno-defer-pop -fno-delayed-branch -fno-dse -fno-forward-propagate -fno-if-conversion -fno-if-conversion2 -fno-inline-functions-called-once -fno-ipa-modref -fno-ipa-profile -fno-ipa-pure-const -fno-ipa-reference -fno-ipa-reference-addressable -fno-merge-constants -fno-move-loop-invariants -fno-move-loop-stores -fno-omit-frame-pointer -fno-reorder-blocks -fno-shrink-wrap -fno-shrink-wrap-separate -fno-split-wide-types -fno-ssa-backprop -fno-ssa-phiopt -fno-tree-bit-ccp -fno-tree-ccp -fno-tree-ch -fno-tree-coalesce-vars -fno-tree-copy-prop -fno-tree-dce -fno-tree-dominator-opts -fno-tree-phiprop -fno-tree-scev-cprop -fno-tree-sink -fno-tree-slsr -fno-tree-sra -fno-tree-ter -fno-unit-at-a-time