[24/32] module mapper

Message ID 6939465f-3896-24e3-e588-e8e246073b52@acm.org
State New
Headers show
Series
  • C++ 20 Modules
Related show

Commit Message

Nathan Sidwell Nov. 3, 2020, 9:17 p.m.
this is the module mapper client and server pieces.  It features a 
default resolver that can read a text file, or generate default mappings 
from module name to cmi name.

By default the compiler will use an in-process resolved, but with 
suitable options can be instructed to communicate with a server over
a) a pipe to a spawned process's stdin/stdout
b) a pair of specified filenos
c) a unix-domain socket (if supported)
d) an ipv6 host/port (if supported)

the server can operat in modes a, c or d.


-- 
Nathan Sidwell

Comments

Boris Kolpackov Nov. 9, 2020, 6:42 a.m. | #1
I've noticed the following issues with the module mapper in the
-fdirectives-only mode:


1. When partially preprocessing the module interface unit, the mapper
   receives the MODULE-EXPORT request that's unnecessary (BMI is not
   written):

   g++ ... -x c++ -E -fdirectives-only -o hello.gcm.ii hello.mxx

   Similarly, in this mode, the mapper receives MODULE-IMPORT for
   (non-header) module imports. Again, this is not necessary and
   replying with a non-existent BMI works.


2. When doing full preprocessing of a partially preprocessed unit,
   the mapper again receives MODULE-EXPORT and MODULE-IMPORT for
   non-header modules:

   g++-m ... -E -x c++ -fpreprocessed -fdirectives-only hello.gcm.ii

   These are also unnecessary.
Nathan Sidwell Nov. 9, 2020, 3:39 p.m. | #2
On 11/9/20 1:42 AM, Boris Kolpackov wrote:
> I've noticed the following issues with the module mapper in the

> -fdirectives-only mode:

> 

> 

> 1. When partially preprocessing the module interface unit, the mapper

>     receives the MODULE-EXPORT request that's unnecessary (BMI is not

>     written):

> 

>     g++ ... -x c++ -E -fdirectives-only -o hello.gcm.ii hello.mxx

> 

>     Similarly, in this mode, the mapper receives MODULE-IMPORT for

>     (non-header) module imports. Again, this is not necessary and

>     replying with a non-existent BMI works.

> 

> 

> 2. When doing full preprocessing of a partially preprocessed unit,

>     the mapper again receives MODULE-EXPORT and MODULE-IMPORT for

>     non-header modules:

> 

>     g++-m ... -E -x c++ -fpreprocessed -fdirectives-only hello.gcm.ii

> 

>     These are also unnecessary.


These are needed as they also serve to inform the mapper of a dependency 
edge.

nathan

-- 
Nathan Sidwell
Nathan Sidwell Nov. 12, 2020, 4:24 p.m. | #3
On 11/3/20 4:17 PM, Nathan Sidwell wrote:
> this is the module mapper client and server pieces.  It features a 

> default resolver that can read a text file, or generate default mappings 

> from module name to cmi name.


Richard rightly suggested on IRC that the sample server for the module 
mapper shouldn't be in the gcc/cp dir.  It happened to be that way 
because it started out much more closely coupled, but then it grew legs.

So this patch creates a new c++tools toplevel directory and places the 
mapper-server and its default resolver there.  That means more changes 
to the toplevel Makefile.def and Makefile.tpl (I've not included the 
regenerated Makefile.in, nor other generated files in gcc/ and c++tools 
in this diff.)

We still need to build the default resolver when building cc1plus, and 
I've placed mapper-resolver.cc there, as a simple #include forwarder to 
the source in c++tools.  I also replace 'gcc/cp/mapper.h' with a 
client-specific 'gcc/cp/mapper-client.h'.  (mapper-client is only linked 
into cc1plus, so gcc/cp seems the right place for it.)

The sample server relies on gcc/version.o to pick up its version number, 
and I place it in the libexecsubdir that we place cc1plus.  I wasn't 
comfortable placing it in the install location of g++ itself.  I call it 
a sample server for a reason :)

I will of course provide changelog when committing.

nathan

-- 
Nathan Sidwell
diff --git c/Makefile.def w/Makefile.def
index 36fd26b0367..6e98d2d3340 100644
--- c/Makefile.def
+++ w/Makefile.def
@@ -125,12 +134,13 @@ host_modules= { module= libtermcap; no_check=true;
                 missing=distclean;
                 missing=maintainer-clean; };
 host_modules= { module= utils; no_check=true; };
+host_modules= { module= c++tools; };
 host_modules= { module= gnattools; };
+host_modules= { module= gotools; };
 host_modules= { module= lto-plugin; bootstrap=true;
 		extra_configure_flags='--enable-shared @extra_linker_plugin_flags@ @extra_linker_plugin_configure_flags@';
 		extra_make_flags='@extra_linker_plugin_flags@'; };
 host_modules= { module= libcc1; extra_configure_flags=--enable-shared; };
-host_modules= { module= gotools; };
 host_modules= { module= libctf; no_install=true; no_check=true;
 		bootstrap=true; };
 
@@ -381,6 +392,8 @@ dependencies = { module=all-lto-plugin; on=all-libiberty-linker-plugin; };
 dependencies = { module=configure-libcc1; on=configure-gcc; };
 dependencies = { module=all-libcc1; on=all-gcc; };
 
+// we want version.o from gcc, and implicitly depend on libcody
+dependencies = { module=all-c++tools; on=all-gcc; };
 dependencies = { module=all-gotools; on=all-target-libgo; };
 
 dependencies = { module=all-utils; on=all-libiberty; };
diff --git c/Makefile.tpl w/Makefile.tpl
index efed1511750..3b88f351d5b 100644
--- c/Makefile.tpl
+++ w/Makefile.tpl
@@ -864,8 +864,8 @@ local-distclean:
 	-rm -f texinfo/doc/Makefile texinfo/po/POTFILES
 	-rmdir texinfo/doc texinfo/info texinfo/intl texinfo/lib 2>/dev/null
 	-rmdir texinfo/makeinfo texinfo/po texinfo/util 2>/dev/null
-	-rmdir fastjar gcc gnattools gotools libcc1 libiberty 2>/dev/null
-	-rmdir texinfo zlib 2>/dev/null
+	-rmdir c++tools fastjar gcc gnattools gotools 2>/dev/null
+	-rmdir libcc1 libiberty texinfo zlib 2>/dev/null
 	-find . -name config.cache -exec rm -f {} \; \; 2>/dev/null
 
 local-maintainer-clean:
diff --git c/c++tools/configure.ac w/c++tools/configure.ac
new file mode 100644
index 00000000000..8d882e541df
--- /dev/null
+++ w/c++tools/configure.ac
@@ -0,0 +1,210 @@
+# Configure script for c++tools
+#   Copyright (C) 2020 Free Software Foundation, Inc.
+#   Written by Nathan Sidwell <nathan@acm.org> while at FaceBook
+#
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.
+
+# C++ has grown a C++20 mapper server.  This may be used to provide
+# and/or learn and/or build required modules.  This sample server
+# shows how the protocol introduced by wg21.link/p1184 may be used.
+# By default g++ uses an in-process mapper.
+
+sinclude(../config/acx.m4)
+
+AC_INIT(c++tools)
+
+AC_CONFIG_SRCDIR([server.cc])
+
+# Determine the noncanonical names used for directories.
+ACX_NONCANONICAL_HOST
+
+AC_CANONICAL_SYSTEM
+AC_PROG_INSTALL
+
+AC_PROG_CXX
+MISSING=`cd $ac_aux_dir && ${PWDCMD-pwd}`/missing
+AC_CHECK_PROGS([AUTOCONF], [autoconf], [$MISSING autoconf])
+AC_CHECK_PROGS([AUTOHEADER], [autoheader], [$MISSING autoheader])
+
+dnl Enabled by default
+AC_MSG_CHECKING([whether to build C++ tools])
+  AC_ARG_ENABLE(c++-tools, 
+    [AS_HELP_STRING([--enable-c++-tools],
+		    [build auxiliary c++ tools])],
+      cxx_aux_tools=$enableval,
+      cxx_aux_tools=yes)
+
+AC_MSG_RESULT($cxx_aux_tools)
+CXX_AUX_TOOLS="$cxx_aux_tools"
+AC_SUBST(CXX_AUX_TOOLS)
+
+AC_ARG_ENABLE([maintainer-mode],
+AS_HELP_STRING([--enable-maintainer-mode],
+[enable maintainer mode.  Add rules to rebuild configurey bits]),,
+[enable_maintainer_mode=no])
+case "$enable_maintainer_mode" in
+  ("yes") maintainer_mode=yes ;;
+  ("no") maintainer=no ;;
+  (*) AC_MSG_ERROR([unknown maintainer mode $enable_maintainer_mode]) ;;
+esac
+AC_MSG_CHECKING([maintainer-mode])
+AC_MSG_RESULT([$maintainer_mode])
+test "$maintainer_mode" = yes && MAINTAINER=yes
+AC_SUBST(MAINTAINER)
+
+# Check if O_CLOEXEC is defined by fcntl
+AC_CACHE_CHECK(for O_CLOEXEC, ac_cv_o_cloexec, [
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+#include <fcntl.h>]], [[
+return open ("/dev/null", O_RDONLY | O_CLOEXEC);]])],
+[ac_cv_o_cloexec=yes],[ac_cv_o_cloexec=no])])
+if test $ac_cv_o_cloexec = yes; then
+  AC_DEFINE(HOST_HAS_O_CLOEXEC, 1,
+  [Define if O_CLOEXEC supported by fcntl.])
+fi
+
+# C++ Modules would like some networking features to provide the mapping
+# server.  You can still use modules without them though.
+# The following network-related checks could probably do with some
+# Windows and other non-linux defenses and checking.
+
+# Local socket connectivity wants AF_UNIX networking
+# Check for AF_UNIX networking
+AC_CACHE_CHECK(for AF_UNIX, ac_cv_af_unix, [
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>]],[[
+sockaddr_un un;
+un.sun_family = AF_UNSPEC;
+int fd = socket (AF_UNIX, SOCK_STREAM, 0);
+connect (fd, (sockaddr *)&un, sizeof (un));]])],
+[ac_cv_af_unix=yes],
+[ac_cv_af_unix=no])])
+if test $ac_cv_af_unix = yes; then
+  AC_DEFINE(HAVE_AF_UNIX, 1,
+  [Define if AF_UNIX supported.])
+fi
+
+# Remote socket connectivity wants AF_INET6 networking
+# Check for AF_INET6 networking
+AC_CACHE_CHECK(for AF_INET6, ac_cv_af_inet6, [
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>]],[[
+sockaddr_in6 in6;
+in6.sin6_family = AF_UNSPEC;
+struct addrinfo *addrs = 0;
+struct addrinfo hints;
+hints.ai_flags = 0;
+hints.ai_family = AF_INET6;
+hints.ai_socktype = SOCK_STREAM;
+hints.ai_protocol = 0;
+hints.ai_canonname = 0;
+hints.ai_addr = 0;
+hints.ai_next = 0;
+int e = getaddrinfo ("localhost", 0, &hints, &addrs);
+const char *str = gai_strerror (e);
+freeaddrinfo (addrs);
+int fd = socket (AF_INET6, SOCK_STREAM, 0);
+connect (fd, (sockaddr *)&in6, sizeof (in6));]])],
+[ac_cv_af_inet6=yes],
+[ac_cv_af_inet6=no])])
+if test $ac_cv_af_inet6 = yes; then
+  AC_DEFINE(HAVE_AF_INET6, 1,
+  [Define if AF_INET6 supported.])
+fi
+
+# Efficient server response wants epoll
+# Check for epoll_create, epoll_ctl, epoll_pwait
+AC_CACHE_CHECK(for epoll, ac_cv_epoll, [
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+#include <sys/epoll.h>]],[[
+int fd = epoll_create (1);
+epoll_event ev;
+ev.events = EPOLLIN;
+ev.data.fd = 0;
+epoll_ctl (fd, EPOLL_CTL_ADD, 0, &ev);
+epoll_pwait (fd, 0, 0, -1, 0);]])],
+[ac_cv_epoll=yes],
+[ac_cv_epoll=no])])
+if test $ac_cv_epoll = yes; then
+  AC_DEFINE(HAVE_EPOLL, 1,
+  [Define if epoll_create, epoll_ctl, epoll_pwait provided.])
+fi
+
+# If we can't use epoll, try pselect.
+# Check for pselect
+AC_CACHE_CHECK(for pselect, ac_cv_pselect, [
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+#include <sys/select.h>]],[[
+pselect (0, 0, 0, 0, 0, 0);]])],
+[ac_cv_pselect=yes],
+[ac_cv_pselect=no])])
+if test $ac_cv_pselect = yes; then
+  AC_DEFINE(HAVE_PSELECT, 1,
+  [Define if pselect provided.])
+fi
+
+# And failing that, use good old select.
+# If we can't even use this, the server is serialized.
+# Check for select
+AC_CACHE_CHECK(for select, ac_cv_select, [
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+#include <sys/select.h>]],[[
+select (0, 0, 0, 0, 0);]])],
+[ac_cv_select=yes],
+[ac_cv_select=no])])
+if test $ac_cv_select = yes; then
+  AC_DEFINE(HAVE_SELECT, 1,
+  [Define if select provided.])
+fi
+
+# Avoid some fnctl calls by using accept4, when available.
+# Check for accept4
+AC_CACHE_CHECK(for accept4, ac_cv_accept4, [
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+#include <sys/socket.h>]],[[
+int err = accept4 (1, 0, 0, SOCK_NONBLOCK);]])],
+[ac_cv_accept4=yes],
+[ac_cv_accept4=no])])
+if test $ac_cv_accept4 = yes; then
+  AC_DEFINE(HAVE_ACCEPT4, 1,
+  [Define if accept4 provided.])
+fi
+
+# For better server messages, look for a way to stringize network addresses
+# Check for inet_ntop
+AC_CACHE_CHECK(for inet_ntop, ac_cv_inet_ntop, [
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+#include <arpa/inet.h>
+#include <netinet/in.h>]],[[
+sockaddr_in6 in6;
+char buf[INET6_ADDRSTRLEN];
+const char *str = inet_ntop (AF_INET6, &in6, buf, sizeof (buf));]])],
+[ac_cv_inet_ntop=yes],
+[ac_cv_inet_ntop=no])])
+if test $ac_cv_inet_ntop = yes; then
+  AC_DEFINE(HAVE_INET_NTOP, 1,
+  [Define if inet_ntop provided.])
+fi
+
+AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_FILES([Makefile])
+
+AC_OUTPUT
diff --git c/c++tools/resolver.cc w/c++tools/resolver.cc
new file mode 100644
index 00000000000..5028d2a4a37
--- /dev/null
+++ w/c++tools/resolver.cc
@@ -0,0 +1,272 @@
+/* C++ modules.  Experimental!	-*- c++ -*-
+   Copyright (C) 2017-2020 Free Software Foundation, Inc.
+   Written by Nathan Sidwell <nathan@acm.org> while at FaceBook
+
+   This file is part of GCC.
+
+   GCC is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3, or (at your option)
+   any later version.
+
+   GCC is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+
+#include "resolver.h"
+// C++
+#include <algorithm>
+// C
+#include <cstring>
+// OS
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifndef DIR_SEPARATOR
+#define DIR_SEPARATOR '/'
+#endif
+
+module_resolver::module_resolver (bool map, bool xlate)
+  : default_map (map), default_translate (xlate)
+{
+}
+
+module_resolver::~module_resolver ()
+{
+  if (fd_repo >= 0)
+    close (fd_repo);
+}
+
+bool
+module_resolver::set_repo (std::string &&r, bool force)
+{
+  if (force || repo.empty ())
+    {
+      repo = std::move (r);
+      force = true;
+    }
+  return force;
+}
+
+bool
+module_resolver::add_mapping (std::string &&module, std::string &&file,
+			      bool force)
+{
+  auto res = map.emplace (std::move (module), std::move (file));
+  if (res.second)
+    force = true;
+  else if (force)
+    res.first->second = std::move (file);
+
+  return force;
+}
+
+int
+module_resolver::read_tuple_file (int fd, char const *prefix, bool force)
+{
+  struct stat stat;
+  if (fstat (fd, &stat) < 0)
+    return -errno;
+
+  if (!stat.st_size)
+    return 0;
+
+  // Just map the file, we're gonna read all of it, so no need for
+  // line buffering
+  void *buffer = mmap (nullptr, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+  if (buffer == MAP_FAILED)
+    return -errno;
+
+  size_t prefix_len = prefix ? strlen (prefix) : 0;
+  unsigned lineno = 0;
+
+  for (char const *begin = reinterpret_cast <char const *> (buffer),
+	 *end = begin + stat.st_size, *eol;
+       begin != end; begin = eol + 1)
+    {
+      lineno++;
+      eol = std::find (begin, end, '\n');
+      if (eol == end)
+	// last line has no \n, ignore the line, you lose
+	break;
+
+      auto *pos = begin;
+      bool pfx_search = prefix_len != 0;
+
+    pfx_search:
+      while (*pos == ' ' || *pos == '\t')
+	pos++;
+
+      auto *space = pos;
+      while (*space != '\n' && *space != ' ' && *space != '\t')
+	space++;
+
+      if (pos == space)
+	// at end of line, nothing here	
+	continue;
+
+      if (pfx_search)
+	{
+	  if (size_t (space - pos) == prefix_len
+	      && std::equal (pos, space, prefix))
+	    pfx_search = false;
+	  pos = space;
+	  goto pfx_search;
+	}
+
+      std::string module (pos, space);
+      while (*space == ' ' || *space == '\t')
+	space++;
+      std::string file (space, eol);
+
+      if (module[0] == '$')
+	{
+	  if (module == "$root")
+	    set_repo (std::move (file));
+	  else
+	    return lineno;
+	}
+      else
+	{
+	  if (file.empty ())
+	    file = GetCMIName (module);
+	  add_mapping (std::move (module), std::move (file), force);
+	}
+    }
+
+  munmap (buffer, stat.st_size);
+
+  return 0;
+}
+
+char const *
+module_resolver::GetCMISuffix ()
+{
+  return "gcm";
+}
+
+module_resolver *
+module_resolver::ConnectRequest (Cody::Server *s, unsigned version,
+				 std::string &a, std::string &i)
+{
+  if (!version || version > Cody::Version)
+    s->ErrorResponse ("version mismatch");
+  else if (a != "GCC")
+    // Refuse anything but GCC
+    ErrorResponse (s, std::string ("only GCC supported"));
+  else if (!ident.empty () && ident != i)
+    // Failed ident check
+    ErrorResponse (s, std::string ("bad ident"));
+  else
+    // Success!
+    s->ConnectResponse ("gcc");
+
+  return this;
+}
+
+int
+module_resolver::ModuleRepoRequest (Cody::Server *s)
+{
+  s->PathnameResponse (repo);
+  return 0;
+}
+
+int
+module_resolver::cmi_response (Cody::Server *s, std::string &module)
+{
+  auto iter = map.find (module);
+  if (iter == map.end ())
+    {
+      std::string file;
+      if (default_map)
+	file = std::move (GetCMIName (module));
+      auto res = map.emplace (module, file);
+      iter = res.first;
+    }
+
+  if (iter->second.empty ())
+    s->ErrorResponse ("no such module");
+  else
+    s->PathnameResponse (iter->second);
+
+  return 0;
+}
+
+int
+module_resolver::ModuleExportRequest (Cody::Server *s, Cody::Flags,
+				      std::string &module)
+{
+  return cmi_response (s, module);
+}
+
+int
+module_resolver::ModuleImportRequest (Cody::Server *s, Cody::Flags,
+				      std::string &module)
+{
+  return cmi_response (s, module);
+}
+
+int
+module_resolver::IncludeTranslateRequest (Cody::Server *s, Cody::Flags,
+					  std::string &include)
+{
+  auto iter = map.find (include);
+  if (iter == map.end () && default_translate)
+    {
+      // Not found, look for it
+      auto file = GetCMIName (include);
+      struct stat statbuf;
+      bool ok = true;
+
+#if HAVE_FSTATAT
+      int fd_dir = AT_FDCWD;
+      if (!repo.empty ())
+	{
+	  if (fd_repo == -1)
+	    {
+	      fd_repo = open (repo.c_str (),
+			      O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+	      if (fd_repo < 0)
+		fd_repo = -2;
+	    }
+	  fd_dir = fd_repo;
+	}
+
+      if (!repo.empty () && fd_repo < 0)
+	ok = false;
+      else if (fstatat (fd_dir, file.c_str (), &statbuf, 0) < 0
+	       || !S_ISREG (statbuf.st_mode))
+	ok = false;
+#else
+      auto append = repo;
+      append.push_back (DIR_SEPARATOR);
+      append.append (file);
+      if (stat (append.c_str (), &statbuf) < 0
+	  || !S_ISREG (statbuf.st_mode))
+	ok = false;
+#endif
+      if (!ok)
+	// Mark as not present
+	file.clear ();
+      auto res = map.emplace (include, file);
+      iter = res.first;
+    }
+
+  if (iter == map.end () || iter->second.empty ())
+    s->BoolResponse (false);
+  else
+    s->PathnameResponse (iter->second);
+
+  return 0;
+}
+
diff --git c/c++tools/resolver.h w/c++tools/resolver.h
new file mode 100644
index 00000000000..19339125b26
--- /dev/null
+++ w/c++tools/resolver.h
@@ -0,0 +1,105 @@
+/* C++ modules.  Experimental!	-*- c++ -*-
+   Copyright (C) 2017-2020 Free Software Foundation, Inc.
+   Written by Nathan Sidwell <nathan@acm.org> while at FaceBook
+
+   This file is part of GCC.
+
+   GCC is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3, or (at your option)
+   any later version.
+
+   GCC is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GXX_RESOLVER_H
+#define GXX_RESOLVER_H 1
+
+// Mapper interface for client and server bits
+#include "cody.hh"
+// C++
+#include <string>
+#include <map>
+
+// This is a GCC class, so GCC coding conventions on new bits.  
+class module_resolver : public Cody::Resolver
+{
+public:
+  using parent = Cody::Resolver;
+  using module_map = std::map<std::string, std::string>;
+
+private:
+  std::string repo;
+  std::string ident;
+  module_map map;
+  int fd_repo = -1;
+  bool default_map = true;
+  bool default_translate = true;
+
+public:
+  module_resolver (bool map = true, bool xlate = false);
+  virtual ~module_resolver () override;
+
+public:
+  void set_default_map (bool d)
+  {
+    default_map = d;
+  }
+  void set_default_translate (bool d)
+  {
+    default_translate = d;
+  }
+  void set_ident (char const *i)
+  {
+    ident = i;
+  }
+  bool set_repo (std::string &&repo, bool force = false);
+  bool add_mapping (std::string &&module, std::string &&file,
+		    bool force = false);
+
+  // Return +ve line number of error, or -ve errno
+  int read_tuple_file (int fd, char const *prefix, bool force = false);
+  int read_tuple_file (int fd, std::string const &prefix,
+			    bool force = false)
+  {
+    return read_tuple_file (fd, prefix.empty () ? nullptr : prefix.c_str (),
+			    force);
+  }
+
+public:
+  // Virtual overriders, names are controlled by Cody::Resolver
+  using parent::ConnectRequest;
+  virtual module_resolver *ConnectRequest (Cody::Server *, unsigned version,
+					   std::string &agent,
+					   std::string &ident)
+    override;
+  using parent::ModuleRepoRequest;
+  virtual int ModuleRepoRequest (Cody::Server *) override;
+  using parent::ModuleExportRequest;
+  virtual int ModuleExportRequest (Cody::Server *s, Cody::Flags,
+				   std::string &module)
+    override;
+  using parent::ModuleImportRequest;
+  virtual int ModuleImportRequest (Cody::Server *s, Cody::Flags,
+				   std::string &module)
+    override;
+  using parent::IncludeTranslateRequest;
+  virtual int IncludeTranslateRequest (Cody::Server *s, Cody::Flags,
+				       std::string &include)
+    override;
+
+private:
+  using parent::GetCMISuffix;
+  virtual char const *GetCMISuffix () override;
+
+private:
+  int cmi_response (Cody::Server *s, std::string &module);
+};
+
+#endif
diff --git c/c++tools/server.cc w/c++tools/server.cc
new file mode 100644
index 00000000000..6457dc5b878
--- /dev/null
+++ w/c++tools/server.cc
@@ -0,0 +1,976 @@
+/* C++ modules.  Experimental!
+   Copyright (C) 2018-2020 Free Software Foundation, Inc.
+   Written by Nathan Sidwell <nathan@acm.org> while at FaceBook
+
+   This file is part of GCC.
+
+   GCC is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3, or (at your option)
+   any later version.
+
+   GCC is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "resolver.h"
+
+// C++
+#include <set>
+#include <vector>
+#include <map>
+// C
+#include <csignal>
+#include <cstring>
+#include <cstdarg>
+// OS
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+// Network
+/* Include network stuff first.  Excitingly OSX10.14 uses bcmp here, which
+   we poison later!  */
+#if defined (HAVE_AF_UNIX) || defined (HAVE_AF_INET6)
+/* socket, bind, listen, accept{4}  */
+# define NETWORKING 1
+# include <sys/socket.h>
+# ifdef HAVE_AF_UNIX
+/* sockaddr_un  */
+#  include <sys/un.h>
+# endif
+# include <netinet/in.h>
+# ifdef HAVE_AF_INET6
+/* sockaddr_in6, getaddrinfo, freeaddrinfo, gai_sterror, ntohs, htons.  */
+#  include <netdb.h>
+# endif
+#ifdef HAVE_INET_NTOP
+/* inet_ntop.  */
+#include <arpa/inet.h>
+#endif
+#endif
+#ifndef HAVE_AF_INET6
+# define gai_strerror(X) ""
+#endif
+
+#include <getopt.h>
+
+// Select or epoll
+#ifdef NETWORKING
+#ifdef HAVE_EPOLL
+/* epoll_create, epoll_ctl, epoll_pwait  */
+#include <sys/epoll.h>
+#endif
+#if defined (HAVE_PSELECT) || defined (HAVE_SELECT)
+/* pselect or select  */
+#include <sys/select.h>
+#endif
+#endif
+
+// GCC
+#include "version.h"
+#include "ansidecl.h"
+#define HAVE_DECL_BASENAME 1 /* See comment in gcc/configure.ac.  */
+#include "libiberty.h"
+
+#if !HOST_HAS_O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
+
+#ifndef IS_DIR_SEPARATOR
+#define IS_DIR_SEPARATOR(C) ((C) == '/')
+#endif
+#ifndef DIR_SEPARATOR
+#define DIR_SEPARATOR '/'
+#endif
+
+#ifdef NETWORKING
+struct netmask {
+  in6_addr addr;
+  unsigned bits;
+
+  netmask (const in6_addr &a, unsigned b)
+  {
+    if (b > sizeof (in6_addr) * 8)
+      b = sizeof (in6_addr) * 8;
+    bits = b;
+    unsigned byte = (b + 7) / 8;
+    unsigned ix = 0;
+    for (ix = 0; ix < byte; ix++)
+      addr.s6_addr[ix] = a.s6_addr[ix];
+    for (; ix != sizeof (in6_addr); ix++)
+      addr.s6_addr[ix] = 0;
+    if (b & 3)
+      addr.s6_addr[b/7] &= (255 << 8) >> (b & 3);
+  }
+
+  bool includes (const in6_addr &a) const
+  {
+    unsigned byte = bits / 8;
+    for (unsigned ix = 0; ix != byte; ix++)
+      if (addr.s6_addr[ix] != a.s6_addr[ix])
+	return false;
+    if (bits & 3)
+      if ((addr.s6_addr[byte] ^ a.s6_addr[byte]) >> (8 - (bits & 3)))
+	return false;
+    return true;
+  }
+};
+
+/* Netmask comparison.  */
+struct netmask_cmp {
+  bool operator() (const netmask &a, const netmask &b) const
+  {
+    if (a.bits != b.bits)
+      return a.bits < b.bits;
+    for (unsigned ix = 0; ix != sizeof (in6_addr); ix++)
+      if (a.addr.s6_addr[ix] != b.addr.s6_addr[ix])
+	return a.addr.s6_addr[ix] < b.addr.s6_addr[ix];
+    return false;
+  }
+};
+
+typedef std::set<netmask, netmask_cmp> netmask_set_t;
+typedef std::vector<netmask> netmask_vec_t;
+#endif
+
+const char *progname;
+
+/* Speak thoughts out loud.  */
+static bool flag_noisy = false;
+
+/* One and done.  */
+static bool flag_one = false;
+
+/* Serialize connections.  */
+static bool flag_sequential = false;
+
+/* Fallback to default if map file is unrewarding.  */
+static bool flag_map = false;
+
+/* Fallback to xlate if map file is unrewarding.  */
+static bool flag_xlate = false;
+
+/* Root binary directory.  */
+static const char *flag_root = "gcm.cache";
+
+#ifdef NETWORKING
+static netmask_set_t netmask_set;
+
+static netmask_vec_t accept_addrs;
+#endif
+
+/* Strip out the source directory from FILE.  */
+
+static const char *
+trim_src_file (const char *file)
+{
+  static const char me[] = __FILE__;
+  unsigned pos = 0;
+
+  while (file[pos] == me[pos] && me[pos])
+    pos++;
+  while (pos && !IS_DIR_SEPARATOR (me[pos-1]))
+    pos--;
+
+  return file + pos;
+}
+
+/* Die screaming.  */
+
+void ATTRIBUTE_NORETURN ATTRIBUTE_PRINTF_1 ATTRIBUTE_COLD
+internal_error (const char *fmt, ...)
+{
+  fprintf (stderr, "%s:Internal error ", progname);
+  va_list args;
+
+  va_start (args, fmt);
+  vfprintf (stderr, fmt, args);
+  va_end (args);
+  fprintf (stderr, "\n");
+
+  exit (2);
+}
+
+/* Hooked to from gcc_assert & gcc_unreachable.  */
+
+void ATTRIBUTE_NORETURN ATTRIBUTE_COLD
+fancy_abort (const char *file, int line, const char *func)
+{
+  internal_error ("in %s, at %s:%d", func, trim_src_file (file), line);
+}
+
+/* Exploded on a signal.  */
+
+static void ATTRIBUTE_NORETURN ATTRIBUTE_COLD
+crash_signal (int sig)
+{
+  signal (sig, SIG_DFL);
+  internal_error ("signal %s", strsignal (sig));
+}
+
+/* A fatal error of some kind.  */
+
+static void ATTRIBUTE_NORETURN ATTRIBUTE_COLD ATTRIBUTE_PRINTF_1
+error (const char *msg, ...)
+{
+  fprintf (stderr, "%s:error: ", progname);
+  va_list args;
+
+  va_start (args, msg);
+  vfprintf (stderr, msg, args);
+  va_end (args);
+  fprintf (stderr, "\n");
+
+  exit (1);
+}
+
+#ifdef NETWORKING
+/* Progress messages to the user.  */
+static bool ATTRIBUTE_PRINTF_1 ATTRIBUTE_COLD
+noisy (const char *fmt, ...)
+{
+  fprintf (stderr, "%s:", progname);
+  va_list args;
+  va_start (args, fmt);
+  vfprintf (stderr, fmt, args);
+  va_end (args);
+  fprintf (stderr, "\n");
+
+  return false;
+}
+#endif
+
+/* More messages to the user.  */
+
+static void ATTRIBUTE_PRINTF_2
+fnotice (FILE *file, const char *fmt, ...)
+{
+  va_list args;
+
+  va_start (args, fmt);
+  vfprintf (file, fmt, args);
+  va_end (args);
+}
+
+static void ATTRIBUTE_NORETURN
+print_usage (int error_p)
+{
+  FILE *file = error_p ? stderr : stdout;
+  int status = error_p ? 1 : 0;
+
+  fnotice (file, "Usage: %s [OPTION...] [CONNECTION] [MAPPINGS...] \n\n",
+	   progname);
+  fnotice (file, "C++ Module Mapper.\n\n");
+  fnotice (file, "  -a, --accept     Netmask to accept from\n");
+  fnotice (file, "  -f, --fallback   Use fallback for missing mappings\n");
+  fnotice (file, "  -h, --help       Print this help, then exit\n");
+  fnotice (file, "  -n, --noisy      Print progress messages\n");
+  fnotice (file, "  -1, --one        One connection and then exit\n");
+  fnotice (file, "  -r, --root DIR   Root compiled module directory\n");
+  fnotice (file, "  -s, --sequential Process connections sequentially\n");
+  fnotice (file, "  -v, --version    Print version number, then exit\n");
+  fnotice (file, "Send SIGTERM(%d) to terminate\n", SIGTERM);
+  fnotice (file, "\nFor bug reporting instructions, please see:\n%s.\n",
+	   bug_report_url);
+  exit (status);
+}
+
+/* Print version information and exit.  */
+
+static void ATTRIBUTE_NORETURN
+print_version (void)
+{
+  fnotice (stdout, "%s %s%s\n", progname, pkgversion_string, version_string);
+  fprintf (stdout, "Copyright %s 2018-2020 Free Software Foundation, Inc.\n",
+	   ("(C)"));
+  fnotice (stdout,
+	   ("This is free software; see the source for copying conditions.\n"
+	    "There is NO warranty; not even for MERCHANTABILITY or \n"
+	    "FITNESS FOR A PARTICULAR PURPOSE.\n\n"));
+  exit (0);
+}
+
+/* ARG is a netmask to accept from.  Add it to the table.  Return
+   false if we fail to resolve it.  */
+
+static bool
+accept_from (char *arg ATTRIBUTE_UNUSED)
+{
+  bool ok = true;
+#if HAVE_AF_INET6
+  unsigned bits = sizeof (in6_addr) * 8;
+  char *slash = strrchr (arg, '/');
+  if (slash)
+    {
+      *slash = 0;
+      if (slash[1])
+	{
+	  char *endp;
+	  bits = strtoul (slash + 1, &endp, 0);
+	}
+    }
+
+  addrinfo hints;
+
+  hints.ai_flags = AI_NUMERICSERV;
+  hints.ai_family = AF_INET6;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_protocol = 0;
+  hints.ai_addrlen = 0;
+  hints.ai_addr = NULL;
+  hints.ai_canonname = NULL;
+  hints.ai_next = NULL;
+
+  struct addrinfo *addrs = NULL;
+  if (int e = getaddrinfo (slash == arg ? NULL : arg, "0", &hints, &addrs))
+    {
+      noisy ("cannot resolve '%s': %s", arg, gai_strerror (e));
+      ok = false;
+    }
+  else
+    for (addrinfo *next = addrs; next; next = next->ai_next)
+      if (next->ai_family == AF_INET6)
+	{
+	  netmask mask (((const sockaddr_in6 *)next->ai_addr)->sin6_addr, bits);
+	  netmask_set.insert (mask);
+	}
+  freeaddrinfo (addrs);
+#endif
+  return ok;
+}
+
+/* Process args, return index to first non-arg.  */
+
+static int
+process_args (int argc, char **argv)
+{
+  static const struct option options[] =
+    {
+     { "accept", required_argument, NULL, 'a' },
+     { "help",	no_argument,	NULL, 'h' },
+     { "map",   no_argument,	NULL, 'm' },
+     { "noisy",	no_argument,	NULL, 'n' },
+     { "one",	no_argument,	NULL, '1' },
+     { "root",	required_argument, NULL, 'r' },
+     { "sequential", no_argument, NULL, 's' },
+     { "translate",no_argument,	NULL, 't' },
+     { "version", no_argument,	NULL, 'v' },
+     { 0, 0, 0, 0 }
+    };
+  int opt;
+  bool bad_accept = false;
+  const char *opts = "a:fhmn1r:stv";
+  while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1)
+    {
+      switch (opt)
+	{
+	case 'a':
+	  if (!accept_from (optarg))
+	    bad_accept = true;
+	  break;
+	case 'h':
+	  print_usage (false);
+	  /* print_usage will exit.  */
+	case 'f': // deprecated alias
+	case 'm':
+	  flag_map = true;
+	  break;
+	case 'n':
+	  flag_noisy = true;
+	  break;
+	case '1':
+	  flag_one = true;
+	  break;
+	case 'r':
+	  flag_root = optarg;
+	  break;
+	case 's':
+	  flag_sequential = true;
+	  break;
+	case 't':
+	  flag_xlate = true;
+	  break;
+	case 'v':
+	  print_version ();
+	  /* print_version will exit.  */
+	default:
+	  print_usage (true);
+	  /* print_usage will exit.  */
+	}
+    }
+
+  if (bad_accept)
+    error ("failed to resolve all accept addresses");
+
+  return optind;
+}
+
+#ifdef NETWORKING
+
+/* Manipulate the EPOLL state, or do nothing, if there is epoll.  */
+
+#ifdef HAVE_EPOLL
+static inline void
+do_epoll_ctl (int epoll_fd, int code, int event, int fd, unsigned data)
+{
+  epoll_event ev;
+  ev.events = event;
+  ev.data.u32 = data;
+  if (epoll_ctl (epoll_fd, code, fd, &ev))
+    {
+      noisy ("epoll_ctl error:%s", xstrerror (errno));
+      gcc_unreachable ();
+    }
+}
+#define my_epoll_ctl(EFD,C,EV,FD,CL) \
+  ((EFD) >= 0 ? do_epoll_ctl (EFD,C,EV,FD,CL) : (void)0)
+#else
+#define my_epoll_ctl(EFD,C,EV,FD,CL) ((void)(EFD), (void)(FD), (void)(CL))
+#endif
+
+/* We increment this to tell the server to shut down.  */
+static volatile int term = false;
+static volatile int kill_sock_fd = -1;
+#if !defined (HAVE_PSELECT) && defined (HAVE_SELECT)
+static int term_pipe[2] = {-1, -1};
+#else
+#define term_pipe ((int *)NULL)
+#endif
+
+/* A terminate signal.  Shutdown gracefully.  */
+
+static void
+term_signal (int sig)
+{
+  signal (sig, term_signal);
+  term = term + 1;
+  if (term_pipe && term_pipe[1] >= 0)
+    write (term_pipe[1], &term_pipe[1], 1);
+}
+
+/* A kill signal.  Shutdown immediately.  */
+
+static void
+kill_signal (int sig)
+{
+  signal (sig, SIG_DFL);
+  int sock_fd = kill_sock_fd;
+  if (sock_fd >= 0)
+    close (sock_fd);
+  exit (2);
+}
+
+bool process_server (Cody::Server *server, unsigned slot, int epoll_fd)
+{
+  switch (server->GetDirection ())
+    {
+    case Cody::Server::READING:
+      if (int err = server->Read ())
+	return !(err == EINTR || err == EAGAIN);
+      server->ProcessRequests ();
+      server->PrepareToWrite ();
+      break;
+
+    case Cody::Server::WRITING:
+      if (int err = server->Write ())
+	return !(err == EINTR || err == EAGAIN);
+      server->PrepareToRead ();
+      break;
+
+    default:
+      // We should never get here
+      return true;
+    }
+
+  // We've changed direction, so update epoll
+  gcc_assert (server->GetFDRead () == server->GetFDWrite ());
+  my_epoll_ctl (epoll_fd, EPOLL_CTL_MOD,
+		server->GetDirection () == Cody::Server::READING
+		? EPOLLIN : EPOLLOUT, server->GetFDRead (), slot + 1);
+
+  return false;
+}
+
+void close_server (Cody::Server *server, int epoll_fd)
+{
+  my_epoll_ctl (epoll_fd, EPOLL_CTL_DEL, EPOLLIN, server->GetFDRead (), 0);
+
+  close (server->GetFDRead ());
+  
+  delete server;
+}
+
+int open_server (bool ip6, int sock_fd)
+{
+  sockaddr_in6 addr;
+  socklen_t addr_len = sizeof (addr);
+
+#ifdef HAVE_ACCEPT4
+  int client_fd = accept4 (sock_fd, ip6 ? (sockaddr *)&addr : nullptr,
+			   &addr_len, SOCK_NONBLOCK);
+#else
+  int client_fd = accept (sock_fd, ip6 ? (sockaddr *)&addr : nullptr, &addr_len);
+#endif
+  if (client_fd < 0)
+    {
+      error ("cannot accept: %s", xstrerror (errno));
+      flag_one = true;
+    }
+  else if (ip6)
+    {
+      const char *str = NULL;
+#if HAVE_INET_NTOP
+      char name[INET6_ADDRSTRLEN];
+      str = inet_ntop (addr.sin6_family, &addr.sin6_addr, name, sizeof (name));
+#endif
+      if (!accept_addrs.empty ())
+	{
+	  netmask_vec_t::iterator e = accept_addrs.end ();
+	  for (netmask_vec_t::iterator i = accept_addrs.begin ();
+	       i != e; ++i)
+	    if (i->includes (addr.sin6_addr))
+	      goto present;
+	  close (client_fd);
+	  client_fd = -1;
+	  noisy ("Rejecting connection from disallowed source '%s'",
+		 str ? str : "");
+	present:;
+	}
+      if (client_fd >= 0)
+	flag_noisy && noisy ("Accepting connection from '%s'", str ? str : "");
+    }
+
+  return client_fd;
+}
+
+/* A server listening on bound socket SOCK_FD.  */
+
+static void
+server (bool ipv6, int sock_fd, module_resolver *resolver)
+{
+  int epoll_fd = -1;
+
+  signal (SIGTERM, term_signal);
+#ifdef HAVE_EPOLL
+  epoll_fd = epoll_create (1);
+#endif
+  if (epoll_fd >= 0)
+    my_epoll_ctl (epoll_fd, EPOLL_CTL_ADD, EPOLLIN, sock_fd, 0);
+
+#if defined (HAVE_EPOLL) || defined (HAVE_PSELECT) || defined (HAVE_SELECT)
+  sigset_t mask;
+  {
+    sigset_t block;
+    sigemptyset (&block);
+    sigaddset (&block, SIGTERM);
+    sigprocmask (SIG_BLOCK, &block, &mask);
+  }
+#endif
+
+#ifdef HAVE_EPOLL
+  const unsigned max_events = 20;
+  epoll_event events[max_events];
+#endif
+#if defined (HAVE_PSELECT) || defined (HAVE_SELECT)
+  fd_set readers, writers;
+#endif
+  if (term_pipe)
+    pipe (term_pipe);
+
+  // We need stable references to servers, so this array can contain nulls
+  std::vector<Cody::Server *> connections;
+  unsigned live = 0;
+  while (sock_fd >= 0 || live)
+    {
+      /* Wait for one or more events.  */
+      bool eintr = false;
+      int event_count;
+
+      if (epoll_fd >= 0)
+	{
+#ifdef HAVE_EPOLL
+	  event_count = epoll_pwait (epoll_fd, events, max_events, -1, &mask);
+#endif
+	}
+      else
+	{
+#if defined (HAVE_PSELECT) || defined (HAVE_SELECT)
+	  FD_ZERO (&readers);
+	  FD_ZERO (&writers);
+
+	  unsigned limit = 0;
+	  if (sock_fd >= 0
+	      && !(term || (live && (flag_one || flag_sequential))))
+	    {
+	      FD_SET (sock_fd, &readers);
+	      limit = sock_fd + 1;
+	    }
+
+	  if (term_pipe && term_pipe[0] >= 0)
+	    {
+	      FD_SET (term_pipe[0], &readers);
+	      if (unsigned (term_pipe[0]) >= limit)
+		limit = term_pipe[0] + 1;
+	    }
+
+	  for (auto iter = connections.begin ();
+	       iter != connections.end (); ++iter)
+	    if (auto *server = *iter)
+	      {
+		int fd = -1;
+		switch (server->GetDirection ())
+		  {
+		  case Cody::Server::READING:
+		    fd = server->GetFDRead ();
+		    FD_SET (fd, &readers);
+		    break;
+		  case Cody::Server::WRITING:
+		    fd = server->GetFDWrite ();
+		    FD_SET (fd, &writers);
+		    break;
+		  default:
+		    break;
+		  }
+
+		if (fd >= 0 && limit <= unsigned (fd))
+		  limit = fd + 1;
+	      }
+
+#ifdef HAVE_PSELECT
+	  event_count = pselect (limit, &readers, &writers, NULL, NULL, &mask);
+#else
+	  event_count = select (limit, &readers, &writers, NULL, NULL);
+#endif
+	  if (term_pipe && FD_ISSET (term_pipe[0], &readers))
+	    {
+	      /* Fake up an interrupted system call.  */
+	      event_count = -1;
+	      errno = EINTR;
+	    }
+#endif
+	}
+
+      if (event_count < 0)
+	{
+	  // Error in waiting
+	  if (errno == EINTR)
+	    {
+	      flag_noisy && noisy ("Interrupted wait");
+	      eintr = true;
+	    }
+	  else
+	    error ("cannot %s: %s", epoll_fd >= 0 ? "epoll_wait"
+#ifdef HAVE_PSELECT
+		   : "pselect",
+#else
+		   : "select",
+#endif
+		   xstrerror (errno));
+	  event_count = 0;
+	}
+
+      auto iter = connections.begin ();
+      while (event_count--)
+	{
+	  // Process an event
+	  int active = -2;
+
+	  if (epoll_fd >= 0)
+	    {
+#ifdef HAVE_EPOLL
+	      /* See PR c++/88664 for why a temporary is used.  */
+	      unsigned data = events[event_count].data.u32;
+	      active = int (data) - 1;
+#endif
+	    }
+	  else
+	    {
+	      for (; iter != connections.end (); ++iter)
+		if (auto *server = *iter)
+		  {
+		    bool found = false;
+		    switch (server->GetDirection ())
+		      {
+#if defined (HAVE_PSELECT) || defined (HAVE_SELECT)
+		      case Cody::Server::READING:
+			found = FD_ISSET (server->GetFDRead (), &readers);
+			break;
+		      case Cody::Server::WRITING:
+			found = FD_ISSET (server->GetFDWrite (), &writers);
+			break;
+#endif
+		      default:
+			break;
+		      }
+
+		    if (found)
+		      {
+			active = iter - connections.begin ();
+			++iter;
+			break;
+		      }
+		  }
+
+	      if (active < 0 && sock_fd >= 0 && FD_ISSET (sock_fd, &readers))
+		active = -1;
+	    }
+
+	  if (active >= 0)
+	    {
+	      // Do the action
+	      auto *server = connections[active];
+	      if (process_server (server, active, epoll_fd))
+		{
+		  connections[active] = nullptr;
+		  close_server (server, epoll_fd);
+		  live--;
+		  if (flag_sequential)
+		    my_epoll_ctl (epoll_fd, EPOLL_CTL_ADD, EPOLLIN, sock_fd, 0);
+		}
+	    }
+	  else if (active == -1 && !eintr)
+	    {
+	      // New connection
+	      int fd = open_server (ipv6, sock_fd);
+	      if (fd >= 0)
+		{
+#if !defined (HAVE_ACCEPT4) \
+  && (defined (HAVE_EPOLL) || defined (HAVE_PSELECT) || defined (HAVE_SELECT))
+		  int flags = fcntl (fd, F_GETFL, 0);
+		  fcntl (fd, F_SETFL, flags | O_NONBLOCK);
+#endif
+		  auto *server = new Cody::Server (resolver, fd);
+
+		  unsigned slot = connections.size ();
+		  if (live == slot)
+		    connections.push_back (server);
+		  else
+		    for (auto iter = connections.begin (); ; ++iter)
+		      if (!*iter)
+			{
+			  *iter = server;
+			  slot = iter - connections.begin ();
+			  break;
+			}
+		  live++;
+		  my_epoll_ctl (epoll_fd, EPOLL_CTL_ADD, EPOLLIN, fd, slot + 1);
+		}
+	    }
+
+	  if (sock_fd >= 0
+	      && (term || (live && (flag_one || flag_sequential))))
+	    {
+	      /* Stop paying attention to sock_fd.  */
+	      my_epoll_ctl (epoll_fd, EPOLL_CTL_DEL, EPOLLIN, sock_fd, 0);
+	      if (flag_one || term)
+		{
+		  close (sock_fd);
+		  sock_fd = -1;
+		}
+	    }
+	}
+    }
+#if defined (HAVE_EPOLL) || defined (HAVE_PSELECT) || defined (HAVE_SELECT)
+  /* Restore the signal mask.  */
+  sigprocmask (SIG_SETMASK, &mask, NULL);
+#endif
+
+  gcc_assert (sock_fd < 0);
+  if (epoll_fd >= 0)
+    close (epoll_fd);
+
+  if (term_pipe && term_pipe[0] >= 0)
+    {
+      close (term_pipe[0]);
+      close (term_pipe[1]);
+    }
+}
+
+#endif
+
+static int maybe_parse_socket (std::string &option, module_resolver *r)
+{
+  /* Local or ipv6 address.  */
+  auto last = option.find_last_of ('?');
+  if (last != option.npos)
+    {
+      r->set_ident (option.c_str () + last + 1);
+      option.erase (last);
+    }
+  int fd = -2;
+  char const *errmsg = nullptr;
+
+  /* Does it look like a socket?  */
+  if (option[0] == '=')
+    {
+      /* A local socket.  */
+#if CODY_NETWORKING
+      fd = Cody::ListenLocal (&errmsg, option.c_str () + 1);
+#endif
+    }
+  else
+    {
+      auto colon = option.find_last_of (':');
+      if (colon != option.npos)
+	{
+	  /* Try a hostname:port address.  */
+	  char const *cptr = option.c_str () + colon;
+	  char *endp;
+	  unsigned port = strtoul (cptr + 1, &endp, 10);
+
+	  if (port && endp != cptr + 1 && !*endp)
+	    {
+	      /* Ends in ':number', treat as ipv6 domain socket.  */
+	      option.erase (colon);
+#if CODY_NETWORKING
+	      fd = Cody::ListenInet6 (&errmsg, option.c_str (), port);
+#endif
+	    }
+	}
+    }
+
+  if (errmsg)
+    error ("failed to open socket: %s", errmsg);
+
+  return fd;
+}
+
+int
+main (int argc, char *argv[])
+{
+  const char *p = argv[0] + strlen (argv[0]);
+  while (p != argv[0] && !IS_DIR_SEPARATOR (p[-1]))
+    --p;
+  progname = p;
+
+#ifdef SIGSEGV
+  signal (SIGSEGV, crash_signal);
+#endif
+#ifdef SIGILL
+  signal (SIGILL, crash_signal);
+#endif
+#ifdef SIGBUS
+  signal (SIGBUS, crash_signal);
+#endif
+#ifdef SIGABRT
+  signal (SIGABRT, crash_signal);
+#endif
+#ifdef SIGFPE
+  signal (SIGFPE, crash_signal);
+#endif
+#ifdef SIGPIPE
+  /* Ignore sigpipe, so read/write get an error.  */
+  signal (SIGPIPE, SIG_IGN);
+#endif
+#ifdef NETWORKING
+#ifdef SIGINT
+  signal (SIGINT, kill_signal);
+#endif
+#endif
+
+  int argno = process_args (argc, argv);
+
+  std::string name;
+  int sock_fd = -1; /* Socket fd, otherwise stdin/stdout.  */
+  module_resolver r (flag_map, flag_xlate);
+
+  if (argno != argc)
+    {
+      name = argv[argno];
+      sock_fd = maybe_parse_socket (name, &r);
+      if (!name.empty ())
+	argno++;
+    }
+
+  if (argno != argc)
+    for (; argno != argc; argno++)
+      {
+	std::string option = argv[argno];
+	char const *prefix = nullptr;
+	auto ident = option.find_last_of ('?');
+	if (ident != option.npos)
+	  {
+	    prefix = option.c_str () + ident + 1;
+	    option[ident] = 0;
+	  }
+	int fd = open (option.c_str (), O_RDONLY | O_CLOEXEC);
+	int err = 0;
+	if (fd < 0)
+	  err = errno;
+	else
+	  {
+	    err = r.read_tuple_file (fd, prefix, false);
+	    close (fd);
+	  }
+
+	if (err)
+	  error ("failed reading '%s': %s", option.c_str (), xstrerror (err));
+      }
+  else
+    r.set_default_map (true);
+
+  if (flag_root)
+    r.set_repo (flag_root);
+
+#ifdef HAVE_AF_INET6
+  netmask_set_t::iterator end = netmask_set.end ();
+  for (netmask_set_t::iterator iter = netmask_set.begin ();
+       iter != end; ++iter)
+    {
+      netmask_vec_t::iterator e = accept_addrs.end ();
+      for (netmask_vec_t::iterator i = accept_addrs.begin (); i != e; ++i)
+	if (i->includes (iter->addr))
+	  goto present;
+      accept_addrs.push_back (*iter);
+    present:;
+    }
+#endif
+
+#ifdef NETWORKING
+  if (sock_fd >= 0)
+    {
+      server (name[0] != '=', sock_fd, &r);
+      if (name[0] == '=')
+	unlink (name.c_str () + 1);
+    }
+  else
+#endif
+    {
+      auto server = Cody::Server (&r, 0, 1);
+
+      int err = 0;
+      for (;;)
+	{
+	  server.PrepareToRead ();
+	  while ((err = server.Read ()))
+	    {
+	      if (err == EINTR || err == EAGAIN)
+		continue;
+	      goto done;
+	    }
+
+	  server.ProcessRequests ();
+
+	  server.PrepareToWrite ();
+	  while ((err = server.Write ()))
+	    {
+	      if (err == EINTR || err == EAGAIN)
+		continue;
+	      goto done;
+	    }
+	}
+    done:;
+      if (err > 0)
+	error ("communication error:%s", xstrerror (err));
+    }
+
+  return 0;
+}
diff --git c/gcc/configure.ac w/gcc/configure.ac
index 73034bb902b..d1c980dd64a 100644
--- c/gcc/configure.ac
+++ w/gcc/configure.ac
@@ -1440,6 +1442,10 @@ fi
 
 AC_CHECK_TYPE(ssize_t, int)
 AC_CHECK_TYPE(caddr_t, char *)
