diff mbox series

Plugin support on Windows/MinGW

Message ID boris.20171114093933@codesynthesis.com
State New
Headers show
Series Plugin support on Windows/MinGW | expand

Commit Message

Boris Kolpackov Nov. 14, 2017, 8:07 a.m. UTC
Hi,

The below patch implements GCC plugin support on MinGW. Some notes on the
functionality and the patch:

1. Due to the way dynamic libraries work on Windows there are limitations.
   Specifically, we have to produce import libraries for backend executables
   (cc1, cc1plus) and plugins have to link to them. For details see the
   changes to plugins.texi in the patch below.

2. Due to these limitations the plugin support on MinGW is only enabled if
   explicitly requested with --enable-plugin. Also, for now, only import
   libraries for cc1 and cc1plus are installed. I am, however, happy to
   adjust both of these if requested.

3. We have tested the plugin functionality on Windows with ODB[1] which
   is implemented as a GCC plugin for parsing C++. It worked essentially
   unmodified.

4. We have also tested for any regressions in the compiler itself by
   building a custom mingw-w64-gcc package from the MSYS2 project
   with plugins enabled and then using it to build various packages
   in our build bot infrastructure[3].

5. The patch also fixes a bug in the gengtype-lex.c's AIX workaround
   as well as a FIXME item in plugin.c (use of platform-specific
   dynamic library extensions).

Copyright assignment is on file.

[1] https://codesynthesis.com/products/odb/
[2] https://github.com/Alexpux/MINGW-packages/tree/master/mingw-w64-gcc
[3] https://stage.build2.org/?builds=&cf=windows_10-mingw_w64_gcc_7.2

Thanks,
Boris


config/ChangeLog:

2017-11-14 Boris Kolpackov  <boris@codesynthesis.com>

	* gcc-plugin.m4: Add support for MinGW.


gcc/ChangeLog:

2017-11-14 Boris Kolpackov  <boris@codesynthesis.com>

	* plugin.c (add_new_plugin): Use platform-specific library extensions.
	(try_init_one_plugin): Alternative implementation for MinGW.
	* Makefile.in (plugin_implib): New.
	(gengtype-lex.c): Fix broken AIX workaround.
	* configure: Regenerate.
	* doc/plugins.texi: Document support for MinGW.


gcc/c/ChangeLog:

2017-11-14 Boris Kolpackov  <boris@codesynthesis.com>

	* Make-lang.in (c.install-plugin): Install backend import library.


gcc/cp/ChangeLog:

2017-11-14 Boris Kolpackov  <boris@codesynthesis.com>

	* Make-lang.in (c++.install-plugin): Install backend import library.


libcc1/ChangeLog:

2017-11-14 Boris Kolpackov  <boris@codesynthesis.com>

	* configure: Regenerate.
diff mbox series

Patch

diff --git a/config/gcc-plugin.m4 b/config/gcc-plugin.m4
index dd06a58cd13..38b2ae6e12e 100644
--- a/config/gcc-plugin.m4
+++ b/config/gcc-plugin.m4
@@ -19,8 +19,21 @@  AC_DEFUN([GCC_ENABLE_PLUGINS],
    enable_plugin=yes; default_plugin=yes)
 
    pluginlibs=
+   plugin_check=yes
 
    case "${host}" in
+     *-*-mingw*)
+       # Since plugin support under MinGW is not as straightforward as on
+       # other platforms (e.g., we have to link import library, etc), we
+       # only enable it if explicitly requested.
+       if test x"$default_plugin" = x"yes"; then
+         enable_plugin=no
+       elif test x"$enable_plugin" = x"yes"; then
+         # Use make's target variable to derive import library name.
+         pluginlibs='-Wl,--export-all-symbols -Wl,--out-implib=[$]@.a'
+	 plugin_check=no
+       fi
+     ;;
      *-*-darwin*)
        if test x$build = x$host; then
 	 export_sym_check="nm${exeext} -g"
