Bug 107579 - [11 Regression] ICE on fold-expression on .* member access operator since r12-2862-g9707d2e5dbb92d2b
Summary: [11 Regression] ICE on fold-expression on .* member access operator since r12...
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 12.2.0
: P3 normal
Target Milestone: 11.4
Assignee: Patrick Palka
URL:
Keywords: ice-on-valid-code
Depends on:
Blocks:
 
Reported: 2022-11-08 19:52 UTC by Jean-Michaël Celerier
Modified: 2023-05-22 03:15 UTC (History)
3 users (show)

See Also:
Host:
Target:
Build:
Known to work: 11.2.0
Known to fail:
Last reconfirmed: 2022-11-21 00:00:00


Attachments
bug report provided by -fbug-report (976.52 KB, application/octet-stream)
2022-11-08 19:58 UTC, Jean-Michaël Celerier
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Jean-Michaël Celerier 2022-11-08 19:52:04 UTC
The function that is causing the ICE:

```
constexpr int index_in_struct(const auto& s, auto... member)
{
  int index = -1;
  int k = 0;

  avnd::for_each_field_ref(s, [&](auto& m) {
    if constexpr(requires { bool(&m == &(s.*....*member)); })
    {
      if(&m == &(s.*....*member))
      {
        index = k;
      }
    }
    ++k;
  });
  assert(index >= 0);
  return index;
}
```

The error:

/home/jcelerier/score/3rdparty/avendish/include/avnd/common/for_nth.hpp:114:26: internal compiler error: trying to capture 'member#0' in instantiation of generic lambda
  114 |       if(&m == &(s.*....*member))
      |                          ^~~~~~
0x19eab38 internal_error(char const*, ...)
	???:0
0x71c547 add_capture(tree_node*, tree_node*, tree_node*, bool, bool)
	???:0
0x71c5b3 add_default_capture(tree_node*, tree_node*, tree_node*)
	???:0
0x7e232a tsubst_copy_and_build(tree_node*, tree_node*, int, tree_node*, bool, bool)
	???:0
0x7e0baa tsubst_copy_and_build(tree_node*, tree_node*, int, tree_node*, bool, bool)
	???:0
0x7e8d8c tsubst_pack_expansion(tree_node*, tree_node*, int, tree_node*)
	???:0
0x7dff89 tsubst_copy_and_build(tree_node*, tree_node*, int, tree_node*, bool, bool)
	???:0
0x7e0d41 tsubst_copy_and_build(tree_node*, tree_node*, int, tree_node*, bool, bool)
	???:0
0x7dfdeb tsubst_copy_and_build(tree_node*, tree_node*, int, tree_node*, bool, bool)
	???:0
0x7db043 instantiate_decl(tree_node*, bool, bool)
	???:0
0x6edb1b mark_used(tree_node*, int)
	???:0
0x68b85f build_op_call(tree_node*, vec<tree_node*, va_gc, vl_embed>**, int)
	???:0
0x810f2d finish_call_expr(tree_node*, vec<tree_node*, va_gc, vl_embed>**, bool, bool, int)
	???:0
0x7e1f0c tsubst_copy_and_build(tree_node*, tree_node*, int, tree_node*, bool, bool)
	???:0
0x7e8d8c tsubst_pack_expansion(tree_node*, tree_node*, int, tree_node*)
	???:0
0x7dff89 tsubst_copy_and_build(tree_node*, tree_node*, int, tree_node*, bool, bool)
	???:0
0x7db043 instantiate_decl(tree_node*, bool, bool)
	???:0
0x6edb1b mark_used(tree_node*, int)
	???:0
0x68b85f build_op_call(tree_node*, vec<tree_node*, va_gc, vl_embed>**, int)
	???:0
0x810f2d finish_call_expr(tree_node*, vec<tree_node*, va_gc, vl_embed>**, bool, bool, int)
	???:0
Comment 1 Jean-Michaël Celerier 2022-11-08 19:58:51 UTC
Created attachment 53851 [details]
bug report provided by -fbug-report
Comment 2 Jean-Michaël Celerier 2022-11-08 20:00:09 UTC
Clang and MSVC compiles the same thing correctly
Comment 3 Jean-Michaël Celerier 2022-11-08 20:24:02 UTC
It worked in 11.2 and started failing in 11.3. 

Short repro: https://gcc.godbolt.org/z/GYYbrTdxh 