+AC_CHECK_TYPE(sighander_t,
+  AC_DEFINE(HAVE_SIGHANDLER_T, 1,
+    [Define if <sys/signal.h> defines sighandler_t]),
+    ,signal.h)
 
 GCC_AC_FUNC_MMAP_BLACKLIST
 
@@ -1585,6 +1591,72 @@ if test $ac_cv_f_setlkw = yes; then
   [Define if F_SETLKW supported by fcntl.])
 fi
 
+# Check if O_CLOEXEC is defined by fcntl
+AC_CACHE_CHECK(for O_CLOEXEC, ac_cv_o_cloexec, [
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+#include <fcntl.h>]], [[
+return open ("/dev/null", O_RDONLY | O_CLOEXEC);]])],
+[ac_cv_o_cloexec=yes],[ac_cv_o_cloexec=no])])
+if test $ac_cv_o_cloexec = yes; then
+  AC_DEFINE(HOST_HAS_O_CLOEXEC, 1,
+  [Define if O_CLOEXEC supported by fcntl.])
+fi
+
+# C++ Modules would like some networking features to provide the mapping
+# server.  You can still use modules without them though.
+# The following network-related checks could probably do with some
+# Windows and other non-linux defenses and checking.
+
+# Local socket connectivity wants AF_UNIX networking
+# Check for AF_UNIX networking
+AC_CACHE_CHECK(for AF_UNIX, ac_cv_af_unix, [
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>]],[[
+sockaddr_un un;
+un.sun_family = AF_UNSPEC;
+int fd = socket (AF_UNIX, SOCK_STREAM, 0);
+connect (fd, (sockaddr *)&un, sizeof (un));]])],
+[ac_cv_af_unix=yes],
+[ac_cv_af_unix=no])])
+if test $ac_cv_af_unix = yes; then
+  AC_DEFINE(HAVE_AF_UNIX, 1,
+  [Define if AF_UNIX supported.])
+fi
+
+# Remote socket connectivity wants AF_INET6 networking
+# Check for AF_INET6 networking
+AC_CACHE_CHECK(for AF_INET6, ac_cv_af_inet6, [
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>]],[[
+sockaddr_in6 in6;
+in6.sin6_family = AF_UNSPEC;
+struct addrinfo *addrs = 0;
+struct addrinfo hints;
+hints.ai_flags = 0;
+hints.ai_family = AF_INET6;
+hints.ai_socktype = SOCK_STREAM;
+hints.ai_protocol = 0;
+hints.ai_canonname = 0;
+hints.ai_addr = 0;
+hints.ai_next = 0;
+int e = getaddrinfo ("localhost", 0, &hints, &addrs);
+const char *str = gai_strerror (e);
+freeaddrinfo (addrs);
+int fd = socket (AF_INET6, SOCK_STREAM, 0);
+connect (fd, (sockaddr *)&in6, sizeof (in6));]])],
+[ac_cv_af_inet6=yes],
+[ac_cv_af_inet6=no])])
+if test $ac_cv_af_inet6 = yes; then
+  AC_DEFINE(HAVE_AF_INET6, 1,
+  [Define if AF_INET6 supported.])
+fi
+
 # Restore CFLAGS, CXXFLAGS from before the gcc_AC_NEED_DECLARATIONS tests.
 CFLAGS="$saved_CFLAGS"
 CXXFLAGS="$saved_CXXFLAGS"
