See attached patch. This bug found when trying to build the Formal package.

Developed with with Assistance from Claude Sonnet 4.6. Two new testsuite files provided. The change wiggled on a few exiting test cases.

Regression tested on x86_64 Linux.

OK for mainline?  What about backport?

Regards,

Jerry

----

fortran: module-contained PRIVATE procedures must have global
 ELF linkage [PR125430]

gcc/fortran/ChangeLog:

        PR fortran/125430
        * trans-decl.cc (build_function_decl): Set TREE_PUBLIC for all
        module-contained procedures so submodules compiled as separate
        translation units can reach them via host association.  Also set
        DECL_VISIBILITY to VISIBILITY_HIDDEN for PRIVATE procedures,
        matching the existing treatment of module variables.

gcc/testsuite/ChangeLog:

        PR fortran/125430
        * gfortran.dg/module_private_2.f90: Remove scan-tree-dump-times
        assertion for 'priv'; PRIVATE module procedures now have global
        linkage with hidden visibility and are no longer optimized away.
        * gfortran.dg/public_private_module_2.f90: Add xfail markers to
        scan-assembler-not for 'two' and 'six'; update comment to mention
        procedures alongside variables.
        * gfortran.dg/public_private_module_7.f90: Add xfail marker to
        scan-assembler-not for '__m_common_attrs_MOD_other'.
        * gfortran.dg/public_private_module_8.f90: Add xfail marker to
        scan-assembler-not for '__m_MOD_myotherlen'.
        * gfortran.dg/submodule_private_host.f90: New test.
        * gfortran.dg/submodule_private_host_aux.f90: New auxiliary file.
        * gfortran.dg/warn_unused_function_2.f90: Remove 'defined but not
        used' expectation for s1; PRIVATE module procedures now have
        global linkage and no longer trigger the unused-function warning.

Assisted by: Claude Sonnet 4.6
---
From 1a42c92126a0414079f69e0e5d4d974f2ffc4b9d Mon Sep 17 00:00:00 2001
From: Jerry DeLisle <[email protected]>
Date: Fri, 22 May 2026 21:56:34 -0700
Subject: [PATCH] fortran: module-contained PRIVATE procedures must have global
 ELF linkage [PR125430]

gcc/fortran/ChangeLog:

	PR fortran/125430
	* trans-decl.cc (build_function_decl): Set TREE_PUBLIC for all
	module-contained procedures so submodules compiled as separate
	translation units can reach them via host association.  Also set
	DECL_VISIBILITY to VISIBILITY_HIDDEN for PRIVATE procedures,
	matching the existing treatment of module variables.

gcc/testsuite/ChangeLog:

	PR fortran/125430
	* gfortran.dg/module_private_2.f90: Remove scan-tree-dump-times
	assertion for 'priv'; PRIVATE module procedures now have global
	linkage with hidden visibility and are no longer optimized away.
	* gfortran.dg/public_private_module_2.f90: Add xfail markers to
	scan-assembler-not for 'two' and 'six'; update comment to mention
	procedures alongside variables.
	* gfortran.dg/public_private_module_7.f90: Add xfail marker to
	scan-assembler-not for '__m_common_attrs_MOD_other'.
	* gfortran.dg/public_private_module_8.f90: Add xfail marker to
	scan-assembler-not for '__m_MOD_myotherlen'.
	* gfortran.dg/submodule_private_host.f90: New test.
	* gfortran.dg/submodule_private_host_aux.f90: New auxiliary file.
	* gfortran.dg/warn_unused_function_2.f90: Remove 'defined but not
	used' expectation for s1; PRIVATE module procedures now have
	global linkage and no longer trigger the unused-function warning.

Assisted by: Claude Sonnet 4.6
---
 gcc/fortran/trans-decl.cc                     | 24 +++++++++++++--
 .../gfortran.dg/module_private_2.f90          |  3 +-
 .../gfortran.dg/public_private_module_2.f90   |  8 +++--
 .../gfortran.dg/public_private_module_7.f90   |  2 +-
 .../gfortran.dg/public_private_module_8.f90   |  2 +-
 .../gfortran.dg/submodule_private_host.f90    | 29 +++++++++++++++++++
 .../submodule_private_host_aux.f90            | 17 +++++++++++
 .../gfortran.dg/warn_unused_function_2.f90    |  2 +-
 8 files changed, 78 insertions(+), 9 deletions(-)
 create mode 100644 gcc/testsuite/gfortran.dg/submodule_private_host.f90
 create mode 100644 gcc/testsuite/gfortran.dg/submodule_private_host_aux.f90