#include <boost/pfr.hpp>

template <class T, class F>
constexpr void for_each_field(T&& value, F&& func)
{
  using namespace boost::pfr;
  using namespace boost::pfr::detail;
  constexpr std::size_t fields_count_val = fields_count<std::remove_reference_t<T>>();

  auto t = boost::pfr::detail::tie_as_tuple(value);

  [&]<std::size_t... I>(std::index_sequence<I...>)
  {
    (func(get<I>(t)), ...);
  }
  (std::make_index_sequence<fields_count_val>{});
}

constexpr int index_in_struct(const auto& s, auto... member)
{
  int index = -1;
  int k = 0;

  for_each_field(s, [&](auto& m) {
    if constexpr(requires { bool(&m == &(s.*....*member)); })
    {
      if(&m == &(s.*....*member))
      {
        index = k;
      }
    }
    ++k;
  });


  return index;
}

struct X
{
    int z;
};

int main()
{
    constexpr int r = index_in_struct(X{}, &X::z);
}
Comment 4 Jean-Michaël Celerier 2022-11-08 20:25:21 UTC
Adding an explicit "member..." to the lambda capture, e.g. [&, member...] makes it work
Comment 5 Martin Liška 2022-11-21 11:40:05 UTC
Reduced test-case:

template <typename _Tp, _Tp> struct integral_constant {};
template <typename _Tp, _Tp> struct integer_sequence {};
template <typename _Tp, _Tp _Num>
using make_integer_sequence = integer_sequence<_Tp, __integer_pack(_Num)...>;
template <long... _Idx>
using index_sequence = integer_sequence<unsigned long, _Idx...>;
template <long N>
using make_index_sequence = make_integer_sequence<unsigned long, N>;
namespace sequence_tuple {
struct tuple {};
template <int> auto get(tuple t) { return t; }
} // namespace sequence_tuple
template <long Index> using size_t_ = integral_constant<unsigned long, Index>;
template <class T> auto tie_as_tuple(T, size_t_<1>) {
  sequence_tuple::tuple __trans_tmp_1;
  return __trans_tmp_1;
}
template <class, unsigned long N>
constexpr auto detect_fields_count_dispatch(size_t_<N>) {
  return 1;
}
template <class> constexpr long fields_count() {
  long result = detect_fields_count_dispatch<int>(size_t_<0>{});
  return result;
}
template <class T> auto tie_as_tuple(T val) { return tie_as_tuple(val, {}); }
void get();
template <class T, class F> void for_each_field(T value, F func) {
  constexpr long fields_count_val = fields_count<T>();
  auto t = tie_as_tuple(value);
  [&]<unsigned long... I>(index_sequence<I...>) { (func(get<I>(t)), ...); }
  (make_index_sequence<fields_count_val>{});
}
void index_in_struct(auto s, auto... member) {
  for_each_field(s, [&](auto) {
    if constexpr (requires { (... * member); })
      (... * member);
  });
}
void z() { index_in_struct(int{}, z); }
Comment 6 Martin Liška 2022-11-21 11:41:39 UTC
Started with r12-2862-g9707d2e5dbb92d2b.
Comment 7 Patrick Palka 2022-12-09 21:00:57 UTC
This is essentially a dup of PR100295, except here the unevaluated context is a requires-expr instead of sizeof, and r12-2862 exposed this bug by making us correctly recognize a requires-expr as an unevaluated context during cp_walk_subtrees
Comment 8 GCC Commits 2022-12-15 21:03:04 UTC
The master branch has been updated by Patrick Palka <ppalka@gcc.gnu.org>:

https://gcc.gnu.org/g:18499b9f848707aee42d810e99ac0a4c9788433c

commit r13-4730-g18499b9f848707aee42d810e99ac0a4c9788433c
Author: Patrick Palka <ppalka@redhat.com>
Date:   Thu Dec 15 16:02:05 2022 -0500

    c++: extract_local_specs and unevaluated contexts [PR100295]
    
    Here during partial instantiation of the constexpr if, extra_local_specs
    walks the statement looking for local specializations within to capture.
    However, we're thwarted by the fact that 'ts' first appears inside an
    unevaluated context, and so the calls to process_outer_var_ref for its
    local specializations are a no-op.  And since we walk each tree exactly
    once, we end up not capturing the local specializations despite 'ts'
    later occurring in an evaluated context.
    
    This patch fixes this by making extract_local_specs walk evaluated
    contexts first before walking unevaluated contexts.  We could probably
    get away with not walking unevaluated contexts at all, but this approach
    seems more clearly safe.
    
            PR c++/100295
            PR c++/107579
    
    gcc/cp/ChangeLog:
    
            * pt.cc (el_data::skip_unevaluated_operands): New data member.
            (extract_locals_r): If skip_unevaluated_operands is true,
            don't walk into unevaluated contexts.
            (extract_local_specs): Walk the pattern twice, first with
            skip_unevaluated_operands true followed by it set to false.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/cpp1z/constexpr-if-lambda5.C: New test.
