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 }