New diff, almost entirely rewritten and slightly redesigned, based on
your previous feedback as well as problems uncovered in testing. Sorry
it's taken so long; I've been a bit stingy with my spare time with
respect to this :)

Description of the design:

 - the top-level make initializes jobserver, if requested, by creating
   a pair of sockets with socketpair(). It keeps one side ("master") for
   itself only (O_CLOEXEC), passing the other side to all children
   ("slave"). It then adds the fd number for the inheritable slave fd to
   MAKEFLAGS with '-J<fd>'. these sockets are set SOCK_NONBLOCK.
 - the master initializes the number of available job tokens to maxJobs
   (ie. from -j).
 - every Job keeps track of the kind of job token it is currently
   holding (enum job_token_type). when the jobserver master starts a
   job, it allocates a JOB_TOKEN_LOCAL type token to the job and
   decrements the number of available tokens.
 - child makes, noticing -J in MAKEFLAGS, initialize the number of
   available job tokens to 1 -- this is because an upper-level make has
   already allocated a job token to the job that ended up starting this
   make process.
 - if a slave make's jobserver.tokens is 0, it asks the master for a
   token via the socket. This happens by writing a '-' character to the
   socket, and waiting for a response.
 - in the master process, handle_running_jobs uses ppoll() to wait for
   either communication from slaves or signals. when it receives a token
   request ('-' character read from the socket), it checks if it has
   available job tokens, and if so, decrements the number of available
   tokens and sends back a response ('.' byte, but the value isn't
   actually checked by slaves, so any byte would do). if the master does
   not have available tokens to fulfill the request, it increments
   jobserver.waiting so that it can fulfill the request later when
   currently in-use tokens are reclaimed.
 - when the slave make receives a byte over the socket from master, it
   stops waiting, sets the Job's token type to JOB_TOKEN_REMOTE and
   continues executing the job. when the job stops executing
   (determine_job_next_step(), or when the job finishes in
   continue_job()), the token is released back to master by writing a
   '+' to the socket.
 - when the master sees a '+' character on the socket, it increments
   jobserver.tokens, and subsequently sends the newly reclaimed token(s)
   to the socket if there are slaves waiting (jobserver.waiting != 0)
 - if the communication between the master and slave has been severed
   (eg. if an intermediate process has closed the fd), the slave sets
   maxJobs to 1, disables the jobserver, prints a warning to stderr and
   proceeds to run one job at a time.
 - more severe error conditions exist; I've commented on these in the
   code so I won't repeat here.

Testing notes:

 - a full release build with the jobserver enabled passed after
   disabling gnu/usr.bin/binutils and gnu/usr.bin/perl subdirs. Those
   dirs may fail to build with jobserver because there are races that
   are hit when parallelizing with the jobserver, but not with
   'expensive' heuristics.
 - as described earlier, my testing setup seems to have high lock
   contention for reasons I don't know. as such, the results for
   enabling the jobserver for 'make -j16 build' are slower in real-time
   than without the jobserver:

        $ time doas make -j16 build
          209m03.63s real   388m15.88s user  3057m43.77s system

        $ time doas make -j16 -x build
          214m27.57s real   381m11.55s user  3179m29.82s system
   
   that means that for this particular setup, the jobserver
   implementation is only a small part of the puzzle of improving
   performance (and does the opposite without the other pieces :).
   subdir.mk is still essentially single-threaded (I sent an unfinished,
   somewhat broken PoC patch for that earlier), which is a big
   opportunity for improvement when using the jobserver, and obviously
   for this machine the high spin% problems should be solved if I want
   the build to actually go faster when it's running more processes.

   I did mention that on actual hardware I had better results in
   previous smaller-scale tests, but I have not run a 'make build' on
   that actual hardware since it's essentially a production machine I
   use.
 - I have not tested ports builds.
 - I have only tested this on amd64.
 - regression tests have not been written.