Comment 9 Patrick Palka 2022-12-15 21:03:47 UTC
Fixed on trunk so far
Comment 10 GCC Commits 2022-12-19 16:54:51 UTC
The releases/gcc-12 branch has been updated by Patrick Palka <ppalka@gcc.gnu.org>:

https://gcc.gnu.org/g:cdc1a14be1182874ccf1ceb27ee5b67c5ce8c62d

commit r12-8998-gcdc1a14be1182874ccf1ceb27ee5b67c5ce8c62d
Author: Patrick Palka <ppalka@redhat.com>
Date:   Thu Dec 15 16:02:05 2022 -0500

    c++: extract_local_specs and unevaluated contexts [PR100295]
    
    Here during partial instantiation of the constexpr if, extra_local_specs
    walks the statement looking for local specializations within to capture.
    However, we're thwarted by the fact that 'ts' first appears inside an
    unevaluated context, and so the calls to process_outer_var_ref for its
    local specializations are a no-op.  And since we walk each tree exactly
    once, we end up not capturing the local specializations despite 'ts'
    later occurring in an evaluated context.
    
    This patch fixes this by making extract_local_specs walk evaluated
    contexts first before walking unevaluated contexts.  We could probably
    get away with not walking unevaluated contexts at all, but this approach
    seems more clearly safe.
    
            PR c++/100295
            PR c++/107579
    
    gcc/cp/ChangeLog:
    
            * pt.cc (el_data::skip_unevaluated_operands): New data member.
            (extract_locals_r): If skip_unevaluated_operands is true,
            don't walk into unevaluated contexts.
            (extract_local_specs): Walk the pattern twice, first with
            skip_unevaluated_operands true followed by it set to false.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/cpp1z/constexpr-if-lambda5.C: New test.
    
    (cherry picked from commit 18499b9f848707aee42d810e99ac0a4c9788433c)
Comment 11 GCC Commits 2023-05-22 03:12:23 UTC
The releases/gcc-11 branch has been updated by Patrick Palka <ppalka@gcc.gnu.org>:

https://gcc.gnu.org/g:58d2a4cdbbafa4431eb9cff3778099d185f9f391

commit r11-10804-g58d2a4cdbbafa4431eb9cff3778099d185f9f391
Author: Patrick Palka <ppalka@redhat.com>
Date:   Thu Dec 15 16:02:05 2022 -0500

    c++: extract_local_specs and unevaluated contexts [PR100295]
    
    Here during partial instantiation of the constexpr if, extra_local_specs
    walks the statement looking for local specializations within to capture.
    However, we're thwarted by the fact that 'ts' first appears inside an
    unevaluated context, and so the calls to process_outer_var_ref for its
    local specializations are a no-op.  And since we walk each tree exactly
    once, we end up not capturing the local specializations despite 'ts'
    later occurring in an evaluated context.
    
    This patch fixes this by making extract_local_specs walk evaluated
    contexts first before walking unevaluated contexts.  We could probably
    get away with not walking unevaluated contexts at all, but this approach
    seems more clearly safe.
    
            PR c++/100295
            PR c++/107579
    
    gcc/cp/ChangeLog:
    
            * pt.c (el_data::skip_unevaluated_operands): New data member.
            (extract_locals_r): If skip_unevaluated_operands is true,
            don't walk into unevaluated contexts.
            (extract_local_specs): Walk the pattern twice, first with
            skip_unevaluated_operands true followed by it set to false.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/cpp1z/constexpr-if-lambda5.C: New test.
    
    (cherry picked from commit 18499b9f848707aee42d810e99ac0a4c9788433c)
Comment 12 Patrick Palka 2023-05-22 03:15:25 UTC
Fixed for GCC 11.4+