#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" enum filetype { NONE, /* detect based on file extension */ ASM, /* assembly source */ ASMPP, /* assembly source requiring preprocessing */ C, /* C source */ CHDR, /* C header */ CPPOUT, /* preprocessed C source */ OBJ, /* object file */ QBE, /* QBE IL */ }; enum stage { PREPROCESS, COMPILE, CODEGEN, ASSEMBLE, LINK, }; #include "config.h" struct stageinfo { const char *name; struct array cmd; size_t cmdbase; pid_t pid; }; struct input { char *name; unsigned stages; enum filetype filetype; bool lib; }; static struct { bool nostdlib; bool verbose; } flags; static struct stageinfo stages[] = { [PREPROCESS] = {.name = "preprocess"}, [COMPILE] = {.name = "compile"}, [CODEGEN] = {.name = "codegen"}, [ASSEMBLE] = {.name = "assemble"}, [LINK] = {.name = "link"}, }; static void usage(const char *fmt, ...) { va_list ap; if (fmt) { fprintf(stderr, "%s: ", argv0); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fputc('\n', stderr); } fprintf(stderr, "usage: %s [-c|-S|-E] [-D name[=value]] [-U name] [-s] [-g] [-o output] input...\n", argv0); exit(2); } static enum filetype detectfiletype(const char *name) { const char *dot; dot = strrchr(name, '.'); if (dot) { ++dot; if (strcmp(dot, "c") == 0) return C; if (strcmp(dot, "h") == 0) return CHDR; if (strcmp(dot, "i") == 0) return CPPOUT; if (strcmp(dot, "qbe") == 0) return QBE; if (strcmp(dot, "s") == 0) return ASM; if (strcmp(dot, "S") == 0) return ASMPP; } return OBJ; } static char * changeext(const char *name, const char *ext) { const char *slash, *dot; char *result; size_t baselen; slash = strrchr(name, '/'); if (slash) name = slash + 1; dot = strrchr(name, '.'); baselen = dot ? (size_t)(--dot - name + 1) : strlen(name); result = xmalloc(baselen + strlen(ext) + 2); memcpy(result, name, baselen); result[baselen] = '.'; strcpy(result + baselen + 1, ext); return result; } static int spawn(pid_t *pid, struct array *args, posix_spawn_file_actions_t *actions) { extern char **environ; char **arg; if (flags.verbose) { fprintf(stderr, "%s: spawning", argv0); for (arg = args->val; *arg; ++arg) fprintf(stderr, " %s", *arg); fputc('\n', stderr); } return posix_spawnp(pid, *(char **)args->val, actions, NULL, args->val, environ); } static int spawnphase(struct stageinfo *phase, int *fd, char *input, char *output, bool last) { int ret, pipefd[2]; posix_spawn_file_actions_t actions; phase->cmd.len = phase->cmdbase; if (last && output) { arrayaddptr(&phase->cmd, "-o"); arrayaddptr(&phase->cmd, output); } if (input && *fd == -1) arrayaddptr(&phase->cmd, input); arrayaddptr(&phase->cmd, NULL); ret = posix_spawn_file_actions_init(&actions); if (ret) goto err0; if (*fd != -1) ret = posix_spawn_file_actions_adddup2(&actions, *fd, 0); if (!last) { if (pipe(pipefd) < 0) { ret = errno; goto err1; } if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) < 0) { ret = errno; goto err2; } if (fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) < 0) { ret = errno; goto err2; } ret = posix_spawn_file_actions_adddup2(&actions, pipefd[1], 1); if (ret) goto err2; } ret = spawn(&phase->pid, &phase->cmd, &actions); if (ret) goto err2; if (!last) { *fd = pipefd[0]; close(pipefd[1]); } posix_spawn_file_actions_destroy(&actions); return 0; err2: if (!last) { close(pipefd[0]); close(pipefd[1]); } err1: posix_spawn_file_actions_destroy(&actions); err0: return ret; } static bool succeeded(const char *phase, pid_t pid, int status) { if (WIFEXITED(status)) { if (WEXITSTATUS(status) == 0) return true; warn("%s: process %ju exited with status %d", phase, (uintmax_t)pid, WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { warn("%s: process signaled: %s", phase, strsignal(WTERMSIG(status))); } else { warn("%s: process failed", phase); } return false; } static void buildobj(struct input *input, char *output) { const char *phase; size_t i, npids; pid_t pid; int status, ret, fd; bool success = true; if (input->filetype == OBJ) return; if (input->stages & 1<stages &= ~(1<stages & 1<name, "o"); } else if (input->stages & 1<name, "s"); } else if (input->stages & 1<name, "qbe"); } if (strcmp(input->name, "-") == 0) input->name = NULL; npids = 0; for (i = PREPROCESS, fd = -1; input->stages; ++i) { if (!(input->stages & 1<stages &= ~(1<name, output, !input->stages); if (ret) { warn("%s: spawn \"%s\": %s", stages[i].name, *(char **)stages[i].cmd.val, strerror(ret)); goto kill; } ++npids; } input->name = output; while (npids > 0) { pid = wait(&status); if (pid < 0) fatal("waitpid:"); for (i = 0; i < LEN(stages); ++i) { if (pid == stages[i].pid) { --npids; stages[i].pid = 0; phase = stages[i].name; break; } } if (i == LEN(stages)) continue; /* unknown process */ if (!succeeded(phase, pid, status)) { kill: if (success && npids > 0) { for (i = 0; i < LEN(stages); ++i) { if (stages[i].pid) kill(stages[i].pid, SIGTERM); } } success = false; } } if (!success) { if (output) unlink(output); exit(1); } } static void buildexe(struct input *inputs, size_t ninputs, char *output) { struct stageinfo *s = &stages[LINK]; size_t i; int ret, status; pid_t pid; arrayaddptr(&s->cmd, "-o"); arrayaddptr(&s->cmd, output); if (!flags.nostdlib && startfiles[0]) arrayaddbuf(&s->cmd, startfiles, sizeof(startfiles)); for (i = 0; i < ninputs; ++i) { if (inputs[i].lib) arrayaddptr(&s->cmd, "-l"); arrayaddptr(&s->cmd, inputs[i].name); } if (!flags.nostdlib && endfiles[0]) arrayaddbuf(&s->cmd, endfiles, sizeof(endfiles)); arrayaddptr(&s->cmd, NULL); ret = spawn(&pid, &s->cmd, NULL); if (ret) fatal("%s: spawn \"%s\": %s", s->name, *(char **)s->cmd.val, strerror(errno)); if (waitpid(pid, &status, 0) < 0) fatal("waitpid %ju:", (uintmax_t)pid); for (i = 0; i < ninputs; ++i) { if (inputs[i].filetype != OBJ) unlink(inputs[i].name); } exit(!succeeded(s->name, pid, status)); } static char * nextarg(char ***argv) { if ((**argv)[2] != '\0') return &(**argv)[2]; ++*argv; if (!**argv) usage(NULL); return **argv; } static char * compilecommand(char *arg) { char self[PATH_MAX], *cmd; size_t n; n = readlink("/proc/self/exe", self, sizeof(self) - 5); if (n == -1) { n = strlen(arg); if (n > sizeof(self) - 5) fatal("argv[0] is too large"); memcpy(self, arg, n); } else if (n == sizeof(self) - 5) { fatal("target of /proc/self/exe is too large"); } strcpy(self + n, "-qbe"); cmd = strdup(self); if (!cmd) fatal("strdup:"); return cmd; } static int hasprefix(const char *str, const char *pfx) { return memcmp(str, pfx, strlen(pfx)) == 0; } int main(int argc, char *argv[]) { enum stage last = LINK; enum filetype filetype = 0; char *arg, *end, *output = NULL, *arch, *qbearch; struct array inputs = {0}, *cmd; struct input *input; size_t i; argv0 = progname(argv[0], "cproc"); arrayaddbuf(&stages[PREPROCESS].cmd, preprocesscmd, sizeof(preprocesscmd)); arrayaddptr(&stages[COMPILE].cmd, compilecommand(argv[0])); arrayaddbuf(&stages[CODEGEN].cmd, codegencmd, sizeof(codegencmd)); arrayaddbuf(&stages[ASSEMBLE].cmd, assemblecmd, sizeof(assemblecmd)); arrayaddbuf(&stages[LINK].cmd, linkcmd, sizeof(linkcmd)); if (hasprefix(target, "x86_64-") || hasprefix(target, "amd64-")) { arch = "x86_64-sysv"; qbearch = "amd64_sysv"; } else if (hasprefix(target, "aarch64-")) { arch = "aarch64"; qbearch = "arm64"; } else if (hasprefix(target, "riscv64-")) { arch = "riscv64"; qbearch = "rv64"; } else { fatal("unsupported target '%s'", target); return 1; /* unreachable */ } arrayaddptr(&stages[COMPILE].cmd, "-t"); arrayaddptr(&stages[COMPILE].cmd, arch); arrayaddptr(&stages[CODEGEN].cmd, "-t"); arrayaddptr(&stages[CODEGEN].cmd, qbearch); for (;;) { ++argv, --argc; arg = *argv; if (!arg) break; if (arg[0] != '-' || arg[1] == '\0') { input = arrayadd(&inputs, sizeof(*input)); input->name = arg; input->lib = false; input->filetype = filetype == NONE && arg[1] ? detectfiletype(arg) : filetype; switch (input->filetype) { case ASM: input->stages = 1<stages = 1<stages = 1<stages = 1<stages = 1<stages = 1<stages = 1<name = nextarg(&argv); input->lib = true; input->filetype = OBJ; input->stages = 1<= ASSEMBLE) usage("cannot write object to stdout"); } else if (last != LINK && inputs.len > sizeof(*input)) { usage("cannot specify -o with multiple input files without linking"); } } arrayforeach (&inputs, input) { /* ignore the input if it doesn't participate in the last stage */ if (!(input->stages & 1 << last)) continue; /* only run up through the last stage */ input->stages &= (1 << last + 1) - 1; buildobj(input, output); } if (last == LINK) { if (!output) output = "a.out"; buildexe(inputs.val, inputs.len / sizeof(*input), output); } return 0; }