@@ -41,7 +54,7 @@  AC_DEFUN([GCC_ENABLE_PLUGINS],
      ;;
    esac
 
-   if test x"$enable_plugin" = x"yes"; then
+   if test x"$enable_plugin" = x"yes" -a x"$plugin_check" = x"yes"; then
 
      AC_MSG_CHECKING([for exported symbols])
      if test "x$export_sym_check" != x; then
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 824cf3e3ea0..fe71d010483 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -57,6 +57,7 @@  MAKEOVERRIDES =
 build=@build@
 host=@host@
 host_noncanonical=@host_noncanonical@
+host_os=@host_os@
 target=@target@
 target_noncanonical:=@target_noncanonical@
 
@@ -393,6 +394,11 @@  PLUGINLIBS = @pluginlibs@
 
 enable_plugin = @enable_plugin@
 
+# On MinGW plugin installation involves installing import libraries.
+ifeq ($(enable_plugin),yes)
+  plugin_implib := $(if $(strip $(filter mingw%,$(host_os))),yes,no)
+endif
+
 enable_host_shared = @enable_host_shared@
 
 enable_as_accelerator = @enable_as_accelerator@
@@ -2826,11 +2832,15 @@  $(genprog:%=build/gen%$(build_exeext)): build/gen%$(build_exeext): build/gen%.o
 	    $(filter-out $(BUILD_LIBDEPS), $^) $(BUILD_LIBS)
 
 # Generated source files for gengtype.  Prepend inclusion of
-# bconfig.h because AIX requires _LARGE_FILES to be defined before
+# config.h/bconfig.h because AIX requires _LARGE_FILES to be defined before
 # any system header is included.
 gengtype-lex.c : gengtype-lex.l
 	-$(FLEX) $(FLEXFLAGS) -o$@ $< && { \
-	  echo '#include "bconfig.h"' > $@.tmp; \
+          echo '#ifdef HOST_GENERATOR_FILE' > $@.tmp; \
+          echo '#include "config.h"'       >> $@.tmp; \
+          echo '#else'                     >> $@.tmp; \
+          echo '#include "bconfig.h"'      >> $@.tmp; \
+          echo '#endif'                    >> $@.tmp; \
           cat $@ >> $@.tmp; \
           mv $@.tmp $@; \
 	}
diff --git a/gcc/c/Make-lang.in b/gcc/c/Make-lang.in
index cfd8cd2b169..6b029d47cae 100644
--- a/gcc/c/Make-lang.in
+++ b/gcc/c/Make-lang.in
@@ -125,7 +125,14 @@  check-c : check-gcc
 
 c.install-common:
 c.install-man:
-c.install-plugin:
+
+c.install-plugin: installdirs
+# Install import library.
+ifeq ($(plugin_implib),yes)
+	$(mkinstalldirs) $(DESTDIR)$(plugin_resourcesdir)
+	$(INSTALL_DATA) cc1$(exeext).a $(DESTDIR)/$(plugin_resourcesdir)/cc1$(exeext).a
+endif
+
 c.uninstall:
 
 #
diff --git a/gcc/configure b/gcc/configure
index fb40ead9204..4d09b99948f 100755
--- a/gcc/configure
+++ b/gcc/configure
@@ -29619,8 +29619,21 @@  fi
 
 
    pluginlibs=
+   plugin_check=yes
 
    case "${host}" in
+     *-*-mingw*)
+       # Since plugin support under MinGW is not as straightforward as on
+       # other platforms (e.g., we have to link import library, etc), we
+       # only enable it if explicitly requested.
+       if test x"$default_plugin" = x"yes"; then
+         enable_plugin=no
+       elif test x"$enable_plugin" = x"yes"; then
+         # Use make's target variable to derive import library name.
+         pluginlibs='-Wl,--export-all-symbols -Wl,--out-implib=$@.a'
+	 plugin_check=no
+       fi
+     ;;
      *-*-darwin*)
        if test x$build = x$host; then
 	 export_sym_check="nm${exeext} -g"
@@ -29641,7 +29654,7 @@  fi
      ;;
    esac
 
-   if test x"$enable_plugin" = x"yes"; then
+   if test x"$enable_plugin" = x"yes" -a x"$plugin_check" = x"yes"; then
 
      { $as_echo "$as_me:${as_lineno-$LINENO}: checking for exported symbols" >&5
 $as_echo_n "checking for exported symbols... " >&6; }
