Plugin support on Windows/MinGW
authorBoris Kolpackov <boris@codesynthesis.com>
Sun, 26 Nov 2017 13:00:48 +0000 (13:00 +0000)
committerJonathan Yong <jyong@gcc.gnu.org>
Sun, 26 Nov 2017 13:00:48 +0000 (13:00 +0000)
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.

From-SVN: r255154

13 files changed:
config/ChangeLog
config/gcc-plugin.m4
gcc/ChangeLog
gcc/Makefile.in
gcc/c/ChangeLog
gcc/c/Make-lang.in
gcc/configure
gcc/cp/ChangeLog
gcc/cp/Make-lang.in
gcc/doc/plugins.texi
gcc/plugin.c
libcc1/ChangeLog
libcc1/configure

index 2bb5244caa4f1c1d3170d593337eff7c79d3136e..a44722ed528a49a2657d066808ef0d6bc700f4a9 100644 (file)
@@ -1,3 +1,7 @@
+2017-11-14 Boris Kolpackov  <boris@codesynthesis.com>
+
+       * gcc-plugin.m4: Add support for MinGW.
+
 2017-11-17  Igor Tsimbalist  <igor.v.tsimbalist@intel.com>
 
        * cet.m4: New file.
index dd06a58cd1329aaee15b1e8624467dbd2e91a1f4..38b2ae6e12e6b5d0d3b9cdfd88eebeaec6841596 100644 (file)
@@ -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
index 3445e3e08ee9359b356977e1c9998c3cf64822c3..e52effababaa81ca2219a3d48681d1fc1a70ac9d 100644 (file)
@@ -1,3 +1,12 @@
+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.
+
 2017-11-25  Jakub Jelinek  <jakub@redhat.com>
 
        PR rtl-optimization/81553
index f2a897c5962396f51bbfe6b23e3c3861644e3f4c..c428eaab023c35faa58e8c944324ddc26f058ca0 100644 (file)
@@ -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@
@@ -2828,11 +2834,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 $@; \
        }
index 1d698508131bef58632546847b15a464105bc68a..087a1e7cfaf484875cb72e02fb0743586fd6b5a0 100644 (file)
@@ -1,3 +1,7 @@
+2017-11-14 Boris Kolpackov  <boris@codesynthesis.com>
+
+       * Make-lang.in (c.install-plugin): Install backend import library.
+
 2017-11-23  Jakub Jelinek  <jakub@redhat.com>
 
        * c-parser.c (c_parser_omp_declare_simd): Reject declare simd in
index cfd8cd2b169773f0f8fc1ed2f32d6095f8a9e039..b194f2276468be942535439c33e8eb9caff18d0d 100644 (file)
@@ -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:
 
 #\f
index d4461e2fdd2ac4a8e20bdc51e1dac70c9b114ee7..39eb3c829307f4f38b434262f4cbc723d18a1d34 100755 (executable)
 
 
    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; }
index e93964178eb5aa2602f557fff008f91595e1f1be..854df5afe9174ba3d161e1c1203c6e5eff2850c1 100644 (file)
@@ -1,3 +1,7 @@
+2017-11-14 Boris Kolpackov  <boris@codesynthesis.com>
+
+       * Make-lang.in (c++.install-plugin): Install backend import library.
+
 2017-11-23  Jakub Jelinek  <jakub@redhat.com>
 
        * parser.c (cp_parser_omp_declare): Change return type to bool from
index c852f6a38b443be98d0ca69519055bf227b77ab2..7af4a4cb0b8640c8ace9e0ebc5613d61b3cae333 100644 (file)
@@ -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)
index 6fc7b9ae9a28ed77a66b1f5cbde2e9426d6f8ae6..dc3fa2128814e619476892f3e6678f48dcd64e9c 100644 (file)
@@ -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.
index 9892748cd1519a75a9cc9df550921201346f6412..db18e642680e9a13c09dbb61b98034bc301f9636 100644 (file)
@@ -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
index ba77d02902dae27515d8151bc9a634ca4b26ae5e..c248bf8dd203231c7509aec979f9b9836fa4b42d 100644 (file)
@@ -1,3 +1,7 @@
+2017-11-14 Boris Kolpackov  <boris@codesynthesis.com>
+
+       * configure: Regenerate.
+
 2017-11-16  Sergio Durigan Junior  <sergiodj@redhat.com>
            Pedro Alves  <palves@redhat.com>
 
index d6f480fe930d384bb3baa532930b840780d997c7..23d1a7645ff3a85059d650380fc14a8f8cb46be1 100755 (executable)
 
 
    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; }