diff mbox series

[v3,2/5] c++/modules: Ignore TU-local entities where necessary

Message ID 6709288e.a70a0220.3b7655.8be9@mx.google.com
State New
Headers show
Series c++/modules: Implement P1815 "Translation-unit-local entities" | expand

Commit Message

Nathaniel Shead Oct. 11, 2024, 1:30 p.m. UTC
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.

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 <nathanieloshead@gmail.com>
Reviewed-by: Jason Merrill <jason@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 mbox series

Patch

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;
+
   ++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);
+
 	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"
+}