Other general notes:

 - manpage parts are not yet written.
 - I don't particularly like the option letter I picked to enable the
   jobserver, '-x'. I don't know what would be better, though; one
   option that crossed my mind was using -J and passing the fd number to
   children in another environment variable instead of MAKEFLAGS -J, but
   I'm not sure. I would appreciate help in picking the color for this
   shed :)
 - a new debug flag J is added for jobserver debug messages. they are
   perhaps a little bit too chatty right now.
 - this jobserver is not compatible with jobservers from other makes
   (but neither is the bmake jobserver compatible with gmake)


diff --git a/usr.bin/make/defines.h b/usr.bin/make/defines.h
index 05b29dcf039..cb19dd48dd9 100644
--- a/usr.bin/make/defines.h
+++ b/usr.bin/make/defines.h
@@ -99,6 +99,7 @@ extern int debug;
 #define DEBUG_HELDJOBS         0x20000
 #define DEBUG_DOUBLE           0x40000
 #define DEBUG_TARGGROUP                0x80000
+#define DEBUG_JOBSERVER                0x100000
 
 #define CONCAT(a,b)    a##b
 
diff --git a/usr.bin/make/engine.c b/usr.bin/make/engine.c
index 4e6f1762f8e..d327e980441 100644
--- a/usr.bin/make/engine.c
+++ b/usr.bin/make/engine.c
@@ -615,6 +615,7 @@ job_attach_node(Job *job, GNode *node)
        job->exit_type = JOB_EXIT_OKAY;
        job->location = NULL;
        job->flags = 0;
+       job->token_type = JOB_TOKEN_NONE;
 }
 
 void
diff --git a/usr.bin/make/engine.h b/usr.bin/make/engine.h
index a72d3665948..4591091b7f1 100644
--- a/usr.bin/make/engine.h
+++ b/usr.bin/make/engine.h
@@ -81,6 +81,12 @@ extern void Make_DoAllVar(GNode *);
  */
 extern int run_gnode(GNode *);
 
+enum job_token_type {
+       JOB_TOKEN_NONE,
+       JOB_TOKEN_LOCAL,
+       JOB_TOKEN_REMOTE,
+};
+
 /*-
  * Job Table definitions.
  *
@@ -116,6 +122,8 @@ struct Job_ {
 #define JOB_IS_EXPENSIVE 0x002
 #define JOB_LOST       0x004   /* sent signal to non-existing pid ? */
 #define JOB_ERRCHECK   0x008   /* command wants errcheck */
+       enum job_token_type token_type; /* what kind of jobserver token is
+                                          currently held by this job? */
 };
 
 /* Continuation-style running commands for the parallel engine */
diff --git a/usr.bin/make/extern.h b/usr.bin/make/extern.h
index 89797d685b9..ed60f0df114 100644
--- a/usr.bin/make/extern.h
+++ b/usr.bin/make/extern.h
@@ -53,5 +53,8 @@ extern bool   touchFlag;      /* true if targets should just 
be 'touched'
 extern bool    queryFlag;      /* true if we aren't supposed to really make
                                 * anything, just see if the targets are out-
                                 * of-date */
+extern bool    usejobserver;   /* true if we should use jobserver instead of
+                                  "expensive" heuristics to avoid exponential
+                                  -j behavior */
 
 #endif
diff --git a/usr.bin/make/job.c b/usr.bin/make/job.c
index ebbae5306da..d833dce0ebd 100644
--- a/usr.bin/make/job.c
+++ b/usr.bin/make/job.c
@@ -90,11 +90,15 @@
  *     Job_Wait                Wait for all running jobs to finish.
  */
 
+#include <sys/socket.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <assert.h>
 #include <ctype.h>
 #include <errno.h>
+#include <err.h>
 #include <fcntl.h>
+#include <poll.h>
 #include <signal.h>
 #include <stdarg.h>
 #include <stdio.h>
@@ -115,6 +119,7 @@
 #include "memory.h"
 #include "make.h"
 #include "buf.h"