diff --git c/gcc/cp/mapper-client.cc w/gcc/cp/mapper-client.cc
new file mode 100644
index 00000000000..22605ca8cf9
--- /dev/null
+++ w/gcc/cp/mapper-client.cc
@@ -0,0 +1,327 @@
+/* C++ modules.  Experimental!
+   Copyright (C) 2017-2020 Free Software Foundation, Inc.
+   Written by Nathan Sidwell <nathan@acm.org> while at FaceBook
+
+   This file is part of GCC.
+
+   GCC is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3, or (at your option)
+   any later version.
+
+   GCC is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+
+#include "line-map.h"
+#include "diagnostic-core.h"
+#include "mapper-client.h"
+#include "intl.h"
+
+#include "../../c++tools/resolver.h"
+
+module_client::module_client (pex_obj *p, int fd_from, int fd_to)
+  : Client (fd_from, fd_to), pex (p)
+{
+}
+
+static module_client *
+spawn_mapper_program (char const **errmsg, std::string &name,
+		      char const *full_program_name)
+{
+  /* Split writable at white-space.  No space-containing args for
+     you!  */
+  // At most every other char could be an argument
+  char **argv = new char *[name.size () / 2 + 2];
+  unsigned arg_no = 0;
+  char *str = new char[name.size ()];
+  memcpy (str, name.c_str () + 1, name.size ());
+
+  for (auto ptr = str; ; ++ptr)
+    {
+      while (*ptr == ' ')
+	ptr++;
+      if (!*ptr)
+	break;
+
+      if (!arg_no)
+	{
+	  /* @name means look in the compiler's install dir.  */
+	  if (ptr[0] == '@')
+	    ptr++;
+	  else
+	    full_program_name = nullptr;
+	}
+
+      argv[arg_no++] = ptr;
+      while (*ptr && *ptr != ' ')
+	ptr++;
+      if (!*ptr)
+	break;
+      *ptr = 0;
+    }
+  argv[arg_no] = nullptr;
+
+  auto *pex = pex_init (PEX_USE_PIPES, progname, NULL);
+  FILE *to = pex_input_pipe (pex, false);
+  name = argv[0];
+  if (!to)
+    *errmsg = "connecting input";
+  else
+    {
+      int flags = PEX_SEARCH;
+
+      if (full_program_name)
+	{
+	  /* Prepend the invoking path, if the mapper is a simple
+	     file name.  */
+	  size_t dir_len = progname - full_program_name;
+	  std::string argv0;
+	  argv0.reserve (dir_len + name.size ());
+	  argv0.append (full_program_name, dir_len).append (name);
+	  name = std::move (argv0);
+	  argv[0] = const_cast <char *> (name.c_str ());
+	  flags = 0;
+	}
+      int err;
+      *errmsg = pex_run (pex, flags, argv[0], argv, NULL, NULL, &err);
+    }
+  delete[] str;
+  delete[] argv;
+
+  int fd_from = -1, fd_to = -1;
+  if (!*errmsg)
+    {
+      FILE *from = pex_read_output (pex, false);
+      if (from && (fd_to = dup (fileno (to))) >= 0)
+	fd_from = fileno (from);
+      else
+	*errmsg = "connecting output";
+      fclose (to);
+    }
+
+  if (*errmsg)
+    {
+      pex_free (pex);
+      return nullptr;
+    }
+
+  return new module_client (pex, fd_from, fd_to);
+}
+
+module_client *
+module_client::open_module_client (location_t loc, const char *o,
+				   void (*set_repo) (const char *),
+				   char const *full_program_name)
+{
+  module_client *c = nullptr;
+  std::string ident;
+  std::string name;
+  char const *errmsg = nullptr;
+  unsigned line = 0;
+
+  if (o && o[0])
+    {
+      /* Maybe a local or ipv6 address.  */
+      name = o;
+      auto last = name.find_last_of ('?');
+      if (last != name.npos)
+	{
+	  ident = name.substr (last + 1);
+	  name.erase (last);
+	}
+
+      if (name.size ())
+	{
+	  switch (name[0])
+	    {
+	    case '<':
+	      // <from>to or <>fromto, or <>
+	      {
+		int fd_from = -1, fd_to = -1;
+		char const *ptr = name.c_str ();
+		char *eptr;
+
+		fd_from = strtoul (++ptr, &eptr, 0);
+		if (*eptr == '>')
+		  {
+		    ptr = eptr;
+		    fd_to = strtoul (++ptr, &eptr, 0);
+		    if (eptr != ptr && ptr == name.c_str () + 1)
+		      fd_from = fd_to;
+		  }
+
+		if (*eptr)
+		  errmsg = "parsing";
+		else
+		  {
+		    if (name.size () == 2)
+		      {
+			fd_from = fileno (stdin);
+			fd_to = fileno (stdout);
+		      }
+		    c = new module_client (fd_from, fd_to);
+		  }
+	      }
+	      break;
+
+	    case '=':
+	      // =localsocket
+	      {
+		int fd = -1;
+#if CODY_NETWORKING
+		fd = Cody::OpenLocal (&errmsg, name.c_str () + 1);
+#endif
+		if (fd >= 0)
+		  c = new module_client (fd, fd);
+	      }
+	      break;
+
+	    case '|':
+	      // |program and args
+	      c = spawn_mapper_program (&errmsg, name, full_program_name);
+	      break;
+
+	    default:
+	      // file or hostname:port
+	      {
+		auto colon = name.find_last_of (':');
+		if (colon != name.npos)
+		  {
+		    char const *cptr = name.c_str () + colon;
+		    char *endp;
+		    unsigned port = strtoul (cptr + 1, &endp, 10);
+
+		    if (port && endp != cptr + 1 && !*endp)
+		      {
+			name[colon] = 0;
+			int fd = 01;
+#if CODY_NETWORKING
+			fd = Cody::OpenInet6 (&errmsg, name.c_str (), port);
+#endif
+			name[colon] = ':';
+
+			if (fd >= 0)
+			  c = new module_client (fd, fd);
+		      }
+		  }
+		
+	      }
+	      break;
+	    }
+	}
+    }
+
+  if (!c)
+    {
+      // Make a default in-process client
+      bool file = !errmsg && !name.empty ();
+      auto r = new module_resolver (!file, true);
+
+      if (file)
+	{
+	int fd = open (name.c_str (), O_RDONLY | O_CLOEXEC);
+	if (fd < 0)
+	  errmsg = "opening";
+	else
+	  {
+	    if (int l = r->read_tuple_file (fd, ident, false))
+	      {
+		if (l > 0)
+		  line = l;
+		errmsg = "reading";
+	      }
+	      
+	    close (fd);
+	  }
+	}
+      else
+	r->set_repo ("gcm.cache");
+
+      auto *s = new Cody::Server (r);
+      c = new module_client (s);
+    }
+
+#ifdef SIGPIPE
+  if (!c->IsDirect ())
+    /* We need to ignore sig pipe for a while.  */
+    c->sigpipe = signal (SIGPIPE, SIG_IGN);
+#endif
+
+  if (errmsg)
+    error_at (loc, line ? G_("failed %s mapper %qs line %u")
+	      : G_("failed %s mapper %qs"), errmsg, name.c_str (), line);
+
+  // now wave hello!
+  c->Cork ();
+  c->Connect (std::string ("GCC"), ident);
+  c->ModuleRepo ();
+  auto packets = c->Uncork ();
+
+  auto &connect = packets[0];
+  if (connect.GetCode () == Cody::Client::PC_CONNECT)
+    c->flags = Cody::Flags (connect.GetInteger ());
+  else if (connect.GetCode () == Cody::Client::PC_ERROR)
+    error_at (loc, "failed mapper handshake %s", connect.GetString ().c_str ());
+
+  auto &repo = packets[1];
+  if (repo.GetCode () == Cody::Client::PC_PATHNAME)
+    set_repo (repo.GetString ().c_str ());
+
+  return c;
+}
+
+void
+module_client::close_module_client (location_t loc, module_client *mapper)
+{
+  if (mapper->IsDirect ())
+    {
+      auto *s = mapper->GetServer ();
+      auto *r = s->GetResolver ();
+      delete s;
+      delete r;
+    }
+  else
+    {
+      if (mapper->pex)
+	{
+	  int fd_write = mapper->GetFDWrite ();
+	  if (fd_write >= 0)
+	    close (fd_write);
+
+	  int status;
+	  pex_get_status (mapper->pex, 1, &status);
+
+	  pex_free (mapper->pex);
+	  mapper->pex = NULL;
+
+	  if (WIFSIGNALED (status))
+	    error_at (loc, "mapper died by signal %s",
+		      strsignal (WTERMSIG (status)));
+	  else if (WIFEXITED (status) && WEXITSTATUS (status) != 0)
+	    error_at (loc, "mapper exit status %d",
+		      WEXITSTATUS (status));
+	}
+      else
+	{
+	  int fd_read = mapper->GetFDRead ();
+	  close (fd_read);
+	}
+
+#ifdef SIGPIPE
+      // Restore sigpipe
+      if (mapper->sigpipe != SIG_IGN)
+	signal (SIGPIPE, mapper->sigpipe);
+#endif
+    }
+
+  delete mapper;
+}
diff --git c/gcc/cp/mapper-client.h w/gcc/cp/mapper-client.h
new file mode 100644
index 00000000000..ca1a0aa5509
--- /dev/null
+++ w/gcc/cp/mapper-client.h
@@ -0,0 +1,63 @@
+/* C++ modules.  Experimental!	-*- c++ -*-
+   Copyright (C) 2020 Free Software Foundation, Inc.
+   Written by Nathan Sidwell <nathan@acm.org> while at FaceBook
+
+   This file is part of GCC.
+
+   GCC is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3, or (at your option)
+   any later version.
+
+   GCC is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+/* Forward to the header in c++tools.  */
+
+#ifndef MAPPER_CLIENT_H
+#define MAPPER_CLIENT_H 1
+
+#include "cody.hh"
+
+#ifndef HAVE_SIGHANDLER_T
+typedef void (*sighandler_t) (int);
+#endif
+
+class module_client : public Cody::Client
+{
+  pex_obj *pex = nullptr;
+  sighandler_t sigpipe = SIG_IGN;
+  Cody::Flags flags = Cody::Flags::None;
+
+public:
+  module_client (Cody::Server *s)
+    : Client (s)
+  {
+  }
+  module_client (pex_obj *pex, int fd_from, int fd_to);
+
+  module_client (int fd_from, int fd_to)
+    : Client (fd_from, fd_to)
+  {
+  }
+
+public:
+  Cody::Flags get_flags () const
+  {
+    return flags;
+  }
+
+public:
+  static module_client *open_module_client (location_t loc, const char *option,
+					    void (*set_repo) (const char *),
+					    char const *);
+  static void close_module_client (location_t loc, module_client *);
+};
+
+#endif
diff --git c/gcc/cp/mapper-resolver.cc w/gcc/cp/mapper-resolver.cc
new file mode 100644
index 00000000000..02ec48c61ea
--- /dev/null
+++ w/gcc/cp/mapper-resolver.cc
@@ -0,0 +1,27 @@
+/* C++ modules.  Experimental!	-*- c++ -*-
+   Copyright (C) 2020 Free Software Foundation, Inc.
+   Written by Nathan Sidwell <nathan@acm.org> while at FaceBook
+
+   This file is part of GCC.
+
+   GCC is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3, or (at your option)
+   any later version.
+
+   GCC is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+/* Forward to the resolver in c++tools.  */
+
+#include "config.h"
+#define INCLUDE_ALGORITHM
+#include "system.h"
+
+#include "../../c++tools/resolver.cc"
Boris Kolpackov Nov. 24, 2020, 7:18 a.m. | #4
Nathan Sidwell <nathan@acm.org> writes:

> These are needed as they also serve to inform the mapper of a dependency 

> edge.


Ok, that makes sense (and thanks for making it optional via a flag).

One thing that is still missing in this area is the dependency on
stdc-predef.h. In other words, we now can get everything via the
mapper as we can get with -M* except for this file.

Do you think it would be possible to report it with INCLUDE-TRANSLATE
(with the only valid response being BOOL FALSE)? There could also be
a flag to indicate that it's not really "translatable".
Richard Biener via Gcc-patches Nov. 30, 2020, 4:18 p.m. | #5
On 11/12/20 9:24 AM, Nathan Sidwell wrote:
> On 11/3/20 4:17 PM, Nathan Sidwell wrote:

>> this is the module mapper client and server pieces.  It features a

>> default resolver that can read a text file, or generate default

>> mappings from module name to cmi name.

>

> Richard rightly suggested on IRC that the sample server for the module

> mapper shouldn't be in the gcc/cp dir.  It happened to be that way

> because it started out much more closely coupled, but then it grew legs.

>

> So this patch creates a new c++tools toplevel directory and places the

> mapper-server and its default resolver there.  That means more changes

> to the toplevel Makefile.def and Makefile.tpl (I've not included the

> regenerated Makefile.in, nor other generated files in gcc/ and

> c++tools in this diff.)

>

> We still need to build the default resolver when building cc1plus, and

> I've placed mapper-resolver.cc there, as a simple #include forwarder

> to the source in c++tools.  I also replace 'gcc/cp/mapper.h' with a

> client-specific 'gcc/cp/mapper-client.h'.  (mapper-client is only

> linked into cc1plus, so gcc/cp seems the right place for it.)

>

> The sample server relies on gcc/version.o to pick up its version

> number, and I place it in the libexecsubdir that we place cc1plus.  I

> wasn't comfortable placing it in the install location of g++ itself. 

> I call it a sample server for a reason :)

>

> I will of course provide changelog when committing.

>

> nathan

>

>

> 24a-c++-mapper.diff

>



So I think you should just own these bits.  You're going to be far more
familiar with them than anyone else involved in GCC work :-)  So, OK for
the trunk as well as any followups into the module mapper.

jeff

Patch

