kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

compile.c (18160B)


      1 #include <kit/compile.h>
      2 #include <kit/core.h>
      3 #include <kit/preprocess.h>
      4 #include <stdint.h>
      5 #include <string.h>
      6 
      7 #include "cflags.h"
      8 #include "compile_engine.h"
      9 #include "driver.h"
     10 #include "runtime.h"
     11 
     12 /* `kit compile` — kit-native, single-language source compiler.
     13  *
     14  * Unlike `cc` (the GCC-compatible C driver + linker), `compile` resolves
     15  * exactly one frontend per invocation — from `-x` or the source suffix — and
     16  * does NOT link: it produces objects (or `-S`/`--emit=c`/`--emit=ir`) and can
     17  * check. The active frontend dictates which extra flags are accepted: it
     18  * declares whether the preprocessor family (-I/-isystem/-D/-U) applies, and
     19  * any flag `compile` does not own itself is handed to the frontend's own
     20  * option parser (e.g. wasm `-mfeature=...`). `.o`/`.a` inputs are rejected —
     21  * use `cc`, `ld`, or `run` to link. */
     22 
     23 #define COMPILE_TOOL "compile"
     24 
     25 typedef enum CompileEmit {
     26   COMPILE_EMIT_OBJ = 0,
     27   COMPILE_EMIT_ASM,
     28   COMPILE_EMIT_C,
     29   COMPILE_EMIT_IR,
     30 } CompileEmit;
     31 
     32 typedef struct CompileOptions {
     33   DriverEnv* env;
     34   const char* driver_path;
     35   DriverCflags cf;
     36   int cpp_flags_used;
     37 
     38   const char** sources; /* source paths (argv-borrowed) */
     39   uint32_t nsources;
     40 
     41   char** fe_args; /* leftover frontend flags (argv-borrowed) */
     42   uint32_t nfe_args;
     43 
     44   int forced_lang; /* -1 = resolve by suffix; else KitLanguage */
     45   int opt_level;
     46   int debug_info;
     47   int check_only;
     48   int emit; /* CompileEmit */
     49   const char* output_path;
     50   int warnings_are_errors;
     51   uint32_t max_errors;
     52   KitTargetSpec target;
     53   DriverTargetFeatures target_features;
     54 } CompileOptions;
     55 
     56 static void compile_usage(void) {
     57   driver_errf(COMPILE_TOOL, "%.*s",
     58               KIT_SLICE_ARG(KIT_SLICE_LIT(
     59                   "usage: kit compile [options] source...\n"
     60                   "       kit compile --help    for full option reference")));
     61 }
     62 
     63 void driver_help_compile(void) {
     64   driver_printf(
     65       "%.*s",
     66       KIT_SLICE_ARG(KIT_SLICE_LIT(
     67           "kit compile — compile one frontend's sources (no link)\n"
     68           "\n"
     69           "USAGE\n"
     70           "  kit compile [options] source...\n"
     71           "\n"
     72           "DESCRIPTION\n"
     73           "  Compiles each source to an object (default) or to assembly / C /\n"
     74           "  semantic IR. Every source must belong to ONE language, chosen by\n"
     75           "  -x or by file suffix (.c .s .toy .wat .wasm). `compile` does not\n"
     76           "  link: pass .o/.a to `cc`, `ld`, or `run` instead.\n"
     77           "\n"
     78           "  The resolved frontend decides which extra flags apply. Only a\n"
     79           "  preprocessor-enabled frontend (C) accepts -I/-isystem/-D/-U; any\n"
     80           "  other unrecognized flag is handed to the frontend's own parser\n"
     81           "  (e.g. wasm: -mfeature=NAME / -mno-feature=NAME).\n"
     82           "\n"
     83           "OPTIONS\n"
     84           "  -c                Compile to object (the default; accepted for "
     85           "parity)\n"
     86           "  -S                Emit assembly (.s)\n"
     87           "  --emit=obj|asm|c|ir   Select output form (ir requires -O1+)\n"
     88           "  -fsyntax-only     Check only; write no output\n"
     89           "  -o PATH           Output path (required with multiple sources or\n"
     90           "                    --emit=c; otherwise a default name is derived)\n"
     91           "  -O0 -O1 -O2       Optimization level (default -O0)\n"
     92           "  -g                Emit DWARF debug info\n"
     93           "  -x LANG           Force language: c | asm | toy | wasm\n"
     94           "  -I/-isystem/-D/-U Preprocessor flags (preprocessor frontends "
     95           "only)\n"
     96           "  -target TRIPLE    Cross-compile target (see `kit cc --help`)\n"
     97           "  -Werror           Treat warnings as errors\n"
     98           "  -fmax-errors=N    Stop after N errors (0 = unlimited)\n"
     99           "  -h, --help        Show this help and exit\n"
    100           "\n"
    101           "EXIT CODES\n"
    102           "  0   success           1   compile error           2   bad "
    103           "usage\n")));
    104 }
    105 
    106 static int compile_parse_u64(const char* s, uint64_t* out) {
    107   uint64_t v = 0;
    108   int any = 0;
    109   if (!s) return 1;
    110   while (*s) {
    111     unsigned d;
    112     if (*s < '0' || *s > '9') return 1;
    113     d = (unsigned)(*s - '0');
    114     if (v > (UINT64_MAX - d) / 10u) return 1;
    115     v = v * 10u + d;
    116     any = 1;
    117     s++;
    118   }
    119   if (!any) return 1;
    120   *out = v;
    121   return 0;
    122 }
    123 
    124 static int compile_set_lang(CompileOptions* o, const char* name) {
    125   if (driver_streq(name, "c")) {
    126     o->forced_lang = KIT_LANG_C;
    127   } else if (driver_streq(name, "asm") || driver_streq(name, "s")) {
    128     o->forced_lang = KIT_LANG_ASM;
    129   } else if (driver_streq(name, "toy")) {
    130     o->forced_lang = KIT_LANG_TOY;
    131   } else if (driver_streq(name, "wasm") || driver_streq(name, "wat")) {
    132     o->forced_lang = KIT_LANG_WASM;
    133   } else {
    134     driver_errf(COMPILE_TOOL, "unsupported -x language: %.*s",
    135                 KIT_SLICE_ARG(kit_slice_cstr(name)));
    136     return 1;
    137   }
    138   return 0;
    139 }
    140 
    141 /* Reject obvious link inputs with a clear pointer to the right tool. */
    142 static int compile_is_link_input(const char* a) {
    143   return driver_has_suffix(a, ".o") || driver_has_suffix(a, ".obj") ||
    144          driver_has_suffix(a, ".a") || driver_has_suffix(a, ".so") ||
    145          driver_has_suffix(a, ".dylib") || driver_has_suffix(a, ".tbd");
    146 }
    147 
    148 static int compile_classify_positional(CompileOptions* o, const char* a) {
    149   if (driver_streq(a, "-")) {
    150     driver_errf(COMPILE_TOOL,
    151                 "stdin ('-') is not supported; pass a source file");
    152     return 1;
    153   }
    154   if (compile_is_link_input(a)) {
    155     driver_errf(COMPILE_TOOL,
    156                 "compile does not link; pass %.*s to `cc`, `ld`, or `run`",
    157                 KIT_SLICE_ARG(kit_slice_cstr(a)));
    158     return 1;
    159   }
    160   o->sources[o->nsources++] = a;
    161   return 0;
    162 }
    163 
    164 static int compile_parse(int argc, char** argv, CompileOptions* o) {
    165   int i;
    166   o->forced_lang = -1;
    167   o->target = driver_host_target();
    168 
    169   for (i = 1; i < argc; ++i) {
    170     const char* a = argv[i];
    171     int r;
    172 
    173     r = driver_cflags_try_consume(&o->cf, o->env, COMPILE_TOOL, argc, argv, &i);
    174     if (r < 0) return 1;
    175     if (r > 0) {
    176       o->cpp_flags_used = 1;
    177       continue;
    178     }
    179 
    180     if (driver_streq(a, "-c")) {
    181       o->emit = COMPILE_EMIT_OBJ;
    182       continue;
    183     }
    184     if (driver_streq(a, "-S")) {
    185       o->emit = COMPILE_EMIT_ASM;
    186       continue;
    187     }
    188     if (driver_streq(a, "--emit=obj")) {
    189       o->emit = COMPILE_EMIT_OBJ;
    190       continue;
    191     }
    192     if (driver_streq(a, "--emit=asm")) {
    193       o->emit = COMPILE_EMIT_ASM;
    194       continue;
    195     }
    196     if (driver_streq(a, "--emit=c")) {
    197       o->emit = COMPILE_EMIT_C;
    198       continue;
    199     }
    200     if (driver_streq(a, "--emit=ir")) {
    201       o->emit = COMPILE_EMIT_IR;
    202       continue;
    203     }
    204     if (driver_streq(a, "-fsyntax-only") || driver_streq(a, "--check")) {
    205       o->check_only = 1;
    206       continue;
    207     }
    208     if (driver_streq(a, "-g")) {
    209       o->debug_info = 1;
    210       continue;
    211     }
    212     if (driver_streq(a, "-O0")) {
    213       o->opt_level = 0;
    214       continue;
    215     }
    216     if (driver_streq(a, "-O1")) {
    217       o->opt_level = 1;
    218       continue;
    219     }
    220     if (driver_streq(a, "-O2")) {
    221       o->opt_level = 2;
    222       continue;
    223     }
    224     if (driver_streq(a, "-Werror")) {
    225       o->warnings_are_errors = 1;
    226       continue;
    227     }
    228     if (driver_strneq(a, "-fmax-errors=", 13)) {
    229       uint64_t v;
    230       if (compile_parse_u64(a + 13, &v) != 0 || v > 0xFFFFFFFFu) {
    231         driver_errf(COMPILE_TOOL,
    232                     "-fmax-errors= requires a non-negative integer");
    233         return 1;
    234       }
    235       o->max_errors = (uint32_t)v;
    236       continue;
    237     }
    238     if (driver_streq(a, "-o")) {
    239       if (++i >= argc) {
    240         driver_errf(COMPILE_TOOL, "-o requires an argument");
    241         return 1;
    242       }
    243       o->output_path = argv[i];
    244       continue;
    245     }
    246     if (driver_strneq(a, "-o", 2)) {
    247       o->output_path = a + 2;
    248       continue;
    249     }
    250     if (driver_streq(a, "-x")) {
    251       if (++i >= argc) {
    252         driver_errf(COMPILE_TOOL, "-x requires an argument");
    253         return 1;
    254       }
    255       if (compile_set_lang(o, argv[i]) != 0) return 1;
    256       continue;
    257     }
    258     if (driver_streq(a, "-target")) {
    259       if (++i >= argc) {
    260         driver_errf(COMPILE_TOOL, "-target requires an argument");
    261         return 1;
    262       }
    263       if (driver_target_from_triple(argv[i], &o->target) != 0) {
    264         driver_errf(COMPILE_TOOL, "unrecognized target: %.*s",
    265                     KIT_SLICE_ARG(kit_slice_cstr(argv[i])));
    266         return 1;
    267       }
    268       continue;
    269     }
    270     if (driver_strneq(a, "--target=", 9)) {
    271       if (driver_target_from_triple(a + 9, &o->target) != 0) {
    272         driver_errf(COMPILE_TOOL, "unrecognized target: %.*s",
    273                     KIT_SLICE_ARG(kit_slice_cstr(a + 9)));
    274         return 1;
    275       }
    276       continue;
    277     }
    278 
    279     {
    280       int tr = driver_target_features_try_consume(&o->target_features, o->env,
    281                                                   COMPILE_TOOL, argc, argv, &i);
    282       if (tr < 0) return 1;
    283       if (tr > 0) continue;
    284     }
    285 
    286     if (driver_streq(a, "--")) {
    287       for (++i; i < argc; ++i)
    288         if (compile_classify_positional(o, argv[i]) != 0) return 1;
    289       break;
    290     }
    291 
    292     /* A leftover flag belongs to the active frontend; defer it. A bare
    293      * positional is a source. */
    294     if (a[0] == '-' && a[1] != '\0') {
    295       o->fe_args[o->nfe_args++] = (char*)a;
    296       continue;
    297     }
    298     if (compile_classify_positional(o, a) != 0) return 1;
    299   }
    300 
    301   if (o->nsources == 0) {
    302     driver_errf(COMPILE_TOOL, "no input files");
    303     compile_usage();
    304     return 1;
    305   }
    306   if (o->output_path && o->nsources > 1) {
    307     driver_errf(COMPILE_TOOL, "-o cannot be used with multiple sources");
    308     return 1;
    309   }
    310   if (o->emit == COMPILE_EMIT_C && !o->output_path && !o->check_only) {
    311     driver_errf(COMPILE_TOOL, "--emit=c requires -o");
    312     return 1;
    313   }
    314   if (o->emit == COMPILE_EMIT_IR && o->opt_level < 1) {
    315     driver_errf(COMPILE_TOOL,
    316                 "--emit=ir requires -O1 or higher (the IR tape is only "
    317                 "recorded when the optimizer runs)");
    318     return 1;
    319   }
    320   return 0;
    321 }
    322 
    323 /* Derive `<base>.<ext>` next to nothing (basename only), where ext follows the
    324  * emit mode and target. Caller frees via driver_free(env, p, *out_size). */
    325 static char* compile_default_out(DriverEnv* env, const CompileOptions* o,
    326                                  const char* src, size_t* out_size) {
    327   int win = (o->target.os == KIT_OS_WINDOWS);
    328   const char* ext;
    329   size_t ext_len;
    330   size_t srclen = driver_strlen(src);
    331   size_t dot = srclen;
    332   size_t slash = 0;
    333   size_t k;
    334   switch ((CompileEmit)o->emit) {
    335     case COMPILE_EMIT_ASM:
    336       ext = ".s";
    337       ext_len = 2u;
    338       break;
    339     case COMPILE_EMIT_IR:
    340       ext = ".ir";
    341       ext_len = 3u;
    342       break;
    343     case COMPILE_EMIT_C:
    344       ext = ".c";
    345       ext_len = 2u;
    346       break;
    347     default:
    348       ext = win ? ".obj" : ".o";
    349       ext_len = win ? 4u : 2u;
    350       break;
    351   }
    352   for (k = srclen; k > 0; --k) {
    353     if (src[k - 1] == '.') {
    354       dot = k - 1;
    355       break;
    356     }
    357     if (src[k - 1] == '/') break;
    358   }
    359   for (k = dot; k > 0; --k) {
    360     if (src[k - 1] == '/') {
    361       slash = k;
    362       break;
    363     }
    364   }
    365   {
    366     size_t name_len = dot - slash;
    367     size_t bufsz = name_len + ext_len + 1u;
    368     char* buf = driver_alloc(env, bufsz);
    369     if (!buf) return NULL;
    370     driver_memcpy(buf, src + slash, name_len);
    371     driver_memcpy(buf + name_len, ext, ext_len);
    372     buf[name_len + ext_len] = '\0';
    373     *out_size = bufsz;
    374     return buf;
    375   }
    376 }
    377 
    378 /* Compile one source. out_path is NULL for check-only. */
    379 static int compile_one(const KitContext* ctx, KitCompiler* compiler,
    380                        KitLanguage lang, const KitCodeOptions* code,
    381                        const KitDiagnosticOptions* diag,
    382                        const KitPreprocessOptions* pp, const void* lang_extra,
    383                        const char* src, const char* out_path) {
    384   KitFileData fd = {0};
    385   KitSlice input;
    386   KitWriter* w = NULL;
    387   KitObjBuilder* ob = NULL;
    388   KitStatus st;
    389   int rc = 1;
    390 
    391   if (ctx->file_io->read_all(ctx->file_io->user, src, &fd) != KIT_OK) {
    392     driver_errf(COMPILE_TOOL, "failed to read: %.*s",
    393                 KIT_SLICE_ARG(kit_slice_cstr(src)));
    394     return 1;
    395   }
    396   input.data = fd.data;
    397   input.len = fd.size;
    398 
    399   if (out_path) {
    400     if (ctx->file_io->open_writer(ctx->file_io->user, out_path, &w) != KIT_OK) {
    401       driver_errf(COMPILE_TOOL, "failed to open output: %.*s",
    402                   KIT_SLICE_ARG(kit_slice_cstr(out_path)));
    403       goto out;
    404     }
    405     st = driver_compile_run(compiler, lang, code, diag, pp, lang_extra,
    406                             kit_slice_cstr(src), &input, w, NULL);
    407   } else {
    408     /* check-only: build and drop the object. */
    409     st = driver_compile_run(compiler, lang, code, diag, pp, lang_extra,
    410                             kit_slice_cstr(src), &input, NULL, &ob);
    411     kit_obj_builder_free(ob);
    412   }
    413   rc = (st == KIT_OK) ? 0 : 1;
    414 
    415 out:
    416   if (w) kit_writer_close(w);
    417   ctx->file_io->release(ctx->file_io->user, &fd);
    418   return rc;
    419 }
    420 
    421 int driver_compile(int argc, char** argv) {
    422   DriverEnv env;
    423   CompileOptions o = {0};
    424   DriverRuntimeSupport runtime = {0};
    425   int runtime_resolved = 0;
    426   KitContext ctx;
    427   KitTarget* target = NULL;
    428   KitCompiler* compiler = NULL;
    429   KitFrontendCaps caps = {0};
    430   KitLanguage lang = KIT_LANG_UNKNOWN;
    431   KitCodeOptions code = {0};
    432   KitDiagnosticOptions diag = {0};
    433   KitPreprocessOptions pp;
    434   void* lang_extra = NULL;
    435   uint32_t i;
    436   int rc = 2;
    437 
    438   if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
    439     driver_help_compile();
    440     return 0;
    441   }
    442 
    443   driver_env_init(&env);
    444   o.env = &env;
    445   o.driver_path = argv[0];
    446 
    447   o.sources = driver_alloc_zeroed(&env, (size_t)argc * sizeof(*o.sources));
    448   o.fe_args = driver_alloc_zeroed(&env, (size_t)argc * sizeof(*o.fe_args));
    449   if (!o.sources || !o.fe_args || driver_cflags_init(&o.cf, &env, argc) != 0 ||
    450       driver_target_features_init(&o.target_features, &env, argc) != 0) {
    451     driver_errf(COMPILE_TOOL, "out of memory");
    452     goto done;
    453   }
    454 
    455   if (compile_parse(argc, argv, &o) != 0) goto done;
    456 
    457   /* Freestanding runtime headers (mirrors `cc`): required once we have
    458    * sources, so C/asm `#include`s resolve; harmless for toy/wasm. */
    459   if (driver_runtime_resolve(&env, NULL, o.driver_path, &runtime) == 0) {
    460     runtime_resolved = 1;
    461     if (driver_runtime_add_freestanding_headers(&runtime, &o.cf) != 0) {
    462       driver_errf(COMPILE_TOOL, "failed to add freestanding headers");
    463       rc = 1;
    464       goto done;
    465     }
    466   } else {
    467     driver_errf(COMPILE_TOOL, "support dir not found");
    468     rc = 1;
    469     goto done;
    470   }
    471 
    472   ctx = driver_env_to_context(&env);
    473   if (driver_target_new(&ctx, o.target, &o.target_features, COMPILE_TOOL,
    474                         &target) != KIT_OK ||
    475       driver_compiler_new(target, &ctx, &compiler) != KIT_OK) {
    476     driver_errf(COMPILE_TOOL, "failed to initialize compiler");
    477     rc = 1;
    478     goto done;
    479   }
    480 
    481   /* Resolve the single language: -x wins, else the first source's suffix.
    482    * Require every source to agree. */
    483   if (o.forced_lang >= 0) {
    484     lang = (KitLanguage)o.forced_lang;
    485   } else {
    486     lang = kit_language_for_path(compiler, o.sources[0]);
    487     if (lang == KIT_LANG_UNKNOWN) {
    488       driver_errf(COMPILE_TOOL,
    489                   "cannot determine language for %.*s (use -x LANG)",
    490                   KIT_SLICE_ARG(kit_slice_cstr(o.sources[0])));
    491       rc = 1;
    492       goto done;
    493     }
    494     for (i = 1; i < o.nsources; ++i) {
    495       if (kit_language_for_path(compiler, o.sources[i]) != lang) {
    496         driver_errf(COMPILE_TOOL,
    497                     "all sources must be one language; %.*s differs (use -x)",
    498                     KIT_SLICE_ARG(kit_slice_cstr(o.sources[i])));
    499         rc = 1;
    500         goto done;
    501       }
    502     }
    503   }
    504 
    505   if (kit_frontend_caps(compiler, lang, &caps) != KIT_OK) {
    506     driver_errf(COMPILE_TOOL, "no frontend registered for the input language");
    507     rc = 1;
    508     goto done;
    509   }
    510   if (o.cpp_flags_used && !caps.preprocessor) {
    511     driver_errf(COMPILE_TOOL,
    512                 "this frontend does not accept preprocessor flags "
    513                 "(-I/-isystem/-D/-U)");
    514     rc = 1;
    515     goto done;
    516   }
    517   if (o.nfe_args) {
    518     if (kit_frontend_parse_options(compiler, lang, (int)o.nfe_args, o.fe_args,
    519                                    &lang_extra) != KIT_OK) {
    520       /* The frontend emitted a specific diagnostic when it has a parser;
    521        * add a generic line for frontends that accept no extra flags. */
    522       driver_errf(COMPILE_TOOL, "unsupported option for this frontend: %.*s",
    523                   KIT_SLICE_ARG(kit_slice_cstr(o.fe_args[0])));
    524       rc = 1;
    525       goto done;
    526     }
    527   }
    528 
    529   code.opt_level = o.check_only ? 0 : o.opt_level;
    530   code.debug_info = o.debug_info;
    531   code.check_only = o.check_only ? true : false;
    532   code.emit_asm_source = (o.emit == COMPILE_EMIT_ASM);
    533   code.emit_c_source = (o.emit == COMPILE_EMIT_C);
    534   code.emit_ir = (o.emit == COMPILE_EMIT_IR);
    535   diag.warnings_are_errors = o.warnings_are_errors;
    536   diag.max_errors = o.max_errors;
    537   driver_cflags_fill_pp(&o.cf, &pp);
    538 
    539   rc = 0;
    540   for (i = 0; i < o.nsources && rc == 0; ++i) {
    541     const char* src = o.sources[i];
    542     char* owned = NULL;
    543     size_t owned_size = 0;
    544     const char* out_path = NULL;
    545     if (!o.check_only) {
    546       if (o.output_path) {
    547         out_path = o.output_path;
    548       } else {
    549         owned = compile_default_out(&env, &o, src, &owned_size);
    550         if (!owned) {
    551           driver_errf(COMPILE_TOOL, "out of memory");
    552           rc = 1;
    553           break;
    554         }
    555         out_path = owned;
    556       }
    557     }
    558     rc = compile_one(&ctx, compiler, lang, &code, &diag,
    559                      caps.preprocessor ? &pp : NULL, lang_extra, src, out_path);
    560     if (owned) driver_free(&env, owned, owned_size);
    561   }
    562 
    563 done:
    564   if (lang_extra) kit_frontend_free_options(compiler, lang, lang_extra);
    565   if (compiler) driver_compiler_free(compiler);
    566   kit_target_free(target);
    567   if (runtime_resolved) driver_runtime_support_fini(&env, &runtime);
    568   driver_target_features_fini(&o.target_features, &env);
    569   driver_cflags_fini(&o.cf, &env);
    570   if (o.sources)
    571     driver_free(&env, o.sources, (size_t)argc * sizeof(*o.sources));
    572   if (o.fe_args)
    573     driver_free(&env, o.fe_args, (size_t)argc * sizeof(*o.fe_args));
    574   driver_env_fini(&env);
    575   return rc;
    576 }