diff mbox series

[07/10] c++/modules: Implement ignored TU-local exposures

Message ID 66f1fdbf.170a0220.11f512.6709@mx.google.com
State New
Headers show
Series c++/modules: Implement P1815 "Translation-unit-local entities" | expand

Commit Message

Nathaniel Shead Sept. 23, 2024, 11:46 p.m. UTC
Currently I just stream DECL_NAME in TU_LOCAL_ENTITYs for use in diagnostics,
but this feels perhaps insufficient.  Are there any better approached here?
Otherwise I don't think it matters too much, as which entity it is will also
be hopefully clear from the 'declared here' notes.

I've put the new warning in Wextra, but maybe it would be better to just
leave it out of any of the normal warning groups since there's currently
no good way to work around the warnings it produces?

Bootstrapped and regtested on x86_64-pc-linux-gnu, 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.

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, '-Wignored-exposures', 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.

gcc/c-family/ChangeLog:

	* c.opt: New warning '-Wignored-exposures'.

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_IGNORED_EXPOSURE_BIT.
	(depset::is_ignored_exposure): New accessor.
	(depset::has_defn): Override for TU-local entities.
	(depset::hash::ignore_exposure): 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 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): Handle ignored exposures.
	(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
	Wignored-exposures.
	(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 -Wignored-exposures.

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.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.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                            | 303 +++++++++++++++++---
 gcc/cp/pt.cc                                |  98 ++++++-
 gcc/doc/invoke.texi                         |  19 +-
 gcc/testsuite/g++.dg/modules/internal-5_a.C | 104 +++++++
 gcc/testsuite/g++.dg/modules/internal-5_b.C |  29 ++
 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 ++
 12 files changed, 657 insertions(+), 48 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

Comments

Jason Merrill Sept. 27, 2024, 3:56 p.m. UTC | #1
On 9/23/24 7:46 PM, Nathaniel Shead wrote:
> Currently I just stream DECL_NAME in TU_LOCAL_ENTITYs for use in diagnostics,
> but this feels perhaps insufficient.  Are there any better approached here?
> Otherwise I don't think it matters too much, as which entity it is will also
> be hopefully clear from the 'declared here' notes.
> 
> I've put the new warning in Wextra, but maybe it would be better to just
> leave it out of any of the normal warning groups since there's currently
> no good way to work around the warnings it produces?
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu, 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.
> 
> 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, '-Wignored-exposures', 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.

I disagree with the term "ignored exposure", in the warning name and in 
the rest of the patch; these references are not exposures.  It's the 
naming of a TU-local entity that is ignored in basic.link/14.

I like the warning, just would change the name. 
"-Wtemplate-names-tu-local"?  "-Wtu-local-in-template"?

I'm not too concerned about false positives, as long as it can be 
effectively suppressed with #pragma GCC diagnostic ignored.  If you only 
use explicit instantiations you don't need to have the template body in 
the interface anyway.

> +	  /* 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.  */

You can't walk DECL_TEMPLATE_INSTANTIATIONS checking 
DECL_EXPLICIT_INSTANTIATION?

What happens with a non-templated, non-inline function body that names a 
TU-local entity?  I don't see any specific handling or testcase.  Should 
we just omit its definition, like you do in the previous patch for 
TU-local variable initializers?

Jason
Nathaniel Shead Sept. 28, 2024, 12:02 a.m. UTC | #2
On Fri, Sep 27, 2024 at 11:56:27AM -0400, Jason Merrill wrote:
> On 9/23/24 7:46 PM, Nathaniel Shead wrote:
> > Currently I just stream DECL_NAME in TU_LOCAL_ENTITYs for use in diagnostics,
> > but this feels perhaps insufficient.  Are there any better approached here?
> > Otherwise I don't think it matters too much, as which entity it is will also
> > be hopefully clear from the 'declared here' notes.
> > 
> > I've put the new warning in Wextra, but maybe it would be better to just
> > leave it out of any of the normal warning groups since there's currently
> > no good way to work around the warnings it produces?
> > 
> > Bootstrapped and regtested on x86_64-pc-linux-gnu, 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.
> > 
> > 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, '-Wignored-exposures', 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.
> 
> I disagree with the term "ignored exposure", in the warning name and in the
> rest of the patch; these references are not exposures.  It's the naming of a
> TU-local entity that is ignored in basic.link/14.
> 
> I like the warning, just would change the name. "-Wtemplate-names-tu-local"?
> "-Wtu-local-in-template"?
> 

I wasn't too happy with "ignored exposures" either, I much prefer these
names.  I'll use these in the next version of this patch.

> I'm not too concerned about false positives, as long as it can be
> effectively suppressed with #pragma GCC diagnostic ignored.  If you only use
> explicit instantiations you don't need to have the template body in the
> interface anyway.
> 

The main case I'm thinking of that would be annoying would be

  static void tu_local_fn() { /* ... */ }

  export template <typename T>
  void foo() {
    if constexpr (std::is_same_v<T, int>)
      tu_local_fn();
    // ...
  }
  template foo<int>();

But this should be able to silenced with #pragma so I suppose that's OK
for Wextra (and is probably fairly unusual code to begin with).  And
even more so if the following is fixed...

> > +	  /* 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.  */
> 
> You can't walk DECL_TEMPLATE_INSTANTIATIONS checking
> DECL_EXPLICIT_INSTANTIATION?
> 

I tried, but it looks like DECL_TEMPLATE_INSTANTIATIONS doesn't include
explicit instantiations for function templates definitions once we get
to module processing: it's only recorded for namespace-scope primary
function templates without definitions.

I think I would be able to update 'register_specialization' to more
eagerly include instantiations for every function template in this list,
but I wasn't sure if we wanted to do that just for this if it's not
already necessary, since that would potentially grow it quite a lot.

It strikes me as I write this though that the fact we currently
correctly mark GMF explicit instantiations as reachable is not because
looking through DECL_TEMPLATE_INSTANTIATIONS works, but because of the
issue we've discussed earlier where GMF instantiations are deferred
until the end of the TU and then get marked as purview... so I suppose
we do need to fix this anyway in anticipation of solving that issue.

See e.g.

  module;
  template <typename T> void foo();
  template void foo<int>();
  export module M;

with -fdump-lang-module-graph which shows that we emit the instantiation
despite never referencing it from purview.

> What happens with a non-templated, non-inline function body that names a
> TU-local entity?  I don't see any specific handling or testcase.  Should we
> just omit its definition, like you do in the previous patch for TU-local
> variable initializers?
> 

Non-template, non-inline function bodies already have their definitions
omitted by has_definition, so this patch didn't need to do anything
special.  There's a testcase for this in internal-5, see e.g.

  // 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>();

and matching calls in the importer.  I should probably add a test for
explicit specialisations too, however; I'll update that in the next
version.

Nathaniel
diff mbox series

Patch

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index ec23249c959..8383210503d 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -828,6 +828,10 @@  Wif-not-aligned
 C ObjC C++ ObjC++ Var(warn_if_not_aligned) Init(1) Warning
 Warn when the field in a struct is not aligned.
 
+Wignored-exposures
+C++ ObjC++ Var(warn_ignored_exposures) Warning EnabledBy(Wextra)
+Warn about ignored exposures of TU-local entities in a module.
+
 Wignored-qualifiers
 C C++ Var(warn_ignored_qualifiers) Warning EnabledBy(Wextra)
 Warn whenever type qualifiers are ignored.
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 99fbd905896..85c1d23c240 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -1779,6 +1779,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
+   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.  */
@@ -1812,7 +1828,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.  */
@@ -1843,6 +1860,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 ff6891ff8d6..7b1e69cb4c0 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -2332,6 +2332,7 @@  private:
     DB_IS_MEMBER_BIT,		/* Is an out-of-class member.  */
     DB_TU_LOCAL_BIT,		/* It is a TU-local entity.  */
     DB_EXPOSURE_BIT,		/* Exposes a TU-local entity.  */
+    DB_IGNORED_EXPOSURE_BIT,	/* Will stream a TU-local entity.  */
     DB_IMPORTED_BIT,		/* An imported entity.  */
     DB_UNREACHED_BIT,		/* A yet-to-be reached entity.  */
     DB_HIDDEN_BIT,		/* A hidden binding.  */
@@ -2401,7 +2402,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:
@@ -2420,6 +2423,10 @@  public:
   {
     return get_flag_bit<DB_EXPOSURE_BIT> ();
   }
+  bool is_ignored_exposure () const
+  {
+    return get_flag_bit<DB_IGNORED_EXPOSURE_BIT> ();
+  }
   bool is_import () const
   {
     return get_flag_bit<DB_IMPORTED_BIT> ();
@@ -2543,11 +2550,12 @@  public:
     depset *current;         /* Current depset being depended.  */
     unsigned section;	     /* When writing out, the section.  */
     bool reached_unreached;  /* We reached an unreached entity.  */
+    bool ignore_exposure;    /* In a context where exposures are ignored.  */
 
   public:
     hash (size_t size, hash *c = NULL)
       : parent (size), chain (c), current (NULL), section (0),
-	reached_unreached (false)
+	reached_unreached (false), ignore_exposure (false)
     {
       worklist.create (size);
     }
@@ -2748,6 +2756,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 +3041,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 +3072,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 +3124,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 +3165,7 @@  public:
 
 public:
   /* Serialize various definitions. */
-  void write_definition (tree decl);
+  void write_definition (tree decl, bool has_tu_local = false);
   void mark_declaration (tree decl, bool do_defn);
 
 private:
@@ -3166,6 +3193,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 +3202,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 +4331,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 +4886,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 +7843,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 +8435,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 +8950,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 +8977,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 +9136,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 +9418,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 +9488,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 expose such decls.
+     We only need to do this when writing a definition of an entity
+     that we know is an ignored exposure.  */
+  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;
 
@@ -10159,7 +10312,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);
 	}
@@ -11933,8 +12087,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_exposure,
+				   !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);
@@ -12036,6 +12200,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_exposure, VAR_P (decl));
+
   tree init = DECL_INITIAL (decl);
   tree_node (init);
   if (!init)
