]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
PE/COFF: Implement visibility attribute via .drectve
authorOleg Tolmatcev <oleg.tolmatcev@gmail.com>
Sun, 9 Aug 2026 14:41:31 +0000 (16:41 +0200)
committerJonathan Yong <10walls@gmail.com>
Mon, 10 Aug 2026 14:49:08 +0000 (14:49 +0000)
On PE/COFF targets (MinGW, Cygwin), the visibility attribute was
previously ignored with a warning.  This patch makes it functional
by emitting -exclude-symbols directives into the .drectve section,
matching Clang's behavior.  The GNU linker already reads and
respects these directives during auto-export, so hidden/internal
symbols are now correctly excluded from DLL exports.

gcc/ChangeLog:

* config/mingw/winnt.cc (i386_pe_drectve_name): New.
(i386_pe_assemble_visibility): Emit -exclude-symbols
directives into .drectve for VISIBILITY_HIDDEN and
VISIBILITY_INTERNAL instead of warning.  Use the external
symbol spelling for 32-bit PE names.

gcc/testsuite/ChangeLog:

* gcc.target/i386/visibility-hidden-mingw.c: New test.
* gcc.target/i386/visibility-hidden-mingw-32.c: New test.

Signed-off-by: Oleg Tolmatcev <oleg.tolmatcev@gmail.com>
Signed-off-by: Jonathan Yong <10walls@gmail.com>
gcc/config/mingw/winnt.cc
gcc/testsuite/gcc.target/i386/visibility-hidden-mingw-32.c [new file with mode: 0644]
gcc/testsuite/gcc.target/i386/visibility-hidden-mingw.c [new file with mode: 0644]

index 66d7450652d24998de7da1b68a3eb6cd838d3601..2e06b0e8146210b663c41c2479bcda6ffcfcac2c 100644 (file)
@@ -254,20 +254,42 @@ i386_pe_maybe_mangle_decl_assembler_name (tree decl, tree id)
 
 #endif
 
+/* Return the symbol spelling used by .drectve exclude-symbols directives.
+   This is the external name without the user label prefix, while preserving
+   calling-convention decoration such as fastcall's leading '@' or a
+   stdcall suffix.  */
+
+static const char *
+i386_pe_drectve_name (tree id)
+{
+  const char *name = targetm.strip_name_encoding (IDENTIFIER_POINTER (id));
+  size_t prefix_len = strlen (user_label_prefix);
+
+  if (prefix_len != 0
+      && strncmp (name, user_label_prefix, prefix_len) == 0)
+    name += prefix_len;
+
+  return name;
+}
+
 /* Emit an assembler directive to set symbol for DECL visibility to
    the visibility type VIS, which must not be VISIBILITY_DEFAULT.
-   As for PE there is no hidden support in gas, we just warn for
-   user-specified visibility attributes.  */
+   Emit a -exclude-symbols directive into .drectve, compatible with
+   what Clang emits for hidden visibility on PE/COFF.  */
 
 void
-i386_pe_assemble_visibility (tree decl, int)
+i386_pe_assemble_visibility (tree decl, int vis)
 {
-  if (!decl
-      || !lookup_attribute ("visibility", DECL_ATTRIBUTES (decl)))
+  if (!decl)
     return;
-  if (!DECL_ARTIFICIAL (decl))
-    warning (OPT_Wattributes, "visibility attribute not supported "
-                             "in this configuration; ignored");
+
+  if (vis == VISIBILITY_HIDDEN || vis == VISIBILITY_INTERNAL)
+    {
+      tree id = DECL_ASSEMBLER_NAME (decl);
+      const char *name = i386_pe_drectve_name (id);
+      drectve_section ();
+      fprintf (asm_out_file, "\t.ascii \" -exclude-symbols:%s\"\n", name);
+    }
 }
 
 #if !defined (TARGET_AARCH64_MS_ABI)
diff --git a/gcc/testsuite/gcc.target/i386/visibility-hidden-mingw-32.c b/gcc/testsuite/gcc.target/i386/visibility-hidden-mingw-32.c
new file mode 100644 (file)
index 0000000..a952c71
--- /dev/null
@@ -0,0 +1,12 @@
+/* { dg-do compile { target i?86-*-mingw32* i?86-*-cygwin* } } */
+/* { dg-options "-fvisibility=hidden" } */
+
+void some_cdecl (int, int) {}
+void __attribute__((stdcall)) some_stdcall (int, int) {}
+void __attribute__((fastcall)) some_fastcall (int, int) {}
+
+/* Hidden visibility on 32-bit PE/COFF drops the user label prefix in
+   .drectve, but keeps stdcall and fastcall decoration.  */
+/* { dg-final { scan-assembler {-exclude-symbols:some_cdecl} } } */
+/* { dg-final { scan-assembler {-exclude-symbols:some_stdcall@8} } } */
+/* { dg-final { scan-assembler {-exclude-symbols:@some_fastcall@8} } } */
diff --git a/gcc/testsuite/gcc.target/i386/visibility-hidden-mingw.c b/gcc/testsuite/gcc.target/i386/visibility-hidden-mingw.c
new file mode 100644 (file)
index 0000000..12d7926
--- /dev/null
@@ -0,0 +1,24 @@
+/* { dg-do link { target *-*-mingw* *-*-cygwin* } } */
+/* { dg-require-dll "" } */
+/* { dg-options "-shared -fvisibility=hidden -Wl,--output-def,visibility-hidden-mingw.def" } */
+
+void __attribute__((visibility("default"))) exported_func(void) {}
+void hidden_func(void) {}
+void __attribute__((visibility("hidden"))) explicit_hidden_func(void) {}
+void __attribute__((visibility("internal"))) internal_func(void) {}
+
+/* exported_func has default visibility, so it should be exported.  */
+/* { dg-final { scan-file visibility-hidden-mingw.def "(?n)^\\s*exported_func(?:\\s+@\[0-9\]+)?$" } } */
+
+/* hidden_func gets hidden from -fvisibility=hidden, so it should not be
+   auto-exported.  */
+/* { dg-final { scan-file-not visibility-hidden-mingw.def "(?n)^\\s*hidden_func(?:\\s+@\[0-9\]+)?$" } } */
+
+/* explicit_hidden_func is explicitly hidden, so it should not be
+   auto-exported.  */
+/* { dg-final { scan-file-not visibility-hidden-mingw.def "(?n)^\\s*explicit_hidden_func(?:\\s+@\[0-9\]+)?$" } } */
+
+/* internal_func has internal visibility, so it should not be auto-exported.  */
+/* { dg-final { scan-file-not visibility-hidden-mingw.def "(?n)^\\s*internal_func(?:\\s+@\[0-9\]+)?$" } } */
+
+/* { dg-final { remove-build-file "visibility-hidden-mingw.def" } } */