diff --git a/gcc/fortran/trans-decl.cc b/gcc/fortran/trans-decl.cc
index 1bcbfdfd2c9..969fe75b946 100644
--- a/gcc/fortran/trans-decl.cc
+++ b/gcc/fortran/trans-decl.cc
@@ -2575,11 +2575,31 @@ build_function_decl (gfc_symbol * sym, bool global)
 	      && flag_module_private)))
     sym->attr.access = ACCESS_PRIVATE;
 
+  /* A procedure contained directly in a module must be globally addressable
+     even when its Fortran access is PRIVATE: a submodule compiled as a
+     separate translation unit can reach it via host association.  The
+     Fortran PRIVATE attribute restricts USE-association visibility, not
+     ELF symbol linkage.  */
+  bool in_module_contains = sym->module && sym->ns->proc_name
+			     && sym->ns->proc_name->attr.flavor == FL_MODULE;
+
   if (!current_function_decl
       && !sym->attr.entry_master && !sym->attr.is_main_program
       && (sym->attr.access != ACCESS_PRIVATE || sym->binding_label
-	  || sym->attr.public_used))
-    TREE_PUBLIC (fndecl) = 1;
+	  || sym->attr.public_used || in_module_contains))
+    {
+      TREE_PUBLIC (fndecl) = 1;
+      /* Mirror the variable treatment (see gfc_finish_var_decl): PRIVATE
+	 module procedures get global linkage but hidden visibility so the
+	 symbol is reachable from submodules in the same link without being
+	 exported to external DSOs.  */
+      if (in_module_contains && sym->attr.access == ACCESS_PRIVATE
+	  && !sym->attr.public_used)
+	{
+	  DECL_VISIBILITY (fndecl) = VISIBILITY_HIDDEN;
+	  DECL_VISIBILITY_SPECIFIED (fndecl) = true;
+	}
+    }
 
   if (sym->attr.referenced || sym->attr.entry_master)
     TREE_USED (fndecl) = 1;
diff --git a/gcc/testsuite/gfortran.dg/module_private_2.f90 b/gcc/testsuite/gfortran.dg/module_private_2.f90
index 58dbb1e23fe..926da364182 100644
--- a/gcc/testsuite/gfortran.dg/module_private_2.f90
+++ b/gcc/testsuite/gfortran.dg/module_private_2.f90
@@ -29,6 +29,7 @@ contains
     b => export1
   end subroutine pub
 end module m
-! { dg-final { scan-tree-dump-times "priv" 0 "optimized" } }
+! priv now has TREE_PUBLIC (VISIBILITY_HIDDEN) for submodule host-association,
+! so it is no longer optimized away.
 ! { dg-final { scan-tree-dump-times "export1 \\(\\)" 1 "optimized" } }
 ! { dg-final { scan-tree-dump-times "export2 \\(\\)" 1 "optimized" } }
diff --git a/gcc/testsuite/gfortran.dg/public_private_module_2.f90 b/gcc/testsuite/gfortran.dg/public_private_module_2.f90
index 87276ccdfd1..e37cfbd57cc 100644
--- a/gcc/testsuite/gfortran.dg/public_private_module_2.f90
+++ b/gcc/testsuite/gfortran.dg/public_private_module_2.f90
@@ -19,8 +19,10 @@ module mod
   integer, bind(C,name='') :: qq
 end module mod
 
-! The two xfails below have appeared with the introduction of submodules. 'iii' and
+! The xfails below have appeared with the introduction of submodules. 'iii' and
 ! 'mmm' now are TREE_PUBLIC but has DECL_VISIBILITY (decl) = VISIBILITY_HIDDEN set.
+! Similarly 'two' and 'six' (PRIVATE procedures) now have TREE_PUBLIC +
+! VISIBILITY_HIDDEN so that submodules can reach them via host association.
 
       ! { dg-final { scan-assembler "__mod_MOD_aa" } }
       ! { dg-final { scan-assembler-not "iii" { xfail *-*-* } } }
@@ -64,11 +66,11 @@ CONTAINS
 END MODULE
 
 ! { dg-final { scan-assembler "__m_MOD_one" } }