diff --git a/gcc/cp/Make-lang.in b/gcc/cp/Make-lang.in
index c852f6a38b4..a84e37f6075 100644
--- a/gcc/cp/Make-lang.in
+++ b/gcc/cp/Make-lang.in
@@ -238,6 +238,11 @@  c++.install-plugin: installdirs
 	  $(mkinstalldirs) $(DESTDIR)$$dir; \
 	  $(INSTALL_DATA) $$path $(DESTDIR)$$dest; \
 	done
+# Install import library.
+ifeq ($(plugin_implib),yes)
+	$(mkinstalldirs) $(DESTDIR)$(plugin_resourcesdir)
+	$(INSTALL_DATA) cc1plus$(exeext).a $(DESTDIR)/$(plugin_resourcesdir)/cc1plus$(exeext).a
+endif
 
 c++.uninstall:
 	-rm -rf $(DESTDIR)$(bindir)/$(CXX_INSTALL_NAME)$(exeext)
diff --git a/gcc/doc/plugins.texi b/gcc/doc/plugins.texi
index 6fc7b9ae9a2..20e161f77ad 100644
--- a/gcc/doc/plugins.texi
+++ b/gcc/doc/plugins.texi
@@ -34,14 +34,17 @@  can be quite useful.
 @section Loading Plugins
 
 Plugins are supported on platforms that support @option{-ldl
--rdynamic}.  They are loaded by the compiler using @code{dlopen}
-and invoked at pre-determined locations in the compilation
-process.
+-rdynamic} as well as Windows/MinGW. They are loaded by the compiler
+using @code{dlopen} or equivalent and invoked at pre-determined
+locations in the compilation process.
 
 Plugins are loaded with
 
-@option{-fplugin=/path/to/@var{name}.so} @option{-fplugin-arg-@var{name}-@var{key1}[=@var{value1}]}
+@option{-fplugin=/path/to/@var{name}.@var{ext}} @option{-fplugin-arg-@var{name}-@var{key1}[=@var{value1}]}
 
+Where @var{name} is the plugin name and @var{ext} is the platform-specific
+dynamic library extension. It should be @code{dll} on Windows/MinGW,
+@code{dylib} on Darwin/Mac OS X, and @code{so} on all other platforms.
 The plugin arguments are parsed by GCC and passed to respective
 plugins as key-value pairs. Multiple plugins can be invoked by
 specifying multiple @option{-fplugin} arguments.
@@ -49,7 +52,7 @@  specifying multiple @option{-fplugin} arguments.
 A plugin can be simply given by its short name (no dots or
 slashes). When simply passing @option{-fplugin=@var{name}}, the plugin is
 loaded from the @file{plugin} directory, so @option{-fplugin=@var{name}} is
-the same as @option{-fplugin=`gcc -print-file-name=plugin`/@var{name}.so},
+the same as @option{-fplugin=`gcc -print-file-name=plugin`/@var{name}.@var{ext}},
 using backquote shell syntax to query the @file{plugin} directory.
 
 @node Plugin API