@@ -12223,21 +12391,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_exposure, 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 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.  */
+      /* 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))
@@ -12590,6 +12765,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));
@@ -12738,8 +12915,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 has_tu_local)
 {
+  auto ovr = make_temp_override (writing_local_entities,
+				 writing_local_entities || has_tu_local);
+
   if (streaming_p ())
     {
       assert_definition (decl);
@@ -13316,7 +13496,12 @@  depset::hash::add_dependency (depset *dep)
   current->deps.safe_push (dep);
 
   if (dep->is_tu_local ())
-    current->set_flag_bit<DB_EXPOSURE_BIT> ();
+    {
+      if (ignore_exposure)
+	current->set_flag_bit<DB_IGNORED_EXPOSURE_BIT> ();
+      else
+	current->set_flag_bit<DB_EXPOSURE_BIT> ();
+    }
 
   if (current->get_entity_kind () == EK_USING
       && DECL_IMPLICIT_TYPEDEF_P (dep->get_entity ())
@@ -13966,7 +14151,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
@@ -13978,15 +14163,16 @@  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->is_ignored_exposure ());
 		}
 	      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))
 		{
@@ -14175,6 +14361,36 @@  depset::hash::finalize_dependencies ()
 	  /* We should have emitted an error above.  */
 	  gcc_checking_assert (explained);
 	}
