diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 52d53589e51..b782385c574 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7397,6 +7397,7 @@ inline bool module_exporting_p ()
 extern module_state *get_module (tree name, module_state *parent = NULL,
 				 bool partition = false);
 extern bool module_may_redeclare (tree decl);
+extern bool module_may_redeclare_friend_class (tree decl, tree friend_tmpl);
 
 extern bool module_global_init_needed ();
 extern bool module_determine_import_inits ();
@@ -7412,6 +7413,7 @@ extern unsigned get_importing_module (tree, bool = false) ATTRIBUTE_PURE;
 extern void set_instantiating_module (tree);
 extern void set_defining_module (tree);
 extern void maybe_key_decl (tree ctx, tree decl);
+extern void propagate_defining_module (tree decl, tree tmpl);
 
 extern void mangle_module (int m, bool include_partition);
 extern void mangle_module_fini ();
@@ -7644,6 +7646,7 @@ extern bool template_guide_p			(const_tree);
 extern bool builtin_guide_p			(const_tree);
 extern void store_explicit_specifier		(tree, tree);
 extern tree lookup_explicit_specifier		(tree);
+extern tree lookup_imported_hidden_friend	(tree);
 extern void walk_specializations		(bool,
 						 void (*)(bool, spec_entry *,
 							  void *),
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 8aab9ea0bae..cd3acca5e52 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -2693,6 +2693,11 @@ vec<tree, va_heap, vl_embed> *post_load_decls;
 typedef hash_map<tree, auto_vec<tree>> keyed_map_t;
 static keyed_map_t *keyed_table;
 
+/* Instantiations of temploid friends imported from another module
+   need to be owned by the same module as their instantiating template.
+   This maps these to the template that instantiated them.  */
+static hash_map<tree, tree> *imported_temploid_friends;
+
 /********************************************************************/
 /* Tree streaming.   The tree streaming is very specific to the tree
    structures themselves.  A tag indicates the kind of tree being
@@ -7884,6 +7889,14 @@ trees_out::decl_value (tree decl, depset *dep)
 	}
     }
 
+  if (TREE_CODE (decl) == TEMPLATE_DECL)
+    {
+      /* Write imported temploid friends so that importers can reconstruct
+	 this information on stream-in.  */
+      tree* slot = imported_temploid_friends->get (decl);
+      tree_node (slot ? *slot : NULL_TREE);
+    }
+
   bool is_typedef = false;
   if (!type && TREE_CODE (inner) == TYPE_DECL)
     {
@@ -8190,6 +8203,13 @@ trees_in::decl_value ()
 	}
     }
 
+  if (TREE_CODE (decl) == TEMPLATE_DECL)
+    if (tree owner = tree_node ())
+      {
+	bool exists = imported_temploid_friends->put (decl, owner);
+	gcc_assert (exists == !is_new);
+      }
+
   /* Regular typedefs will have a NULL TREE_TYPE at this point.  */
   unsigned tdef_flags = 0;
   bool is_typedef = false;
@@ -18717,6 +18737,12 @@ get_originating_module_decl (tree decl)
 	  && DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (decl))
 	decl = TYPE_NAME (DECL_CHAIN (decl));
 
+      /* An imported temploid friend is attached to the same module the
+	 befriending class was.  */
+      if (TREE_CODE (decl) == TEMPLATE_DECL)
+	if (tree *slot = imported_temploid_friends->get (decl))
+	  decl = *slot;
+
       int use;
       if (tree ti = node_template_info (decl, use))
 	{
@@ -18851,6 +18877,46 @@ module_may_redeclare (tree decl)
   return me && get_primary (them) == get_primary (me);
 }
 
