/* * a simple routine to call out to a proper * compiler with our c source artifacts produced * by our bootstrapping transpiler to turn them * into real executable code. * currently we call out directly to the llvm-based * `clang` tool found in the system path to do * the real heavy lifting for us. :) * the catskill compiler does have an eventual goal * to achieve full self-hosting with real assembler * and linker implementations, however that is not * of interest to our bootstrap phase. * * Copyright (c) 2026, Mel G. * * SPDX-License-Identifier: MPL-2.0 */ #pragma once #include "catboot.h" #ifndef MUSL_LIB #error "MUSL_LIB not defined" #endif #ifndef MUSL_DEV #error "MUSL_DEV not defined" #endif const ascii* musl_lib = MUSL_LIB; const ascii* musl_dev = MUSL_DEV; struct Compiler_Command_Result { integer exit_code; struct String log; }; struct Compiler_Command_Result run_compiler_command(const ascii* command) { log_debug("running backend command: %s\n", command); FILE* command_pipe = popen(command, "r"); if (!command_pipe) return (struct Compiler_Command_Result){ .exit_code = -1 }; uint bytes_read; ascii log_buffer[8192] = { 0 }; while ((bytes_read = fread(log_buffer, 1, ARRAY_SIZE(log_buffer), command_pipe)) > 0) { if (bytes_read >= ARRAY_SIZE(log_buffer)) { log_error("compiler output surpassed maximum output length... truncating to %lu bytes\n", ARRAY_SIZE(log_buffer) - 1); break; } } integer exit_code = pclose(command_pipe); struct String log = string_from_c_string(log_buffer); return (struct Compiler_Command_Result){ .exit_code = exit_code, .log = log }; } struct Build_Result { bool success; integer compiler_exit_code; struct String compiler_log; struct String output_path; }; struct Build_Result build_executable(struct String build_path) { struct String source_path = string_append_c_str(build_path, "/input.c"); struct String output_path = string_append_c_str(build_path, "/output"); ascii* compiler = "clang"; log_debug("current backend: %s\n", compiler); // TODO: right now we always run the compiler from this source directory, // so we can refer to the runtime libraries by a relative path "./boot/runtime/", // but in the future we should embed these files into this compiler, and write // them out into the build directory. // probably too many flags for our purposes, but we kind of want // this simple bootstrapping output to be the eventual target // of our self-hosted compiler stack, so we avoid anything fancy // for an easier target to hit. const ascii* arguments[] = { compiler, // high-level compiler behavior "-O0", "-g", "-std=c99", // low-level compiler settings, which we optimize to generate // extremely simple and human-readable assembly in our final // executable. we want it to feel almost hand-written. "-fno-omit-frame-pointer", "-fno-stack-protector", "-fno-plt", "-fno-builtin", "-fno-inline", "-fno-common", "-fno-ident", "-fno-exceptions", "-fno-asynchronous-unwind-tables", // we rely on frame pointer instead // output options, for integrating clang output into ours "-fno-color-diagnostics", "-Wall", "-Wextra", // with -w flag or on error // linker and header options, static compilation w/ musl "-static", "-nostdlib", "-I./boot/runtime/", "-isystem", MUSL_DEV "/include", // linking must follow this exact order MUSL_LIB "/lib/crt1.o", MUSL_LIB "/lib/crti.o", "./boot/runtime/stubs.c", // stub out some software float implementations string_c_str(source_path), MUSL_LIB "/lib/libc.a", MUSL_LIB "/lib/crtn.o", "-o", string_c_str(output_path), }; // append all flags to single command ascii command[1024] = { 0 }; for (uint i = 0; i < ARRAY_SIZE(arguments); ++i) { strcat(command, arguments[i]); strcat(command, " "); } struct Compiler_Command_Result result = run_compiler_command(command); return (struct Build_Result){ .success = result.exit_code == 0, .compiler_exit_code = result.exit_code, .compiler_log = result.log, .output_path = output_path, }; }