cproc

Unnamed repository; edit this file 'description' to name the repository.
git clone git://git.nihaljere.xyz/cproc
Log | Files | Refs | Submodules | README | LICENSE

driver.c (14236B)


      1 #define _POSIX_C_SOURCE 200809L
      2 #include <errno.h>
      3 #include <stdarg.h>
      4 #include <stdbool.h>
      5 #include <stdint.h>
      6 #include <stdio.h>
      7 #include <stdlib.h>
      8 #include <stdnoreturn.h>
      9 #include <string.h>
     10 
     11 #include <fcntl.h>
     12 #include <limits.h>
     13 #include <signal.h>
     14 #include <spawn.h>
     15 #include <sys/wait.h>
     16 #include <unistd.h>
     17 
     18 #include "util.h"
     19 
     20 enum filetype {
     21 	NONE,   /* detect based on file extension */
     22 	ASM,    /* assembly source */
     23 	ASMPP,  /* assembly source requiring preprocessing */
     24 	C,      /* C source */
     25 	CHDR,   /* C header */
     26 	CPPOUT, /* preprocessed C source */
     27 	OBJ,    /* object file */
     28 	QBE,    /* QBE IL */
     29 };
     30 
     31 enum stage {
     32 	PREPROCESS,
     33 	COMPILE,
     34 	CODEGEN,
     35 	ASSEMBLE,
     36 	LINK,
     37 };
     38 
     39 #include "config.h"
     40 
     41 struct stageinfo {
     42 	const char *name;
     43 	struct array cmd;
     44 	size_t cmdbase;
     45 	pid_t pid;
     46 };
     47 
     48 struct input {
     49 	char *name;
     50 	unsigned stages;
     51 	enum filetype filetype;
     52 	bool lib;
     53 };
     54 
     55 static struct {
     56 	bool nostdlib;
     57 	bool verbose;
     58 } flags;
     59 static struct stageinfo stages[] = {
     60 	[PREPROCESS] = {.name = "preprocess"},
     61 	[COMPILE]    = {.name = "compile"},
     62 	[CODEGEN]    = {.name = "codegen"},
     63 	[ASSEMBLE]   = {.name = "assemble"},
     64 	[LINK]       = {.name = "link"},
     65 };
     66 
     67 static void
     68 usage(const char *fmt, ...)
     69 {
     70 	va_list ap;
     71 
     72 	if (fmt) {
     73 		fprintf(stderr, "%s: ", argv0);
     74 		va_start(ap, fmt);
     75 		vfprintf(stderr, fmt, ap);
     76 		va_end(ap);
     77 		fputc('\n', stderr);
     78 	}
     79 	fprintf(stderr, "usage: %s [-c|-S|-E] [-D name[=value]] [-U name] [-s] [-g] [-o output] input...\n", argv0);
     80 	exit(2);
     81 }
     82 
     83 static enum filetype
     84 detectfiletype(const char *name)
     85 {
     86 	const char *dot;
     87 
     88 	dot = strrchr(name, '.');
     89 	if (dot) {
     90 		++dot;
     91 		if (strcmp(dot, "c") == 0)
     92 			return C;
     93 		if (strcmp(dot, "h") == 0)
     94 			return CHDR;
     95 		if (strcmp(dot, "i") == 0)
     96 			return CPPOUT;
     97 		if (strcmp(dot, "qbe") == 0)
     98 			return QBE;
     99 		if (strcmp(dot, "s") == 0)
    100 			return ASM;
    101 		if (strcmp(dot, "S") == 0)
    102 			return ASMPP;
    103 	}
    104 
    105 	return OBJ;
    106 }
    107 
    108 static char *
    109 changeext(const char *name, const char *ext)
    110 {
    111 	const char *slash, *dot;
    112 	char *result;
    113 	size_t baselen;
    114 
    115 	slash = strrchr(name, '/');
    116 	if (slash)
    117 		name = slash + 1;
    118 	dot = strrchr(name, '.');
    119 	baselen = dot ? (size_t)(--dot - name + 1) : strlen(name);
    120 	result = xmalloc(baselen + strlen(ext) + 2);
    121 	memcpy(result, name, baselen);
    122 	result[baselen] = '.';
    123 	strcpy(result + baselen + 1, ext);
    124 
    125 	return result;
    126 }
    127 
    128 static int
    129 spawn(pid_t *pid, struct array *args, posix_spawn_file_actions_t *actions)
    130 {
    131 	extern char **environ;
    132 	char **arg;
    133 
    134 	if (flags.verbose) {
    135 		fprintf(stderr, "%s: spawning", argv0);
    136 		for (arg = args->val; *arg; ++arg)
    137 			fprintf(stderr, " %s", *arg);
    138 		fputc('\n', stderr);
    139 	}
    140 	return posix_spawnp(pid, *(char **)args->val, actions, NULL, args->val, environ);
    141 }
    142 
    143 static int
    144 spawnphase(struct stageinfo *phase, int *fd, char *input, char *output, bool last)
    145 {
    146 	int ret, pipefd[2];
    147 	posix_spawn_file_actions_t actions;
    148 
    149 	phase->cmd.len = phase->cmdbase;
    150 	if (last && output) {
    151 		arrayaddptr(&phase->cmd, "-o");
    152 		arrayaddptr(&phase->cmd, output);
    153 	}
    154 	if (input && *fd == -1)
    155 		arrayaddptr(&phase->cmd, input);
    156 	arrayaddptr(&phase->cmd, NULL);
    157 
    158 	ret = posix_spawn_file_actions_init(&actions);
    159 	if (ret)
    160 		goto err0;
    161 	if (*fd != -1)
    162 		ret = posix_spawn_file_actions_adddup2(&actions, *fd, 0);
    163 	if (!last) {
    164 		if (pipe(pipefd) < 0) {
    165 			ret = errno;
    166 			goto err1;
    167 		}
    168 		if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) < 0) {
    169 			ret = errno;
    170 			goto err2;
    171 		}
    172 		if (fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) < 0) {
    173 			ret = errno;
    174 			goto err2;
    175 		}
    176 		ret = posix_spawn_file_actions_adddup2(&actions, pipefd[1], 1);
    177 		if (ret)
    178 			goto err2;
    179 	}
    180 
    181 	ret = spawn(&phase->pid, &phase->cmd, &actions);
    182 	if (ret)
    183 		goto err2;
    184 	if (!last) {
    185 		*fd = pipefd[0];
    186 		close(pipefd[1]);
    187 	}
    188 	posix_spawn_file_actions_destroy(&actions);
    189 
    190 	return 0;
    191 
    192 err2:
    193 	if (!last) {
    194 		close(pipefd[0]);
    195 		close(pipefd[1]);
    196 	}
    197 err1:
    198 	posix_spawn_file_actions_destroy(&actions);
    199 err0:
    200 	return ret;
    201 }
    202 
    203 static bool
    204 succeeded(const char *phase, pid_t pid, int status)
    205 {
    206 	if (WIFEXITED(status)) {
    207 		if (WEXITSTATUS(status) == 0)
    208 			return true;
    209 		warn("%s: process %ju exited with status %d", phase, (uintmax_t)pid, WEXITSTATUS(status));
    210 	} else if (WIFSIGNALED(status)) {
    211 		warn("%s: process signaled: %s", phase, strsignal(WTERMSIG(status)));
    212 	} else {
    213 		warn("%s: process failed", phase);
    214 	}
    215 	return false;
    216 }
    217 
    218 static void
    219 buildobj(struct input *input, char *output)
    220 {
    221 	const char *phase;
    222 	size_t i, npids;
    223 	pid_t pid;
    224 	int status, ret, fd;
    225 	bool success = true;
    226 
    227 	if (input->filetype == OBJ)
    228 		return;
    229 	if (input->stages & 1<<LINK) {
    230 		input->stages &= ~(1<<LINK);
    231 		output = strdup("/tmp/cproc-XXXXXX");
    232 		if (!output)
    233 			fatal("strdup:");
    234 		fd = mkstemp(output);
    235 		if (fd < 0)
    236 			fatal("mkstemp:");
    237 		close(fd);
    238 	} else if (output) {
    239 		if (strcmp(output, "-") == 0)
    240 			output = NULL;
    241 	} else if (input->stages & 1<<ASSEMBLE) {
    242 		output = changeext(input->name, "o");
    243 	} else if (input->stages & 1<<CODEGEN) {
    244 		output = changeext(input->name, "s");
    245 	} else if (input->stages & 1<<COMPILE) {
    246 		output = changeext(input->name, "qbe");
    247 	}
    248 	if (strcmp(input->name, "-") == 0)
    249 		input->name = NULL;
    250 
    251 	npids = 0;
    252 	for (i = PREPROCESS, fd = -1; input->stages; ++i) {
    253 		if (!(input->stages & 1<<i))
    254 			continue;
    255 		input->stages &= ~(1<<i);
    256 		ret = spawnphase(&stages[i], &fd, input->name, output, !input->stages);
    257 		if (ret) {
    258 			warn("%s: spawn \"%s\": %s", stages[i].name, *(char **)stages[i].cmd.val, strerror(ret));
    259 			goto kill;
    260 		}
    261 		++npids;
    262 	}
    263 	input->name = output;
    264 
    265 	while (npids > 0) {
    266 		pid = wait(&status);
    267 		if (pid < 0)
    268 			fatal("waitpid:");
    269 		for (i = 0; i < LEN(stages); ++i) {
    270 			if (pid == stages[i].pid) {
    271 				--npids;
    272 				stages[i].pid = 0;
    273 				phase = stages[i].name;
    274 				break;
    275 			}
    276 		}
    277 		if (i == LEN(stages))
    278 			continue;  /* unknown process */
    279 		if (!succeeded(phase, pid, status)) {
    280 kill:
    281 			if (success && npids > 0) {
    282 				for (i = 0; i < LEN(stages); ++i) {
    283 					if (stages[i].pid)
    284 						kill(stages[i].pid, SIGTERM);
    285 				}
    286 			}
    287 			success = false;
    288 		}
    289 	}
    290 	if (!success) {
    291 		if (output)
    292 			unlink(output);
    293 		exit(1);
    294 	}
    295 }
    296 
    297 static noreturn void
    298 buildexe(struct input *inputs, size_t ninputs, char *output)
    299 {
    300 	struct stageinfo *s = &stages[LINK];
    301 	size_t i;
    302 	int ret, status;
    303 	pid_t pid;
    304 
    305 	arrayaddptr(&s->cmd, "-o");
    306 	arrayaddptr(&s->cmd, output);
    307 	if (!flags.nostdlib && startfiles[0])
    308 		arrayaddbuf(&s->cmd, startfiles, sizeof(startfiles));
    309 	for (i = 0; i < ninputs; ++i) {
    310 		if (inputs[i].lib)
    311 			arrayaddptr(&s->cmd, "-l");
    312 		arrayaddptr(&s->cmd, inputs[i].name);
    313 	}
    314 	if (!flags.nostdlib && endfiles[0])
    315 		arrayaddbuf(&s->cmd, endfiles, sizeof(endfiles));
    316 	arrayaddptr(&s->cmd, NULL);
    317 
    318 	ret = spawn(&pid, &s->cmd, NULL);
    319 	if (ret)
    320 		fatal("%s: spawn \"%s\": %s", s->name, *(char **)s->cmd.val, strerror(errno));
    321 	if (waitpid(pid, &status, 0) < 0)
    322 		fatal("waitpid %ju:", (uintmax_t)pid);
    323 	for (i = 0; i < ninputs; ++i) {
    324 		if (inputs[i].filetype != OBJ)
    325 			unlink(inputs[i].name);
    326 	}
    327 	exit(!succeeded(s->name, pid, status));
    328 }
    329 
    330 static char *
    331 nextarg(char ***argv)
    332 {
    333 	if ((**argv)[2] != '\0')
    334 		return &(**argv)[2];
    335 	++*argv;
    336 	if (!**argv)
    337 		usage(NULL);
    338 	return **argv;
    339 }
    340 
    341 static char *
    342 compilecommand(char *arg)
    343 {
    344 	char self[PATH_MAX], *cmd;
    345 	size_t n;
    346 
    347 	n = readlink("/proc/self/exe", self, sizeof(self) - 5);
    348 	if (n == -1) {
    349 		n = strlen(arg);
    350 		if (n > sizeof(self) - 5)
    351 			fatal("argv[0] is too large");
    352 		memcpy(self, arg, n);
    353 	} else if (n == sizeof(self) - 5) {
    354 		fatal("target of /proc/self/exe is too large");
    355 	}
    356 	strcpy(self + n, "-qbe");
    357 	cmd = strdup(self);
    358 	if (!cmd)
    359 		fatal("strdup:");
    360 	return cmd;
    361 }
    362 
    363 static int
    364 hasprefix(const char *str, const char *pfx)
    365 {
    366 	return memcmp(str, pfx, strlen(pfx)) == 0;
    367 }
    368 
    369 int
    370 main(int argc, char *argv[])
    371 {
    372 	enum stage last = LINK;
    373 	enum filetype filetype = 0;
    374 	char *arg, *end, *output = NULL, *arch, *qbearch;
    375 	struct array inputs = {0}, *cmd;
    376 	struct input *input;
    377 	size_t i;
    378 
    379 	argv0 = progname(argv[0], "cproc");
    380 
    381 	arrayaddbuf(&stages[PREPROCESS].cmd, preprocesscmd, sizeof(preprocesscmd));
    382 	arrayaddptr(&stages[COMPILE].cmd, compilecommand(argv[0]));
    383 	arrayaddbuf(&stages[CODEGEN].cmd, codegencmd, sizeof(codegencmd));
    384 	arrayaddbuf(&stages[ASSEMBLE].cmd, assemblecmd, sizeof(assemblecmd));
    385 	arrayaddbuf(&stages[LINK].cmd, linkcmd, sizeof(linkcmd));
    386 
    387 	if (hasprefix(target, "x86_64-") || hasprefix(target, "amd64-")) {
    388 		arch = "x86_64";
    389 		qbearch = "amd64_sysv";
    390 	} else if (hasprefix(target, "aarch64-")) {
    391 		arch = "aarch64";
    392 		qbearch = "arm64";
    393 	} else {
    394 		fatal("unsupported target '%s'", target);
    395 	}
    396 	arrayaddptr(&stages[COMPILE].cmd, "-t");
    397 	arrayaddptr(&stages[COMPILE].cmd, arch);
    398 	arrayaddptr(&stages[CODEGEN].cmd, "-t");
    399 	arrayaddptr(&stages[CODEGEN].cmd, qbearch);
    400 
    401 	for (;;) {
    402 		++argv, --argc;
    403 		arg = *argv;
    404 		if (!arg)
    405 			break;
    406 		if (arg[0] != '-' || arg[1] == '\0') {
    407 			input = arrayadd(&inputs, sizeof(*input));
    408 			input->name = arg;
    409 			input->lib = false;
    410 			input->filetype = filetype == NONE && arg[1] ? detectfiletype(arg) : filetype;
    411 			switch (input->filetype) {
    412 			case ASM:    input->stages =                                     1<<ASSEMBLE|1<<LINK; break;
    413 			case ASMPP:  input->stages = 1<<PREPROCESS|                      1<<ASSEMBLE|1<<LINK; break;
    414 			case C:      input->stages = 1<<PREPROCESS|1<<COMPILE|1<<CODEGEN|1<<ASSEMBLE|1<<LINK; break;
    415 			case CHDR:   input->stages = 1<<PREPROCESS                                          ; break;
    416 			case CPPOUT: input->stages =               1<<COMPILE|1<<CODEGEN|1<<ASSEMBLE|1<<LINK; break;
    417 			case QBE:    input->stages =                          1<<CODEGEN|1<<ASSEMBLE|1<<LINK; break;
    418 			case OBJ:    input->stages =                                                 1<<LINK; break;
    419 			default:     usage("reading from standard input requires -x");
    420 			}
    421 			continue;
    422 		}
    423 		/* TODO: use a binary search for these long parameters */
    424 		if (strcmp(arg, "-nostdlib") == 0) {
    425 			flags.nostdlib = true;
    426 		} else if (strcmp(arg, "-static") == 0) {
    427 			arrayaddptr(&stages[LINK].cmd, arg);
    428 		} else if (strcmp(arg, "-emit-qbe") == 0) {
    429 			last = COMPILE;
    430 		} else if (strcmp(arg, "-include") == 0 || strcmp(arg, "-idirafter") == 0 || strcmp(arg, "-isystem") == 0) {
    431 			if (!--argc)
    432 				usage(NULL);
    433 			arrayaddptr(&stages[PREPROCESS].cmd, arg);
    434 			arrayaddptr(&stages[PREPROCESS].cmd, *++argv);
    435 		} else if (strcmp(arg, "-pipe") == 0) {
    436 			/* ignore */
    437 		} else if (strncmp(arg, "-std=", 5) == 0) {
    438 			/* pass through to the preprocessor, it may
    439 			 * affect its default definitions */
    440 			arrayaddptr(&stages[PREPROCESS].cmd, arg);
    441 		} else if (strcmp(arg, "-pedantic") == 0) {
    442 			/* ignore */
    443 		} else if (strcmp(arg, "-pthread") == 0) {
    444 			arrayaddptr(&stages[LINK].cmd, "-l");
    445 			arrayaddptr(&stages[LINK].cmd, "pthread");
    446 		} else {
    447 			if (arg[2] != '\0' && strchr("cESsv", arg[1]))
    448 				usage(NULL);
    449 			switch (arg[1]) {
    450 			case 'c':
    451 				last = ASSEMBLE;
    452 				break;
    453 			case 'D':
    454 				arrayaddptr(&stages[PREPROCESS].cmd, "-D");
    455 				arrayaddptr(&stages[PREPROCESS].cmd, nextarg(&argv));
    456 				break;
    457 			case 'E':
    458 				last = PREPROCESS;
    459 				break;
    460 			case 'g':
    461 				/* ignore */
    462 				break;
    463 			case 'I':
    464 				arrayaddptr(&stages[PREPROCESS].cmd, "-I");
    465 				arrayaddptr(&stages[PREPROCESS].cmd, nextarg(&argv));
    466 				break;
    467 			case 'L':
    468 				arrayaddptr(&stages[LINK].cmd, "-L");
    469 				arrayaddptr(&stages[LINK].cmd, nextarg(&argv));
    470 				break;
    471 			case 'l':
    472 				input = arrayadd(&inputs, sizeof(*input));
    473 				input->name = nextarg(&argv);
    474 				input->lib = true;
    475 				input->filetype = OBJ;
    476 				input->stages = 1<<LINK;
    477 				break;
    478 			case 'M':
    479 				if (strcmp(arg, "-M") == 0 || strcmp(arg, "-MM") == 0) {
    480 					arrayaddptr(&stages[PREPROCESS].cmd, arg);
    481 					last = PREPROCESS;
    482 				} else if (strcmp(arg, "-MD") == 0 || strcmp(arg, "-MMD") == 0) {
    483 					arrayaddptr(&stages[PREPROCESS].cmd, arg);
    484 				} else if (strcmp(arg, "-MT") == 0 || strcmp(arg, "-MF") == 0) {
    485 					if (!--argc)
    486 						usage(NULL);
    487 					arrayaddptr(&stages[PREPROCESS].cmd, arg);
    488 					arrayaddptr(&stages[PREPROCESS].cmd, *++argv);
    489 				} else {
    490 					usage(NULL);
    491 				}
    492 				break;
    493 			case 'O':
    494 				/* ignore */
    495 				break;
    496 			case 'o':
    497 				output = nextarg(&argv);
    498 				break;
    499 			case 'P':
    500 				/* ignore */
    501 				break;
    502 			case 'S':
    503 				last = CODEGEN;
    504 				break;
    505 			case 's':
    506 				arrayaddptr(&stages[LINK].cmd, "-s");
    507 				break;
    508 			case 'U':
    509 				arrayaddptr(&stages[PREPROCESS].cmd, "-U");
    510 				arrayaddptr(&stages[PREPROCESS].cmd, nextarg(&argv));
    511 				break;
    512 			case 'v':
    513 				flags.verbose = true;
    514 				break;
    515 			case 'W':
    516 				if (arg[2] && arg[3] == ',') {
    517 					switch (arg[2]) {
    518 					case 'p': cmd = &stages[PREPROCESS].cmd; break;
    519 					case 'a': cmd = &stages[ASSEMBLE].cmd; break;
    520 					case 'l': cmd = &stages[LINK].cmd; break;
    521 					default: usage(NULL);
    522 					}
    523 					for (arg += 4; arg; arg = end ? end + 1 : NULL) {
    524 						end = strchr(arg, ',');
    525 						if (end)
    526 							*end = '\0';
    527 						arrayaddptr(cmd, arg);
    528 					}
    529 				} else {
    530 					/* ignore warning flag */
    531 				}
    532 				break;
    533 			case 'x':
    534 				arg = nextarg(&argv);
    535 				if (strcmp(arg, "none") == 0)
    536 					filetype = NONE;
    537 				else if (strcmp(arg, "c") == 0)
    538 					filetype = C;
    539 				else if (strcmp(arg, "c-header") == 0)
    540 					filetype = CHDR;
    541 				else if (strcmp(arg, "cpp-output") == 0)
    542 					filetype = CPPOUT;
    543 				else if (strcmp(arg, "qbe") == 0)
    544 					filetype = QBE;
    545 				else if (strcmp(arg, "assembler") == 0)
    546 					filetype = ASM;
    547 				else if (strcmp(arg, "assembler-with-cpp") == 0)
    548 					filetype = ASMPP;
    549 				else
    550 					usage("unknown language '%s'", arg);
    551 				break;
    552 			default:
    553 				usage("unknown option '%s'", arg);
    554 			}
    555 		}
    556 	}
    557 
    558 	for (i = 0; i < LEN(stages); ++i)
    559 		stages[i].cmdbase = stages[i].cmd.len;
    560 	if (inputs.len == 0)
    561 		usage(NULL);
    562 	if (output) {
    563 		if (strcmp(output, "-") == 0) {
    564 			if (last >= ASSEMBLE)
    565 				usage("cannot write object to stdout");
    566 		} else if (last != LINK && inputs.len > sizeof(*input)) {
    567 			usage("cannot specify -o with multiple input files without linking");
    568 		}
    569 	}
    570 	arrayforeach (&inputs, input) {
    571 		/* ignore the input if it doesn't participate in the last stage */
    572 		if (!(input->stages & 1 << last))
    573 			continue;
    574 		/* only run up through the last stage */
    575 		input->stages &= (1 << last + 1) - 1;
    576 		buildobj(input, output);
    577 	}
    578 	if (last == LINK) {
    579 		if (!output)
    580 			output = "a.out";
    581 		buildexe(inputs.val, inputs.len / sizeof(*input), output);
    582 	}
    583 }