+/* Like module_may_redeclare, but compares the attachment and module
+   with FRIEND_TMPL rather than the current scope.  */
+
+bool
+module_may_redeclare_friend_class (tree tmpl, tree friend_tmpl)
+{
+  tree inner = DECL_TEMPLATE_RESULT (tmpl);
+  tree friend_inner = DECL_TEMPLATE_RESULT (friend_tmpl);
+
+  bool tmpl_attached_p = (DECL_LANG_SPECIFIC (inner)
+			  && DECL_MODULE_ATTACH_P (inner));
+  bool friend_attached_p = (DECL_LANG_SPECIFIC (friend_inner)
+			    && DECL_MODULE_ATTACH_P (friend_inner));
+
+  if (!friend_attached_p)
+    /* We should be attached to the GMF if the befriending class is.  */
+    return !tmpl_attached_p;
+
+  if (!tmpl_attached_p)
+    /* The befriending class is attached to a named module, so should we. */
+    return false;
+
+  module_state *me = (*modules)[0];
+  module_state *them = me;
+
+  if (DECL_LANG_SPECIFIC (inner)
+      && DECL_MODULE_IMPORT_P (inner))
+    if (tree* slot = imported_temploid_friends->get (tmpl))
+      me = import_entity_module (import_entity_index (*slot));
+
+  if (DECL_LANG_SPECIFIC (friend_inner)
+      && DECL_MODULE_IMPORT_P (friend_inner))
+    them = import_entity_module (import_entity_index (friend_tmpl));
+
+  if (me == them)
+    return true;
+
+  return me && get_primary (me) == get_primary (them);
+}
+
 /* DECL is being created by this TU.  Record it came from here.  We
    record module purview, so we can see if partial or explicit
    specialization needs to be written out, even though its purviewness
@@ -18985,6 +19051,34 @@ maybe_key_decl (tree ctx, tree decl)
   vec.safe_push (decl);
 }
 
+/* DECL is an instantiated friend that should be attached to the same
+   module that TMPL is.  */
+
+void
+propagate_defining_module (tree decl, tree tmpl)
+{
+  if (!modules_p ())
+    return;
+
+  tree not_tmpl = DECL_TEMPLATE_RESULT (tmpl);
+  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_ATTACH_P (not_tmpl))
+    {
+      tree inner = DECL_TEMPLATE_RESULT (decl);
+      retrofit_lang_decl (inner);
+      DECL_MODULE_ATTACH_P (inner) = true;
+    }
+
+  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_IMPORT_P (not_tmpl))
+    {
+      bool exists = imported_temploid_friends->put (decl, tmpl);
+
+      /* We should only be called if lookup for an imported decl
+	 failed in tsubst_friend_class, in which case there shouldn't
+	 already be an entry in the map.  */
+      gcc_assert (!exists);
+    }
+}
+
 /* Create the flat name string.  It is simplest to have it handy.  */
 
 void
@@ -20200,6 +20294,8 @@ init_modules (cpp_reader *reader)
       vec_safe_reserve (entity_ary, EXPERIMENT (1, 400));
     }
 
+  imported_temploid_friends = new hash_map<tree,tree> (EXPERIMENT (1, 400));
+
 #if CHECKING_P
   note_defs = note_defs_table_t::create_ggc (1000);
 #endif
diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
index dce4caf8981..4e79ee84aa2 100644
--- a/gcc/cp/name-lookup.cc
+++ b/gcc/cp/name-lookup.cc
@@ -4453,6 +4453,48 @@ push_local_binding (tree id, tree decl, bool is_using)
   add_decl_to_level (b, decl);
 }
 