@@ -508,6 +511,48 @@  A single source file plugin may be built with @code{g++ -I`gcc
 plugin.so}, using backquote shell syntax to query the @file{plugin}
 directory.
 
+Plugin support on Windows/MinGW has a number of limitations and
+additional requirements. When building a plugin on Windows we have to
+link an import library for the corresponding backend executable, for
+example, @file{cc1.exe}, @file{cc1plus.exe}, etc., in order to gain
+access to the symbols provided by GCC. This means that on Windows a
+plugin is language-specific, for example, for C, C++, etc. If you wish
+to use your plugin with multiple languages, then you will need to
+build multiple plugin libraries and either instruct your users on how
+to load the correct version or provide a compiler wrapper that does
+this automatically.
+
+Additionally, on Windows the plugin library has to export the
+@code{plugin_is_GPL_compatible} and @code{plugin_init} symbols. If you
+do not wish to modify the source code of your plugin, then you can use
+the @option{-Wl,--export-all-symbols} option or provide a suitable DEF
+file. Alternatively, you can export just these two symbols by decorating
+them with @code{__declspec(dllexport)}, for example:
+
+@smallexample
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int plugin_is_GPL_compatible;
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int plugin_init (plugin_name_args *, plugin_gcc_version *)
+@end smallexample
+
+The import libraries are installed into the @code{plugin} directory
+and their names are derived by appending the @code{.a} extension to
+the backend executable names, for example, @file{cc1.exe.a},
+@file{cc1plus.exe.a}, etc. The following command line shows how to
+build the single source file plugin on Windows to be used with the C++
+compiler:
+
+@smallexample
+g++ -I`gcc -print-file-name=plugin`/include -shared -Wl,--export-all-symbols \
+-o plugin.dll plugin.c `gcc -print-file-name=plugin`/cc1plus.exe.a
+@end smallexample
+
 When a plugin needs to use @command{gengtype}, be sure that both
 @file{gengtype} and @file{gtype.state} have the same version as the
 GCC for which the plugin is built.
diff --git a/gcc/plugin.c b/gcc/plugin.c
index 9892748cd15..fb3e3590537 100644
--- a/gcc/plugin.c
+++ b/gcc/plugin.c
@@ -34,6 +34,16 @@  along with GCC; see the file COPYING3.  If not see
 #include "plugin-version.h"
 #endif
 
+#ifdef __MINGW32__
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+#include <windows.h>
+#endif
+
 #define GCC_PLUGIN_STRINGIFY0(X) #X
 #define GCC_PLUGIN_STRINGIFY1(X) GCC_PLUGIN_STRINGIFY0 (X)
 
@@ -144,7 +154,7 @@  get_plugin_base_name (const char *full_name)
   /* First get the base name part of the full-path name, i.e. NAME.so.  */
   char *base_name = xstrdup (lbasename (full_name));
 
-  /* Then get rid of '.so' part of the name.  */
+  /* Then get rid of the extension in the name, e.g., .so.  */
   strip_off_ending (base_name, strlen (base_name));
 
   return base_name;
@@ -175,12 +185,27 @@  add_new_plugin (const char* plugin_name)
   if (name_is_short)
     {
       base_name = CONST_CAST (char*, plugin_name);
-      /* FIXME: the ".so" suffix is currently builtin, since plugins
-	 only work on ELF host systems like e.g. Linux or Solaris.
-	 When plugins shall be available on non ELF systems such as
-	 Windows or MacOS, this code has to be greatly improved.  */
+
+#if defined(__MINGW32__)
+      static const char plugin_ext[] = ".dll";
+#elif defined(__APPLE__)
+      /* Mac OS has two types of libraries: dynamic libraries (.dylib) and
+         plugins (.bundle). Both can be used with dlopen()/dlsym() but the
+         former cannot be linked at build time (i.e., with the -lfoo linker
+         option). A GCC plugin is therefore probably a Mac OS plugin but their
+         use seems to be quite rare and the .bundle extension is more of a
+         recommendation rather than the rule. This raises the questions of how
+         well they are supported by tools (e.g., libtool). So to avoid
+         complications let's use the .dylib extension for now. In the future,
+         if this proves to be an issue, we can always check for both
+         extensions.  */
+      static const char plugin_ext[] = ".dylib";
+#else
+      static const char plugin_ext[] = ".so";
+#endif
+
       plugin_name = concat (default_plugin_dir_name (), "/",
-			    plugin_name, ".so", NULL);
+			    plugin_name, plugin_ext, NULL);
       if (access (plugin_name, R_OK))
 	fatal_error
 	  (input_location,
@@ -573,6 +598,85 @@  invoke_plugin_callbacks_full (int event, void *gcc_data)
 }
 
 #ifdef ENABLE_PLUGIN
+
+/* Try to initialize PLUGIN. Return true if successful. */
+
+#ifdef __MINGW32__
+
+// Return a message string for last error or NULL if unknown. Must be freed
+// with LocalFree().
+static inline char *
+win32_error_msg ()
+{
+  char *msg;
+  return FormatMessageA (FORMAT_MESSAGE_ALLOCATE_BUFFER |
+			 FORMAT_MESSAGE_FROM_SYSTEM |
+			 FORMAT_MESSAGE_IGNORE_INSERTS |
+			 FORMAT_MESSAGE_MAX_WIDTH_MASK,
+			 0,
+			 GetLastError (),
+			 MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
+			 (char*)&msg,
+			 0,
+			 0)
+    ? msg
+    : NULL;
+}
+
+static bool
+try_init_one_plugin (struct plugin_name_args *plugin)
+{
+  HMODULE dl_handle;
+  plugin_init_func plugin_init;
+
+  dl_handle = LoadLibrary (plugin->full_name);
+  if (!dl_handle)
+    {
+      char *err = win32_error_msg ();
+      error ("cannot load plugin %s\n%s", plugin->full_name, err);
+      LocalFree (err);
+      return false;
+    }
+
+  /* Check the plugin license. Unlike the name suggests, GetProcAddress()
+     can be used for both functions and variables.  */
+  if (GetProcAddress (dl_handle, str_license) == NULL)
+    {
+      char *err = win32_error_msg ();
+      fatal_error (input_location,
+		   "plugin %s is not licensed under a GPL-compatible license\n"
+		   "%s", plugin->full_name, err);
+    }
+
+  /* Unlike dlsym(), GetProcAddress() returns a pointer to a function so we
+     can cast directly without union tricks.  */
+  plugin_init = (plugin_init_func)
+    GetProcAddress (dl_handle, str_plugin_init_func_name);
+
+  if (plugin_init == NULL)
+    {
+      char *err = win32_error_msg ();
+      FreeLibrary (dl_handle);
+      error ("cannot find %s in plugin %s\n%s", str_plugin_init_func_name,
+             plugin->full_name, err);
+      LocalFree (err);
+      return false;
+    }
+
+  /* Call the plugin-provided initialization routine with the arguments.  */
+  if ((*plugin_init) (plugin, &gcc_version))
+    {
+      FreeLibrary (dl_handle);
+      error ("fail to initialize plugin %s", plugin->full_name);
+      return false;
+    }
+  /* Leak dl_handle on purpose to ensure the plugin is loaded for the
+     entire run of the compiler. */
+  return true;
+}
+
+#else // POSIX-like with dlopen()/dlsym().
+
 /* We need a union to cast dlsym return value to a function pointer
    as ISO C forbids assignment between function pointer and 'void *'.
    Use explicit union instead of __extension__(<union_cast>) for
@@ -581,8 +685,6 @@  invoke_plugin_callbacks_full (int event, void *gcc_data)
 #define PTR_UNION_AS_VOID_PTR(NAME) (NAME._q)
 #define PTR_UNION_AS_CAST_PTR(NAME) (NAME._nq)
 
-/* Try to initialize PLUGIN. Return true if successful. */
-
 static bool
 try_init_one_plugin (struct plugin_name_args *plugin)
 {
@@ -634,7 +736,7 @@  try_init_one_plugin (struct plugin_name_args *plugin)
      entire run of the compiler. */
   return true;
 }
-
+#endif
 
 /* Routine to dlopen and initialize one plugin. This function is passed to
    (and called by) the hash table traverse routine. Return 1 for the
diff --git a/libcc1/configure b/libcc1/configure
index d6f480fe930..23d1a7645ff 100755
--- a/libcc1/configure
+++ b/libcc1/configure
@@ -14552,8 +14552,21 @@  fi
 
 
    pluginlibs=
+   plugin_check=yes
 
    case "${host}" in
+     *-*-mingw*)
+       # Since plugin support under MinGW is not as straightforward as on
+       # other platforms (e.g., we have to link import library, etc), we
+       # only enable it if explicitly requested.
+       if test x"$default_plugin" = x"yes"; then
+         enable_plugin=no
+       elif test x"$enable_plugin" = x"yes"; then
+         # Use make's target variable to derive import library name.
+         pluginlibs='-Wl,--export-all-symbols -Wl,--out-implib=$@.a'
+	 plugin_check=no
+       fi
+     ;;
      *-*-darwin*)
        if test x$build = x$host; then
 	 export_sym_check="nm${exeext} -g"
@@ -14574,7 +14587,7 @@  fi
      ;;
    esac
 
-   if test x"$enable_plugin" = x"yes"; then
+   if test x"$enable_plugin" = x"yes" -a x"$plugin_check" = x"yes"; then
 
      { $as_echo "$as_me:${as_lineno-$LINENO}: checking for exported symbols" >&5
 $as_echo_n "checking for exported symbols... " >&6; }