about summary refs log tree commit diff
path: root/boot/build.c
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2026-01-22 03:55:02 +0100
committerMel <mel@rnrd.eu>2026-01-22 03:55:02 +0100
commit7f5d765c929a4dc2deddb7b68a41a3a841940837 (patch)
treec442166ede9f6b4c3a82621a39d754dde8c407ac /boot/build.c
parent30b04e4b2a90981570ae04095aeccd746ccdea6a (diff)
downloadcatskill-7f5d765c929a4dc2deddb7b68a41a3a841940837.tar.zst
catskill-7f5d765c929a4dc2deddb7b68a41a3a841940837.zip
LLVM clang compiler backend
Signed-off-by: Mel <mel@rnrd.eu>
Diffstat (limited to 'boot/build.c')
-rw-r--r--boot/build.c152
1 files changed, 152 insertions, 0 deletions
diff --git a/boot/build.c b/boot/build.c
new file mode 100644
index 0000000..5d902d6
--- /dev/null
+++ b/boot/build.c
@@ -0,0 +1,152 @@
+/*
+ * 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. <mel@rnrd.eu>
+ *
+ * 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,
+    };
+}