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