+#include "main.h"
 
 static int     aborting = 0;       /* why is the make aborting? */
 #define ABORT_ERROR    1           /* Because of an error */
@@ -123,6 +128,18 @@ static int aborting = 0;       /* why is the make 
aborting? */
 
 static int     maxJobs;        /* The most children we can run at once */
 static int     nJobs;          /* Number of jobs already allocated */
+
+struct jobserver {
+       int     master;         /* master deposits job tokens into this fd */
+       int     slave;          /* slaves consume job tokens from this fd */
+       unsigned tokens;        /* number of available tokens. starting a job
+                                  consumes a token; if this is 0, the current
+                                  process must wait for a token to start a
+                                  job. */
+       unsigned waiting;       /* master only: number of slaves currently
+                                  waiting for a token */
+};
+static struct jobserver jobserver;
 static bool    no_new_jobs;    /* Mark recursive shit so we shouldn't start
                                 * something else at the same time
                                 */
@@ -149,6 +166,16 @@ static void continue_job(Job *);
 static Job *reap_finished_job(pid_t);
 static bool reap_jobs(void);
 
+static void jobserver_init(int);
+static void jobserver_shutdown(void);
+static void jobserver_disable(void);
+static bool jobserver_is_master(void);
+static bool jobserver_is_slave(void);
+static bool jobserver_token_available(void);
+static void jobserver_master_communicate(void);
+static void jobserver_decrement_token(Job *job);
+static void jobserver_acquire_token(Job *job);
+static void jobserver_release_token(Job *job);
 static void loop_handle_running_jobs(void);
 static bool expensive_job(Job *);
 static bool expensive_command(const char *);