+/* Lookup the FRIEND_TMPL within all module imports.  Used to dedup
+   instantiations of temploid hidden friends from imported modules.  */
+
+tree
+lookup_imported_hidden_friend (tree friend_tmpl)
+{
+  tree inner = DECL_TEMPLATE_RESULT (friend_tmpl);
+  if (!DECL_LANG_SPECIFIC (inner)
+      || !DECL_MODULE_IMPORT_P (inner))
+    return NULL_TREE;
+
+  tree name = DECL_NAME (inner);
+  tree *slot = find_namespace_slot (current_namespace, name);
+  if (!slot || !*slot || TREE_CODE (*slot) != BINDING_VECTOR)
+    return NULL_TREE;
+
+  /* Look in the appropriate slot, as with check_module_override.  */
+  binding_slot mslot;
+  if (named_module_p ())
+    mslot = BINDING_VECTOR_CLUSTER (*slot, BINDING_SLOT_PARTITION
+				    / BINDING_VECTOR_SLOTS_PER_CLUSTER)
+      .slots[BINDING_SLOT_PARTITION % BINDING_VECTOR_SLOTS_PER_CLUSTER];
+  else
+    mslot = BINDING_VECTOR_CLUSTER (*slot, 0).slots[BINDING_SLOT_GLOBAL];
+  gcc_assert (!mslot.is_lazy ());
+
+  tree ovl = mslot;
+  if (!ovl)
+    return NULL_TREE;
+
+  /* We're only interested in declarations coming from the same module
+     of the friend class we're attempting to instantiate.  */
+  int m = get_originating_module (friend_tmpl);
+  gcc_assert (m != 0);
+
+  for (ovl_iterator iter (ovl); iter; ++iter)
+    if (get_originating_module (*iter) == m)
+      return *iter;
+
+  return NULL_TREE;
+}
+
 
 /* true means unconditionally make a BLOCK for the next level pushed.  */
 
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 8cf0d5b7a8d..1e0d3efe5d2 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -11693,6 +11693,12 @@ tsubst_friend_class (tree friend_tmpl, tree args)
   tmpl = lookup_name (DECL_NAME (friend_tmpl), LOOK_where::CLASS_NAMESPACE,
 		      LOOK_want::NORMAL | LOOK_want::HIDDEN_FRIEND);
 
+  if (!tmpl)
+    /* If we didn't find by name lookup, the type may still exist but as a
+       'hidden' import; we should check for this too to avoid accidentally
+       instantiating a duplicate.  */
+    tmpl = lookup_imported_hidden_friend (friend_tmpl);
+
   if (tmpl && DECL_CLASS_TEMPLATE_P (tmpl))
     {
       /* The friend template has already been declared.  Just
@@ -11701,6 +11707,16 @@ tsubst_friend_class (tree friend_tmpl, tree args)
 	 of course.  We only need the innermost template parameters
 	 because that is all that redeclare_class_template will look
 	 at.  */
+
+      if (modules_p ()
+	  && !module_may_redeclare_friend_class (tmpl, friend_tmpl))
+	{
+	  auto_diagnostic_group d;
+	  error_at (DECL_SOURCE_LOCATION (friend_tmpl),
+		    "cannot declare %qD in different module", tmpl);
+	  inform (DECL_SOURCE_LOCATION (tmpl), "previously declared here");
+	}
+
       if (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (friend_tmpl))
 	  > TMPL_ARGS_DEPTH (args))
 	{
@@ -11750,6 +11766,10 @@ tsubst_friend_class (tree friend_tmpl, tree args)
 						     args, tf_warning_or_error);
 	    }
 
