[incremental] Patch: FYI: initial server-izing

Tom Tromey tromey@redhat.com
Thu Aug 30 17:36:00 GMT 2007


I'm checking this in on the incremental-compiler branch.

This is the first draft of server-izing GCC.  This works well enough
to compile a single file, so it seemed like a good time to check it
in.  It still has bugs, for instance it crashes if you try to compile
a second file.

Tom

Index: ChangeLog
from  Tom Tromey  <tromey@redhat.com>

	* Makefile.in (OBJS-common): Added server.o.
	(GCC_OBJS): Likewise.
	(toplev.o): Added server.h.
	(gcc.o): Likewise.
	(server.o): New target.
	* toplev.c (toplev_main): Handle -fserver.
	Include server.h.
	(init_asm_output): Don't open file if already open.
	(server_callback): New function.
	* server.h: New file.
	* server.c: New file.
	* gcc.c (server): New global.
	(use_server): Likewise.
	(option_map): Added --server.
	(process_command): Recognize -server.
	Include server.h.
	(execute): Send commands to server.
	(server_callback): New function.

Index: gcc.c
===================================================================
--- gcc.c	(revision 127650)
+++ gcc.c	(working copy)
@@ -86,6 +86,7 @@
 #include "gcc.h"
 #include "flags.h"
 #include "opts.h"
+#include "server.h"
 
 /* By default there is no special suffix for target executables.  */
 /* FIXME: when autoconf is fixed, remove the host check - dj */
@@ -195,6 +196,15 @@
 
 static int report_times;
 
+/* Flag indicating that we should start a server process.  */
+
+static int server;
+
+/* Flag indicating that we should attempt to connect to a server
+   process.  */
+
+static int use_server;
+
 /* Nonzero means place this string before uses of /, so that include
    and library files can be found in an alternate location.  */
 
@@ -1154,6 +1164,7 @@
    {"--quiet", "-q", 0},
    {"--resource", "-fcompile-resource=", "aj"},
    {"--save-temps", "-save-temps", 0},
+   {"--server", "-server", 0},
    {"--shared", "-shared", 0},
    {"--silent", "-q", 0},
    {"--specs", "-specs=", "aj"},
@@ -2931,6 +2942,23 @@
 #endif /* DEBUG */
     }
 
+  if (use_server)
+    {
+      for (i = 0; i < n_commands; ++i)
+	{
+	  if (i == 0)
+	    {
+	      if (!client_connect (commands[i].argv[0]))
+		fatal ("couldn't start server: %s", commands[i].argv[0]);
+	    }
+	  if (!client_send_command (commands[i].argv))
+	    fatal ("couldn't send command to server: %s", commands[i].argv[0]);
+	}
+      client_wait ();
+      execution_count++;
+      return 0;
+    }
+
 #ifdef ENABLE_VALGRIND_CHECKING
   /* Run the each command through valgrind.  To simplify prepending the
      path to valgrind and the option "-q" (for quiet operation unless
@@ -3788,6 +3816,11 @@
 	  use_pipes = 1;
 	  n_switches++;
 	}
+      else if (strcmp (argv[i], "-server") == 0)
+	{
+	  server = 1;
+	  n_switches++;
+	}
       else if (strcmp (argv[i], "-###") == 0)
 	{
 	  /* This is similar to -v except that there is no execution
@@ -4159,6 +4192,8 @@
 	;
       else if (strcmp (argv[i], "-time") == 0)
 	;
+      else if (strcmp (argv[i], "-server") == 0)
+	;
       else if (strcmp (argv[i], "-###") == 0)
 	;
       else if (argv[i][0] == '-' && argv[i][1] != 0)
@@ -6128,6 +6163,12 @@
 
   xmalloc_set_program_name (programname);
 
+  if (getenv ("GCCSERVER") != NULL)
+    {
+      use_server = 1;
+      use_pipes = 1;		/* FIXME... need better processing */
+    }
+
   expandargv (&argc, &argv);
 
   /* Determine if any expansions were made.  */
@@ -6484,6 +6525,13 @@
 	 on the various sub-processes, along with the --help switch.  */
     }
 
+  if (server)
+    {
+      /* FIXME: allow more than cc1... */
+      server_start (find_a_file (&exec_prefixes, "cc1", X_OK, 0));
+      return 0;
+    }
+
   if (verbose_flag)
     {
       int n;
@@ -7938,3 +7986,13 @@
   fflush (stdout);
   return NULL;
 }
