On Mon, Jan 27, 2025 at 10:20:05AM -0500, Patrick Palka wrote:
> On Sat, 12 Oct 2024, Nathaniel Shead wrote:
> 
> > This version rewords all "ignored exposures" language.
> > 
> > I haven't fixed up the issue with DECL_TEMPLATE_INSTANTIATIONS for this
> > patch.  I'll try to get to that as a separate patch if I find the time,
> > but it's not 100% needed here I don't think.
> > 
> > It seems the discussion around PR115126 RE: the libgcc changes that
> > people are leaning towards reverting the changes I made to the gthread
> > static variables and instead having some sort of attribute to instruct
> > the compiler to ignore the ODR issues; that should be relatively
> > straight-forward to add (maybe something like [[gnu::ignore_tu_local]]?)
> > but I'll probably handle that as a separate patch once I'm sure that's
> > what we actually want to do, and would probably be worth corresponding
> > with the Clang folks to see what thoughts they have.
> > 
> > Finally, I wasn't able to work out a good way to fold non-ODR usages of
> > TU-local constants early.  I attempted modifying 'mark_use', which
> > helped for 'constexpr' usages (though I ran into issues with e.g. 
> > 
> >   constexpr int n[1] = 0;
> >   constexpr int x = n ? 1 : 2;
> > 
> > segfaulting as build_address was not expecting to see a constructor
> > rather than a declaration here).  But I didn't look too hard into
> > solving this as it appears that any modifications made by 'mark_use' are
> > not actually applied to the primary template at all, and this is
> > consistent across many places I investigated.
> > 
> > Given that erroring on these cases is still the status-quo, how easy it
> > is to workaround most of the time, and that I'm still not sure how to
> > solve this, I've also left this as a FIXME (with an XFAILed testcase) to
> > revisit later.
> > 
> > OK for trunk?
> > 
> > -- >8 --
> > 
> > [basic.link] p14 lists a number of circumstances where a declaration
> > naming a TU-local entity is not an exposure, notably the bodies of
> > non-inline templates and friend declarations in classes.  This patch
> > ensures that these references do not error when exporting the module.
> > 
> > We do need to still error on instantiation from a different module,
> > however, in case this refers to a TU-local entity.  As such this patch
> > adds a new tree TU_LOCAL_ENTITY which is used purely as a placeholder to
> > poison any attempted template instantiations that refer to it.
> 
> I'm a bit late to the party, but this is a nice patch series!  I have a
> couple of comments below.
> 
> > 
> > This is also streamed for friend decls so that merging (based on the
> > index of an entity into the friend decl list) doesn't break and to
> > prevent complicating the logic; I imagine this shouldn't ever come up
> > though.
> > 
> > We also add a new warning, '-Wtemplate-names-tu-local', to handle the
> > case where someone accidentally refers to a TU-local value from within a
> > non-inline function template.  This will compile without errors as-is,
> > but any attempt to instantiate the decl will fail; this warning can be
> > used to ensure that this doesn't happen.  Unfortunately the warning has
> > quite some false positives; for instance, a user could deliberately only
> > call explicit instantiations of the decl, or use 'if constexpr' to avoid
> > instantiating the TU-local entity from other TUs, neither of which are
> > currently detected.
> > 
> > The main piece that this patch doesn't yet attempt to solve is ADL: as
> > specified, if ADL adds an overload set that includes a translation-unit
> > local entity when instantiating a template, that overload set is now
> > poisoned and counts as an exposure.  Unfortunately, we don't currently
> > differentiate between decls that are hidden due to not being exported,
> > or decls that are hidden due to being hidden friends, so this patch
> > instead just keeps the current (wrong) behaviour of non-exported
> > entities not being visible to ADL at all.
> > 
> > Additionally, this patch doesn't attempt to ignore non-ODR uses of
> > constants in constexpr functions or templates.  The obvious approach of
> > folding them early in 'mark_use' doesn't seem to work (for a variety of
> > reasons), so this leaves this to a later patch to implement, as it's at
> > least no worse than the current behaviour and easy enough to workaround.
> > 
> > For completeness this patch adds a new xtreme-header testcase to ensure
> > that we have no regressions with regards to exposures of TU-local
> > declarations in the standard library header files.  A more restrictive
> > test would be to do 'export extern "C++"' here, but unfortunately the
> > system headers on some targets declare TU-local entities, so we'll make
> > do with checking that at least the C++ standard library headers don't
> > refer to such entities.
> > 
> > gcc/c-family/ChangeLog:
> > 
> >     * c.opt: New warning '-Wtemplate-names-tu-local'.
> > 
> > gcc/cp/ChangeLog:
> > 
> >     * cp-objcp-common.cc (cp_tree_size): Add TU_LOCAL_ENTITY.
> >     * cp-tree.def (TU_LOCAL_ENTITY): New tree code.
> >     * cp-tree.h (struct tree_tu_local_entity): New type.
> >     (TU_LOCAL_ENTITY_NAME): New accessor.
> >     (TU_LOCAL_ENTITY_LOCATION): New accessor.
> >     (enum cp_tree_node_structure_enum): Add TS_CP_TU_LOCAL_ENTITY.
> >     (union GTY): Add tu_local_entity field.
> >     * module.cc (enum tree_tag): New flag DB_REFS_TU_LOCAL_BIT.
> >     (depset::has_defn): Override for TU-local entities.
> >     (depset::refs_tu_local): New accessor.
> >     (depset::hash::ignore_tu_local): New field.
> >     (depset::hash::hash): Initialize it.
> >     (trees_out::tree_tag::tt_tu_local): New flag.
> >     (trees_out::writing_local_entities): New field.
> >     (trees_out::is_initial_scan): New function.
> >     (trees_out::tu_local_count): New counter.
> >     (trees_out::trees_out): Initialize writing_local_entities.
> >     (dumper::impl::nested_name): Handle TU_LOCAL_ENTITY.
> >     (trees_out::instrument): Report TU-local entity counts.
> >     (trees_out::decl_value): Early exit for TU-local entities.
> >     (trees_in::decl_value): Handle typedefs of TU-local entities.
> >     (trees_out::decl_node): Adjust assertion to cope with early exit
> >     of TU-local deps.  Always write TU-local entities by value.
> >     (trees_out::type_node): Handle TU-local types.
> >     (trees_out::has_tu_local_dep): New function.
> >     (trees_out::find_tu_local_decl): New function.
> >     (trees_out::tree_node): Intercept TU-local entities and write
> >     placeholder values for them instead of normal streaming.
> >     (trees_in::tree_node): Handle TU-local entities and TU-local
> >     template results.
> >     (trees_out::write_function_def): Ignore exposures in non-inline
> >     function bodies.
> >     (trees_out::write_var_def): Ignore exposures in initializers.
> >     (trees_out::write_class_def): Ignore exposures in friend decls.
> >     (trees_in::read_class_def): Skip TU-local friends.
> >     (trees_out::write_definition): Record whether we're writing a
> >     decl which refers to TU-local entities.
> >     (depset::hash::add_dependency): Only mark as exposure if we're not
> >     ignoring TU-local entities.
> >     (depset::hash::find_dependencies): Use depset's own is_key_order
> >     function rather than delegating via walker.  Pass whether the
> >     decl has ignored TU-local entities in its definition.
> >     (depset::hash::finalize_dependencies): Implement new warning
> >     Wtemplate-names-tu-local.
> >     (module_state::intercluster_seed): Don't seed TU-local deps.
> >     (module_state::write_cluster): Pass whether the decl has ignored
> >     TU-local entities in its definition.
> >     * pt.cc (complain_about_tu_local_entity): New function.
> >     (expr_contains_tu_local_entity): New function.
> >     (function_contains_tu_local_entity): New function.
> >     (instantiate_class_template): Skip TU-local friends.
> >     (tsubst_decl): Handle typedefs of TU-local entities.
> >     (tsubst): Complain about TU-local entities.
> >     (dependent_operand_p): Early exit for TU-local entities so we
> >     don't attempt to constant-evaluate them.
> >     (tsubst_expr): Detect and complain about TU-local entities.
> > 
> > gcc/ChangeLog:
> > 
> >     * doc/invoke.texi: Document -Wtemplate-names-tu-local.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> >     * g++.dg/modules/internal-5_a.C: New test.
> >     * g++.dg/modules/internal-5_b.C: New test.
> >     * g++.dg/modules/internal-6.C: New test.
> >     * g++.dg/modules/internal-7_a.C: New test.
> >     * g++.dg/modules/internal-7_b.C: New test.
> >     * g++.dg/modules/internal-8_a.C: New test.
> >     * g++.dg/modules/xtreme-header-8.C: New test.
> > 
> > Signed-off-by: Nathaniel Shead <nathanielosh...@gmail.com>
> > Reviewed-by: Jason Merrill <ja...@redhat.com>
> > ---
> >  gcc/c-family/c.opt                            |   4 +
> >  gcc/cp/cp-objcp-common.cc                     |   1 +
> >  gcc/cp/cp-tree.def                            |   6 +
> >  gcc/cp/cp-tree.h                              |  21 +-
> >  gcc/cp/module.cc                              | 318 +++++++++++++++---
> >  gcc/cp/pt.cc                                  |  98 +++++-
> >  gcc/doc/invoke.texi                           |  19 +-
> >  gcc/testsuite/g++.dg/modules/internal-5_a.C   | 110 ++++++
> >  gcc/testsuite/g++.dg/modules/internal-5_b.C   |  30 ++
> >  gcc/testsuite/g++.dg/modules/internal-6.C     |  24 ++
> >  gcc/testsuite/g++.dg/modules/internal-7_a.C   |  75 +++++
> >  gcc/testsuite/g++.dg/modules/internal-7_b.C   |  21 ++
> >  gcc/testsuite/g++.dg/modules/internal-8_a.C   |  35 ++
> >  .../g++.dg/modules/xtreme-header-8.C          |   8 +
> >  14 files changed, 721 insertions(+), 49 deletions(-)
> >  create mode 100644 gcc/testsuite/g++.dg/modules/internal-5_a.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/internal-5_b.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/internal-6.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/internal-7_a.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/internal-7_b.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/internal-8_a.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/xtreme-header-8.C
> > 
> > diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
> > index 9d1fccadbf9..6e5ddefd0e2 100644
> > --- a/gcc/c-family/c.opt
> > +++ b/gcc/c-family/c.opt
> > @@ -1450,6 +1450,10 @@ Wtemplate-id-cdtor
> >  C++ ObjC++ Var(warn_template_id_cdtor) Warning
> >  Warn about simple-template-id in a constructor or destructor.
> >  
> > +Wtemplate-names-tu-local
> > +C++ ObjC++ Var(warn_template_names_tu_local) Warning EnabledBy(Wextra)
> > +Warn about templates naming TU-local entities in a module.
> > +
> >  Wterminate
> >  C++ ObjC++ Warning Var(warn_terminate) Init(1)
> >  Warn if a throw expression will always result in a call to terminate().
> > diff --git a/gcc/cp/cp-objcp-common.cc b/gcc/cp/cp-objcp-common.cc
> > index cd379514991..b959533bcb1 100644
> > --- a/gcc/cp/cp-objcp-common.cc
> > +++ b/gcc/cp/cp-objcp-common.cc
> > @@ -233,6 +233,7 @@ cp_tree_size (enum tree_code code)
> >      case ASSERTION_STMT:   return sizeof (tree_exp);
> >      case PRECONDITION_STMT:        return sizeof (tree_exp);
> >      case POSTCONDITION_STMT:       return sizeof (tree_exp);
> > +    case TU_LOCAL_ENTITY:  return sizeof (tree_tu_local_entity);
> >      default:
> >        switch (TREE_CODE_CLASS (code))
> >     {
> > diff --git a/gcc/cp/cp-tree.def b/gcc/cp/cp-tree.def
> > index 18f75108c7b..7580dc3667d 100644
> > --- a/gcc/cp/cp-tree.def
> > +++ b/gcc/cp/cp-tree.def
> > @@ -573,6 +573,12 @@ DEFTREECODE (ASSERTION_STMT, "assertion_stmt", 
> > tcc_statement, 3)
> >  DEFTREECODE (PRECONDITION_STMT, "precondition_stmt", tcc_statement, 3)
> >  DEFTREECODE (POSTCONDITION_STMT, "postcondition_stmt", tcc_statement, 4)
> >  
> > +/* A reference to a translation-unit local entity.
> > +
> > +   This is emitted by modules streaming when writing a TU-local entity that
> > +   wasn't an exposure (e.g. in a non-inline function template).  */
> > +DEFTREECODE (TU_LOCAL_ENTITY, "tu_local_entity", tcc_exceptional, 0)
> > +
> >  /*
> >  Local variables:
> >  mode:c
> > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > index dc153a97dc4..a71d0704f8b 100644
> > --- a/gcc/cp/cp-tree.h
> > +++ b/gcc/cp/cp-tree.h
> > @@ -1778,6 +1778,22 @@ check_constraint_info (tree t)
> >     it for unscoped enums.  */
> >  #define DECL_MODULE_EXPORT_P(NODE) TREE_LANG_FLAG_3 (NODE)
> >  
> > +/* Represents a streamed-in translation-unit-local entity.  Any use of
> > +   this node when instantiating a template should emit an error.  */
> > +struct GTY(()) tree_tu_local_entity {
> > +  struct tree_base base;
> > +  tree name;
> > +  location_t loc;
> > +};
> > +
> > +/* The name of a translation-unit-local entity.  */
> > +#define TU_LOCAL_ENTITY_NAME(NODE) \
> > +  (((struct tree_tu_local_entity *)TU_LOCAL_ENTITY_CHECK (NODE))->name)
> > +
> > +/* The source location of the translation-unit-local entity.  */
> > +#define TU_LOCAL_ENTITY_LOCATION(NODE) \
> > +  (((struct tree_tu_local_entity *)TU_LOCAL_ENTITY_CHECK (NODE))->loc)
> > +
> >  
> >  /* The list of local parameters introduced by this requires-expression,
> >     in the form of a chain of PARM_DECLs.  */
> > @@ -1811,7 +1827,8 @@ enum cp_tree_node_structure_enum {
> >    TS_CP_LAMBDA_EXPR,
> >    TS_CP_TEMPLATE_INFO,
> >    TS_CP_CONSTRAINT_INFO,
> > -  TS_CP_USERDEF_LITERAL
> > +  TS_CP_USERDEF_LITERAL,
> > +  TS_CP_TU_LOCAL_ENTITY
> >  };
> >  
> >  /* The resulting tree type.  */
> > @@ -1842,6 +1859,8 @@ union GTY((desc ("cp_tree_node_structure (&%h)"),
> >      constraint_info;
> >    struct tree_userdef_literal GTY ((tag ("TS_CP_USERDEF_LITERAL")))
> >      userdef_literal;
> > +  struct tree_tu_local_entity GTY ((tag ("TS_CP_TU_LOCAL_ENTITY")))
> > +    tu_local_entity;
> >  };
> >  
> >  
> > diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> > index 136ffb1a7ac..4b26dc5d367 100644
> > --- a/gcc/cp/module.cc
> > +++ b/gcc/cp/module.cc
> > @@ -2330,7 +2330,9 @@ private:
> >      DB_KIND_BITS = EK_BITS,
> >      DB_DEFN_BIT = DB_KIND_BIT + DB_KIND_BITS,
> >      DB_IS_MEMBER_BIT,              /* Is an out-of-class member.  */
> > -    DB_TU_LOCAL_BIT,               /* It is a TU-local entity.  */
> > +    DB_TU_LOCAL_BIT,               /* Is a TU-local entity.  */
> > +    DB_REFS_TU_LOCAL_BIT,  /* Refers to a TU-local entity (but is not
> > +                              necessarily an exposure.)  */
> >      DB_EXPOSURE_BIT,               /* Exposes a TU-local entity.  */
> >      DB_IMPORTED_BIT,               /* An imported entity.  */
> >      DB_UNREACHED_BIT,              /* A yet-to-be reached entity.  */
> > @@ -2401,7 +2403,9 @@ public:
> >  public:
> >    bool has_defn () const
> >    {
> > -    return get_flag_bit<DB_DEFN_BIT> ();
> > +    /* Never consider TU-local entities as having definitions, since
> > +       we will never be accessing them from importers anyway.  */
> > +    return get_flag_bit<DB_DEFN_BIT> () && !is_tu_local ();
> >    }
> >  
> >  public:
> > @@ -2416,6 +2420,10 @@ public:
> >    {
> >      return get_flag_bit<DB_TU_LOCAL_BIT> ();
> >    }
> > +  bool refs_tu_local () const
> > +  {
> > +    return get_flag_bit<DB_REFS_TU_LOCAL_BIT> ();
> > +  }
> >    bool is_exposure () const
> >    {
> >      return get_flag_bit<DB_EXPOSURE_BIT> ();
> > @@ -2543,11 +2551,13 @@ public:
> >      depset *current;         /* Current depset being depended.  */
> >      unsigned section;           /* When writing out, the section.  */
> >      bool reached_unreached;  /* We reached an unreached entity.  */
> > +    bool ignore_tu_local;    /* In a context where referencing a TU-local
> > +                           entity is not an exposure.  */
> >  
> >    public:
> >      hash (size_t size, hash *c = NULL)
> >        : parent (size), chain (c), current (NULL), section (0),
> > -   reached_unreached (false)
> > +   reached_unreached (false), ignore_tu_local (false)
> >      {
> >        worklist.create (size);
> >      }
> > @@ -2748,6 +2758,7 @@ static GTY((cache)) decl_tree_cache_map 
> > *imported_temploid_friends;
> >  /* Tree tags.  */
> >  enum tree_tag {
> >    tt_null,         /* NULL_TREE.  */
> > +  tt_tu_local,             /* A TU-local entity.  */
> >    tt_fixed,                /* Fixed vector index.  */
> >  
> >    tt_node,         /* By-value node.  */
> > @@ -3032,6 +3043,8 @@ private:
> >    depset::hash *dep_hash;          /* Dependency table.  */
> >    int ref_num;                     /* Back reference number.  */
> >    unsigned section;
> > +  bool writing_local_entities;     /* Whether we might walk into a TU-local
> > +                              entity we need to emit placeholders for.  */
> >  #if CHECKING_P
> >    int importedness;                /* Checker that imports not occurring
> >                                inappropriately.  +ve imports ok,
> > @@ -3061,6 +3074,18 @@ public:
> >    };
> >  
> >  public:
> > +  /* The walk is used for three similar purposes:
> > +
> > +      1. The initial scan for dependencies.
> > +      2. Once dependencies have been found, ordering them.
> > +      3. Writing dependencies to file (streaming_p).
> > +
> > +     For cases where it matters, these accessers can be used to determine
> > +     which state we're in.  */
> > +  bool is_initial_scan () const
> > +  {
> > +    return !streaming_p () && !is_key_order ();
> > +  }
> >    bool is_key_order () const
> >    {
> >      return dep_hash->is_key_order ();
> > @@ -3101,6 +3126,10 @@ private:
> >    void tree_pair_vec (vec<tree_pair_s, va_gc> *);
> >    void tree_list (tree, bool has_purpose);
> >  
> > +private:
> > +  bool has_tu_local_dep (tree) const;
> > +  tree find_tu_local_decl (tree);
> > +
> >  public:
> >    /* Mark a node for by-value walking.  */
> >    void mark_by_value (tree);
> > @@ -3138,7 +3167,7 @@ public:
> >  
> >  public:
> >    /* Serialize various definitions. */
> > -  void write_definition (tree decl);
> > +  void write_definition (tree decl, bool refs_tu_local = false);
> >    void mark_declaration (tree decl, bool do_defn);
> >  
> >  private:
> > @@ -3166,6 +3195,7 @@ private:
> >    static unsigned tree_val_count;
> >    static unsigned decl_val_count;
> >    static unsigned back_ref_count;
> > +  static unsigned tu_local_count;
> >    static unsigned null_count;
> >  };
> >  } // anon namespace
> > @@ -3174,12 +3204,14 @@ private:
> >  unsigned trees_out::tree_val_count;
> >  unsigned trees_out::decl_val_count;
> >  unsigned trees_out::back_ref_count;
> > +unsigned trees_out::tu_local_count;
> >  unsigned trees_out::null_count;
> >  
> >  trees_out::trees_out (allocator *mem, module_state *state, depset::hash 
> > &deps,
> >                   unsigned section)
> >    :parent (mem), state (state), tree_map (500),
> > -   dep_hash (&deps), ref_num (0), section (section)
> > +   dep_hash (&deps), ref_num (0), section (section),
> > +   writing_local_entities (false)
> >  {
> >  #if CHECKING_P
> >    importedness = 0;
> > @@ -4301,6 +4333,9 @@ dumper::impl::nested_name (tree t)
> >    int origin = -1;
> >    tree name = NULL_TREE;
> >  
> > +  if (t && TREE_CODE (t) == TU_LOCAL_ENTITY)
> > +    t = TU_LOCAL_ENTITY_NAME (t);
> > +
> >    if (t && TREE_CODE (t) == TREE_BINFO)
> >      t = BINFO_TYPE (t);
> >  
> > @@ -4853,6 +4888,7 @@ trees_out::instrument ()
> >        dump ("  %u decl trees", decl_val_count);
> >        dump ("  %u other trees", tree_val_count);
> >        dump ("  %u back references", back_ref_count);
> > +      dump ("  %u TU-local entities", tu_local_count);
> >        dump ("  %u null trees", null_count);
> >      }
> >  }
> > @@ -7809,6 +7845,17 @@ trees_out::decl_value (tree decl, depset *dep)
> >                    || DECL_ORIGINAL_TYPE (decl)
> >                    || !TYPE_PTRMEMFUNC_P (TREE_TYPE (decl)));
> >  
> > +  /* There's no need to walk any of the contents of a known TU-local 
> > entity,
> > +     since importers should never see any of it regardless.  But make sure 
> > we
> > +     at least note its location so importers can use it for diagnostics.  
> > */
> > +  if (dep && dep->is_tu_local ())
> > +    {
> > +      gcc_checking_assert (is_initial_scan ());
> > +      insert (decl, WK_value);
> > +      state->note_location (DECL_SOURCE_LOCATION (decl));
> > +      return;
> > +    }
> > +
> >    merge_kind mk = get_merge_kind (decl, dep);
> >    bool is_imported_temploid_friend = imported_temploid_friends->get (decl);
> >  
> > @@ -8390,14 +8437,17 @@ trees_in::decl_value ()
> >       /* Frob it to be ready for cloning.  */
> >       TREE_TYPE (inner) = DECL_ORIGINAL_TYPE (inner);
> >       DECL_ORIGINAL_TYPE (inner) = NULL_TREE;
> > -     set_underlying_type (inner);
> > -     if (tdef_flags & 2)
> > +     if (TREE_CODE (TREE_TYPE (inner)) != TU_LOCAL_ENTITY)
> >         {
> > -         /* Match instantiate_alias_template's handling.  */
> > -         tree type = TREE_TYPE (inner);
> > -         TYPE_DEPENDENT_P (type) = true;
> > -         TYPE_DEPENDENT_P_VALID (type) = true;
> > -         SET_TYPE_STRUCTURAL_EQUALITY (type);
> > +         set_underlying_type (inner);
> > +         if (tdef_flags & 2)
> > +           {
> > +             /* Match instantiate_alias_template's handling.  */
> > +             tree type = TREE_TYPE (inner);
> > +             TYPE_DEPENDENT_P (type) = true;
> > +             TYPE_DEPENDENT_P_VALID (type) = true;
> > +             SET_TYPE_STRUCTURAL_EQUALITY (type);
> > +           }
> >         }
> >     }
> >  
> > @@ -8902,10 +8952,14 @@ trees_out::decl_node (tree decl, walk_kind ref)
> >     }
> >        tree_node (tpl);
> >  
> > -      /* Streaming TPL caused us to visit DECL and maybe its type.  */
> > -      gcc_checking_assert (TREE_VISITED (decl));
> > -      if (DECL_IMPLICIT_TYPEDEF_P (decl))
> > -   gcc_checking_assert (TREE_VISITED (TREE_TYPE (decl)));
> > +      /* Streaming TPL caused us to visit DECL and maybe its type,
> > +    if it wasn't TU-local.  */
> > +      if (CHECKING_P && !has_tu_local_dep (tpl))
> > +   {
> > +     gcc_checking_assert (TREE_VISITED (decl));
> > +     if (DECL_IMPLICIT_TYPEDEF_P (decl))
> > +       gcc_checking_assert (TREE_VISITED (TREE_TYPE (decl)));
> > +   }
> >        return false;
> >      }
> >  
> > @@ -8925,10 +8979,10 @@ trees_out::decl_node (tree decl, walk_kind ref)
> >        dep = dep_hash->add_dependency (decl, kind);
> >      }
> >  
> > -  if (!dep)
> > +  if (!dep || dep->is_tu_local ())
> >      {
> >        /* Some internal entity of context.  Do by value.  */
> > -      decl_value (decl, NULL);
> > +      decl_value (decl, dep);
> >        return false;
> >      }
> >  
> > @@ -9084,7 +9138,10 @@ trees_out::type_node (tree type)
> >     if (streaming_p ())
> >       dump (dumper::TREE) && dump ("Wrote typedef %C:%N%S",
> >                                    TREE_CODE (name), name, name);
> > -   gcc_checking_assert (TREE_VISITED (type));
> > +
> > +   /* We'll have either visited this type or have newly discovered
> > +      that it's TU-local; either way we won't need to visit it again.  */
> > +   gcc_checking_assert (TREE_VISITED (type) || has_tu_local_dep (name));
> >     return;
> >        }
> >  
> > @@ -9363,6 +9420,64 @@ trees_in::tree_value ()
> >    return existing;
> >  }
> >  
> > +/* Whether DECL has a TU-local dependency in the hash.  */
> > +
> > +bool
> > +trees_out::has_tu_local_dep (tree decl) const
> > +{
> > +  /* Only the contexts of fields or enums remember that they're
> > +     TU-local.  */
> > +  if (DECL_CONTEXT (decl)
> > +      && (TREE_CODE (decl) == FIELD_DECL
> > +     || TREE_CODE (decl) == CONST_DECL))
> > +    decl = TYPE_NAME (DECL_CONTEXT (decl));
> > +
> > +  depset *dep = dep_hash->find_dependency (decl);
> > +  return dep && dep->is_tu_local ();
> > +}
> > +
> > +/* If T depends on a TU-local entity, return that decl.  */
> > +
> > +tree
> > +trees_out::find_tu_local_decl (tree t)
> > +{
> > +  /* We need to have walked all deps first before we can check.  */
> > +  gcc_checking_assert (!is_initial_scan ());
> > +
> > +  auto walker = [](tree *tp, int *walk_subtrees, void *data) -> tree
> > +    {
> > +      auto self = (trees_out *)data;
> > +
> > +      tree decl = NULL_TREE;
> > +      if (TYPE_P (*tp))
> > +   {
> > +     /* A PMF type is a record type, which we otherwise wouldn't walk;
> > +        return whether the function type is TU-local.  */
> > +     if (TYPE_PTRMEMFUNC_P (*tp))
> > +       {
> > +         *walk_subtrees = 0;
> > +         return self->find_tu_local_decl (TYPE_PTRMEMFUNC_FN_TYPE (*tp));
> > +       }
> > +     else
> > +       decl = TYPE_MAIN_DECL (*tp);
> > +   }
> > +      else if (DECL_P (*tp))
> > +   decl = *tp;
> > +
> > +      if (decl)
> > +   {
> > +     /* We found a DECL, this will tell us whether we're TU-local.  */
> > +     *walk_subtrees = 0;
> > +     return self->has_tu_local_dep (decl) ? decl : NULL_TREE;
> > +   }
> > +      return NULL_TREE;
> > +    };
> > +
> > +  /* We need to walk without duplicates so that we step into the pointed-to
> > +     types of array types.  */
> > +  return cp_walk_tree_without_duplicates (&t, walker, this);
> > +}
> > +
> >  /* Stream out tree node T.  We automatically create local back
> >     references, which is essentially a single pass lisp
> >     self-referential structure pretty-printer.  */
> > @@ -9375,6 +9490,46 @@ trees_out::tree_node (tree t)
> >    if (ref == WK_none)
> >      goto done;
> >  
> > +  /* Find TU-local entities and intercept streaming to instead write a
> > +     placeholder value; this way we don't need to emit such decls.
> > +     We only need to do this when writing a definition of an entity
> > +     that we know names a TU-local entity.  */
> > +  if (!is_initial_scan () && writing_local_entities)
> > +    {
> > +      tree local_decl = NULL_TREE;
> > +      if (DECL_P (t) && has_tu_local_dep (t))
> > +   local_decl = t;
> > +      /* Consider a type to be TU-local if it refers to any TU-local decl,
> > +    no matter how deep.
> > +
> > +    This worsens diagnostics slightly, as we often no longer point
> > +    directly to the at-fault entity when instantiating.  However, this
> > +    reduces the module size slightly and means that much less of pt.cc
> > +    needs to know about us.  */
> > +      else if (TYPE_P (t))
> > +   local_decl = find_tu_local_decl (t);
> > +      else if (EXPR_P (t))
> > +   local_decl = find_tu_local_decl (TREE_TYPE (t));
> > +
> > +      if (local_decl)
> > +   {
> > +     int tag = insert (t, WK_value);
> > +     if (streaming_p ())
> > +       {
> > +         tu_local_count++;
> > +         i (tt_tu_local);
> > +         dump (dumper::TREE)
> > +           && dump ("Writing TU-local entity:%d %C:%N",
> > +                    tag, TREE_CODE (t), t);
> > +       }
> > +     /* TODO: Get a more descriptive name?  */
> > +     tree_node (DECL_NAME (local_decl));
> > +     if (state)
> > +       state->write_location (*this, DECL_SOURCE_LOCATION (local_decl));
> > +     goto done;
> > +   }
> > +    }
> > +
> >    if (ref != WK_normal)
> >      goto skip_normal;
> >  
> > @@ -9531,6 +9686,18 @@ trees_in::tree_node (bool is_use)
> >        /* NULL_TREE.  */
> >        break;
> >  
> > +    case tt_tu_local:
> > +      {
> > +   /* A translation-unit-local entity.  */
> > +   res = make_node (TU_LOCAL_ENTITY);
> > +   int tag = insert (res);
> > +
> > +   TU_LOCAL_ENTITY_NAME (res) = tree_node ();
> > +   TU_LOCAL_ENTITY_LOCATION (res) = state->read_location (*this);
> > +   dump (dumper::TREE) && dump ("Read TU-local entity:%d %N", tag, res);
> > +      }
> > +      break;
> > +
> >      case tt_fixed:
> >        /* A fixed ref, find it in the fixed_ref array.   */
> >        {
> > @@ -10147,7 +10314,8 @@ trees_in::tree_node (bool is_use)
> >        /* A template.  */
> >        if (tree tpl = tree_node ())
> >     {
> > -     res = DECL_TEMPLATE_RESULT (tpl);
> > +     res = (TREE_CODE (tpl) == TU_LOCAL_ENTITY ?
> > +            tpl : DECL_TEMPLATE_RESULT (tpl));
> >       dump (dumper::TREE)
> >         && dump ("Read template %C:%N", TREE_CODE (res), res);
> >     }
> > @@ -11917,8 +12085,18 @@ void
> >  trees_out::write_function_def (tree decl)
> >  {
> >    tree_node (DECL_RESULT (decl));
> > -  tree_node (DECL_INITIAL (decl));
> > -  tree_node (DECL_SAVED_TREE (decl));
> > +
> > +  {
> > +    /* The function body for a non-inline function or function template
> > +       is ignored for determining exposures.  This should only matter
> > +       for templates (we don't emit the bodies of non-inline functions
> > +       to begin with).  */
> > +    auto ovr = make_temp_override (dep_hash->ignore_tu_local,
> > +                              !DECL_DECLARED_INLINE_P (decl));
> > +    tree_node (DECL_INITIAL (decl));
> > +    tree_node (DECL_SAVED_TREE (decl));
> > +  }
> > +
> >    tree_node (DECL_FRIEND_CONTEXT (decl));
> >  
> >    constexpr_fundef *cexpr = retrieve_constexpr_fundef (decl);
> > @@ -12020,6 +12198,10 @@ trees_in::read_function_def (tree decl, tree 
> > maybe_template)
> >  void
> >  trees_out::write_var_def (tree decl)
> >  {
> > +  /* The initializer of a variable or variable template is ignored for
> > +     determining exposures.  */
> > +  auto ovr = make_temp_override (dep_hash->ignore_tu_local, VAR_P (decl));
> > +
> >    tree init = DECL_INITIAL (decl);
> >    tree_node (init);
> >    if (!init)
> > @@ -12207,21 +12389,28 @@ trees_out::write_class_def (tree defn)
> >        for (; vtables; vtables = TREE_CHAIN (vtables))
> >     write_definition (vtables);
> >  
> > -      /* Write the friend classes.  */
> > -      tree_list (CLASSTYPE_FRIEND_CLASSES (type), false);
> > +      {
> > +   /* Friend declarations in class definitions are ignored when
> > +      determining exposures.  */
> > +   auto ovr = make_temp_override (dep_hash->ignore_tu_local, true);
> >  
> > -      /* Write the friend functions.  */
> > -      for (tree friends = DECL_FRIENDLIST (defn);
> > -      friends; friends = TREE_CHAIN (friends))
> > -   {
> > -     /* Name of these friends.  */
> > -     tree_node (TREE_PURPOSE (friends));
> > -     tree_list (TREE_VALUE (friends), false);
> > -   }
> > -      /* End of friend fns.  */
> > -      tree_node (NULL_TREE);
> > +   /* Write the friend classes.  */
> > +   tree_list (CLASSTYPE_FRIEND_CLASSES (type), false);
> >  
> > -      /* Write the decl list.  */
> > +   /* Write the friend functions.  */
> > +   for (tree friends = DECL_FRIENDLIST (defn);
> > +        friends; friends = TREE_CHAIN (friends))
> > +     {
> > +       tree_node (FRIEND_NAME (friends));
> > +       tree_list (FRIEND_DECLS (friends), false);
> > +     }
> > +   /* End of friend fns.  */
> > +   tree_node (NULL_TREE);
> > +      }
> > +
> > +      /* Write the decl list.  We don't need to ignore exposures of friend
> > +    decls here as any such decls should already have been added and
> > +    ignored above.  */
> >        tree_list (CLASSTYPE_DECL_LIST (type), true);
> >  
> >        if (TYPE_CONTAINS_VPTR_P (type))
> > @@ -12574,6 +12763,8 @@ trees_in::read_class_def (tree defn, tree 
> > maybe_template)
> >              friend_decls; friend_decls = TREE_CHAIN (friend_decls))
> >           {
> >             tree f = TREE_VALUE (friend_decls);
> > +           if (TREE_CODE (f) == TU_LOCAL_ENTITY)
> > +             continue;
> >             
> >             DECL_BEFRIENDING_CLASSES (f)
> >               = tree_cons (NULL_TREE, type, DECL_BEFRIENDING_CLASSES (f));
> > @@ -12722,8 +12913,11 @@ trees_in::read_enum_def (tree defn, tree 
> > maybe_template)
> >  /* Write out the body of DECL.  See above circularity note.  */
> >  
> >  void
> > -trees_out::write_definition (tree decl)
> > +trees_out::write_definition (tree decl, bool refs_tu_local)
> >  {
> > +  auto ovr = make_temp_override (writing_local_entities,
> > +                            writing_local_entities || refs_tu_local);
> > +
> >    if (streaming_p ())
> >      {
> >        assert_definition (decl);
> > @@ -13309,7 +13503,11 @@ depset::hash::add_dependency (depset *dep)
> >    current->deps.safe_push (dep);
> >  
> >    if (dep->is_tu_local ())
> > -    current->set_flag_bit<DB_EXPOSURE_BIT> ();
> > +    {
> > +      current->set_flag_bit<DB_REFS_TU_LOCAL_BIT> ();
> > +      if (!ignore_tu_local)
> > +   current->set_flag_bit<DB_EXPOSURE_BIT> ();
> > +    }
> >  
> >    if (current->get_entity_kind () == EK_USING
> >        && DECL_IMPLICIT_TYPEDEF_P (dep->get_entity ())
> > @@ -13959,7 +14157,7 @@ depset::hash::find_dependencies (module_state 
> > *module)
> >             {
> >               walker.mark_declaration (decl, current->has_defn ());
> >  
> > -             if (!walker.is_key_order ()
> > +             if (!is_key_order ()
> >                   && (item->get_entity_kind () == EK_SPECIALIZATION
> >                       || item->get_entity_kind () == EK_PARTIAL
> >                       || (item->get_entity_kind () == EK_DECL
> > @@ -13971,15 +14169,15 @@ depset::hash::find_dependencies (module_state 
> > *module)
> >  
> >               walker.decl_value (decl, current);
> >               if (current->has_defn ())
> > -               walker.write_definition (decl);
> > +               walker.write_definition (decl, current->refs_tu_local ());
> >             }
> >           walker.end ();
> >  
> > -         if (!walker.is_key_order ()
> > +         if (!is_key_order ()
> >               && DECL_CLASS_TEMPLATE_P (decl))
> >             add_deduction_guides (decl);
> >  
> > -         if (!walker.is_key_order ()
> > +         if (!is_key_order ()
> >               && TREE_CODE (decl) == TEMPLATE_DECL
> >               && !DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (decl))
> >             {
> > @@ -14168,6 +14366,37 @@ depset::hash::finalize_dependencies ()
> >       /* We should have emitted an error above.  */
> >       gcc_checking_assert (explained);
> >     }
> > +      else if (warn_template_names_tu_local
> > +          && dep->refs_tu_local () && !dep->is_tu_local ())
> > +   {
> > +     tree decl = dep->get_entity ();
> > +
> > +     /* Friend decls in a class body are ignored, but this is harmless:
> > +        it should not impact any consumers.  */
> > +     if (RECORD_OR_UNION_TYPE_P (TREE_TYPE (decl)))
> > +       continue;
> > +
> > +     /* We should now only be warning about templates.  */
> > +     gcc_checking_assert
> > +       (TREE_CODE (decl) == TEMPLATE_DECL
> > +        && VAR_OR_FUNCTION_DECL_P (DECL_TEMPLATE_RESULT (decl)));
> > +
> > +     /* Ideally we would only warn in cases where there are no explicit
> > +        instantiations of the template, but we don't currently track this
> > +        in an easy-to-find way.  */
> > +     for (depset *rdep : dep->deps)
> > +       if (!rdep->is_binding () && rdep->is_tu_local ())
> > +         {
> > +           tree ref = rdep->get_entity ();
> > +           auto_diagnostic_group d;
> > +           if (warning_at (DECL_SOURCE_LOCATION (decl),
> > +                           OPT_Wtemplate_names_tu_local,
> > +                           "%qD refers to TU-local entity %qD and cannot "
> > +                           "be instantiated in other TUs", decl, ref))
> > +             is_tu_local_entity (ref, /*explain=*/true);
> > +           break;
> > +         }
> > +   }
> >      }
> >  
> >    return ok;
> > @@ -15331,8 +15560,9 @@ enum ct_bind_flags
> >  void
> >  module_state::intercluster_seed (trees_out &sec, unsigned index_hwm, 
> > depset *dep)
> >  {
> > -  if (dep->is_import ()
> > -      || dep->cluster < index_hwm)
> > +  if (dep->is_tu_local ())
> > +    /* We only stream placeholders for TU-local entities anyway.  */;
> > +  else if (dep->is_import () || dep->cluster < index_hwm)
> >      {
> >        tree ent = dep->get_entity ();
> >        if (!TREE_VISITED (ent))
> > @@ -15553,7 +15783,7 @@ module_state::write_cluster (elf_out *to, depset 
> > *scc[], unsigned size,
> >           sec.u (ct_defn);
> >           sec.tree_node (decl);
> >           dump () && dump ("Writing definition %N", decl);
> > -         sec.write_definition (decl);
> > +         sec.write_definition (decl, b->refs_tu_local ());
> >  
> >           if (!namer->has_defn ())
> >             namer = b;
> > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > index 03a1144765b..c7c6da0969e 100644
> > --- a/gcc/cp/pt.cc
> > +++ b/gcc/cp/pt.cc
> > @@ -9881,6 +9881,71 @@ add_pending_template (tree d)
> >      pop_tinst_level ();
> >  }
> >  
> > +/* Emit a diagnostic about instantiating a reference to TU-local entity E. 
> >  */
> > +
> > +static void
> > +complain_about_tu_local_entity (tree e)
> > +{
> > +  auto_diagnostic_group d;
> > +  error ("instantiation exposes TU-local entity %qD",
> > +    TU_LOCAL_ENTITY_NAME (e));
> > +  inform (TU_LOCAL_ENTITY_LOCATION (e), "declared here");
> > +}
> > +
> > +/* Checks if T contains a TU-local entity.  */
> > +
> > +static bool
> > +expr_contains_tu_local_entity (tree t)
> > +{
> > +  if (!modules_p ())
> > +    return false;
> > +
> > +  auto walker = [](tree *tp, int *walk_subtrees, void *) -> tree
> > +    {
> > +      if (TREE_CODE (*tp) == TU_LOCAL_ENTITY)
> > +   return *tp;
> > +      if (!EXPR_P (*tp))
> > +   *walk_subtrees = false;
> > +      return NULL_TREE;
> > +    };
> > +  return cp_walk_tree (&t, walker, nullptr, nullptr);
> > +}
> > +
> > +/* Errors and returns TRUE if X is a function that contains a TU-local
> > +   entity in its overload set.  */
> > +
> > +static bool
> > +function_contains_tu_local_entity (tree x)
> > +{
> > +  if (!modules_p ())
> > +    return false;
> > +
> > +  if (!x || x == error_mark_node)
> > +    return false;
> > +
> > +  if (TREE_CODE (x) == OFFSET_REF
> > +      || TREE_CODE (x) == COMPONENT_REF)
> > +    x = TREE_OPERAND (x, 1);
> > +  x = MAYBE_BASELINK_FUNCTIONS (x);
> > +  if (TREE_CODE (x) == TEMPLATE_ID_EXPR)
> > +    x = TREE_OPERAND (x, 0);
> > +
> > +  if (OVL_P (x))
> > +    for (tree ovl : lkp_range (x))
> > +      if (TREE_CODE (ovl) == TU_LOCAL_ENTITY)
> > +   {
> > +     x = ovl;
> > +     break;
> > +   }
> > +
> > +  if (TREE_CODE (x) == TU_LOCAL_ENTITY)
> > +    {
> > +      complain_about_tu_local_entity (x);
> > +      return true;
> > +    }
> > +
> > +  return false;
> > +}
> >  
> >  /* Return a TEMPLATE_ID_EXPR corresponding to the indicated FNS and
> >     ARGLIST.  Valid choices for FNS are given in the cp-tree.def
> > @@ -12743,8 +12808,10 @@ instantiate_class_template (tree type)
> >     }
> >        else
> >     {
> > -     if (TYPE_P (t) || DECL_CLASS_TEMPLATE_P (t)
> > -         || DECL_TEMPLATE_TEMPLATE_PARM_P (t))
> > +     if (TREE_CODE (t) == TU_LOCAL_ENTITY)
> > +       /* Ignore.  */;
> > +     else if (TYPE_P (t) || DECL_CLASS_TEMPLATE_P (t)
> > +              || DECL_TEMPLATE_TEMPLATE_PARM_P (t))
> >         {
> >           /* Build new CLASSTYPE_FRIEND_CLASSES.  */
> >  
> > @@ -15522,7 +15589,8 @@ tsubst_decl (tree t, tree args, tsubst_flags_t 
> > complain,
> >       RETURN (error_mark_node);
> >  
> >     if (TREE_CODE (t) == TYPE_DECL
> > -       && t == TYPE_MAIN_DECL (TREE_TYPE (t)))
> > +       && (TREE_CODE (TREE_TYPE (t)) == TU_LOCAL_ENTITY
> > +           || t == TYPE_MAIN_DECL (TREE_TYPE (t))))
> >       {
> >         /* If this is the canonical decl, we don't have to
> >            mess with instantiations, and often we can't (for
> > @@ -16250,6 +16318,14 @@ tsubst (tree t, tree args, tsubst_flags_t 
> > complain, tree in_decl)
> >        || TREE_CODE (t) == TRANSLATION_UNIT_DECL)
> >      return t;
> >  
> > +  /* Any instantiation of a template containing a TU-local entity is an
> > +     exposure, so always issue a hard error irrespective of complain.  */
> > +  if (TREE_CODE (t) == TU_LOCAL_ENTITY)
> > +    {
> > +      complain_about_tu_local_entity (t);
> > +      return error_mark_node;
> > +    }
> > +
> >    tsubst_flags_t tst_ok_flag = (complain & tf_tst_ok);
> >    complain &= ~tf_tst_ok;
> >  
> > @@ -18486,6 +18562,12 @@ dependent_operand_p (tree t)
> >  {
> >    while (TREE_CODE (t) == IMPLICIT_CONV_EXPR)
> >      t = TREE_OPERAND (t, 0);
> > +
> > +  /* If we contain a TU_LOCAL_ENTITY assume we're non-dependent; we'll 
> > error
> > +     later when instantiating.  */
> > +  if (expr_contains_tu_local_entity (t))
> > +    return false;
> 
> I think it'd be more robust and cheaper (avoiding a separate tree walk)
> to teach the general constexpr/dependence predicates about
> TU_LOCAL_ENTITY instead of handling it only here.
> 

This was what I'd initially tried, but had ran into some issues with
handling call expressions I believe, and felt that this was a simpler
way to avoid needing too many special cases and touching more things.

That said I agree that this would probably be nicer!  I'll try to take
another look at this if I get a chance.

> > +
> >    ++processing_template_decl;
> >    bool r = (potential_constant_expression (t)
> >         ? value_dependent_expression_p (t)
> > @@ -20255,6 +20337,9 @@ tsubst_expr (tree t, tree args, tsubst_flags_t 
> > complain, tree in_decl)
> >     else
> >       object = NULL_TREE;
> >  
> > +   if (function_contains_tu_local_entity (templ))
> > +     RETURN (error_mark_node);
> > +
> >     tree tid = lookup_template_function (templ, targs);
> >     protected_set_expr_location (tid, EXPR_LOCATION (t));
> >  
> > @@ -20947,6 +21032,9 @@ tsubst_expr (tree t, tree args, tsubst_flags_t 
> > complain, tree in_decl)
> >           qualified_p = true;
> >       }
> >  
> > +   if (function_contains_tu_local_entity (function))
> > +     RETURN (error_mark_node);
> 
> Similarly, maybe it'd suffice to check this more generally in the
> OVERLOAD case of tsubst_expr?
> 

Good thought, I'll give this a shot at some point.

> > +
> >     nargs = call_expr_nargs (t);
> >     releasing_vec call_args;
> >     tsubst_call_args (t, args, complain, in_decl, call_args);
> > @@ -21968,6 +22056,10 @@ tsubst_expr (tree t, tree args, tsubst_flags_t 
> > complain, tree in_decl)
> >     RETURN (op);
> >        }
> >  
> > +    case TU_LOCAL_ENTITY:
> > +      complain_about_tu_local_entity (t);
> > +      RETURN (error_mark_node);
> > +
> >      default:
> >        /* Handle Objective-C++ constructs, if appropriate.  */
> >        if (tree subst = objcp_tsubst_expr (t, args, complain, in_decl))
> > diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> > index d38c1feb86f..b5f767f6f12 100644
> > --- a/gcc/doc/invoke.texi
> > +++ b/gcc/doc/invoke.texi
> > @@ -272,7 +272,7 @@ in the following sections.
> >  -Woverloaded-virtual  -Wno-pmf-conversions -Wself-move -Wsign-promo
> >  -Wsized-deallocation  -Wsuggest-final-methods
> >  -Wsuggest-final-types  -Wsuggest-override  -Wno-template-body
> > --Wno-template-id-cdtor
> > +-Wno-template-id-cdtor  -Wtemplate-names-tu-local
> >  -Wno-terminate  -Wno-vexing-parse  -Wvirtual-inheritance
> >  -Wno-virtual-move-assign  -Wvolatile  -Wzero-as-null-pointer-constant}
> >  
> > @@ -4686,6 +4686,23 @@ template<typename T> struct S @{
> >  @option{-Wtemplate-id-cdtor} is enabled by default with
> >  @option{-std=c++20}; it is also enabled by @option{-Wc++20-compat}.
> >  
> > +@opindex Wtemplate-names-tu-local
> > +@opindex Wno-template-names-tu-local
> > +@item -Wtemplate-names-tu-local
> > +Warn when a template body hides an exposure of a translation-unit-local
> > +entity.  In most cases, referring to a translation-unit-local entity
> > +(such as an internal linkage declaration) within an entity that is
> > +emitted into a module's CMI is an error.  However, within the
> > +initializer of a variable, or in the body of a non-inline function,
> > +this is not an exposure and no error is emitted.
> > +
> > +This can cause variable or function templates to accidentally become
> > +unusable if they reference such an entity, because other translation
> > +units that import the template will never be able to instantiate it.
> > +This warning attempts to detect cases where this might occur.
> > +
> > +This flag is enabled by @option{-Wextra}.
> > +
> >  @opindex Wterminate
> >  @opindex Wno-terminate
> >  @item -Wno-terminate @r{(C++ and Objective-C++ only)}
> > diff --git a/gcc/testsuite/g++.dg/modules/internal-5_a.C 
> > b/gcc/testsuite/g++.dg/modules/internal-5_a.C
> > new file mode 100644
> > index 00000000000..c5ef3752f5a
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/internal-5_a.C
> > @@ -0,0 +1,110 @@
> > +// { dg-additional-options "-fmodules-ts -Wtemplate-names-tu-local" }
> > +// { dg-module-cmi M }
> > +// Ignore exposures in these cases
> > +
> > +export module M;
> > +
> > +namespace {
> > +  inline namespace ns {
> > +    struct internal_t {};
> > +    template <typename T> struct internal_tmpl_t {};
> > +
> > +    int internal_x;
> > +    void internal_ovl(int&) {}
> > +    void internal_ovl(internal_t) {}
> > +
> > +    template <typename T> void internal_tmpl() {}
> > +  }
> > +}
> > +export struct ok_inst_tag {};
> > +
> > +
> > +// The function body for a non-inline function or function template
> > +export void function() {
> > +  internal_t i {};
> > +  internal_tmpl_t<int> ii {};
> > +  internal_ovl(internal_x);
> > +  internal_tmpl<int>();
> > +}
> > +
> > +export template <typename T> void function_tmpl() {  // { dg-warning 
> > "refers to TU-local entity" }
> > +  internal_t i {};
> > +  internal_tmpl_t<T> ii {};
> > +  internal_ovl(internal_x);
> > +  internal_tmpl<T>();
> > +}
> > +template void function_tmpl<ok_inst_tag>();
> > +template <> void function_tmpl<ok_inst_tag*>() {}
> > +
> > +
> > +// The initializer for a variable or variable template
> > +export int var
> > +  = (internal_t{}, internal_tmpl_t<int>{},
> > +     internal_ovl(internal_x), internal_tmpl<int>(), 0);
> > +
> > +export template <typename T> int var_tmpl  // { dg-warning "refers to 
> > TU-local entity" }
> > +  = (internal_t{}, internal_tmpl_t<T>{},
> > +     internal_ovl(internal_x), internal_tmpl<T>(), 0);
> > +
> > +template <typename T> int var_tmpl<T*>  // { dg-warning "refers to 
> > TU-local entity" }
> > +  = (internal_t{}, internal_tmpl_t<T*>{},
> > +     internal_ovl(internal_x), internal_tmpl<T*>(), 0);
> > +
> > +template int var_tmpl<ok_inst_tag>;
> > +template <> int var_tmpl<ok_inst_tag*> = 0;
> > +
> > +export int& constant_ref = internal_x;
> > +static_assert (&constant_ref == &internal_x);
> > +
> > +
> > +// Friend declarations in a class definition
> > +export struct klass {  // { dg-bogus "TU-local" }
> > +  friend ns::internal_t;
> > +  friend ns::internal_tmpl_t<int>;
> > +  friend void ns::internal_ovl(int&);
> > +  friend void ns::internal_ovl(internal_t);
> > +  friend void ns::internal_tmpl<int>();
> > +
> > +  template <typename> friend struct ns::internal_tmpl_t;
> > +  template <typename> friend void ns::internal_tmpl();
> > +};
> > +
> > +export template <typename T>
> > +class klass_tmpl {  // { dg-bogus "TU-local" }
> > +  friend ns::internal_t;
> > +  friend ns::internal_tmpl_t<int>;
> > +  friend void ns::internal_ovl(int&);
> > +  friend void ns::internal_ovl(internal_t);
> > +  friend void ns::internal_tmpl<int>();
> > +
> > +  template <typename> friend struct ns::internal_tmpl_t;
> > +  template <typename> friend void ns::internal_tmpl();
> > +};
> > +
> > +template <typename T> class klass_tmpl<T*> {  // { dg-bogus "TU-local" }
> > +  friend ns::internal_t;
> > +  friend ns::internal_tmpl_t<int>;
> > +  friend void ns::internal_ovl(int&);
> > +  friend void ns::internal_ovl(internal_t);
> > +  friend void ns::internal_tmpl<int>();
> > +
> > +  template <typename> friend struct ns::internal_tmpl_t;
> > +  template <typename> friend void ns::internal_tmpl();
> > +};
> > +
> > +
> > +// Any reference to a non-volatile const object or reference with internal 
> > or
> > +// no linkage initialized with a constant expression that is not an ODR-use
> > +static const int value = 123;
> > +static const int& ref = 456;
> > +static const internal_t internal {};
> > +void f(int) {}
> > +export inline void no_odr_use() {
> > +  int x = value;
> > +  int y = ref;
> > +  int z = (internal, 0);
> > +
> > +  value;
> > +  bool b = value < value;
> > +  f(value);
> > +}
> > diff --git a/gcc/testsuite/g++.dg/modules/internal-5_b.C 
> > b/gcc/testsuite/g++.dg/modules/internal-5_b.C
> > new file mode 100644
> > index 00000000000..baf60fdafa2
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/internal-5_b.C
> > @@ -0,0 +1,30 @@
> > +// { dg-additional-options "-fmodules-ts" }
> > +
> > +import M;
> > +
> > +int main() {
> > +  // These are all OK
> > +  function();
> > +  int a = var;
> > +  klass k;
> > +  klass_tmpl<int> kt;
> > +  klass_tmpl<int*> ktp;
> > +  no_odr_use();
> > +
> > +  function_tmpl<ok_inst_tag>();
> > +  function_tmpl<ok_inst_tag*>();
> > +  int b = var_tmpl<ok_inst_tag>;
> > +  int c = var_tmpl<ok_inst_tag*>;
> > +
> > +  // But don't ignore exposures in these cases
> > +  function_tmpl<int>();  // { dg-message "required from here" }
> > +  int x = var_tmpl<int>;  // { dg-message "required from here" }
> > +  int y = var_tmpl<int*>;  // { dg-message "required from here" }
> > +
> > +  // And decls initialized to a TU-local value are not constant here
> > +  // Unfortunately the error does not currently point to this decl
> > +  constexpr int& r = constant_ref;
> > +  // { dg-error "is not a constant expression" "" { target *-*-* } 0 }
> > +}
> > +
> > +// { dg-error "instantiation exposes TU-local entity" "" { target *-*-* } 
> > 0 }
> > diff --git a/gcc/testsuite/g++.dg/modules/internal-6.C 
> > b/gcc/testsuite/g++.dg/modules/internal-6.C
> > new file mode 100644
> > index 00000000000..0f138781ad5
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/internal-6.C
> > @@ -0,0 +1,24 @@
> > +// { dg-additional-options "-fmodules-ts" }
> > +// { dg-module-cmi !M }
> > +// Exposures (or not) of TU-local values
> > +
> > +export module M;
> > +
> > +static void f() {}
> > +auto& fr = f;   // OK
> > +constexpr auto& fr2 = fr;  // { dg-error "initialized to a TU-local value" 
> > }
> > +static constexpr auto fp2 = fr;  // OK
> > +
> > +struct S { void (&ref)(); } s{ f };  // OK, value is TU-local
> > +constexpr extern struct W { S& s; } wrap{ s };  // OK, value is not 
> > TU-local
> > +constexpr S s2{ f };  // { dg-error "initialized to a TU-local value" }
> > +
> > +constexpr int a = 123;
> > +static constexpr int b = 456;
> > +struct X {
> > +  union {
> > +    const int* p[2];
> > +  };
> > +};
> > +constexpr X x { &a };  // OK
> > +constexpr X y { &a, &b };  // { dg-error "initialized to a TU-local value" 
> > }
> > diff --git a/gcc/testsuite/g++.dg/modules/internal-7_a.C 
> > b/gcc/testsuite/g++.dg/modules/internal-7_a.C
> > new file mode 100644
> > index 00000000000..39f53ea382e
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/internal-7_a.C
> > @@ -0,0 +1,75 @@
> > +// { dg-additional-options "-fmodules-ts -Wtemplate-names-tu-local" }
> > +// Test streaming and instantiations of various kinds of exposures
> > +
> > +export module M;
> > +
> > +namespace {
> > +  int x;
> > +  constexpr int y = 1;
> > +
> > +  struct S { int m; void d(); };
> > +  enum class E { e };
> > +
> > +  template <typename T> int f(T t) { return (int)t; }
> > +}
> > +
> > +template <auto N> void g() {}
> > +
> > +template <typename T>
> > +int expose_1() {  // { dg-warning "TU-local" }
> > +  return x;
> > +}
> > +
> > +template <typename T>
> > +void expose_2() {  // { dg-warning "TU-local" }
> > +  T t = &y;
> > +}
> > +
> > +template <typename T>
> > +bool expose_3() {  // { dg-warning "TU-local" }
> > +  return !(T{} * (x + 5) > 123);
> > +}
> > +
> > +template <typename T>
> > +bool expose_4() {  // { dg-warning "TU-local" }
> > +  return __is_same(S, T);
> > +}
> > +
> > +template <typename T>
> > +void expose_5() {  // { dg-warning "TU-local" }
> > +  static_assert(T{} == (int)E::e);
> > +}
> > +
> > +template <typename T>
> > +void expose_6() {  // { dg-warning "TU-local" }
> > +  f(T{});
> > +}
> > +
> > +template <typename T>
> > +void expose_7() {  // { dg-warning "TU-local" }
> > +  g<&y>();
> > +}
> > +
> > +template <typename T>
> > +void expose_8() {  // { dg-warning "TU-local" }
> > +  decltype(T{} .* &S::m)* (*x)[5][10];
> > +};
> > +
> > +template <typename T>
> > +bool expose_9() {  // { dg-warning "TU-local" }
> > +  return noexcept((T{} .* &S::d)());
> > +}
> > +
> > +template <typename T>
> > +void expose_10() {  // { dg-warning "TU-local" }
> > +  using U = decltype(f<T>());
> > +}
> > +
> > +template <typename T>
> > +void expose_11() {  // { dg-warning "TU-local" }
> > +  static thread_local E r;
> > +}
> > +
> > +template <typename T>
> > +int expose_var  // { dg-warning "TU-local" }
> > +  = f(sizeof(T));
> > diff --git a/gcc/testsuite/g++.dg/modules/internal-7_b.C 
> > b/gcc/testsuite/g++.dg/modules/internal-7_b.C
> > new file mode 100644
> > index 00000000000..2a11e449d6e
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/internal-7_b.C
> > @@ -0,0 +1,21 @@
> > +// { dg-additional-options "-fmodules-ts" }
> > +
> > +module M;
> > +
> > +void inst() {
> > +  expose_1<int>();  // { dg-message "required from here" }
> > +  expose_2<int>();  // { dg-message "required from here" }
> > +  expose_3<int>();  // { dg-message "required from here" }
> > +  expose_4<int>();  // { dg-message "required from here" }
> > +  expose_5<int>();  // { dg-message "required from here" }
> > +  expose_6<int>();  // { dg-message "required from here" }
> > +  expose_7<int>();  // { dg-message "required from here" }
> > +  expose_8<int>();  // { dg-message "required from here" }
> > +  expose_9<int>();  // { dg-message "required from here" }
> > +  expose_10<int>();  // { dg-message "required from here" }
> > +  expose_11<int>();  // { dg-message "required from here" }
> > +
> > +  expose_var<int>;  // { dg-message "required from here" }
> > +}
> > +
> > +// { dg-error "instantiation exposes TU-local entity" "" { target *-*-* } 
> > 0 }
> > diff --git a/gcc/testsuite/g++.dg/modules/internal-8_a.C 
> > b/gcc/testsuite/g++.dg/modules/internal-8_a.C
> > new file mode 100644
> > index 00000000000..46e07a23cf0
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/internal-8_a.C
> > @@ -0,0 +1,35 @@
> > +// { dg-additional-options "-fmodules-ts -Wtemplate-names-tu-local" }
> > +// Non-ODR usages of const variables currently are erroneously
> > +// reported in templates and constexpr functions; this test
> > +// XFAILS them until we can implement a fix.
> > +
> > +export module M;
> > +
> > +namespace { struct internal_t {}; };
> > +static const int value = 123;
> > +static const int& ref = 456;
> > +static const internal_t internal {};
> > +
> > +constexpr void f(int) {}
> > +
> > +export constexpr
> > +void no_odr_use_cexpr() {  // { dg-bogus "TU-local" "" { xfail *-*-* } }
> > +  int x = value;
> > +  int y = ref;
> > +  int z = (internal, 0);
> > +
> > +  value;
> > +  bool b = value < value;
> > +  f(value);
> > +}
> > +
> > +export template <typename T>
> > +void no_odr_use_templ() {  // { dg-bogus "TU-local" "" { xfail *-*-* } }
> > +  int x = value;
> > +  int y = ref;
> > +  int z = (internal, 0);
> > +
> > +  value;
> > +  bool b = value < value;
> > +  f(value);
> > +}
> > diff --git a/gcc/testsuite/g++.dg/modules/xtreme-header-8.C 
> > b/gcc/testsuite/g++.dg/modules/xtreme-header-8.C
> > new file mode 100644
> > index 00000000000..82c0b59fefe
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/xtreme-header-8.C
> > @@ -0,0 +1,8 @@
> > +// PR c++/115126
> > +// { dg-additional-options "-fmodules-ts -Wtemplate-names-tu-local" }
> > +// { dg-module-cmi xstd }
> > +
> > +export module xstd;
> > +extern "C++" {
> > +  #include "xtreme-header.h"
> > +}
> > -- 
> > 2.46.0
> > 
> > 
> 

Reply via email to