-! { dg-final { scan-assembler-not "two" } }
+! { dg-final { scan-assembler-not "two" { xfail *-*-* } } }
 ! { dg-final { scan-assembler "three" } }
 ! { dg-final { scan-assembler-not "four" } }
 ! { dg-final { scan-assembler "five" } }
-! { dg-final { scan-assembler-not "six" } }
+! { dg-final { scan-assembler-not "six" { xfail *-*-* } } }
 ! { dg-final { scan-assembler "seven" } }
 ! { dg-final { scan-assembler "nine" } }
 ! { dg-final { scan-assembler "__m_MOD_ten" } }
diff --git a/gcc/testsuite/gfortran.dg/public_private_module_7.f90 b/gcc/testsuite/gfortran.dg/public_private_module_7.f90
index d03b7047a12..4b62d23dc6f 100644
--- a/gcc/testsuite/gfortran.dg/public_private_module_7.f90
+++ b/gcc/testsuite/gfortran.dg/public_private_module_7.f90
@@ -25,5 +25,5 @@ contains
   end function get_key
 end module m_common_attrs
 
-! { dg-final { scan-assembler-not "__m_common_attrs_MOD_other" } }
+! { dg-final { scan-assembler-not "__m_common_attrs_MOD_other" { xfail *-*-* } } }
 ! { dg-final { scan-assembler "__m_common_attrs_MOD_get_key_len" } }
diff --git a/gcc/testsuite/gfortran.dg/public_private_module_8.f90 b/gcc/testsuite/gfortran.dg/public_private_module_8.f90
index bfc1b368f46..addf93440ab 100644
--- a/gcc/testsuite/gfortran.dg/public_private_module_8.f90
+++ b/gcc/testsuite/gfortran.dg/public_private_module_8.f90
@@ -44,6 +44,6 @@ contains
   end subroutine bar
 end module m
 
-! { dg-final { scan-assembler-not "__m_MOD_myotherlen" } }
+! { dg-final { scan-assembler-not "__m_MOD_myotherlen" { xfail *-*-* } } }
 ! { dg-final { scan-assembler "__m_MOD_bar" } }
 ! { dg-final { scan-assembler "__m_MOD_mylen" } }
diff --git a/gcc/testsuite/gfortran.dg/submodule_private_host.f90 b/gcc/testsuite/gfortran.dg/submodule_private_host.f90
new file mode 100644
index 00000000000..e50faaad111
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/submodule_private_host.f90
@@ -0,0 +1,29 @@
+! { dg-do run }
+! { dg-additional-sources submodule_private_host_aux.f90 }
+!
+! PR fortran/125430
+!
+! Module-contained procedures with PRIVATE access must retain global ELF
+! linkage so that submodules compiled as separate translation units can
+! reach them via host association.
+
+module m
+  implicit none
+  private
+  public :: pub
+
+  interface
+    module subroutine pub(x)
+      double precision, intent(out) :: x
+    end subroutine
+  end interface
+
+contains
+
+  pure function priv(n) result(x)
+    integer, intent(in) :: n
+    double precision :: x
+    x = dble(n) * 0.5d0
+  end function
+
+end module
diff --git a/gcc/testsuite/gfortran.dg/submodule_private_host_aux.f90 b/gcc/testsuite/gfortran.dg/submodule_private_host_aux.f90
new file mode 100644
index 00000000000..e237cf2650c
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/submodule_private_host_aux.f90
@@ -0,0 +1,17 @@
+! Auxiliary file for submodule_private_host.f90
+! { dg-skip-if "" { *-*-* } }
+submodule(m) ms
+  implicit none
+contains
+  module procedure pub
+    x = priv(6)
+  end procedure
+end submodule
+
+program p
+  use m, only : pub
+  implicit none
+  double precision x
+  call pub(x)
+  if (abs(x - 3.0d0) > 1d-10) stop 1
+end program
diff --git a/gcc/testsuite/gfortran.dg/warn_unused_function_2.f90 b/gcc/testsuite/gfortran.dg/warn_unused_function_2.f90
index 43b211e57bf..894a343b75c 100644
--- a/gcc/testsuite/gfortran.dg/warn_unused_function_2.f90
+++ b/gcc/testsuite/gfortran.dg/warn_unused_function_2.f90
@@ -13,7 +13,7 @@ module m
 
 contains
 
-  subroutine s1            ! { dg-warning "defined but not used" }
+  subroutine s1
     call s2(s3)
     contains
       subroutine s4        ! { dg-warning "defined but not used" }
-- 
2.54.0

Reply via email to