On Fri, 1 Mar 2024, Jason Merrill wrote:

> On 3/1/24 12:08, Patrick Palka wrote:
> > On Fri, 1 Mar 2024, Patrick Palka wrote:
> > 
> > > On Fri, 1 Mar 2024, Jason Merrill wrote:
> > > 
> > > > On 3/1/24 10:00, Patrick Palka wrote:
> > > > > On Fri, 1 Mar 2024, Jason Merrill wrote:
> > > > > 
> > > > > > On 2/29/24 15:56, Patrick Palka wrote:
> > > > > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look
> > > > > > > OK for trunk?
> > > > > > > 
> > > > > > > -- >8 --
> > > > > > > 
> > > > > > > For local enums defined in a non-template function or a function
> > > > > > > template
> > > > > > > instantiation it seems we neglect to make the function depend on
> > > > > > > the
> > > > > > > enum
> > > > > > > definition, which ultimately causes streaming to fail due to the
> > > > > > > enum
> > > > > > > definition not being streamed before uses of its enumerators are
> > > > > > > streamed,
> > > > > > > as far as I can tell.
> > > > > > 
> > > > > > I would think that the function doesn't need to depend on the local
> > > > > > enum
> > > > > > in
> > > > > > order for the local enum to be streamed before the use of the
> > > > > > enumerator,
> > > > > > which comes after the definition of the enum in the function body?
> > > > > > 
> > > > > > Why isn't streaming the body of the function outputting the enum
> > > > > > definition
> > > > > > before the use of the enumerator?
> > > > > 
> > > > > IIUC (based on observing the behavior for local classes) streaming the
> > > > > definition of a local class/enum as part of the function definition is
> > > > > what we want to avoid; we want to treat a local type definition as a
> > > > > logically separate definition and stream it separately (similar
> > > > > to class defns vs member defns I guess).  And by not registering a
> > > > > dependency
> > > > > between the function and the local enum, we end up never streaming out
> > > > > the local enum definition separately and instead stream it out as part
> > > > > of the function definition (accidentally) which we then can't stream
> > > > > in
> > > > > properly.
> > > > > 
> > > > > Perhaps the motivation for treating local type definitions as
> > > > > logically
> > > > > separate from the function definition is because they can leak out of
> > > > > a
> > > > > function with a deduced return type:
> > > > > 
> > > > >     auto f() {
> > > > >       struct A { };
> > > > >       return A();
> > > > >     }
> > > > > 
> > > > >     using type = decltype(f()); // refers directly to f()::A
> > > > 
> > > > Yes, I believe that's what modules.cc refers to as a "voldemort".
> > > > 
> > > > But for non-voldemort local types, the declaration of the function
> > > > doesn't
> > > > depend on them, only the definition.  Why doesn't streaming them in the
> > > > definition work properly?
> > > 
> > > I should note that for a templated local type we already always add a
> > > dependency between the function template _pattern_ and the local type
> > > _pattern_ and therefore always stream the local type pattern separately
> > > (even if its not actually a voldemort), thanks to the TREE_CODE (decl) ==
> > > TEMPLATE_DECL
> > > case guarding the add_dependency call (inside a template pattern we
> > > see the TEMPLATE_DECL of the local TYPE_DECL).  The dependency is
> > > missing only when the function is a non-template or non-template-pattern.
> > > My patch makes us consistently add the dependency and in turn consistently
> > > stream the definitions separately.
> > > 
> > > (For a local _class_, in the non-template and non-template-pattern case
> > > we currently add a dependency between the function and the
> > > injected-class-name of the class as opposed to the class itself, which
> > > seems quite accidental but suffices.  And that's why only local enums
> > > are problematic currently.  After my patch we instead add a dependency
> > > to the local class itself.)
> > > 
> > > Part of the puzzle of why we don't/can't stream them as part of the
> > > function definition is because we don't mark the enumerators for
> > > by-value walking when marking the function definition.  So when
> > > streaming out the enumerator definition we stream out _references_
> > > to the enumerators (tt_const_decl tags) instead of the actual
> > > definitions which breaks stream-in.
> > > 
> > > The best place to mark local types for by-value walking would be
> > > in trees_out::mark_function_def which is suspiciously empty!  I
> > > experimented with (never mind that it only marks the outermost block's
> > > types):
> > > 
> > > @@ -11713,8 +11713,12 @@ trees_out::write_function_def (tree decl)
> > >   }
> > >     void
> > > -trees_out::mark_function_def (tree)
> > > +trees_out::mark_function_def (tree decl)
> > >   {
> > > +  tree initial = DECL_INITIAL (decl);
> > > +  for (tree var = BLOCK_VARS (initial); var; var = DECL_CHAIN (var))
> > > +    if (DECL_IMPLICIT_TYPEDEF_P (var))
> > > +      mark_declaration (var, true);
> > >   }
> > > 
> > > Which actually fixes the non-template PR104919 testcase, but it
> > > breaks streaming of templated local types wherein we run into
> > > the sanity check:
> > > 
> > > @@ -7677,16 +7677,6 @@ trees_out::decl_value (tree decl, depset *dep)
> > >       merge_kind mk = get_merge_kind (decl, dep);
> > >   !  if (CHECKING_P)
> > > !    {
> > > !      /* Never start in the middle of a template.  */
> > > !      int use_tpl = -1;
> > > !      if (tree ti = node_template_info (decl, use_tpl))
> > > ! gcc_checking_assert (TREE_CODE (TI_TEMPLATE (ti)) == OVERLOAD
> > > !                      || TREE_CODE (TI_TEMPLATE (ti)) == FIELD_DECL
> > > !                      || (DECL_TEMPLATE_RESULT (TI_TEMPLATE (ti))
> > > !                          != decl));
> > > !    }
> > >       if (streaming_p ())
> > >       {
> > > 
> > > If we try to work around this sanity check by only marking local types
> > > when inside a non-template and non-template-pattern (i.e. inside an
> > > instantiation):
> > > 
> > > @@ -11713,8 +11713,16 @@ trees_out::write_function_def (tree decl)
> > >   }
> > >     void
> > > -trees_out::mark_function_def (tree)
> > > +trees_out::mark_function_def (tree decl)
> > >   {
> > > +  tree initial = DECL_INITIAL (decl);
> > > +  for (tree var = BLOCK_VARS (initial); var; var = DECL_CHAIN (var))
> > > +    if (DECL_IMPLICIT_TYPEDEF_P (var))
> > > +      {
> > > + tree ti = get_template_info (var);
> > > + if (!ti || DECL_TEMPLATE_RESULT (TI_TEMPLATE (ti)) != var)
> > > +   mark_declaration (var, true);
> > > +      }
> > >   }
> > > 
> > > Then we trip over this other sanity check (when streaming a local
> > > TYPE_DECL from a function template instantiation):
> > > 
> > > gcc/cp/module.cc
> > > @@ -7859,16 +7859,6 @@ trees_out::decl_value (tree decl, depset *dep)
> > >         tree_node (get_constraints (decl));
> > >       }
> > >   !  if (streaming_p ())
> > > !    {
> > > !      /* Do not stray outside this section.  */
> > > !      gcc_checking_assert (!dep || dep->section == dep_hash->section);
> > > !
> > > !      /* Write the entity index, so we can insert it as soon as we
> > > !  know this is new.  */
> > > !      install_entity (decl, dep);
> > > !    }
> > > !
> > >     if (DECL_LANG_SPECIFIC (inner)
> > >         && DECL_MODULE_KEYED_DECLS_P (inner)
> > >         && !is_key_order ())
> > > 
> > > At this point I gave up on this approach.  It seems simpler to just
> > > consistently add the dependencies.
> 
> Fair enough.
> 
> > Ah, it just occurred to me we might as well remove the sneakoscope
> > flag altogether, since the code it affects is only used for dependency
> > analysis (!streaming_p ()) which happens from find_dependencies.  This
> > seems like a nice simplification and passes the testsuite and other
> > smoke tests.
> 
> Makes sense.
> 
> > @@ -6610,7 +6610,15 @@ copy_fn (tree fn, tree& parms, tree& result)
> >     id.src_cfun = DECL_STRUCT_FUNCTION (fn);
> >     id.decl_map = &decl_map;
> >   -  id.copy_decl = copy_decl_no_change;
> > +  id.copy_decl = [] (tree decl, copy_body_data *id)
> > +    {
> > +      if (TREE_CODE (decl) == TYPE_DECL || TREE_CODE (decl) == CONST_DECL)
> > +   /* Don't make copies of local types or enumerators, the C++
> > +      constexpr evaluator doesn't need them and they cause problems
> > +      with modules.  */
> 
> As mentioned in my other reply, I'm not sure this is safe for e.g. an
> enumerator with the value sizeof(local-var).

Wouldn't such a value get reduced to a constant?  If the local variable
has a variably modified type then we reject the enumerator as non-constant:

    void f(int n) {
      int arr[n];
      enum E { e = sizeof(arr); }; // error: 'n' is not a constant expression
    }


> 
> Why are we streaming constexpr function copies, anyway?  Can we not?

Ah sorry, what I meant was that we stream the pre-gimplified version of
a constexpr function created by maybe_save_constexpr_fundef (alongside
the canonical gimplified version), not the various copies thereof
created by get_fundef_copy.  I don't think we can avoid not streaming
it or else we would not need to do maybe_save_constexpr_fundef in
the first place?

> 
> Jason
> 
> 

Reply via email to