summaryrefslogtreecommitdiffhomepage
path: root/src/3p/chibicc/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/3p/chibicc/main.c')
-rw-r--r--src/3p/chibicc/main.c791
1 files changed, 791 insertions, 0 deletions
diff --git a/src/3p/chibicc/main.c b/src/3p/chibicc/main.c
new file mode 100644
index 0000000..ffaabf4
--- /dev/null
+++ b/src/3p/chibicc/main.c
@@ -0,0 +1,791 @@
+#include "chibicc.h"
+
+typedef enum {
+ FILE_NONE, FILE_C, FILE_ASM, FILE_OBJ, FILE_AR, FILE_DSO,
+} FileType;
+
+StringArray include_paths;
+bool opt_fcommon = true;
+bool opt_fpic;
+
+static FileType opt_x;
+static StringArray opt_include;
+static bool opt_E;
+static bool opt_M;
+static bool opt_MD;
+static bool opt_MMD;
+static bool opt_MP;
+static bool opt_S;
+static bool opt_c;
+static bool opt_cc1;
+static bool opt_hash_hash_hash;
+static bool opt_static;
+static bool opt_shared;
+static char *opt_MF;
+static char *opt_MT;
+static char *opt_o;
+
+static StringArray ld_extra_args;
+static StringArray std_include_paths;
+
+char *base_file;
+static char *output_file;
+
+static StringArray input_paths;
+static StringArray tmpfiles;
+
+static void usage(int status) {
+ fprintf(stderr, "chibicc [ -o <path> ] <file>\n");
+ exit(status);
+}
+
+static bool take_arg(char *arg) {
+ char *x[] = {
+ "-o", "-I", "-idirafter", "-include", "-x", "-MF", "-MT", "-Xlinker",
+ };
+
+ for (int i = 0; i < sizeof(x) / sizeof(*x); i++)
+ if (!strcmp(arg, x[i]))
+ return true;
+ return false;
+}
+
+static void add_default_include_paths(char *argv0) {
+ // We expect that chibicc-specific include files are installed
+ // to ./include relative to argv[0].
+ strarray_push(&include_paths, format("%s/include", dirname(strdup(argv0))));
+
+ // Add standard include paths.
+ strarray_push(&include_paths, "/usr/local/include");
+ strarray_push(&include_paths, "/usr/include/x86_64-linux-gnu");
+ strarray_push(&include_paths, "/usr/include");
+
+ // Keep a copy of the standard include paths for -MMD option.
+ for (int i = 0; i < include_paths.len; i++)
+ strarray_push(&std_include_paths, include_paths.data[i]);
+}
+
+static void define(char *str) {
+ char *eq = strchr(str, '=');
+ if (eq)
+ define_macro(strndup(str, eq - str), eq + 1);
+ else
+ define_macro(str, "1");
+}
+
+static FileType parse_opt_x(char *s) {
+ if (!strcmp(s, "c"))
+ return FILE_C;
+ if (!strcmp(s, "assembler"))
+ return FILE_ASM;
+ if (!strcmp(s, "none"))
+ return FILE_NONE;
+ error("<command line>: unknown argument for -x: %s", s);
+}
+
+static char *quote_makefile(char *s) {
+ char *buf = calloc(1, strlen(s) * 2 + 1);
+
+ for (int i = 0, j = 0; s[i]; i++) {
+ switch (s[i]) {
+ case '$':
+ buf[j++] = '$';
+ buf[j++] = '$';
+ break;
+ case '#':
+ buf[j++] = '\\';
+ buf[j++] = '#';
+ break;
+ case ' ':
+ case '\t':
+ for (int k = i - 1; k >= 0 && s[k] == '\\'; k--)
+ buf[j++] = '\\';
+ buf[j++] = '\\';
+ buf[j++] = s[i];
+ break;
+ default:
+ buf[j++] = s[i];
+ break;
+ }
+ }
+ return buf;
+}
+
+static void parse_args(int argc, char **argv) {
+ // Make sure that all command line options that take an argument
+ // have an argument.
+ for (int i = 1; i < argc; i++)
+ if (take_arg(argv[i]))
+ if (!argv[++i])
+ usage(1);
+
+ StringArray idirafter = {};
+
+ for (int i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "-###")) {
+ opt_hash_hash_hash = true;
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-cc1")) {
+ opt_cc1 = true;
+ continue;
+ }
+
+ if (!strcmp(argv[i], "--help"))
+ usage(0);
+
+ if (!strcmp(argv[i], "-o")) {
+ opt_o = argv[++i];
+ continue;
+ }
+
+ if (!strncmp(argv[i], "-o", 2)) {
+ opt_o = argv[i] + 2;
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-S")) {
+ opt_S = true;
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-fcommon")) {
+ opt_fcommon = true;
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-fno-common")) {
+ opt_fcommon = false;
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-c")) {
+ opt_c = true;
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-E")) {
+ opt_E = true;
+ continue;
+ }
+
+ if (!strncmp(argv[i], "-I", 2)) {
+ strarray_push(&include_paths, argv[i] + 2);
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-D")) {
+ define(argv[++i]);
+ continue;
+ }
+
+ if (!strncmp(argv[i], "-D", 2)) {
+ define(argv[i] + 2);
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-U")) {
+ undef_macro(argv[++i]);
+ continue;
+ }
+
+ if (!strncmp(argv[i], "-U", 2)) {
+ undef_macro(argv[i] + 2);
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-include")) {
+ strarray_push(&opt_include, argv[++i]);
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-x")) {
+ opt_x = parse_opt_x(argv[++i]);
+ continue;
+ }
+
+ if (!strncmp(argv[i], "-x", 2)) {
+ opt_x = parse_opt_x(argv[i] + 2);
+ continue;
+ }
+
+ if (!strncmp(argv[i], "-l", 2) || !strncmp(argv[i], "-Wl,", 4)) {
+ strarray_push(&input_paths, argv[i]);
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-Xlinker")) {
+ strarray_push(&ld_extra_args, argv[++i]);
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-s")) {
+ strarray_push(&ld_extra_args, "-s");
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-M")) {
+ opt_M = true;
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-MF")) {
+ opt_MF = argv[++i];
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-MP")) {
+ opt_MP = true;
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-MT")) {
+ if (opt_MT == NULL)
+ opt_MT = argv[++i];
+ else
+ opt_MT = format("%s %s", opt_MT, argv[++i]);
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-MD")) {
+ opt_MD = true;
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-MQ")) {
+ if (opt_MT == NULL)
+ opt_MT = quote_makefile(argv[++i]);
+ else
+ opt_MT = format("%s %s", opt_MT, quote_makefile(argv[++i]));
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-MMD")) {
+ opt_MD = opt_MMD = true;
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-fpic") || !strcmp(argv[i], "-fPIC")) {
+ opt_fpic = true;
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-cc1-input")) {
+ base_file = argv[++i];
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-cc1-output")) {
+ output_file = argv[++i];
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-idirafter")) {
+ strarray_push(&idirafter, argv[i++]);
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-static")) {
+ opt_static = true;
+ strarray_push(&ld_extra_args, "-static");
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-shared")) {
+ opt_shared = true;
+ strarray_push(&ld_extra_args, "-shared");
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-L")) {
+ strarray_push(&ld_extra_args, "-L");
+ strarray_push(&ld_extra_args, argv[++i]);
+ continue;
+ }
+
+ if (!strncmp(argv[i], "-L", 2)) {
+ strarray_push(&ld_extra_args, "-L");
+ strarray_push(&ld_extra_args, argv[i] + 2);
+ continue;
+ }
+
+ if (!strcmp(argv[i], "-hashmap-test")) {
+ hashmap_test();
+ exit(0);
+ }
+
+ // These options are ignored for now.
+ if (!strncmp(argv[i], "-O", 2) ||
+ !strncmp(argv[i], "-W", 2) ||
+ !strncmp(argv[i], "-g", 2) ||
+ !strncmp(argv[i], "-std=", 5) ||
+ !strcmp(argv[i], "-ffreestanding") ||
+ !strcmp(argv[i], "-fno-builtin") ||
+ !strcmp(argv[i], "-fno-omit-frame-pointer") ||
+ !strcmp(argv[i], "-fno-stack-protector") ||
+ !strcmp(argv[i], "-fno-strict-aliasing") ||
+ !strcmp(argv[i], "-m64") ||
+ !strcmp(argv[i], "-mno-red-zone") ||
+ !strcmp(argv[i], "-w"))
+ continue;
+
+ if (argv[i][0] == '-' && argv[i][1] != '\0')
+ error("unknown argument: %s", argv[i]);
+
+ strarray_push(&input_paths, argv[i]);
+ }
+
+ for (int i = 0; i < idirafter.len; i++)
+ strarray_push(&include_paths, idirafter.data[i]);
+
+ if (input_paths.len == 0)
+ error("no input files");
+
+ // -E implies that the input is the C macro language.
+ if (opt_E)
+ opt_x = FILE_C;
+}
+
+static FILE *open_file(char *path) {
+ if (!path || strcmp(path, "-") == 0)
+ return stdout;
+
+ FILE *out = fopen(path, "w");
+ if (!out)
+ error("cannot open output file: %s: %s", path, strerror(errno));
+ return out;
+}
+
+static bool endswith(char *p, char *q) {
+ int len1 = strlen(p);
+ int len2 = strlen(q);
+ return (len1 >= len2) && !strcmp(p + len1 - len2, q);
+}
+
+// Replace file extension
+static char *replace_extn(char *tmpl, char *extn) {
+ char *filename = basename(strdup(tmpl));
+ char *dot = strrchr(filename, '.');
+ if (dot)
+ *dot = '\0';
+ return format("%s%s", filename, extn);
+}
+
+static void cleanup(void) {
+ for (int i = 0; i < tmpfiles.len; i++)
+ unlink(tmpfiles.data[i]);
+}
+
+static char *create_tmpfile(void) {
+ char *path = strdup("/tmp/chibicc-XXXXXX");
+ int fd = mkstemp(path);
+ if (fd == -1)
+ error("mkstemp failed: %s", strerror(errno));
+ close(fd);
+
+ strarray_push(&tmpfiles, path);
+ return path;
+}
+
+static void run_subprocess(char **argv) {
+ // If -### is given, dump the subprocess's command line.
+ if (opt_hash_hash_hash) {
+ fprintf(stderr, "%s", argv[0]);
+ for (int i = 1; argv[i]; i++)
+ fprintf(stderr, " %s", argv[i]);
+ fprintf(stderr, "\n");
+ }
+
+ if (fork() == 0) {
+ // Child process. Run a new command.
+ execvp(argv[0], argv);
+ fprintf(stderr, "exec failed: %s: %s\n", argv[0], strerror(errno));
+ _exit(1);
+ }
+
+ // Wait for the child process to finish.
+ int status;
+ while (wait(&status) > 0);
+ if (status != 0)
+ exit(1);
+}
+
+static void run_cc1(int argc, char **argv, char *input, char *output) {
+ char **args = calloc(argc + 10, sizeof(char *));
+ memcpy(args, argv, argc * sizeof(char *));
+ args[argc++] = "-cc1";
+
+ if (input) {
+ args[argc++] = "-cc1-input";
+ args[argc++] = input;
+ }
+
+ if (output) {
+ args[argc++] = "-cc1-output";
+ args[argc++] = output;
+ }
+
+ run_subprocess(args);
+}
+
+// Print tokens to stdout. Used for -E.
+static void print_tokens(Token *tok) {
+ FILE *out = open_file(opt_o ? opt_o : "-");
+
+ int line = 1;
+ for (; tok->kind != TK_EOF; tok = tok->next) {
+ if (line > 1 && tok->at_bol)
+ fprintf(out, "\n");
+ if (tok->has_space && !tok->at_bol)
+ fprintf(out, " ");
+ fprintf(out, "%.*s", tok->len, tok->loc);
+ line++;
+ }
+ fprintf(out, "\n");
+}
+
+static bool in_std_include_path(char *path) {
+ for (int i = 0; i < std_include_paths.len; i++) {
+ char *dir = std_include_paths.data[i];
+ int len = strlen(dir);
+ if (strncmp(dir, path, len) == 0 && path[len] == '/')
+ return true;
+ }
+ return false;
+}
+
+// If -M options is given, the compiler write a list of input files to
+// stdout in a format that "make" command can read. This feature is
+// used to automate file dependency management.
+static void print_dependencies(void) {
+ char *path;
+ if (opt_MF)
+ path = opt_MF;
+ else if (opt_MD)
+ path = replace_extn(opt_o ? opt_o : base_file, ".d");
+ else if (opt_o)
+ path = opt_o;
+ else
+ path = "-";
+
+ FILE *out = open_file(path);
+ if (opt_MT)
+ fprintf(out, "%s:", opt_MT);
+ else
+ fprintf(out, "%s:", quote_makefile(replace_extn(base_file, ".o")));
+
+ File **files = get_input_files();
+
+ for (int i = 0; files[i]; i++) {
+ if (opt_MMD && in_std_include_path(files[i]->name))
+ continue;
+ fprintf(out, " \\\n %s", files[i]->name);
+ }
+
+ fprintf(out, "\n\n");
+
+ if (opt_MP) {
+ for (int i = 1; files[i]; i++) {
+ if (opt_MMD && in_std_include_path(files[i]->name))
+ continue;
+ fprintf(out, "%s:\n\n", quote_makefile(files[i]->name));
+ }
+ }
+}
+
+static Token *must_tokenize_file(char *path) {
+ Token *tok = tokenize_file(path);
+ if (!tok)
+ error("%s: %s", path, strerror(errno));
+ return tok;
+}
+
+static Token *append_tokens(Token *tok1, Token *tok2) {
+ if (!tok1 || tok1->kind == TK_EOF)
+ return tok2;
+
+ Token *t = tok1;
+ while (t->next->kind != TK_EOF)
+ t = t->next;
+ t->next = tok2;
+ return tok1;
+}
+
+static void cc1(void) {
+ Token *tok = NULL;
+
+ // Process -include option
+ for (int i = 0; i < opt_include.len; i++) {
+ char *incl = opt_include.data[i];
+
+ char *path;
+ if (file_exists(incl)) {
+ path = incl;
+ } else {
+ path = search_include_paths(incl);
+ if (!path)
+ error("-include: %s: %s", incl, strerror(errno));
+ }
+
+ Token *tok2 = must_tokenize_file(path);
+ tok = append_tokens(tok, tok2);
+ }
+
+ // Tokenize and parse.
+ Token *tok2 = must_tokenize_file(base_file);
+ tok = append_tokens(tok, tok2);
+ tok = preprocess(tok);
+
+ // If -M or -MD are given, print file dependencies.
+ if (opt_M || opt_MD) {
+ print_dependencies();
+ if (opt_M)
+ return;
+ }
+
+ // If -E is given, print out preprocessed C code as a result.
+ if (opt_E) {
+ print_tokens(tok);
+ return;
+ }
+
+ Obj *prog = parse(tok);
+
+ // Open a temporary output buffer.
+ char *buf;
+ size_t buflen;
+ FILE *output_buf = open_memstream(&buf, &buflen);
+
+ // Traverse the AST to emit assembly.
+ codegen(prog, output_buf);
+ fclose(output_buf);
+
+ // Write the asembly text to a file.
+ FILE *out = open_file(output_file);
+ fwrite(buf, buflen, 1, out);
+ fclose(out);
+}
+
+static void assemble(char *input, char *output) {
+ char *cmd[] = {"as", "-c", input, "-o", output, NULL};
+ run_subprocess(cmd);
+}
+
+static char *find_file(char *pattern) {
+ char *path = NULL;
+ glob_t buf = {};
+ glob(pattern, 0, NULL, &buf);
+ if (buf.gl_pathc > 0)
+ path = strdup(buf.gl_pathv[buf.gl_pathc - 1]);
+ globfree(&buf);
+ return path;
+}
+
+// Returns true if a given file exists.
+bool file_exists(char *path) {
+ struct stat st;
+ return !stat(path, &st);
+}
+
+static char *find_libpath(void) {
+ if (file_exists("/usr/lib/x86_64-linux-gnu/crti.o"))
+ return "/usr/lib/x86_64-linux-gnu";
+ if (file_exists("/usr/lib64/crti.o"))
+ return "/usr/lib64";
+ error("library path is not found");
+}
+
+static char *find_gcc_libpath(void) {
+ char *paths[] = {
+ "/usr/lib/gcc/x86_64-linux-gnu/*/crtbegin.o",
+ "/usr/lib/gcc/x86_64-pc-linux-gnu/*/crtbegin.o", // For Gentoo
+ "/usr/lib/gcc/x86_64-redhat-linux/*/crtbegin.o", // For Fedora
+ };
+
+ for (int i = 0; i < sizeof(paths) / sizeof(*paths); i++) {
+ char *path = find_file(paths[i]);
+ if (path)
+ return dirname(path);
+ }
+
+ error("gcc library path is not found");
+}
+
+static void run_linker(StringArray *inputs, char *output) {
+ StringArray arr = {};
+
+ strarray_push(&arr, "ld");
+ strarray_push(&arr, "-o");
+ strarray_push(&arr, output);
+ strarray_push(&arr, "-m");
+ strarray_push(&arr, "elf_x86_64");
+
+ char *libpath = find_libpath();
+ char *gcc_libpath = find_gcc_libpath();
+
+ if (opt_shared) {
+ strarray_push(&arr, format("%s/crti.o", libpath));
+ strarray_push(&arr, format("%s/crtbeginS.o", gcc_libpath));
+ } else {
+ strarray_push(&arr, format("%s/crt1.o", libpath));
+ strarray_push(&arr, format("%s/crti.o", libpath));
+ strarray_push(&arr, format("%s/crtbegin.o", gcc_libpath));
+ }
+
+ strarray_push(&arr, format("-L%s", gcc_libpath));
+ strarray_push(&arr, "-L/usr/lib/x86_64-linux-gnu");
+ strarray_push(&arr, "-L/usr/lib64");
+ strarray_push(&arr, "-L/lib64");
+ strarray_push(&arr, "-L/usr/lib/x86_64-linux-gnu");
+ strarray_push(&arr, "-L/usr/lib/x86_64-pc-linux-gnu");
+ strarray_push(&arr, "-L/usr/lib/x86_64-redhat-linux");
+ strarray_push(&arr, "-L/usr/lib");
+ strarray_push(&arr, "-L/lib");
+
+ if (!opt_static) {
+ strarray_push(&arr, "-dynamic-linker");
+ strarray_push(&arr, "/lib64/ld-linux-x86-64.so.2");
+ }
+
+ for (int i = 0; i < ld_extra_args.len; i++)
+ strarray_push(&arr, ld_extra_args.data[i]);
+
+ for (int i = 0; i < inputs->len; i++)
+ strarray_push(&arr, inputs->data[i]);
+
+ if (opt_static) {
+ strarray_push(&arr, "--start-group");
+ strarray_push(&arr, "-lgcc");
+ strarray_push(&arr, "-lgcc_eh");
+ strarray_push(&arr, "-lc");
+ strarray_push(&arr, "--end-group");
+ } else {
+ strarray_push(&arr, "-lc");
+ strarray_push(&arr, "-lgcc");
+ strarray_push(&arr, "--as-needed");
+ strarray_push(&arr, "-lgcc_s");
+ strarray_push(&arr, "--no-as-needed");
+ }
+
+ if (opt_shared)
+ strarray_push(&arr, format("%s/crtendS.o", gcc_libpath));
+ else
+ strarray_push(&arr, format("%s/crtend.o", gcc_libpath));
+
+ strarray_push(&arr, format("%s/crtn.o", libpath));
+ strarray_push(&arr, NULL);
+
+ run_subprocess(arr.data);
+}
+
+static FileType get_file_type(char *filename) {
+ if (opt_x != FILE_NONE)
+ return opt_x;
+
+ if (endswith(filename, ".a"))
+ return FILE_AR;
+ if (endswith(filename, ".so"))
+ return FILE_DSO;
+ if (endswith(filename, ".o"))
+ return FILE_OBJ;
+ if (endswith(filename, ".c"))
+ return FILE_C;
+ if (endswith(filename, ".s"))
+ return FILE_ASM;
+
+ error("<command line>: unknown file extension: %s", filename);
+}
+
+int main(int argc, char **argv) {
+ atexit(cleanup);
+ init_macros();
+ parse_args(argc, argv);
+
+ if (opt_cc1) {
+ add_default_include_paths(argv[0]);
+ cc1();
+ return 0;
+ }
+
+ if (input_paths.len > 1 && opt_o && (opt_c || opt_S | opt_E))
+ error("cannot specify '-o' with '-c,' '-S' or '-E' with multiple files");
+
+ StringArray ld_args = {};
+
+ for (int i = 0; i < input_paths.len; i++) {
+ char *input = input_paths.data[i];
+
+ if (!strncmp(input, "-l", 2)) {
+ strarray_push(&ld_args, input);
+ continue;
+ }
+
+ if (!strncmp(input, "-Wl,", 4)) {
+ char *s = strdup(input + 4);
+ char *arg = strtok(s, ",");
+ while (arg) {
+ strarray_push(&ld_args, arg);
+ arg = strtok(NULL, ",");
+ }
+ continue;
+ }
+
+ char *output;
+ if (opt_o)
+ output = opt_o;
+ else if (opt_S)
+ output = replace_extn(input, ".s");
+ else
+ output = replace_extn(input, ".o");
+
+ FileType type = get_file_type(input);
+
+ // Handle .o or .a
+ if (type == FILE_OBJ || type == FILE_AR || type == FILE_DSO) {
+ strarray_push(&ld_args, input);
+ continue;
+ }
+
+ // Handle .s
+ if (type == FILE_ASM) {
+ if (!opt_S)
+ assemble(input, output);
+ continue;
+ }
+
+ assert(type == FILE_C);
+
+ // Just preprocess
+ if (opt_E || opt_M) {
+ run_cc1(argc, argv, input, NULL);
+ continue;
+ }
+
+ // Compile
+ if (opt_S) {
+ run_cc1(argc, argv, input, output);
+ continue;
+ }
+
+ // Compile and assemble
+ if (opt_c) {
+ char *tmp = create_tmpfile();
+ run_cc1(argc, argv, input, tmp);
+ assemble(tmp, output);
+ continue;
+ }
+
+ // Compile, assemble and link
+ char *tmp1 = create_tmpfile();
+ char *tmp2 = create_tmpfile();
+ run_cc1(argc, argv, input, tmp1);
+ assemble(tmp1, tmp2);
+ strarray_push(&ld_args, tmp2);
+ continue;
+ }
+
+ if (ld_args.len > 0)
+ run_linker(&ld_args, opt_o ? opt_o : "a.out");
+ return 0;
+}