@@ -574,7 +601,8 @@ determine_expensive_job(Job *job)
 { 
        if (expensive_job(job)) {
                job->flags |= JOB_IS_EXPENSIVE;
-               no_new_jobs = true;
+               if (!usejobserver)
+                       no_new_jobs = true;
        } else
                job->flags &= ~JOB_IS_EXPENSIVE;
        if (DEBUG(EXPENSIVE))
@@ -675,7 +703,7 @@ prepare_job(GNode *gn)
 static void
 may_continue_job(Job *job)
 {
-       if (no_new_jobs) {
+       if (!usejobserver && no_new_jobs) {
                if (DEBUG(EXPENSIVE))
                        fprintf(stderr, "[%ld] expensive -> hold %s\n",
                            (long)mypid, job->node->name);
@@ -688,9 +716,16 @@ may_continue_job(Job *job)
 static void
 continue_job(Job *job)
 {
-       bool finished = job_run_next(job);
-       if (finished)
+       bool finished;
+
+       if (usejobserver)
+               jobserver_acquire_token(job);
+       finished = job_run_next(job);
+       if (finished) {
+               if (usejobserver)
+                       jobserver_release_token(job);
                remove_job(job, true);
+       }
        else
                determine_expensive_job(job);
 }
@@ -722,7 +757,8 @@ static void
 determine_job_next_step(Job *job)
 {
        bool okay;
-       if (job->flags & JOB_IS_EXPENSIVE) {
+
+       if (!usejobserver && job->flags & JOB_IS_EXPENSIVE) {
                no_new_jobs = false;
                if (DEBUG(EXPENSIVE))
                        fprintf(stderr, "[%ld] "
@@ -731,6 +767,8 @@ determine_job_next_step(Job *job)
                            job->node->name);
        }
 
+       if (usejobserver)
+               jobserver_release_token(job);
        okay = job->exit_type == JOB_EXIT_OKAY;
        if (!okay || job->next_cmd == NULL)
                remove_job(job, okay);
@@ -830,11 +868,35 @@ handle_running_jobs(void)
                handle_all_signals();
                if (reap_jobs())
                        break;
-               /* okay, so it's safe to suspend, we have nothing to do but
-                * wait...
-                */
-               sigsuspend(&emptyset);
+               if (usejobserver && jobserver_is_master()) {
+                       struct pollfd pfd = {
+                               .fd = jobserver.master,
+                               .events = POLLIN,
+                       };
+                       int r;
+
+                       /* Wait for a signal or for a slave to communicate over
+                        * the jobserver socket */
+                       r = ppoll(&pfd, 1, NULL, &emptyset);
+                       if (r == -1 && errno != EINTR)
+                               Punt("jobserver master: poll failed");
+
+                       if (pfd.revents & (POLLHUP|POLLERR|POLLNVAL)) {
+                               warnx("jobserver master: error on socket %d, "
+                                   "disabling jobserver", jobserver.master);
+                               jobserver_shutdown();
+                               continue;
+                       }
+                       if (pfd.revents & POLLIN)
+                               jobserver_master_communicate();
+               } else {
+                       /* okay, so it's safe to suspend, we have nothing to do
+                        * but wait...
+                        */
+                       sigsuspend(&emptyset);
+               }
        }
+
        sigprocmask(SIG_SETMASK, &old, NULL);
 }
 
@@ -865,14 +927,394 @@ loop_handle_running_jobs()
                handle_running_jobs();
 }
 
+int
+jobserver_get_slave_fd(void)
+{
+       return jobserver.slave;
+}
+
+static void
+jobserver_shutdown(void)
+{
+       if (jobserver.master != -1)
+               close(jobserver.master);
+       if (jobserver.slave != -1)
+               close(jobserver.slave);
+       usejobserver = false;
+}
+
+static void
+jobserver_disable(void)
+{
+       assert(jobserver_is_slave());
+       /* jobserver communication has failed non-fatally; disable the
+        * jobserver and fall back to starting no more than 1 job at a time.
+        *
+        * we must wait for all running jobs to complete before starting more.
+        * the tokens for those running jobs must be released to master, or
+        * master will run out of tokens.
+        *
+        * therefore, before we can disable the jobserver and fall back to
+        * single-job semantics, we need to wait for all running jobs to
+        * complete. of course, while reaping those jobs, we may fail to
+        * release tokens already acquired for them, so we may end up fatally
+        * exiting from jobserver_release_token().
+        *
+        * NOTE: we don't edit MAKEFLAGS, so future children of this make will
+        * still receive -J and -j. */
+       Job_Wait();
+       jobserver_shutdown();
+       maxJobs = 1;
+}
+
+static void
+jobserver_init(int fd)
+{
+       int s[2];
+
+       if (!usejobserver)
+               return;
+
+       jobserver.slave = -1;
+       jobserver.master = -1;
+
+       if (fd != -1) {
+               /* inherited fd from master via -J */
+               jobserver.slave = fd;
+               /* a higher-level make (master or slave) has allocated a token
+                * for the job that eventually started this make. make itself
+                * should not consume a job token (it just waits on jobs it
+                * starts), so slaves can start one job "for free". */
+               jobserver.tokens = 1;
+       } else {
+               /* we are the master; create sockets */
+               if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK, 0, s) == -1) 
{
+                       warn("jobserver init: cannot create sockets");
+                       jobserver_shutdown();
+                       return;
+               }
+               jobserver.master = s[0];
+               jobserver.slave = s[1];
+               jobserver.tokens = (unsigned)maxJobs;
+
+               if (fcntl(jobserver.master, F_SETFD, O_CLOEXEC) == -1) {
+                       warn("jobserver init: cannot set master fd flags");
+                       jobserver_shutdown();
+                       return;
+               }
+       }
+
+       if (DEBUG(JOBSERVER))
+               fprintf(stderr, "[%ld] jobserver %s initialized in %s\n",
+                   (long)mypid, jobserver_is_master() ? "master" : "slave",
+                   shortened_curdir());
+}
+
+static bool
+jobserver_is_master(void)
+{
+       return jobserver.master != -1;
+}
+
+static bool
+jobserver_is_slave(void)
+{
+       return jobserver.master == -1 && jobserver.slave != -1;
+}
+
+static bool
+jobserver_token_available(void)
+{
+       return jobserver.tokens > 0;
+}
+
+static void
+jobserver_master_communicate(void)
+{
+       ssize_t r;
+       char tok[64];
+       unsigned reclaimed = 0, requested = 0, sent = 0;
+
+       assert(jobserver_is_master());
+
+       do {
+               if ((r = recv(jobserver.master, tok, sizeof(tok),
+                   MSG_DONTWAIT)) == -1) {
+                       if (errno == EAGAIN)
+                               break;
+                       Punt("jobserver token reclaim failed: recv: %s",
+                           strerror(errno));
+               }
+               for (const char *c = tok; c < tok + r; c++) {
+                       if (*c == '-')
+                               requested++;
+                       else if (*c == '+')
+                               reclaimed++;
+                       else
+                               Punt("unknown job token '%c' received from "
+                                   "slave", *c);
+               }
+       } while (r > 0);
+
+       jobserver.tokens += reclaimed;
+       jobserver.waiting += requested;
+
+       if (jobserver.tokens > (unsigned)maxJobs)
+               Punt("jobserver token reclaim: total %u exceeds "
+                   "maximum %d", jobserver.tokens, maxJobs);
+
+       while (jobserver.waiting != 0) {
+               if (!jobserver_token_available())
+                       break;
+
+               if ((r = send(jobserver.master, ".", 1, 0)) == -1) {
+                       Punt("jobserver token send failed: %s",
+                           strerror(errno));
+               }
+               jobserver.tokens -= r;
+               jobserver.waiting -= r;
+               sent += r;
+       }
+
+       if (DEBUG(JOBSERVER))
+               fprintf(stderr, "[%ld] master: %u job tokens reclaimed, %u "
+                   "sent, %u available. %u jobs waiting\n", (long)mypid,
+                   reclaimed, sent, jobserver.tokens, jobserver.waiting);
+}
+
+void
+jobserver_decrement_token(Job *job)
+{
+       assert(jobserver_token_available());
+       jobserver.tokens--;
+       job->token_type = JOB_TOKEN_LOCAL;
+       if (DEBUG(JOBSERVER)) {
+               if (jobserver_is_master())
+                       fprintf(stderr, "[%ld] master: target %s: used "
+                           "token, %u available\n", (long)mypid,
+                           job->node->name, jobserver.tokens);
+               else
+                       fprintf(stderr, "[%ld] slave: target %s: used "
+                           "local token\n", (long)mypid,
+                           job->node->name);
+       }
+}
+
+void
+jobserver_acquire_token(Job *job)
+{
+       ssize_t r;
+       char c;
+       bool acquired = false, requested = false;
+
+       if (jobserver_token_available()) {
+               jobserver_decrement_token(job);
+               return;
+       }
+
+       /* master process should never get here without tokens available. */
+       if (jobserver_is_master())
+               Punt("jobserver master ran out of tokens");
+
+       if (DEBUG(JOBSERVER))
+               fprintf(stderr, "[%ld] slave: target %s: waiting for job "
+                   "token\n", (long)mypid, job->node->name);
+
+       while (!acquired) {
+               struct pollfd pfd = {
+                       .fd = jobserver.slave,
+               };
+
+               if (!requested)
+                       pfd.events = POLLOUT;
+               else
+                       pfd.events = POLLIN;
+
+               /* if either ppoll or recv fails below, we are no longer able
+                * to communicate to the jobserver master. we must disable the
+                * jobserver and start no more than 1 job at a time.
+                *
+                * one job token has been allocated to this make by a
+                * higher-level make, but it has already been used to start
+                * another job (or we would not be trying to acquire a token).
+                * we must wait for all running jobs to complete before
+                * starting more. the tokens for those running jobs must be
+                * released to master, or master will run out of tokens.
+                *
+                * therefore, before we can disable the jobserver and fall back
+                * to single-job semantics, we keep usejobserver = true and
+                * wait for all running jobs to complete. of course, they may
+                * fail to release their tokens, which
+                * jobserver_release_token() considers fatal. */
+
+               if ((r = ppoll(&pfd, 1, NULL, &emptyset)) == -1) {
+                       if (errno == EINTR) {
+                               /* we received a signal; handle it and any
+                                * finished jobs. */
+                               handle_all_signals();
+                               /* if we reap a job that did not originally
+                                * acquire a token from the socket, we
+                                * should've gotten our own local token back.
+                                * if so, stop waiting. */
+                               if (reap_jobs() && jobserver_token_available()) 
{
+                                       jobserver_decrement_token(job);
+                                       return;
+                               }
+                               continue;
+                       }
+                       warn("disabling jobserver: token poll failed");
+                       jobserver_disable();
+                       return;
+               }
+
+               if (pfd.revents & POLLHUP) {
+                       warnx("disabling jobserver: socket disconnected");
+                       jobserver_disable();
+                       return;
+               }
+
+               if (pfd.revents & (POLLERR|POLLNVAL)) {
+                       warnx("disabling jobserver: bad fd %d",
+                           jobserver.slave);
+                       jobserver_disable();
+                       return;
+               }
+
+               if (pfd.revents & POLLOUT) {
+                       if ((r = send(jobserver.slave, "-", 1, 0)) == -1) {
+                               if (errno == EAGAIN)
+                                       continue;
+                               warn("disabling jobserver: token request "
+                                   "failed");
+                               jobserver_disable();
+                               return;
+                       }
+                       requested = (r == 1);
+                       continue;
+               }
+
+               if (pfd.revents & POLLIN) {
+                       if ((r = recv(jobserver.slave, &c, 1, 0)) == -1) {
+                               if (errno == EAGAIN)
+                                       continue;
+                               warn("disabling jobserver: token receive "
+                                   "failed");
+                               jobserver_disable();
+                               return;
+                       }
+                       acquired = (r == 1);
+               }
+       }
+
+       job->token_type = JOB_TOKEN_REMOTE;
+
+       if (DEBUG(JOBSERVER))
+               fprintf(stderr, "[%ld] slave: target %s: acquired job token\n",
+                   (long)mypid, job->node->name);
+}
+
+
+static void
+jobserver_release_token(Job *job)
+{
+       ssize_t r;
+       bool released = false;
+       unsigned short hadtype = job->token_type;
+
+       job->token_type = JOB_TOKEN_NONE;
+
+       switch (hadtype) {
+       case JOB_TOKEN_NONE:
+               return;
+       case JOB_TOKEN_LOCAL:
+               jobserver.tokens++;
+               if (jobserver_is_master()) {
+                       if (DEBUG(JOBSERVER))
+                               fprintf(stderr, "[%ld] master: target %s: "
+                                   "returned token, %u available\n",
+                                   (long)mypid, job->node->name,
+                                   jobserver.tokens);
+                       if (jobserver.tokens > (unsigned)maxJobs)
+                               Punt("jobserver master: token release: total %u 
"
+                                   "exceeds maximum %d", jobserver.tokens, 
maxJobs);
+               } else {
+                       if (DEBUG(JOBSERVER))
+                               fprintf(stderr, "[%ld] slave: target %s: "
+                                   "local token returned\n", (long)mypid,
+                                   job->node->name);
+                       assert(jobserver.tokens == 1);
+               }
+               return;
+       case JOB_TOKEN_REMOTE:
+               break;
+       }
+
+       while (!released) {
+               struct pollfd pfd = {
+                       .fd = jobserver.slave,
+                       .events = POLLOUT,
+               };
+
+               /* if releasing a token fails for any reason, a token deficit
+                * is created in the jobserver master; that should be fatal to
+                * the entire make process tree rooted at the jobserver master.
+                * in that scenario, by definition we are unable to communicate
+                * to the master via the jobserver socket, so the only thing we
+                * can do is abort this slave make and all its jobs, without
+                * waiting for them to finish.
+                *
+                * this enables a pathological case: if other makes are waiting
+                * for tokens, but the job that started this make does not
+                * terminate (perhaps because an intermediate process is
+                * waiting on a sibling make), or terminates with zero exit
+                * status, we have effectively lost a job token. if this causes
+                * the total number of tokens to go to 0, we deadlock: no jobs
+                * can be started, and all existing slaves will keep waiting
+                * forever.
+                */
+
+               if ((r = ppoll(&pfd, 1, NULL, &emptyset)) == -1) {
+                       if (errno == EINTR) {
+                               /* handle_all_signals will exit if the signal
+                                * was fatal; keep trying to release token if
+                                * not */
+                               handle_all_signals();
+                               continue;
+                       }
+                       Punt("jobserver token release failed: ppoll: %s",
+                           strerror(errno));
+               }
+
+               if (pfd.events & (POLLERR|POLLNVAL)) {
+                       Punt("jobserver token release failed: bad fd %d",
+                           jobserver.slave);
+               }
+
+               if (pfd.revents & POLLOUT) {
+                       if ((r = send(jobserver.slave, "+", 1, 0)) == -1) {
+                               if (errno == EAGAIN)
+                                       continue;
+                               Punt("jobserver token release failed: send: "
+                                   "%s", strerror(errno));
+                       }
+                       released = (r == 1);
+               }
+       }
+
+       if (DEBUG(JOBSERVER))
+               fprintf(stderr, "[%ld] slave: target %s: released job "
+                   "token\n", (long)mypid, job->node->name);
+}
+
 void
-Job_Init(int maxproc)
+Job_Init(int maxproc, int fd)
 {
        runningJobs = NULL;
        heldJobs = NULL;
        errorJobs = NULL;
        maxJobs = maxproc;
        mypid = getpid();
+       jobserver_init(fd);
 
        nJobs = 0;
 
@@ -887,6 +1329,7 @@ can_start_job(void)
                return false;
        else
                return true;
+       return true;
 }
 
 bool
diff --git a/usr.bin/make/job.h b/usr.bin/make/job.h
index e8152d18fd0..0a047e54673 100644
--- a/usr.bin/make/job.h
+++ b/usr.bin/make/job.h
@@ -49,10 +49,10 @@
  *     register a new job running commands associated with building gn.
  */
 extern void Job_Make(GNode *);
-/* Job_Init(maxproc);
+/* Job_Init(maxproc, jobserverfd);
  *     setup job handling framework
  */
-extern void Job_Init(int);
+extern void Job_Init(int, int);
 
 /* interface with the normal build in make.c */
 /* okay = can_start_job();
@@ -79,6 +79,9 @@ extern void Job_Wait(void);
 extern void Job_AbortAll(void);
 extern void print_errors(void);
 
+/* return the jobserver communication fd that should be used by slaves */
+extern int jobserver_get_slave_fd(void);
+
 /* handle_running_jobs();
  *     wait until something happens, like a job finishing running a command
  *     or a signal coming in.
diff --git a/usr.bin/make/main.c b/usr.bin/make/main.c
index 2eef3a2a040..f9dadd0f55f 100644
--- a/usr.bin/make/main.c
+++ b/usr.bin/make/main.c
@@ -41,6 +41,7 @@
 #include <sys/utsname.h>
 #include <err.h>
 #include <errno.h>
+#include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -67,8 +68,6 @@
 #include "make.h"
 #include "dump.h"
 
-#define MAKEFLAGS      ".MAKEFLAGS"
-
 static LIST            to_create;      /* Targets to be made */
 Lst create = &to_create;
 bool           allPrecious;    /* .PRECIOUS given on line by itself */
@@ -77,6 +76,7 @@ static bool           noBuiltins;     /* -r flag */
 static LIST            makefiles;      /* ordered list of makefiles to read */
 static LIST            varstoprint;    /* list of variables to print */
 int                    maxJobs;        /* -j argument */
+static int             jobserverfd = -1; /* -J argument */
 bool           compatMake;     /* -B argument */
 static bool            forceJobs = false;
 int            debug;          /* -d flag */
@@ -87,6 +87,7 @@ bool          touchFlag;      /* -t flag */
 bool           ignoreErrors;   /* -i flag */
 bool           beSilent;       /* -s flag */
 bool           dumpData;       /* -p flag */
+bool           usejobserver = false; /* -x flag */
 
 struct dirs {
        char *current;
@@ -187,7 +188,7 @@ MainParseArgs(int argc, char **argv)
 {
        int c, optend;
 
-#define OPTFLAGS "BC:D:I:SV:d:ef:ij:km:npqrst"
+#define OPTFLAGS "BC:D:I:J:SV:d:ef:ij:km:npqrstx"
 #define OPTLETTERS "BSiknpqrst"
 
        if (pledge("stdio rpath wpath cpath fattr proc exec", NULL) == -1)
@@ -218,6 +219,18 @@ MainParseArgs(int argc, char **argv)
                        Parse_AddIncludeDir(optarg);
                        record_option(c, optarg);
                        break;
+               case 'J': {
+                       const char *errstr;
+
+                       usejobserver = true;
+                       jobserverfd = strtonum(optarg, STDERR_FILENO + 1,
+                           INT_MAX, &errstr);
+                       if (errstr != NULL)
+                               errx(1, "illegal argument -J%s: "
+                                   "fd number is %s", optarg, errstr);
+                       record_option(c, optarg);
+                       break;
+               }
                case 'V':
                        Lst_AtEnd(&varstoprint, optarg);
                        record_option(c, optarg);
@@ -265,7 +278,7 @@ MainParseArgs(int argc, char **argv)
                                        debug |= DEBUG_JOB | DEBUG_KILL;
                                        break;
                                case 'J':
-                                       /* ignore */
+                                       debug |= DEBUG_JOBSERVER;
                                        break;
                                case 'k':
                                        debug |= DEBUG_KILL;
@@ -327,6 +340,10 @@ MainParseArgs(int argc, char **argv)
                        Dir_AddDir(systemIncludePath, optarg);
                        record_option(c, optarg);
                        break;
+               case 'x':
+                       usejobserver = true;
+                       /* don't record_option; -J implies this for children */
+                       break;
                case -1:
                        /* Check for variable assignments and targets. */
                        if (argv[optind] != NULL &&
@@ -723,6 +740,17 @@ main(int argc, char **argv)
        if (!forceJobs)
                compatMake = true;
 
+       Job_Init(maxJobs, jobserverfd);
+       if (usejobserver && jobserverfd == -1) {
+               char optstr[16];
+
+               /* We are jobserver master; add -J for children */
+               if (snprintf(optstr, sizeof(optstr), "%d",
+                   jobserver_get_slave_fd()) < 0)
+                       err(1, "could not create -J option string");
+               record_option('J', optstr);
+       }
+
        /* And set up everything for sub-makes */
        Var_AddCmdline(MAKEFLAGS);
 
@@ -796,7 +824,6 @@ main(int argc, char **argv)
                else
                        Targ_FindList(&targs, create);
 
-               Job_Init(maxJobs);
                /* If the user has defined a .BEGIN target, execute the commands
                 * attached to it.  */
                if (!queryFlag)
diff --git a/usr.bin/make/main.h b/usr.bin/make/main.h
index 469487ee058..41a74af9d0d 100644
--- a/usr.bin/make/main.h
+++ b/usr.bin/make/main.h
@@ -29,6 +29,9 @@
 /* main
  *     User interface to make.
  */
+
+#define MAKEFLAGS      ".MAKEFLAGS"
+
 /* Main_ParseArgLine(string);
  *     Parse string as a line of arguments, and treats them as if they
  *     were given at make's invocation. Used to implement .MFLAGS. */

-- 
Lauri Tirkkonen | lotheac @ IRCnet

Reply via email to