diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 8e0fa3c5bfc..9aa86c939f6 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -6399,6 +6399,9 @@ trees_out::core_vals (tree t)
 
       /* Decls.  */
     case VAR_DECL:
+      if (streaming_p ())
+	if (CP_DECL_THREAD_LOCAL_P (t))
+	  u (decl_tls_model (t));
       if (DECL_CONTEXT (t)
 	  && TREE_CODE (DECL_CONTEXT (t)) != FUNCTION_DECL)
 	{
@@ -6691,6 +6694,22 @@ trees_out::core_vals (tree t)
       break;
     }
 
+  if (VAR_OR_FUNCTION_DECL_P (t)
+      && (DECL_EXTERNAL (t) || TREE_PUBLIC (t) || TREE_STATIC (t)))
+    {
+      if (streaming_p ())
+	{
+	  const char *name = DECL_SECTION_NAME (t);
+	  size_t len = name ? strlen (name) : 0;
+	  str (name, len);
+	}
+
+      tree comdat_group = NULL_TREE;
+      if (DECL_ONE_ONLY (t))
+	comdat_group = symtab_node::get (t)->get_comdat_group ();
+      WT (comdat_group);
+    }
+
   if (CODE_CONTAINS_STRUCT (code, TS_TYPED))
     {
       /* We want to stream the type of a expression-like nodes /after/
@@ -6933,6 +6952,11 @@ trees_in::core_vals (tree t)
 
       /* Decls.  */
     case VAR_DECL:
+      if (CP_DECL_THREAD_LOCAL_P (t))
+	{
+	  enum tls_model model = tls_model (u());
+	  set_decl_tls_model (t, model);
+	}
       if (DECL_CONTEXT (t)
 	  && TREE_CODE (DECL_CONTEXT (t)) != FUNCTION_DECL)
 	{
@@ -7227,6 +7251,23 @@ trees_in::core_vals (tree t)
       break;
     }
 
+  if (VAR_OR_FUNCTION_DECL_P (t)
+      && (DECL_EXTERNAL (t) || TREE_PUBLIC (t) || TREE_STATIC (t)))
+    {
+      size_t section_len = 0;
+      const char *section_name = str (&section_len);
+      if (section_len)
+	set_decl_section_name (t, section_name);
+
+      if (tree comdat_group = tree_node ())
+	{
+	  if (TREE_CODE (t) == FUNCTION_DECL)
+	    cgraph_node::get_create (t)->set_comdat_group (comdat_group);
+	  else
+	    varpool_node::get_create (t)->set_comdat_group (comdat_group);
+	}
+    }
+
   if (CODE_CONTAINS_STRUCT (code, TS_TYPED))
     {
       tree type = tree_node ();
@@ -8316,9 +8357,6 @@ trees_out::decl_value (tree decl, depset *dep)
 				   decl, cloned_p ? "" : "not ");
     }
 
-  if (streaming_p () && VAR_P (decl) && CP_DECL_THREAD_LOCAL_P (decl))
-    u (decl_tls_model (decl));
-
   if (streaming_p ())
     dump (dumper::TREE) && dump ("Written decl:%d %C:%N", tag,
 				 TREE_CODE (decl), decl);
@@ -8757,6 +8795,12 @@ trees_in::decl_value ()
 	    DECL_CONTEXT (parm) = e_inner;
 	}
 
+      /* We will not need to track the comdat group etc of the
+	 to-be-discarded decl, so remove it from the symbol table.  */
+      if (VAR_OR_FUNCTION_DECL_P (inner))
+	if (struct symtab_node *snode = symtab_node::get (inner))
+	  snode->remove ();
+
       /* And our result is the existing node.  */
       decl = existing;
     }
@@ -8802,13 +8846,6 @@ trees_in::decl_value ()
 	}
     }
 