+      else if (warn_ignored_exposures && dep->is_ignored_exposure ())
+	{
+	  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 exposed = rdep->get_entity ();
+		auto_diagnostic_group d;
+		if (warning_at (DECL_SOURCE_LOCATION (decl),
+				OPT_Wignored_exposures,
+				"%qD refers to TU-local entity %qD and cannot "
+				"be instantiated in other TUs", decl, exposed))
+		  is_tu_local_entity (exposed, /*explain=*/true);
+		break;
+	      }
+	}
     }
 
   return ok;
@@ -15338,8 +15554,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))
@@ -15560,7 +15777,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->is_ignored_exposure ());
 
 	      if (!namer->has_defn ())
 		namer = b;
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 769e7999dac..2d0b65854be 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -9862,6 +9862,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
@@ -12727,8 +12792,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.  */
 
@@ -15506,7 +15573,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
@@ -16234,6 +16302,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;
 
@@ -18470,6 +18546,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;
+
   ++processing_template_decl;
   bool r = (potential_constant_expression (t)
 	    ? value_dependent_expression_p (t)
@@ -20202,6 +20284,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));
 
@@ -20894,6 +20979,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);
+
 	nargs = call_expr_nargs (t);
 	releasing_vec call_args;
 	tsubst_call_args (t, args, complain, in_decl, call_args);
@@ -21915,6 +22003,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 a6cd5111d47..10a407a2e24 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -258,7 +258,7 @@  in the following sections.
 -Weffc++ -Wno-elaborated-enum-base
 -Wno-exceptions -Wextra-semi -Wno-global-module -Wno-inaccessible-base
 -Wno-inherited-variadic-ctor  -Wno-init-list-lifetime
--Winvalid-constexpr -Winvalid-imported-macros
+-Wignored-exposures -Winvalid-constexpr -Winvalid-imported-macros
 -Wno-invalid-offsetof  -Wno-literal-suffix
 -Wmismatched-new-delete -Wmismatched-tags
 -Wmultiple-inheritance  -Wnamespaces  -Wnarrowing
@@ -4076,6 +4076,23 @@  the variable declaration statement.
 
 @end itemize
 
+@opindex Wignored-exposures
+@opindex Wno-ignored-exposures
+@item -Wignored-exposures
+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 error is suppressed.
+
+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 warning is enabled by @option{-Wextra}.
+
 @opindex Winvalid-constexpr
 @opindex Wno-invalid-constexpr
 @item -Winvalid-constexpr
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..a36fcb0d834
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/internal-5_a.C
@@ -0,0 +1,104 @@ 
+// { dg-additional-options "-fmodules-ts -Wignored-exposures" }
+// { 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>();
+
+
+// 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*>;
+
+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 = value;
+static const internal_t internal {};
+export inline void no_odr_use() {
+  int x = value;
+  int y = ref;
+  int z = (internal, 0);
+}
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..ecf78861fb7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/internal-5_b.C
@@ -0,0 +1,29 @@ 
+// { 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>();
+  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..47b7f2eb54e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/internal-7_a.C
@@ -0,0 +1,75 @@ 
+// { dg-additional-options "-fmodules-ts -Wignored-exposures" }
+// 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 }