+	  /* We need to propagate the attachment of the original template to the
+	     newly instantiated template type.  */
+	  propagate_defining_module (tmpl, friend_tmpl);
+
 	  /* Inject this template into the enclosing namspace scope.  */
 	  tmpl = pushdecl_namespace_level (tmpl, /*hiding=*/true);
 	}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10.h b/gcc/testsuite/g++.dg/modules/tpl-friend-10.h
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
new file mode 100644
index 00000000000..7547326e554
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
@@ -0,0 +1,15 @@
+// PR c++/105320
+// { dg-additional-options "-fmodules-ts -Wno-global-module" }
+// { dg-module-cmi test_support }
+
+module;
+template<class> struct _Sp_atomic;
+template<class> struct shared_ptr {
+  template<class> friend struct _Sp_atomic;
+  using atomic_type = _Sp_atomic<int>;
+};
+export module test_support;
+export
+template<class T> struct A {
+   shared_ptr<T> data;
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
new file mode 100644
index 00000000000..6b88ee4258b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
@@ -0,0 +1,5 @@
+// PR c++/105320
+// { dg-additional-options "-fmodules-ts" }
+
+import test_support;
+A<int> a;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
new file mode 100644
index 00000000000..789bdeb64d5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
@@ -0,0 +1,7 @@
+// PR c++/105320
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi user }
+
+export module user;
+import test_support; 
+A<int> b;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
new file mode 100644
index 00000000000..f29eebd1a7f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
@@ -0,0 +1,14 @@
+// PR c++/114275
+// { dg-additional-options "-fmodules-ts -Wno-global-module" }
+// { dg-module-cmi M }
+
+module;
+
+template <typename... _Elements> struct T;
+
+template <typename H> struct T<H> {
+  template <typename...> friend struct T;
+};
+
+export module M;
+export template <typename=void> void fun() { T<int> t; }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
new file mode 100644
index 00000000000..5bf79998139
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
@@ -0,0 +1,5 @@
+// PR c++/114275
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+int main() { fun(); }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
new file mode 100644
index 00000000000..216dbf62c71
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
@@ -0,0 +1,10 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:A }
+
+module M:A;
+
+template <typename T> struct A {
+  template <typename U> friend struct B;
+private:
+  int x = 42;
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
new file mode 100644
index 00000000000..26e1c38b518
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
@@ -0,0 +1,9 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:B }
+
+export module M:B;
+import :A;
+
+export template <typename U> struct B {
+  int foo(A<U> a) { return a.x; }
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
new file mode 100644
index 00000000000..e44c2819cfd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
@@ -0,0 +1,10 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:C }
+
+export module M:C;
+import :A;
+
+template <typename T> struct B;
+export template <typename T, typename U> int bar(B<T> t, U u) {
+  return t.foo(u);
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
new file mode 100644
index 00000000000..9a575ad5046
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
@@ -0,0 +1,8 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+export import :B;
+export import :C;
+
+export int go_in_module();
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
new file mode 100644
index 00000000000..329d1e8b263
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
@@ -0,0 +1,7 @@
+// { dg-additional-options "-fmodules-ts" }
+
+module M;
+
+int go_in_module() {
+  return bar(B<int>{}, A<int>{});
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
new file mode 100644
index 00000000000..c9855663fbd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
@@ -0,0 +1,8 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+
+int main() {
+  B<double> b{};
+  go_in_module();
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
new file mode 100644
index 00000000000..dda5c544564
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
@@ -0,0 +1,7 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+export template <typename> struct A {
+  template <typename> friend struct B;
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
new file mode 100644
index 00000000000..1fa5cc7b57b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
@@ -0,0 +1,5 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+A<int> a;
+template <typename> struct B {};  // { dg-error "different module" }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
new file mode 100644
index 00000000000..1f293fe0785
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
@@ -0,0 +1,7 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+template <typename> struct B {};  // { dg-message "previously declared here" }
+A<int> a;  // { dg-message "required from here" }
+
+// { dg-error "different module" "" { target *-*-* } 0 }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
new file mode 100644
index 00000000000..34eb3249186
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
@@ -0,0 +1,6 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi O }
+
+export module O;
+export import M;
+A<int> a;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
new file mode 100644
index 00000000000..6d50bc487b3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
@@ -0,0 +1,8 @@
+// { dg-additional-options "-fmodules-ts" }
+
+template <typename> struct B {};  // { dg-message "previous declaration" }
+import O;
+A<double> b;  // { dg-message "required from here" }
+
+// specifically, B is defined in M, not O, despite the instantiation being in O
+// { dg-error "conflicting declaration \[^\n\r\]* B@M" "" { target *-*-* } 0 }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-9.C b/gcc/testsuite/g++.dg/modules/tpl-friend-9.C
new file mode 100644
index 00000000000..487b44327b0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-9.C
@@ -0,0 +1,11 @@
+// PR c++/114275
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+
+template<class> struct A {
+  template<class> friend struct B;
+};
+A<int> a;
+template<class> struct B { };