diff --git c/gcc/cp/mapper-client.cc w/gcc/cp/mapper-client.cc
new file mode 100644
index 00000000000..de259b0564c
--- /dev/null
+++ w/gcc/cp/mapper-client.cc
@@ -0,0 +1,315 @@ 
+/* C++ modules.  Experimental!
+   Copyright (C) 2017-2020 Free Software Foundation, Inc.
+   Written by Nathan Sidwell <nathan@acm.org> while at FaceBook
+
+   This file is part of GCC.
+
+   GCC is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3, or (at your option)
+   any later version.
+
+   GCC is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+
+#include "line-map.h"
+#include "diagnostic-core.h"
+#define MAPPER_FOR_GCC 1
+#include "mapper.h"
+#include "intl.h"
+
+module_client::module_client (pex_obj *p, int fd_from, int fd_to)
+  : Client (fd_from, fd_to), pex (p)
+{
+}
+
+static module_client *
+spawn_mapper_program (char const **errmsg, std::string &name,
+		      char const *full_program_name)
+{
+  /* Split writable at white-space.  No space-containing args for
+     you!  */
+  // At most every other char could be an argument
+  char **argv = new char *[name.size () / 2 + 2];
+  unsigned arg_no = 0;
+  char *str = new char[name.size ()];
+  memcpy (str, name.c_str () + 1, name.size ());
+
+  for (auto ptr = str; ; ++ptr)
+    {
+      while (*ptr == ' ')
+	ptr++;
+      if (!*ptr)
+	break;
+      argv[arg_no++] = ptr;
+      while (*ptr && *ptr != ' ')
+	ptr++;
+      if (!*ptr)
+	break;
+      *ptr = 0;
+    }
+  argv[arg_no] = nullptr;
+
+  auto *pex = pex_init (PEX_USE_PIPES, progname, NULL);
+  FILE *to = pex_input_pipe (pex, false);
+  name = argv[0];
+  if (!to)
+    *errmsg = "connecting input";
+  else
+    {
+      int flags = PEX_SEARCH;
+	  
+      if (full_program_name)
+	{
+	  /* Prepend the invoking path.  */
+	  size_t dir_len = progname - full_program_name;
+	  std::string argv0;
+	  argv0.reserve (dir_len + name.size ());
+	  argv0.append (full_program_name, dir_len).append (name);
+	  name = std::move (argv0);
+	  argv[0] = const_cast <char *> (name.c_str ());
+	  flags = 0;
+	}
+      int err;
+      *errmsg = pex_run (pex, flags, argv[0], argv, NULL, NULL, &err);
+    }
+  delete[] str;
+  delete[] argv;
+
+  int fd_from = -1, fd_to = -1;
+  if (!*errmsg)
+    {
+      FILE *from = pex_read_output (pex, false);
+      if (from && (fd_to = dup (fileno (to))) >= 0)
+	fd_from = fileno (from);
+      else
+	*errmsg = "connecting output";
+      fclose (to);
+    }
+
+  if (*errmsg)
+    {
+      pex_free (pex);
+      return nullptr;
+    }
+
+  return new module_client (pex, fd_from, fd_to);
+}
+
+module_client *
+module_client::open_module_client (location_t loc, const char *o,
+				   void (*set_repo) (const char *),
+				   char const *full_program_name)
+{
+  module_client *c = nullptr;
+  std::string ident;
+  std::string name;
+  char const *errmsg = nullptr;
+  unsigned line = 0;
+
+  if (o && o[0])
+    {
+      /* Maybe a local or ipv6 address.  */
+      name = o;
+      auto last = name.find_last_of ('?');
+      if (last != name.npos)
+	{
+	  ident = name.substr (last + 1);
+	  name.erase (last);
+	}
+
+      if (name.size ())
+	{
+	  switch (name[0])
+	    {
+	    case '<':
+	      // <from>to or <>fromto, or <>
+	      {
+		int fd_from = -1, fd_to = -1;
+		char const *ptr = name.c_str ();
+		char *eptr;
+
+		fd_from = strtoul (++ptr, &eptr, 0);
+		if (*eptr == '>')
+		  {
+		    ptr = eptr;
+		    fd_to = strtoul (++ptr, &eptr, 0);
+		    if (eptr != ptr && ptr == name.c_str () + 1)
+		      fd_from = fd_to;
+		  }
+
+		if (*eptr)
+		  errmsg = "parsing";
+		else
+		  {
+		    if (name.size () == 2)
+		      {
+			fd_from = fileno (stdin);
+			fd_to = fileno (stdout);
+		      }
+		    c = new module_client (fd_from, fd_to);
+		  }
+	      }
+	      break;
+
+	    case '=':
+	      // =localsocket
+	      {
+		int fd = -1;
+#if CODY_NETWORKING
+		fd = Cody::OpenLocal (&errmsg, name.c_str () + 1);
+#endif
+		if (fd >= 0)
+		  c = new module_client (fd, fd);
+	      }
+	      break;
+
+	    case '|':
+	      // |program and args
+	      c = spawn_mapper_program (&errmsg, name, full_program_name);
+	      break;
+
+	    default:
+	      // file or hostname:port
+	      {
+		auto colon = name.find_last_of (':');
+		if (colon != name.npos)
+		  {
+		    char const *cptr = name.c_str () + colon;
+		    char *endp;
+		    unsigned port = strtoul (cptr + 1, &endp, 10);
+
+		    if (port && endp != cptr + 1 && !*endp)
+		      {
+			name[colon] = 0;
+			int fd = 01;
+#if CODY_NETWORKING
+			fd = Cody::OpenInet6 (&errmsg, name.c_str (), port);
+#endif
+			name[colon] = ':';
+
+			if (fd >= 0)
+			  c = new module_client (fd, fd);
+		      }
+		  }
+		
+	      }
+	      break;
+	    }
+	}
+    }
+
+  if (!c)
+    {
+      // Make a default in-process client
+      bool file = !errmsg && !name.empty ();
+      auto r = new module_resolver (!file, true);
+
+      if (file)
+	{
+	int fd = open (name.c_str (), O_RDONLY | O_CLOEXEC);
+	if (fd < 0)
+	  errmsg = "opening";
+	else
+	  {
+	    if (int l = r->read_tuple_file (fd, ident, false))
+	      {
+		if (l > 0)
+		  line = l;
+		errmsg = "reading";
+	      }
+	      
+	    close (fd);
+	  }
+	}
+      else
+	r->set_repo ("gcm.cache");
+
+      auto *s = new Cody::Server (r);
+      c = new module_client (s);
+    }
+
+#ifdef SIGPIPE
+  if (!c->IsDirect ())
+    /* We need to ignore sig pipe for a while.  */
+    c->sigpipe = signal (SIGPIPE, SIG_IGN);
+#endif
+
+  if (errmsg)
+    error_at (loc, line ? G_("failed %s mapper %qs line %u")
+	      : G_("failed %s mapper %qs"), errmsg, name.c_str (), line);
+
+  // now wave hello!
+  c->Cork ();
+  c->Connect (std::string ("GCC"), ident);
+  c->ModuleRepo ();
+  auto packets = c->Uncork ();
+
+  auto &connect = packets[0];
+  if (connect.GetCode () == Cody::Client::PC_CONNECT)
+    ;
+  else if (connect.GetCode () == Cody::Client::PC_ERROR)
+    error_at (loc, "failed mapper handshake %s", connect.GetString ().c_str ());
+
+  auto &repo = packets[1];
+  if (repo.GetCode () == Cody::Client::PC_PATHNAME)
+    set_repo (repo.GetString ().c_str ());
+
+  return c;
+}
+
+void
+module_client::close_module_client (location_t loc, module_client *mapper)
+{
+  if (mapper->IsDirect ())
+    {
+      auto *s = mapper->GetServer ();
+      auto *r = s->GetResolver ();
+      delete s;
+      delete r;
+    }
+  else
+    {
+      if (mapper->pex)
+	{
+	  int fd_write = mapper->GetFDWrite ();
+	  if (fd_write >= 0)
+	    close (fd_write);
+
+	  int status;
+	  pex_get_status (mapper->pex, 1, &status);
+
+	  pex_free (mapper->pex);
+	  mapper->pex = NULL;
+
+	  if (WIFSIGNALED (status))
+	    error_at (loc, "mapper died by signal %s",
+		      strsignal (WTERMSIG (status)));
+	  else if (WIFEXITED (status) && WEXITSTATUS (status) != 0)
+	    error_at (loc, "mapper exit status %d",
+		      WEXITSTATUS (status));
+	}
+      else
+	{
+	  int fd_read = mapper->GetFDRead ();
+	  close (fd_read);
+	}
+
+#ifdef SIGPIPE
+      // Restore sigpipe
+      if (mapper->sigpipe != SIG_IGN)
+	signal (SIGPIPE, mapper->sigpipe);
+#endif
+    }
+
+  delete mapper;
+}
diff --git c/gcc/cp/mapper-resolver.cc w/gcc/cp/mapper-resolver.cc
new file mode 100644
index 00000000000..7692380812a
--- /dev/null
+++ w/gcc/cp/mapper-resolver.cc
@@ -0,0 +1,266 @@ 
+/* C++ modules.  Experimental!	-*- c++ -*-
+   Copyright (C) 2017-2020 Free Software Foundation, Inc.
+   Written by Nathan Sidwell <nathan@acm.org> while at FaceBook
+
+   This file is part of GCC.
+
+   GCC is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3, or (at your option)
+   any later version.
+
+   GCC is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+
+#include "mapper.h"
+// C++
+#include <algorithm>
+// C
+#include <cstring>
+// OS
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+module_resolver::module_resolver (bool map, bool xlate)
+  : default_map (map), default_translate (xlate)
+{
+}
+
+module_resolver::~module_resolver ()
+{
+  if (fd_repo >= 0)
+    close (fd_repo);
+}
+
+bool
+module_resolver::set_repo (std::string &&r, bool force)
+{
+  if (force || repo.empty ())
+    {
+      repo = std::move (r);
+      force = true;
+    }
+  return force;
+}
+
+bool
+module_resolver::add_mapping (std::string &&module, std::string &&file,
+			      bool force)
+{
+  auto res = map.emplace (std::move (module), std::move (file));
+  if (res.second)
+    force = true;
+  else if (force)
+    res.first->second = std::move (file);
+
+  return force;
+}
+
+int
+module_resolver::read_tuple_file (int fd, char const *prefix, bool force)
+{
+  struct stat stat;
+  if (fstat (fd, &stat) < 0)
+    return -errno;
+
+  if (!stat.st_size)
+    return 0;
+
+  // Just map the file, we're gonna read all of it, so no need for
+  // line buffering
+  void *buffer = mmap (nullptr, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+  if (buffer == MAP_FAILED)
+    return -errno;
+
+  size_t prefix_len = prefix ? strlen (prefix) : 0;
+  unsigned lineno = 0;
+
+  for (char const *begin = reinterpret_cast <char const *> (buffer),
+	 *end = begin + stat.st_size, *eol;
+       begin != end; begin = eol + 1)
+    {
+      lineno++;
+      eol = std::find (begin, end, '\n');
+      if (eol == end)
+	// last line has no \n, ignore the line, you lose
+	break;
+
+      auto *pos = begin;
+      bool pfx_search = prefix_len != 0;
+
+    pfx_search:
+      while (*pos == ' ' || *pos == '\t')
+	pos++;
+
+      auto *space = pos;
+      while (*space != '\n' && *space != ' ' && *space != '\t')
+	space++;
+
+      if (pos == space)
+	// at end of line, nothing here	
+	continue;
+
+      if (pfx_search)
+	{
+	  if (size_t (space - pos) == prefix_len
+	      && std::equal (pos, space, prefix))
+	    pfx_search = false;
+	  pos = space;
+	  goto pfx_search;
+	}
+
+      std::string module (pos, space);
+      while (*space == ' ' || *space == '\t')
+	space++;
+      std::string file (space, eol);
+
+      if (module[0] == '$')
+	{
+	  if (module == "$root")
+	    set_repo (std::move (file));
+	  else
+	    return lineno;
+	}
+      else
+	{
+	  if (file.empty ())
+	    file = GetCMIName (module);
+	  add_mapping (std::move (module), std::move (file), force);
+	}
+    }
+
+  munmap (buffer, stat.st_size);
+
+  return 0;
+}
+
+char const *
+module_resolver::GetCMISuffix ()
+{
+  return "gcm";
+}
+
+module_resolver *
+module_resolver::ConnectRequest (Cody::Server *s, unsigned version,
+				 std::string &a, std::string &i)
+{
+  if (!version || version > Cody::Version)
+    s->ErrorResponse ("version mismatch");
+  else if (a != "GCC")
+    // Refuse anything but GCC
+    ErrorResponse (s, std::string ("only GCC supported"));
+  else if (!ident.empty () && ident != i)
+    // Failed ident check
+    ErrorResponse (s, std::string ("bad ident"));
+  else
+    // Success!
+    s->ConnectResponse ("gcc");
+
+  return this;
+}
+
+int
+module_resolver::ModuleRepoRequest (Cody::Server *s)
+{
+  s->PathnameResponse (repo);
+  return 0;
+}
+
+int
+module_resolver::cmi_response (Cody::Server *s, std::string &module)
+{
+  auto iter = map.find (module);
+  if (iter == map.end ())
+    {
+      std::string file;
+      if (default_map)
+	file = std::move (GetCMIName (module));
+      auto res = map.emplace (module, file);
+      iter = res.first;
+    }
+
+  if (iter->second.empty ())
+    s->ErrorResponse ("no such module");
+  else
+    s->PathnameResponse (iter->second);
+
+  return 0;
+}
+
+int
+module_resolver::ModuleExportRequest (Cody::Server *s, std::string &module)
+{
+  return cmi_response (s, module);
+}
+
+int
+module_resolver::ModuleImportRequest (Cody::Server *s, std::string &module)
+{
+  return cmi_response (s, module);
+}
+
+int
+module_resolver::IncludeTranslateRequest (Cody::Server *s, std::string &include)
+{
+  auto iter = map.find (include);
+  if (iter == map.end () && default_translate)
+    {
+      // Not found, look for it
+      auto file = GetCMIName (include);
+      struct stat statbuf;
+      bool ok = true;
+
+#if HAVE_FSTATAT
+      int fd_dir = AT_FDCWD;
+      if (!repo.empty ())
+	{
+	  if (fd_repo == -1)
+	    {
+	      fd_repo = open (repo.c_str (),
+			      O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+	      if (fd_repo < 0)
+		fd_repo = -2;
+	    }
+	  fd_dir = fd_repo;
+	}
+
+      if (!repo.empty () && fd_repo < 0)
+	ok = false;
+      else if (fstatat (fd_dir, file.c_str (), &statbuf, 0) < 0
+	       || !S_ISREG (statbuf.st_mode))
+	ok = false;
+#else
+      auto append = repo;
+      append.push_back (DIR_SEPARATOR);
+      append.append (file);
+      if (stat (append.c_str (), &statbuf) < 0
+	  || !S_ISREG (statbuf.st_mode))
+	ok = false;
+#endif
+      if (!ok)
+	// Mark as not present
+	file.clear ();
+      auto res = map.emplace (include, file);
+      iter = res.first;
+    }
+
+  if (iter == map.end () || iter->second.empty ())
+    s->BoolResponse (false);
+  else
+    s->PathnameResponse (iter->second);
+
+  return 0;
+}
+
diff --git c/gcc/cp/mapper-server.cc w/gcc/cp/mapper-server.cc
new file mode 100644
index 00000000000..f0d26810501
--- /dev/null
+++ w/gcc/cp/mapper-server.cc
@@ -0,0 +1,968 @@ 
+/* C++ modules.  Experimental!
+   Copyright (C) 2018-2020 Free Software Foundation, Inc.
+   Written by Nathan Sidwell <nathan@acm.org> while at FaceBook
+
+   This file is part of GCC.
+
+   GCC is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3, or (at your option)
+   any later version.
+
+   GCC is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "mapper.h"
+
+// C++
+#include <set>
+#include <vector>
+// GCC
+#define INCLUDE_VECTOR
+#define INCLUDE_MAP
+#define INCLUDE_SET
+
+// Network
+/* Include network stuff first.  Excitingly OSX10.14 uses bcmp here, which
+   we poison later!  */
+#if defined (HAVE_AF_UNIX) || defined (HAVE_AF_INET6)
+/* socket, bind, listen, accept{4}  */
+# define NETWORKING 1
+# include <sys/socket.h>
+# ifdef HAVE_AF_UNIX
+/* sockaddr_un  */
+#  include <sys/un.h>
+# endif
+# include <netinet/in.h>
+# ifdef HAVE_AF_INET6
+/* sockaddr_in6, getaddrinfo, freeaddrinfo, gai_sterror, ntohs, htons.  */
+#  include <netdb.h>
+# endif
+#ifdef HAVE_INET_NTOP
+/* inet_ntop.  */
+#include <arpa/inet.h>
+#endif
+#endif
+#ifndef HAVE_AF_INET6
+# define gai_strerror(X) ""
+#endif
+
+// Excitingly Darwin uses bcmp in its network headers, and we poison
+// that in our setup.
+#include "system.h"
+#include "version.h"
+#include "intl.h"
+#include <getopt.h>
+
+// Select or epoll
+#ifdef NETWORKING
+#ifdef HAVE_EPOLL
+/* epoll_create, epoll_ctl, epoll_pwait  */
+#include <sys/epoll.h>
+#endif
+#if defined (HAVE_PSELECT) || defined (HAVE_SELECT)
+/* pselect or select  */
+#include <sys/select.h>
+#endif
+#endif
+
+#if !HOST_HAS_O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
+
+#ifndef HAVE_SIGHANDLER_T
+typedef void (*sighandler_t) (int);
+#endif
+
+#ifdef NETWORKING
+struct netmask {
+  in6_addr addr;
+  unsigned bits;
+
+  netmask (const in6_addr &a, unsigned b)
+  {
+    if (b > sizeof (in6_addr) * 8)
+      b = sizeof (in6_addr) * 8;
+    bits = b;
+    unsigned byte = (b + 7) / 8;
+    unsigned ix = 0;
+    for (ix = 0; ix < byte; ix++)
+      addr.s6_addr[ix] = a.s6_addr[ix];
+    for (; ix != sizeof (in6_addr); ix++)
+      addr.s6_addr[ix] = 0;
+    if (b & 3)
+      addr.s6_addr[b/7] &= (255 << 8) >> (b & 3);
+  }
+
+  bool includes (const in6_addr &a) const
+  {
+    unsigned byte = bits / 8;
+    for (unsigned ix = 0; ix != byte; ix++)
+      if (addr.s6_addr[ix] != a.s6_addr[ix])
+	return false;
+    if (bits & 3)
+      if ((addr.s6_addr[byte] ^ a.s6_addr[byte]) >> (8 - (bits & 3)))
+	return false;
+    return true;
+  }
+};
+
+/* Netmask comparison.  */
+struct netmask_cmp {
+  bool operator() (const netmask &a, const netmask &b) const
+  {
+    if (a.bits != b.bits)
+      return a.bits < b.bits;
+    for (unsigned ix = 0; ix != sizeof (in6_addr); ix++)
+      if (a.addr.s6_addr[ix] != b.addr.s6_addr[ix])
+	return a.addr.s6_addr[ix] < b.addr.s6_addr[ix];
+    return false;
+  }
+};
+
+typedef std::set<netmask, netmask_cmp> netmask_set_t;
+typedef std::vector<netmask> netmask_vec_t;
+#endif
+
+const char *progname;
+
+/* Speak thoughts out loud.  */
+static bool flag_noisy = false;
+
+/* One and done.  */
+static bool flag_one = false;
+
+/* Serialize connections.  */
+static bool flag_sequential = false;
+
+/* Fallback to default if map file is unrewarding.  */
+static bool flag_map = false;
+
+/* Fallback to xlate if map file is unrewarding.  */
+static bool flag_xlate = false;
+
+/* Root binary directory.  */
+static const char *flag_root = "gcm.cache";
+
+#ifdef NETWORKING
+static netmask_set_t netmask_set;
+
+static netmask_vec_t accept_addrs;
+#endif
+
+/* Strip out the source directory from FILE.  */
+
+static const char *
+trim_src_file (const char *file)
+{
+  static const char me[] = __FILE__;
+  unsigned pos = 0;
+
+  while (file[pos] == me[pos] && me[pos])
+    pos++;
+  while (pos && !IS_DIR_SEPARATOR (me[pos-1]))
+    pos--;
+
+  return file + pos;
+}
+
+/* Die screaming.  */
+
+void ATTRIBUTE_NORETURN ATTRIBUTE_PRINTF_1 ATTRIBUTE_COLD
+internal_error (const char *fmt, ...)
+{
+  fprintf (stderr, "%s:Internal error ", progname);
+  va_list args;
+
+  va_start (args, fmt);
+  vfprintf (stderr, fmt, args);
+  va_end (args);
+  fprintf (stderr, "\n");
+
+  exit (FATAL_EXIT_CODE);
+}
+
+/* Hooked to from gcc_assert & gcc_unreachable.  */
+
+void ATTRIBUTE_NORETURN ATTRIBUTE_COLD
+fancy_abort (const char *file, int line, const char *func)
+{
+  internal_error ("in %s, at %s:%d", func, trim_src_file (file), line);
+}
+
+/* Exploded on a signal.  */
+
+static void ATTRIBUTE_NORETURN ATTRIBUTE_COLD
+crash_signal (int sig)
+{
+  signal (sig, SIG_DFL);
+  internal_error ("signal %s", strsignal (sig));
+}
+
+/* A fatal error of some kind.  */
+
+static void ATTRIBUTE_NORETURN ATTRIBUTE_COLD ATTRIBUTE_PRINTF_1
+error (const char *msg, ...)
+{
+  fprintf (stderr, "%s:error: ", progname);
+  va_list args;
+
+  va_start (args, msg);
+  vfprintf (stderr, msg, args);
+  va_end (args);
+  fprintf (stderr, "\n");
+
+  exit (1);
+}
+
+#ifdef NETWORKING
+/* Progress messages to the user.  */
+static bool ATTRIBUTE_PRINTF_1 ATTRIBUTE_COLD
+noisy (const char *fmt, ...)
+{
+  fprintf (stderr, "%s:", progname);
+  va_list args;
+  va_start (args, fmt);
+  vfprintf (stderr, fmt, args);
+  va_end (args);
+  fprintf (stderr, "\n");
+
+  return false;
+}
+#endif
+
+/* More messages to the user.  */
+
+static void ATTRIBUTE_PRINTF_2
+fnotice (FILE *file, const char *fmt, ...)
+{
+  va_list args;
+
+  va_start (args, fmt);
+  vfprintf (file, _(fmt), args);
+  va_end (args);
+}
+
+static void ATTRIBUTE_NORETURN
+print_usage (int error_p)
+{
+  FILE *file = error_p ? stderr : stdout;
+  int status = error_p ? FATAL_EXIT_CODE : SUCCESS_EXIT_CODE;
+
+  fnotice (file, "Usage: cxx-mapper [OPTION...] [CONNECTION] [MAPPINGS...] \n\n");
+  fnotice (file, "C++ Module Mapper.\n\n");
+  fnotice (file, "  -a, --accept     Netmask to accept from\n");
+  fnotice (file, "  -f, --fallback   Use fallback for missing mappings\n");
+  fnotice (file, "  -h, --help       Print this help, then exit\n");
+  fnotice (file, "  -n, --noisy      Print progress messages\n");
+  fnotice (file, "  -1, --one        One connection and then exit\n");
+  fnotice (file, "  -r, --root DIR   Root compiled module directory\n");
+  fnotice (file, "  -s, --sequential Process connections sequentially\n");
+  fnotice (file, "  -v, --version    Print version number, then exit\n");
+  fnotice (file, "Send SIGTERM(%d) to terminate\n", SIGTERM);
+  fnotice (file, "\nFor bug reporting instructions, please see:\n%s.\n",
+	   bug_report_url);
+  exit (status);
+}
+
+/* Print version information and exit.  */
+
+static void ATTRIBUTE_NORETURN
+print_version (void)
+{
+  fnotice (stdout, "cxx-mapper %s%s\n", pkgversion_string, version_string);
+  fprintf (stdout, "Copyright %s 2018-2020 Free Software Foundation, Inc.\n",
+	   _("(C)"));
+  fnotice (stdout,
+	   _("This is free software; see the source for copying conditions.\n"
+	     "There is NO warranty; not even for MERCHANTABILITY or \n"
+	     "FITNESS FOR A PARTICULAR PURPOSE.\n\n"));
+  exit (SUCCESS_EXIT_CODE);
+}
+
+/* ARG is a netmask to accept from.  Add it to the table.  Return
+   false if we fail to resolve it.  */
+
+static bool
+accept_from (char *arg ATTRIBUTE_UNUSED)
+{
+  bool ok = true;
+#if HAVE_AF_INET6
+  unsigned bits = sizeof (in6_addr) * 8;
+  char *slash = strrchr (arg, '/');
+  if (slash)
+    {
+      *slash = 0;
+      if (slash[1])
+	{
+	  char *endp;
+	  bits = strtoul (slash + 1, &endp, 0);
+	}
+    }
+
+  addrinfo hints;
+
+  hints.ai_flags = AI_NUMERICSERV;
+  hints.ai_family = AF_INET6;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_protocol = 0;
+  hints.ai_addrlen = 0;
+  hints.ai_addr = NULL;
+  hints.ai_canonname = NULL;
+  hints.ai_next = NULL;
+
+  struct addrinfo *addrs = NULL;
+  if (int e = getaddrinfo (slash == arg ? NULL : arg, "0", &hints, &addrs))
+    {
+      noisy ("cannot resolve '%s': %s", arg, gai_strerror (e));
+      ok = false;
+    }
+  else
+    for (addrinfo *next = addrs; next; next = next->ai_next)
+      if (next->ai_family == AF_INET6)
+	{
+	  netmask mask (((const sockaddr_in6 *)next->ai_addr)->sin6_addr, bits);
+	  netmask_set.insert (mask);
+	}
+  freeaddrinfo (addrs);
+#endif
+  return ok;
+}
+
+/* Process args, return index to first non-arg.  */
+
+static int
+process_args (int argc, char **argv)
+{
+  static const struct option options[] =
+    {
+     { "accept", required_argument, NULL, 'a' },
+     { "help",	no_argument,	NULL, 'h' },
+     { "map",   no_argument,	NULL, 'm' },
+     { "noisy",	no_argument,	NULL, 'n' },
+     { "one",	no_argument,	NULL, '1' },
+     { "root",	required_argument, NULL, 'r' },
+     { "sequential", no_argument, NULL, 's' },
+     { "translate",no_argument,	NULL, 't' },
+     { "version", no_argument,	NULL, 'v' },
+     { 0, 0, 0, 0 }
+    };
+  int opt;
+  bool bad_accept = false;
+  const char *opts = "a:fhmn1r:stv";
+  while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1)
+    {
+      switch (opt)
+	{
+	case 'a':
+	  if (!accept_from (optarg))
+	    bad_accept = true;
+	  break;
+	case 'h':
+	  print_usage (false);
+	  /* print_usage will exit.  */
+	case 'f': // deprecated alias
+	case 'm':
+	  flag_map = true;
+	  break;
+	case 'n':
+	  flag_noisy = true;
+	  break;
+	case '1':
+	  flag_one = true;
+	  break;
+	case 'r':
+	  flag_root = optarg;
+	  break;
+	case 's':
+	  flag_sequential = true;
+	  break;
+	case 't':
+	  flag_xlate = true;
+	  break;
+	case 'v':
+	  print_version ();
+	  /* print_version will exit.  */
+	default:
+	  print_usage (true);
+	  /* print_usage will exit.  */
+	}
+    }
+
+  if (bad_accept)
+    error ("failed to resolve all accept addresses");
+
+  return optind;
+}
+
+#ifdef NETWORKING
+
+/* Manipulate the EPOLL state, or do nothing, if there is epoll.  */
+
+#ifdef HAVE_EPOLL
+static inline void
+do_epoll_ctl (int epoll_fd, int code, int event, int fd, unsigned data)
+{
+  epoll_event ev;
+  ev.events = event;
+  ev.data.u32 = data;
+  if (epoll_ctl (epoll_fd, code, fd, &ev))
+    {
+      noisy ("epoll_ctl error:%s", xstrerror (errno));
+      gcc_unreachable ();
+    }
+}
+#define my_epoll_ctl(EFD,C,EV,FD,CL) \
+  ((EFD) >= 0 ? do_epoll_ctl (EFD,C,EV,FD,CL) : (void)0)
+#else
+#define my_epoll_ctl(EFD,C,EV,FD,CL) ((void)(EFD), (void)(FD), (void)(CL))
+#endif
+
+/* We increment this to tell the server to shut down.  */
+static volatile int term = false;
+static volatile int kill_sock_fd = -1;
+#if !defined (HAVE_PSELECT) && defined (HAVE_SELECT)
+static int term_pipe[2] = {-1, -1};
+#else
+#define term_pipe ((int *)NULL)
+#endif
+
+/* A terminate signal.  Shutdown gracefully.  */
+
+static void
+term_signal (int sig)
+{
+  signal (sig, term_signal);
+  term = term + 1;
+  if (term_pipe && term_pipe[1] >= 0)
+    write (term_pipe[1], &term_pipe[1], 1);
+}
+
+/* A kill signal.  Shutdown immediately.  */
+
+static void
+kill_signal (int sig)
+{
+  signal (sig, SIG_DFL);
+  int sock_fd = kill_sock_fd;
+  if (sock_fd >= 0)
+    close (sock_fd);
+  exit (2);
+}
+
+bool process_server (Cody::Server *server, unsigned slot, int epoll_fd)
+{
+  switch (server->GetDirection ())
+    {
+    case Cody::Server::READING:
+      if (int err = server->Read ())
+	return !(err == EINTR || err == EAGAIN);
+      server->ProcessRequests ();
+      server->PrepareToWrite ();
+      break;
+
+    case Cody::Server::WRITING:
+      if (int err = server->Write ())
+	return !(err == EINTR || err == EAGAIN);
+      server->PrepareToRead ();
+      break;
+
+    default:
+      // We should never get here
+      return true;
+    }
+
+  // We've changed direction, so update epoll
+  gcc_assert (server->GetFDRead () == server->GetFDWrite ());
+  my_epoll_ctl (epoll_fd, EPOLL_CTL_MOD,
+		server->GetDirection () == Cody::Server::READING
+		? EPOLLIN : EPOLLOUT, server->GetFDRead (), slot + 1);
+
+  return false;
+}
+
+void close_server (Cody::Server *server, int epoll_fd)
+{
+  my_epoll_ctl (epoll_fd, EPOLL_CTL_DEL, EPOLLIN, server->GetFDRead (), 0);
+
+  close (server->GetFDRead ());
+  
+  delete server;
+}
+
+int open_server (bool ip6, int sock_fd)
+{
+  sockaddr_in6 addr;
+  socklen_t addr_len = sizeof (addr);
+
+#ifdef HAVE_ACCEPT4
+  int client_fd = accept4 (sock_fd, ip6 ? (sockaddr *)&addr : nullptr,
+			   &addr_len, SOCK_NONBLOCK);
+#else
+  int client_fd = accept (sock_fd, ip6 ? (sockaddr *)&addr : nullptr, &addr_len);
+#endif
+  if (client_fd < 0)
+    {
+      error ("cannot accept: %s", xstrerror (errno));
+      flag_one = true;
+    }
+  else if (ip6)
+    {
+      const char *str = NULL;
+#if HAVE_INET_NTOP
+      char name[INET6_ADDRSTRLEN];
+      str = inet_ntop (addr.sin6_family, &addr.sin6_addr, name, sizeof (name));
+#endif
+      if (!accept_addrs.empty ())
+	{
+	  netmask_vec_t::iterator e = accept_addrs.end ();
+	  for (netmask_vec_t::iterator i = accept_addrs.begin ();
+	       i != e; ++i)
+	    if (i->includes (addr.sin6_addr))
+	      goto present;
+	  close (client_fd);
+	  client_fd = -1;
+	  noisy ("Rejecting connection from disallowed source '%s'",
+		 str ? str : "");
+	present:;
+	}
+      if (client_fd >= 0)
+	flag_noisy && noisy ("Accepting connection from '%s'", str ? str : "");
+    }
+
+  return client_fd;
+}
+
+/* A server listening on bound socket SOCK_FD.  */
+
+static void
+server (bool ipv6, int sock_fd, module_resolver *resolver)
+{
+  int epoll_fd = -1;
+
+  signal (SIGTERM, term_signal);
+#ifdef HAVE_EPOLL
+  epoll_fd = epoll_create (1);
+#endif
+  if (epoll_fd >= 0)
+    my_epoll_ctl (epoll_fd, EPOLL_CTL_ADD, EPOLLIN, sock_fd, 0);
+
+#if defined (HAVE_EPOLL) || defined (HAVE_PSELECT) || defined (HAVE_SELECT)
+  sigset_t mask;
+  {
+    sigset_t block;
+    sigemptyset (&block);
+    sigaddset (&block, SIGTERM);
+    sigprocmask (SIG_BLOCK, &block, &mask);
+  }
+#endif
+
+#ifdef HAVE_EPOLL
+  const unsigned max_events = 20;
+  epoll_event events[max_events];
+#endif
+#if defined (HAVE_PSELECT) || defined (HAVE_SELECT)
+  fd_set readers, writers;
+#endif
+  if (term_pipe)
+    pipe (term_pipe);
+
+  // We need stable references to servers, so this array can contain nulls
+  std::vector<Cody::Server *> connections;
+  unsigned live = 0;
+  while (sock_fd >= 0 || live)
+    {
+      /* Wait for one or more events.  */
+      bool eintr = false;
+      int event_count;
+
+      if (epoll_fd >= 0)
+	{
+#ifdef HAVE_EPOLL
+	  event_count = epoll_pwait (epoll_fd, events, max_events, -1, &mask);
+#endif
+	}
+      else
+	{
+#if defined (HAVE_PSELECT) || defined (HAVE_SELECT)
+	  FD_ZERO (&readers);
+	  FD_ZERO (&writers);
+
+	  unsigned limit = 0;
+	  if (sock_fd >= 0
+	      && !(term || (live && (flag_one || flag_sequential))))
+	    {
+	      FD_SET (sock_fd, &readers);
+	      limit = sock_fd + 1;
+	    }
+
+	  if (term_pipe && term_pipe[0] >= 0)
+	    {
+	      FD_SET (term_pipe[0], &readers);
+	      if (unsigned (term_pipe[0]) >= limit)
+		limit = term_pipe[0] + 1;
+	    }
+
+	  for (auto iter = connections.begin ();
+	       iter != connections.end (); ++iter)
+	    if (auto *server = *iter)
+	      {
+		int fd = -1;
+		switch (server->GetDirection ())
+		  {
+		  case Cody::Server::READING:
+		    fd = server->GetFDRead ();
+		    FD_SET (fd, &readers);
+		    break;
+		  case Cody::Server::WRITING:
+		    fd = server->GetFDWrite ();
+		    FD_SET (fd, &writers);
+		    break;
+		  default:
+		    break;
+		  }
+
+		if (fd >= 0 && limit <= unsigned (fd))
+		  limit = fd + 1;
+	      }
+
+#ifdef HAVE_PSELECT
+	  event_count = pselect (limit, &readers, &writers, NULL, NULL, &mask);
+#else
+	  event_count = select (limit, &readers, &writers, NULL, NULL);
+#endif
+	  if (term_pipe && FD_ISSET (term_pipe[0], &readers))
+	    {
+	      /* Fake up an interrupted system call.  */
+	      event_count = -1;
+	      errno = EINTR;
+	    }
+#endif
+	}
+
+      if (event_count < 0)
+	{
+	  // Error in waiting
+	  if (errno == EINTR)
+	    {
+	      flag_noisy && noisy ("Interrupted wait");
+	      eintr = true;
+	    }
+	  else
+	    error ("cannot %s: %s", epoll_fd >= 0 ? "epoll_wait"
+#ifdef HAVE_PSELECT
+		   : "pselect",
+#else
+		   : "select",
+#endif
+		   xstrerror (errno));
+	  event_count = 0;
+	}
+
+      auto iter = connections.begin ();
+      while (event_count--)
+	{
+	  // Process an event
+	  int active = -2;
+
+	  if (epoll_fd >= 0)
+	    {
+#ifdef HAVE_EPOLL
+	      /* See PR c++/88664 for why a temporary is used.  */
+	      unsigned data = events[event_count].data.u32;
+	      active = int (data) - 1;
+#endif
+	    }
+	  else
+	    {
+	      for (; iter != connections.end (); ++iter)
+		if (auto *server = *iter)
+		  {
+		    bool found = false;
+		    switch (server->GetDirection ())
+		      {
+#if defined (HAVE_PSELECT) || defined (HAVE_SELECT)
+		      case Cody::Server::READING:
+			found = FD_ISSET (server->GetFDRead (), &readers);
+			break;
+		      case Cody::Server::WRITING:
+			found = FD_ISSET (server->GetFDWrite (), &writers);
+			break;
+#endif
+		      default:
+			break;
+		      }
+
+		    if (found)
+		      {
+			active = iter - connections.begin ();
+			++iter;
+			break;
+		      }
+		  }
+
+	      if (active < 0 && sock_fd >= 0 && FD_ISSET (sock_fd, &readers))
+		active = -1;
+	    }
+
+	  if (active >= 0)
+	    {
+	      // Do the action
+	      auto *server = connections[active];
+	      if (process_server (server, active, epoll_fd))
+		{
+		  connections[active] = nullptr;
+		  close_server (server, epoll_fd);
+		  live--;
+		  if (flag_sequential)
+		    my_epoll_ctl (epoll_fd, EPOLL_CTL_ADD, EPOLLIN, sock_fd, 0);
+		}
+	    }
+	  else if (active == -1 && !eintr)
+	    {
+	      // New connection
+	      int fd = open_server (ipv6, sock_fd);
+	      if (fd >= 0)
+		{
+#if !defined (HAVE_ACCEPT4) \
+  && (defined (HAVE_EPOLL) || defined (HAVE_PSELECT) || defined (HAVE_SELECT))
+		  int flags = fcntl (fd, F_GETFL, 0);
+		  fcntl (fd, F_SETFL, flags | O_NONBLOCK);
+#endif
+		  auto *server = new Cody::Server (resolver, fd);
+
+		  unsigned slot = connections.size ();
+		  if (live == slot)
+		    connections.push_back (server);
+		  else
+		    for (auto iter = connections.begin (); ; ++iter)
+		      if (!*iter)
+			{
+			  *iter = server;
+			  slot = iter - connections.begin ();
+			  break;
+			}
+		  live++;
+		  my_epoll_ctl (epoll_fd, EPOLL_CTL_ADD, EPOLLIN, fd, slot + 1);
+		}
+	    }
+
+	  if (sock_fd >= 0
+	      && (term || (live && (flag_one || flag_sequential))))
+	    {
+	      /* Stop paying attention to sock_fd.  */
+	      my_epoll_ctl (epoll_fd, EPOLL_CTL_DEL, EPOLLIN, sock_fd, 0);
+	      if (flag_one || term)
+		{
+		  close (sock_fd);
+		  sock_fd = -1;
+		}
+	    }
+	}
+    }
+#if defined (HAVE_EPOLL) || defined (HAVE_PSELECT) || defined (HAVE_SELECT)
+  /* Restore the signal mask.  */
+  sigprocmask (SIG_SETMASK, &mask, NULL);
+#endif
+
+  gcc_assert (sock_fd < 0);
+  if (epoll_fd >= 0)
+    close (epoll_fd);
+
+  if (term_pipe && term_pipe[0] >= 0)
+    {
+      close (term_pipe[0]);
+      close (term_pipe[1]);
+    }
+}
+
+#endif
+
+static int maybe_parse_socket (std::string &option, module_resolver *r)
+{
+  /* Local or ipv6 address.  */
+  auto last = option.find_last_of ('?');
+  if (last != option.npos)
+    {
+      r->set_ident (option.c_str () + last + 1);
+      option.erase (last);
+    }
+  int fd = -2;
+  char const *errmsg = nullptr;
+
+  /* Does it look like a socket?  */
+  if (option[0] == '=')
+    {
+      /* A local socket.  */
+#if CODY_NETWORKING
+      fd = Cody::ListenLocal (&errmsg, option.c_str () + 1);
+#endif
+    }
+  else
+    {
+      auto colon = option.find_last_of (':');
+      if (colon != option.npos)
+	{
+	  /* Try a hostname:port address.  */
+	  char const *cptr = option.c_str () + colon;
+	  char *endp;
+	  unsigned port = strtoul (cptr + 1, &endp, 10);
+
+	  if (port && endp != cptr + 1 && !*endp)
+	    {
+	      /* Ends in ':number', treat as ipv6 domain socket.  */
+	      option.erase (colon);
+#if CODY_NETWORKING
+	      fd = Cody::ListenInet6 (&errmsg, option.c_str (), port);
+#endif
+	    }
+	}
+    }
+
+  if (errmsg)
+    error ("failed to open socket: %s", errmsg);
+
+  return fd;
+}
+
+int
+main (int argc, char *argv[])
+{
+  const char *p = argv[0] + strlen (argv[0]);
+  while (p != argv[0] && !IS_DIR_SEPARATOR (p[-1]))
+    --p;
+  progname = p;
+
+  xmalloc_set_program_name (progname);
+
+#ifdef SIGSEGV
+  signal (SIGSEGV, crash_signal);
+#endif
+#ifdef SIGILL
+  signal (SIGILL, crash_signal);
+#endif
+#ifdef SIGBUS
+  signal (SIGBUS, crash_signal);
+#endif
+#ifdef SIGABRT
+  signal (SIGABRT, crash_signal);
+#endif
+#ifdef SIGFPE
+  signal (SIGFPE, crash_signal);
+#endif
+#ifdef SIGPIPE
+  /* Ignore sigpipe, so read/write get an error.  */
+  signal (SIGPIPE, SIG_IGN);
+#endif
+#ifdef NETWORKING
+#ifdef SIGINT
+  signal (SIGINT, kill_signal);
+#endif
+#endif
+
+  int argno = process_args (argc, argv);
+
+  std::string name;
+  int sock_fd = -1; /* Socket fd, otherwise stdin/stdout.  */
+  module_resolver r (flag_map, flag_xlate);
+
+  if (argno != argc)
+    {
+      name = argv[argno];
+      sock_fd = maybe_parse_socket (name, &r);
+      if (!name.empty ())
+	argno++;
+    }
+
+  if (argno != argc)
+    for (; argno != argc; argno++)
+      {
+	std::string option = argv[argno];
+	char const *prefix = nullptr;
+	auto ident = option.find_last_of ('?');
+	if (ident != option.npos)
+	  {
+	    prefix = option.c_str () + ident + 1;
+	    option[ident] = 0;
+	  }
+	int fd = open (option.c_str (), O_RDONLY | O_CLOEXEC);
+	int err = 0;
+	if (fd < 0)
+	  err = errno;
+	else
+	  {
+	    err = r.read_tuple_file (fd, prefix, false);
+	    close (fd);
+	  }
+
+	if (err)
+	  error ("failed reading '%s': %s", option.c_str (), xstrerror (err));
+      }
+  else
+    r.set_default_map (true);
+
+  if (flag_root)
+    r.set_repo (flag_root);
+
+#ifdef HAVE_AF_INET6
+  netmask_set_t::iterator end = netmask_set.end ();
+  for (netmask_set_t::iterator iter = netmask_set.begin ();
+       iter != end; ++iter)
+    {
+      netmask_vec_t::iterator e = accept_addrs.end ();
+      for (netmask_vec_t::iterator i = accept_addrs.begin (); i != e; ++i)
+	if (i->includes (iter->addr))
+	  goto present;
+      accept_addrs.push_back (*iter);
+    present:;
+    }
+#endif
+
+#ifdef NETWORKING
+  if (sock_fd >= 0)
+    {
+      server (name[0] != '=', sock_fd, &r);
+      if (name[0] == '=')
+	unlink (name.c_str () + 1);
+    }
+  else
+#endif
+    {
+      gcc_assert (sock_fd < 0);
+      auto server = Cody::Server (&r, 0, 1);
+
+      int err = 0;
+      for (;;)
+	{
+	  server.PrepareToRead ();
+	  while ((err = server.Read ()))
+	    {
+	      if (err == EINTR || err == EAGAIN)
+		continue;
+	      goto done;
+	    }
+
+	  server.ProcessRequests ();
+
+	  server.PrepareToWrite ();
+	  while ((err = server.Write ()))
+	    {
+	      if (err == EINTR || err == EAGAIN)
+		continue;
+	      goto done;
+	    }
+	}
+    done:;
+      if (err > 0)
+	error ("communication error:%s", xstrerror (err));
+    }
+
+  return 0;
+}
diff --git c/gcc/cp/mapper.h w/gcc/cp/mapper.h
new file mode 100644
index 00000000000..86562252b53
--- /dev/null
+++ w/gcc/cp/mapper.h
@@ -0,0 +1,122 @@ 
+/* C++ modules.  Experimental!	-*- c++ -*-
+   Copyright (C) 2017-2020 Free Software Foundation, Inc.
+   Written by Nathan Sidwell <nathan@acm.org> while at FaceBook
+
+   This file is part of GCC.
+
+   GCC is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3, or (at your option)
+   any later version.
+
+   GCC is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+// Mapper interface for client and server bits
+#include "cody.hh"
+// C++
+#include <string>
+#include <map>
+
+// This is a GCC class, so GCC coding conventions on new bits.  
+class module_resolver : public Cody::Resolver
+{
+public:
+  using parent = Cody::Resolver;
+  using module_map = std::map<std::string, std::string>;
+
+private:
+  std::string repo;
+  std::string ident;
+  module_map map;
+  int fd_repo = -1;
+  bool default_map = true;
+  bool default_translate = true;
+
+public:
+  module_resolver (bool map = true, bool xlate = false);
+  virtual ~module_resolver () override;
+
+public:
+  void set_default_map (bool d)
+  {
+    default_map = d;
+  }
+  void set_default_translate (bool d)
+  {
+    default_translate = d;
+  }
+  void set_ident (char const *i)
+  {
+    ident = i;
+  }
+  bool set_repo (std::string &&repo, bool force = false);
+  bool add_mapping (std::string &&module, std::string &&file,
+		    bool force = false);
+
+  // Return +ve line number of error, or -ve errno
+  int read_tuple_file (int fd, char const *prefix, bool force = false);
+  int read_tuple_file (int fd, std::string const &prefix,
+			    bool force = false)
+  {
+    return read_tuple_file (fd, prefix.empty () ? nullptr : prefix.c_str (),
+			    force);
+  }
+
+public:
+  // Virtual overriders, names are controlle by Cody::Resolver
+  virtual module_resolver *ConnectRequest (Cody::Server *, unsigned version,
+					   std::string &agent,
+					   std::string &ident)
+    override;
+  virtual int ModuleRepoRequest (Cody::Server *) override;
+  virtual int ModuleExportRequest (Cody::Server *s, std::string &module)
+    override;
+  virtual int ModuleImportRequest (Cody::Server *s, std::string &module)
+    override;
+  virtual int IncludeTranslateRequest (Cody::Server *s, std::string &include)
+    override;
+
+private:
+  virtual char const *GetCMISuffix () override;
+
+private:
+  int cmi_response (Cody::Server *s, std::string &module);
+};
+
+#ifdef MAPPER_FOR_GCC
+#ifndef HAVE_SIGHANDLER_T
+typedef void (*sighandler_t) (int);
+#endif
+
+class module_client : public Cody::Client
+{
+  pex_obj *pex = nullptr;
+  sighandler_t sigpipe = SIG_IGN;
+
+public:
+  module_client (Cody::Server *s)
+    : Client (s)
+  {
+  }
+  module_client (pex_obj *pex, int fd_from, int fd_to);
+
+  module_client (int fd_from, int fd_to)
+    : Client (fd_from, fd_to)
+  {
+  }
+
+public:
+  static module_client *open_module_client (location_t loc, const char *option,
+					    void (*set_repo) (const char *),
+					    char const *);
+  static void close_module_client (location_t loc, module_client *);
+};
+
+#endif