+
+
+
+void
+server_callback (int ARG_UNUSED (fd),
+		 char ** ARG_UNUSED (cc1_argv),
+		 char ** ARG_UNUSED (as_argv))
+{
+  gcc_unreachable ();
+}
Index: toplev.c
===================================================================
--- toplev.c	(revision 127650)
+++ toplev.c	(working copy)
@@ -82,6 +82,7 @@
 #include "alloc-pool.h"
 #include "tree-mudflap.h"
 #include "tree-pass.h"
+#include "server.h"
 
 #if defined (DWARF2_UNWIND_INFO) || defined (DWARF2_DEBUGGING_INFO)
 #include "dwarf2out.h"
@@ -1386,7 +1387,11 @@
 static void
 init_asm_output (const char *name)
 {
-  if (name == NULL && asm_file_name == 0)
+  if (asm_out_file)
+    {
+      /* Already initialized elsewhere.  */
+    }
+  else if (name == NULL && asm_file_name == 0)
     asm_out_file = stdout;
   else
     {
@@ -2159,6 +2164,73 @@
   timevar_print (stderr);
 }
 
+static struct pex_obj *
+start_as (char **as_argv)
+{
+  struct pex_obj *px;
+  int pxerr;
+
+  px = pex_init (PEX_USE_PIPES, "as", NULL);
+  if (px)
+    {
+      const char *errstr;
+
+      /* Note that asm_out_file is conditional on BUFSIZ and is
+	 redeclared in a number of files.  Yay.  */
+      asm_out_file = pex_input_pipe (px, 0);
+
+      errstr = pex_run (px, PEX_LAST | PEX_SEARCH, as_argv[0], as_argv + 1,
+			NULL, NULL, &pxerr);
+      if (errstr)
+	{
+	  /* FIXME.  */
+	  error ("failed to exec as: %s", errstr);
+	  pex_free (px);
+	  px = NULL;
+	}
+    }
+  
+  return px;
+}
+
+void
+server_callback (int fd, char **cc1_argv, char **as_argv)
+{
+  struct pex_obj *px;
+
+  /* For now, work single-threaded and just stomp on global state as
+     needed.  */
+
+  /* The diagnostic machinery doesn't need this, but pex does.  Also
+     GCC itself seems to write to stderr a lot ... */
+  dup2 (fd, 2);
+
+  /* FIXME: reset errorcount and sorrycount?  Make a new
+     global_dc?  */
+
+  px = start_as (as_argv);
+
+  if (px)
+    {
+      int n;
+      for (n = 0; cc1_argv[n]; ++n)
+	;
+      decode_options (n, (const char **) cc1_argv);
+      init_local_tick ();		/* FIXME... */
+      do_compile ();
+
+      /* FIXME: send a single byte back to the client for status?
+	 FIXME: this function should return an error indication.  */
+
+      pex_free (px);
+    }
+
+  /* Make sure to close dup'd stderr, so that client will terminate
+     properly.  The server loop will take care of the fd we were
+     passed.  */
+  close (2);
+}
+
 /* Entry point of cc1, cc1plus, jc1, f771, etc.
    Exit code is FATAL_EXIT_CODE if can't open files or if there were
    any errors, or SUCCESS_EXIT_CODE if compilation succeeded.
@@ -2173,6 +2245,13 @@
   /* Initialization of GCC's environment, and diagnostics.  */
   general_init (argv[0]);
 
+  if (argc == 2 && !strncmp (argv[1], "-fserver=", 9))
+    {
+      int fd = atoi (argv[1] + 9);
+      server_main_loop (argv[0], fd);
+      return SUCCESS_EXIT_CODE;
+    }
+
   /* Parse the options and do minimal processing; basically just
      enough to default flags appropriately.  */
   decode_options (argc, argv);
Index: server.c
===================================================================
--- server.c	(revision 0)
+++ server.c	(revision 0)
@@ -0,0 +1,406 @@
+/* Shared code for GCC server
+   Copyright (C) 2007
+   Free Software Foundation, Inc.
+
+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 "coretypes.h"
+#include "server.h"
+#include "errors.h"
+#include "dyn-string.h"
+#include "opts.h"
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+/* The name of the server socket we're using.  */
+static char *server_socket_name;
+
+/* The file descriptor of our connection to the server.  */
+static int connection_fd = -1;
+
+/* Return the name of the server socket.  The value is cached in
+   'server_socket_name'.  PROGNAME is the path name of the server
+   executable and is used to construct the socket name.  */
+static char *
+get_socket_name (const char *progname)
+{
+  if (!server_socket_name)
+    {
+      const char *basename;
+
+      basename = strrchr (progname, '/');
+      if (basename == NULL)
+	basename = progname;
+
+      /* FIXME: put in subdir and use UID.  */
+      server_socket_name = concat ("/tmp/", ".", basename, "-server", NULL);
+    }
+
+  return server_socket_name;
+}
+
+/* Start a compile server.  PROGRAM is the full path name of the
+   server to start.  Returns when the server is ready, or prints a
+   message to stderr and exits on failure.  */
+void
+server_start (char *program)
+{
+  int fds[2];
+
+  /* Make a pipe so we can tell when the server is ready.  */
+  pipe (fds);			/* FIXME: error checking */
+
+  switch (fork ())
+    {
+    case 0:
+      {
+	/* Child.  */
+	char fdstr[40];
+	char *args[5];
+	int i = 0;
+#ifdef ENABLE_VALGRIND_CHECKING
+	args[i++] = VALGRIND_PATH;
+	args[i++] = "-q";
+#endif
+	args[i++] = program;
+	sprintf (fdstr, "-fserver=%d", fds[1]);
+	args[i++] = fdstr;
+	args[i++] = NULL;
+	/* Close read end of notification pipe.  */
+	close (fds[0]);
+	/* Don't need these any more -- but keep stderr while
+	   debugging.  */
+	close (0);
+	close (1);
+	execv (args[0], args);
+	error ("exec of server failed: %s", xstrerror (errno));
+	exit (FATAL_EXIT_CODE);
+      }
+      break;
+
+    case -1:
+      error ("fork failed while starting server: %s", xstrerror (errno));
+      exit (FATAL_EXIT_CODE);
+      break;
+
+    default:
+      {
+	/* Parent.  */
+	char x;
+	close (fds[1]);
+	int r = read (fds[0], &x, 1);
+	if (r == -1)
+	  error ("server failed to start: %s", xstrerror (errno));
+	close (fds[0]);
+      }
+    }
+}
+
+/* Open a server socket.  PROGNAME is the path name of the server.
+   Returns the new file descriptor, or -1 on error. */
+static int
+open_socket (const char *progname)
+{
+  struct sockaddr_un address;
+  int sockfd, save_mask;
+  char *sname = get_socket_name (progname);
+
+  sockfd = socket (AF_UNIX, SOCK_STREAM, 0);
+  if (sockfd < 0)
+    return -1;
+
+  address.sun_family = AF_UNIX;
+  /* FIXME: buffer overflow.  */
+  strcpy (address.sun_path, sname);
+
+  save_mask = umask (077);
+  if (bind (sockfd, (struct sockaddr *) &address, SUN_LEN (&address)) < 0)
+    return -1;
+
+  umask (save_mask);
+  return sockfd;
+}
+
+/* Free the contents of an ARGV array, then the array itself.  */
+static void
+free_argv (char **argv)
+{
+  int i;
+  for (i = 0; argv[i]; ++i)
+    free (argv[i]);
+  free (argv);
+}
+
+/* Split a string into elements at spaces.  A backslash in the string
+   quotes the following character.  The resulting array should later
+   be freed with free_argv.  */
+static char **
+split_argv (char *buffer)
+{
+  char **argv = NULL;
+  int alloc = 0;
+  int used = 0;
+  char *p = buffer;
+  dyn_string_t str = dyn_string_new (20);
+
+  while (*p)
+    {
+      if (*p == '\\')
+	{
+	  ++p;
+	  if (!*p)
+	    {
+	      /* Not actually valid...  */
+	      break;
+	    }
+	  dyn_string_append_char (str, *p);
+	}
+      else if (*p == ' ')
+	{
+	  if (used == alloc)
+	    {
+	      alloc = 2 * alloc + 20;
+	      argv = (char **) xrealloc (argv, alloc * sizeof (char *));
+	    }
+	  argv[used++] = dyn_string_release (str);
+	  if (!*p)
+	    {
+	      str = NULL;
+	      break;
+	    }
+	  str = dyn_string_new (20);
+	}
+      else
+	dyn_string_append_char (str, *p);
+      ++p;
+    }
+
+  if (used + 2 >= alloc)
+    {
+      alloc = used + 2;
+      argv = (char **) xrealloc (argv, alloc * sizeof (char *));
+    }
+
+  if (str)
+    argv[used++] = dyn_string_release (str);
+  argv[used] = NULL;
+
+  return argv;
+}
+
+/* Helper function for server_main_loop.  Read a request from the file
+   descriptor REQFD and take action.  */
+static void
+request_and_response (int reqfd)
+{
+  char **argvs[2];
+  int count = 0;
+
+  while (true)
+    {
+      char cmd;
+      if (read (reqfd, &cmd, 1) != 1)
+	break;
+      if (cmd == 'X')
+	{
+	  /* Execute a command.  */
+	  int len;
+	  char *buffer, **argv;
+
+	  if (read (reqfd, &len, sizeof (len)) != sizeof (len))
+	    break;
+	  buffer = (char *) xmalloc (len + 1);
+	  if (read (reqfd, buffer, len) != len)
+	    break;
+	  buffer[len] = '\0';
+
+	  argv = split_argv (buffer);
+	  free (buffer);
+	  /* FIXME: obviously this is pretty lame.  */
+	  if (count >= 2)
+	    {
+	      fprintf (stderr, "DIE!\n");
+	      continue;
+	    }
+
+	  argvs[count++] = argv;
+	}
+      else if (cmd == 'D')
+	{
+	  /* Done with requests, compile away.  */
+	  if (count != 2)
+	    {
+	      fprintf (stderr, "DIE 2!\n");
+	      count = 0;	/* And leak memory while we're at it.  */
+	      continue;
+	    }
+	  server_callback (reqfd, argvs[0], argvs[1]);
+	  free_argv (argvs[0]);
+	  free_argv (argvs[1]);
+	  count = 0;
+	  break;
+	}
+      else if (cmd == 'K')
+	{
+	  /* Kill server.  FIXME.  */
+	}
+      else
+	{
+	  /* FIXME: error.  */
+	  break;
+	}
+    }
+}
+
+/* Main loop of the server.  Creates a server socket and listens to
+   it, accepting requests and acting on them.  PROGNAME is the full
+   path to the server executable.  FD is the completion file
+   descriptor, used to notify the gcc driver when the server is ready
+   to accept connections.  */
+void
+server_main_loop (const char *progname, int fd)
+{
+  int sockfd = open_socket (progname);
+  char reply = 't';
+
+  if (sockfd < 0)
+    {
+      error ("couldn't create server socket: %s", xstrerror (errno));
+      /* FIXME: unlink the socket if it exists.  */
+      /* Simply exiting is ok -- GCC will detect that the socket has
+	 been closed.  */
+      exit (FATAL_EXIT_CODE);
+    }
+
+  write (fd, &reply, 1);
+  close (fd);
+  fprintf (stderr, "server is ready\n");
+
+  listen (sockfd, 5);
+
+  while (true)
+    {
+      int reqfd = accept (sockfd, NULL, 0);
+      if (reqfd < 0)
+	{
+	  error ("error while accepting: %s", xstrerror (errno));
+	  break;
+	}
+      request_and_response (reqfd);
+      close (reqfd);
+    }
+
+  close (sockfd);
+  if (server_socket_name)
+    unlink (server_socket_name);
+}
+
+/* Make a connection to a running server.  PROGNAME is the name of the
+   server to connect to.  Returns true on success, false on
+   failure.  */
+bool
+client_connect (const char *progname)
+{
+  char *sname = get_socket_name (progname);
+  struct sockaddr_un address;
+
+  connection_fd = socket (AF_UNIX, SOCK_STREAM, 0);
+  if (connection_fd < 0)
+    return -1;
+
+  address.sun_family = AF_UNIX;
+  /* FIXME: buffer overflow.  */
+  strcpy (address.sun_path, sname);
+
+  if (connect (connection_fd, (struct sockaddr *) &address,
+	       SUN_LEN (&address)) < 0)
+    return false;
+
+  return true;
+}
+
+/* Send a command to the server.  'client_connect' must already have
+   been called successfully.  ARGV is the command line that the server
+   should execute (or "emulate").  Returns true on success, false on
+   failure.  */
+bool
+client_send_command (const char **argv)
+{
+  dyn_string_t quoted;
+  char cmd, *line;
+  int i, len;
+  bool result;
+
+  gcc_assert (connection_fd >= 0);
+
+  quoted = dyn_string_new (50);
+
+  for (i = 0; argv[i]; ++i)
+    {
+      int j;
+      if (i > 0)
+	dyn_string_append_char (quoted, ' ');
+      for (j = 0; argv[i][j]; ++j)
+	{
+	  if (argv[i][j] == ' ' || argv[i][j] == '\\')
+	    dyn_string_append_char (quoted, '\\');
+	  dyn_string_append_char (quoted, argv[i][j]);
+	}
+    }
+
+  line = dyn_string_release (quoted);
+
+  len = strlen (line);
+  cmd = 'X';
+
+  if (write (connection_fd, &cmd, sizeof (cmd)) != sizeof (cmd)
+      || write (connection_fd, &len, sizeof (len)) != sizeof (len))
+    result = false;
+  else
+    result = (write (connection_fd, line, len) == len);
+  free (line);
+  return result;
+}
+
+/* Called by the client after submitting all its jobs.  This waits for
+   the server to complete the tasks.  It reads from the server
+   connection and prints errors to stderr.  Both 'client_connect' and
+   'client_send_command' must have been successfully called before
+   calling this.  */
+void
+client_wait (void)
+{
+  gcc_assert (connection_fd >= 0);
+  char cmd = 'D';
+
+  if (write (connection_fd, &cmd, 1) != 1)
+    return;
+
+  while (true)
+    {
+      int len;
+      char buffer[100];
+
+      len = read (connection_fd, buffer, 100);
+      if (len <= 0)
+	break;
+      fwrite (buffer, 1, len, stderr);
+    }
+}
Index: server.h
===================================================================
--- server.h	(revision 0)
+++ server.h	(revision 0)
@@ -0,0 +1,35 @@
+/* Declarations for shared server code
+   Copyright (C) 2007 Free Software Foundation, Inc.
+
+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 GCC_SERVER_H
+#define GCC_SERVER_H
+
+/* Functions for the server to use.  */
+extern void server_start (char *);
+extern void server_main_loop (const char *progname, int);
+
+/* Functions for the client to use.  */
+extern bool client_connect (const char *);
+extern bool client_send_command (const char **);
+extern void client_wait (void);
+
+/* The main loop calls this when a command is read.  */
+extern void server_callback (int, char **, char **);
+
+#endif /* GCC_SERVER_H */
Index: Makefile.in
===================================================================
--- Makefile.in	(revision 127650)
+++ Makefile.in	(working copy)
@@ -962,7 +962,7 @@
 CXX_TARGET_OBJS=@cxx_target_objs@
 
 # Object files for gcc driver.
-GCC_OBJS = gcc.o opts-common.o gcc-options.o
+GCC_OBJS = gcc.o opts-common.o gcc-options.o server.o
 
 # Language-specific object files for C and Objective C.
 C_AND_OBJC_OBJS = attribs.o c-errors.o c-lex.o c-pragma.o c-decl.o c-typeck.o \
@@ -1116,6 +1116,7 @@
 	sched-vis.o \
 	sdbout.o \
 	see.o \
+	server.o \
 	simplify-rtx.o \
 	sreal.o \
 	stack-ptr-mod.o \
@@ -1843,7 +1844,7 @@
 
 gcc.o: gcc.c $(CONFIG_H) $(SYSTEM_H) coretypes.h $(TM_H) intl.h multilib.h \
     Makefile $(lang_specs_files) specs.h prefix.h $(GCC_H) $(FLAGS_H) \
-    configargs.h $(OBSTACK_H) opts.h
+    configargs.h $(OBSTACK_H) opts.h server.h
 	(SHLIB_LINK='$(SHLIB_LINK)' \
 	SHLIB_MULTILIB='$(SHLIB_MULTILIB)'; \
 	$(CC) $(ALL_CFLAGS) $(ALL_CPPFLAGS) \
@@ -1942,6 +1943,7 @@
 
 double-int.o: double-int.c $(CONFIG_H) $(SYSTEM_H) coretypes.h $(TM_H) $(TREE_H)
 
+server.o : server.c $(CONFIG_H) $(SYSTEM_H) coretypes.h server.h errors.h
 langhooks.o : langhooks.c $(CONFIG_H) $(SYSTEM_H) coretypes.h $(TM_H) \
    $(TREE_H) toplev.h $(TREE_INLINE_H) $(RTL_H) insn-config.h $(INTEGRATE_H) \
    langhooks.h $(LANGHOOKS_DEF_H) $(FLAGS_H) $(GGC_H) $(DIAGNOSTIC_H) intl.h \
@@ -2311,7 +2313,7 @@
    value-prof.h $(PARAMS_H) $(TM_P_H) reload.h dwarf2asm.h $(TARGET_H) \
    langhooks.h insn-flags.h $(CFGLAYOUT_H) $(CFGLOOP_H) hosthooks.h \
    $(CGRAPH_H) $(COVERAGE_H) alloc-pool.h $(GGC_H) $(INTEGRATE_H) \
-   $(CPPLIB_H) opts.h params.def tree-mudflap.h $(REAL_H) tree-pass.h
+   $(CPPLIB_H) opts.h params.def tree-mudflap.h $(REAL_H) tree-pass.h server.h
 	$(CC) $(ALL_CFLAGS) $(ALL_CPPFLAGS) \
 	  -DTARGET_NAME=\"$(target_noncanonical)\" \
 	  -c $(srcdir)/toplev.c $(OUTPUT_OPTION)



More information about the Gcc-patches mailing list