-  if (VAR_P (decl) && CP_DECL_THREAD_LOCAL_P (decl))
-    {
-      enum tls_model model = tls_model (u ());
-      if (is_new)
-	set_decl_tls_model (decl, model);
-    }
-
   if (!NAMESPACE_SCOPE_P (inner)
       && ((TREE_CODE (inner) == TYPE_DECL
 	   && !is_typedef
@@ -12098,6 +12135,53 @@ trees_in::is_matching_decl (tree existing, tree decl, bool is_typedef)
   if (!DECL_EXTERNAL (d_inner))
     DECL_EXTERNAL (e_inner) = false;
 
+  if (VAR_P (d_inner))
+    {
+      if (CP_DECL_THREAD_LOCAL_P (d_inner) != CP_DECL_THREAD_LOCAL_P (e_inner))
+	{
+	  auto_diagnostic_group d;
+	  error_at (DECL_SOURCE_LOCATION (decl),
+		    "conflicting %<thread_local%> for %#qD", decl);
+	  inform (DECL_SOURCE_LOCATION (existing), "existing declaration here");
+	  return false;
+	}
+      if (CP_DECL_THREAD_LOCAL_P (d_inner)
+	  && decl_tls_model (d_inner) != decl_tls_model (e_inner))
+	{
+	  auto_diagnostic_group d;
+	  error_at (DECL_SOURCE_LOCATION (decl),
+		    "conflicting TLS model %qs for %q#D",
+		    tls_model_names[decl_tls_model (d_inner)], decl);
+	  inform (DECL_SOURCE_LOCATION (existing),
+		  "existing declaration here as %qs",
+		  tls_model_names[decl_tls_model (e_inner)]);
+	  return false;
+	}
+    }
+
+  if (VAR_OR_FUNCTION_DECL_P (d_inner))
+    {
+      if (DECL_SECTION_NAME (d_inner) != DECL_SECTION_NAME (e_inner))
+	{
+	  auto_diagnostic_group d;
+	  if (DECL_SECTION_NAME (d_inner))
+	    error_at (DECL_SOURCE_LOCATION (decl),
+		      "conflicting section %qs for %q#D",
+		      DECL_SECTION_NAME (d_inner), decl);
+	  else
+	    error_at (DECL_SOURCE_LOCATION (decl),
+		      "conflicting section for %q#D", decl);
+	  if (DECL_SECTION_NAME (e_inner))
+	    inform (DECL_SOURCE_LOCATION (existing),
+		    "existing declaration here in section %qs",
+		    DECL_SECTION_NAME (e_inner));
+	  else
+	    inform (DECL_SOURCE_LOCATION (existing),
+		    "existing declaration here with no section");
+	  return false;
+	}
+    }
+
   if (TREE_CODE (decl) == TEMPLATE_DECL)
     {
       /* Merge default template arguments.  */
diff --git a/gcc/testsuite/g++.dg/modules/attrib-3_a.C b/gcc/testsuite/g++.dg/modules/attrib-3_a.C
new file mode 100644
index 00000000000..ea265762cbf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/attrib-3_a.C
@@ -0,0 +1,9 @@
+// { dg-additional-options "-fmodules" }
+
+export module M;
+export extern "C++" {
+  inline void a() __attribute__((section("bar")));
+  inline void a() {}
+
+  inline void b() {}
+}
diff --git a/gcc/testsuite/g++.dg/modules/attrib-3_b.C b/gcc/testsuite/g++.dg/modules/attrib-3_b.C
new file mode 100644
index 00000000000..67c78567883
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/attrib-3_b.C
@@ -0,0 +1,14 @@
+// { dg-additional-options "-fmodules" }
+// { dg-error "conflicting section" "" { target *-*-* } 0 }
+
+inline void a() {}  // { dg-message "here" }
+
+inline void b() __attribute__((section("qux")));
+inline void b() {}  // { dg-message "here" }
+
+import M;
+
+int main() {
+  a();
+  b();
+}
