about summary refs log tree commit diff
path: root/boot
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2026-01-27 04:15:32 +0100
committerMel <mel@rnrd.eu>2026-01-27 04:15:32 +0100
commit129e5669015a7ffd78d0f665f46cc434bcf126d2 (patch)
tree97c203070590cf27bba2209c1998bcc546260753 /boot
parent38bc2d06fff322a99ba6ba46df69bbd2b40c6173 (diff)
downloadcatskill-main.tar.zst
catskill-main.zip
TinyCC (libtcc) backend for catboot HEAD main
Signed-off-by: Mel <mel@rnrd.eu>
Diffstat (limited to 'boot')
-rw-r--r--boot/build.c137
-rw-r--r--boot/catboot.c6
-rw-r--r--boot/common.c4
-rwxr-xr-xboot/scripts/embed.sh8
4 files changed, 135 insertions, 20 deletions
diff --git a/boot/build.c b/boot/build.c
index 1a1cb21..0c05372 100644
--- a/boot/build.c
+++ b/boot/build.c
@@ -99,9 +99,9 @@ write_build_file(struct String source, struct String input_path)
 bool
 copy_runtime_library(struct String build_path)
 {
-    const ascii core[] = CATSKILL_EMBED("./boot/runtime/core.c");
-    const ascii runtime[] = CATSKILL_EMBED("./boot/runtime/runtime.c");
-    const ascii stubs[] = CATSKILL_EMBED("./boot/runtime/stubs.c");
+    const ascii core[] = CATBOOT_EMBED("./boot/runtime/core.c");
+    const ascii runtime[] = CATBOOT_EMBED("./boot/runtime/runtime.c");
+    const ascii stubs[] = CATBOOT_EMBED("./boot/runtime/stubs.c");
 
     struct String build_runtime_path = string_append_c_str(build_path, "/runtime/");
     if (mkdir(string_c_str(build_runtime_path), 0700) == -1) {
@@ -166,17 +166,11 @@ struct Build_Result
     struct String output_path;
 };
 
+#ifdef CATBOOT_BACKEND_CLANG
 struct Build_Result
-compile_using_backend(
+compile_using_backend_clang(
     struct String build_path, struct String source_path, struct String output_path)
 {
-    log_debug("current backend: LLVM Clang (CLI)\n");
-
-    // 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
@@ -246,6 +240,127 @@ compile_using_backend(
         .output_path = output_path,
     };
 }
+#endif
+
+#ifdef CATBOOT_BACKEND_TCC
+
+#include <libtcc.h>
+
+#define BUILD_TCC_MAX_ERROR_LOG_LENGTH 65536
+
+static struct String_Buffer tcc_errors;
+bool tcc_errors_truncated;
+
+void
+build_tcc_on_error(void* something, const ascii* message)
+{
+    (void)something;
+    if (tcc_errors_truncated) return;
+
+    const ascii* log_prefix = ANSI_BOLD " tcc: " ANSI_NO_BOLD;
+    const ascii* log_suffix = "\n";
+
+    uint added_length = strlen(message) + strlen(log_prefix) + strlen(log_suffix);
+    uint free_space = string_buffer_capacity(&tcc_errors) - string_buffer_length(&tcc_errors);
+
+    if (free_space <= added_length) {
+        tcc_errors_truncated = true;
+        return;
+    }
+
+    string_buffer_append_c_str(&tcc_errors, log_prefix);
+    string_buffer_append_c_str(&tcc_errors, message);
+    string_buffer_append_c_str(&tcc_errors, log_suffix);
+}
+
+void
+build_tcc_reset_errors()
+{
+    if (tcc_errors.data == nil)
+        tcc_errors = string_buffer_new(BUILD_TCC_MAX_ERROR_LOG_LENGTH);
+    string_buffer_clear(&tcc_errors);
+    tcc_errors_truncated = false;
+}
+
+struct Build_Result
+compile_using_backend_tcc(
+    struct String build_path, struct String source_path, struct String output_path)
+{
+    TCCState* tcc = tcc_new();
+    if (!tcc) {
+        log_error("failed to initialize backend (TCC) compiler\n");
+        goto error;
+    }
+
+    build_tcc_reset_errors();
+    tcc_set_error_func(tcc, nil, build_tcc_on_error);
+
+    // NOTE: this has to be called prior to `tcc_set_output_type`, apparently setting the output
+    // to EXE pulls in some kind of default runtime which declares the _start & co. symbols before we can!
+    tcc_set_options(tcc, "-static -nostdlib -nostdinc");
+
+    // we only want a simple executable output.
+    // other options can create a dynamic library, or add the output
+    // to our memory to run directly, like for a just-in-time compiler, pretty neat!
+    tcc_set_output_type(tcc, TCC_OUTPUT_EXE);
+
+    // add necessary include paths
+    struct String runtime_path = string_append_c_str(build_path, "/runtime/");
+    tcc_add_include_path(tcc, string_c_str(runtime_path));
+
+    tcc_add_sysinclude_path(tcc, MUSL_DEV "/include");
+
+    // linking order, must be exact:
+    // crt1.o -> crti.o -> stubs.c -> source.c -> libc.a -> crtn.o
+    tcc_add_file(tcc, MUSL_LIB "/lib/crt1.o");
+    tcc_add_file(tcc, MUSL_LIB "/lib/crti.o");
+
+    struct String stubs_path = string_append_c_str(runtime_path, "stubs.c");
+    tcc_add_file(tcc, string_c_str(stubs_path));
+
+    if (tcc_add_file(tcc, string_c_str(source_path)) == -1) {
+        log_error(
+            "failed to add transpiled source to backend compiler (TCC), probably malformed?\n");
+        goto error;
+    }
+
+    tcc_add_file(tcc, MUSL_LIB "/lib/libc.a");
+    tcc_add_file(tcc, MUSL_LIB "/lib/crtn.o");
+
+    // now run the compilation, and output!
+    if (tcc_output_file(tcc, string_c_str(output_path)) == -1) {
+        log_error("error during backend (TCC) compilation!\n");
+        goto error;
+    }
+
+    tcc_delete(tcc);
+    return (struct Build_Result){ .success = true };
+
+error:
+    tcc_delete(tcc);
+    struct String log = string_buffer_to_string(&tcc_errors);
+    return (struct Build_Result){
+        .success = false,
+        .compiler_exit_code = -1,
+        .compiler_log = log,
+    };
+}
+#endif
+
+struct Build_Result
+compile_using_backend(
+    struct String build_path, struct String source_path, struct String output_path)
+{
+#if defined(CATBOOT_BACKEND_CLANG)
+    log_debug("current backend: LLVM Clang (CLI)\n");
+    return compile_using_backend_clang(build_path, source_path, output_path);
+#elif defined(CATBOOT_BACKEND_TCC)
+    log_debug("current backend: TCC (libtcc)\n");
+    return compile_using_backend_tcc(build_path, source_path, output_path);
+#else
+#error "no backend defined for compilation"
+#endif
+}
 
 struct Build_Result
 build_executable(struct String source, struct String output_path)
diff --git a/boot/catboot.c b/boot/catboot.c
index db100dd..e78a401 100644
--- a/boot/catboot.c
+++ b/boot/catboot.c
@@ -22,7 +22,7 @@
 
 #include "catboot.h"
 
-#define VERSION "0.0.0"
+#define CATBOOT_VERSION "0.0.0"
 
 struct String
 read_file(const ascii* path)
@@ -66,7 +66,7 @@ version(void)
 {
     fprintf(
         stderr,
-        "catboot (catskill), version " VERSION "\n"
+        "catboot (catskill), version " CATBOOT_VERSION "\n"
         "\n"
         "This program's source code is subject to the terms of the Mozilla Public\n"
         "License, v. 2.0. If a copy of the MPL was not distributed with this\n"
@@ -265,7 +265,7 @@ default_command(struct Command_Arguments* arguments)
     if (!build_result.success) {
         log_error("backend failure with exit code %ld\n", build_result.compiler_exit_code);
         log_error(
-            "compiler output:\n%.*s\n", (int)build_result.compiler_log.length,
+            "compiler output:\n%.*s", (int)build_result.compiler_log.length,
             build_result.compiler_log.data);
 
         return COMMAND_FAIL;
diff --git a/boot/common.c b/boot/common.c
index 125af86..2f9bdd0 100644
--- a/boot/common.c
+++ b/boot/common.c
@@ -53,11 +53,11 @@
 #define NORETURN _Noreturn
 
 // macro used as target for embedding binary files during build into a byte array.
-// usage: `const byte test_data[] = CATSKILL_EMBED(./files/test_data.txt);`
+// usage: `const byte test_data[] = CATBOOT_EMBED(./files/test_data.txt);`
 // the array will be filled with hexadecimal values of the file contents,
 // with a nil byte appended at the end.
 // the size of the array can be retrieved through `strlen(array)`, or `ARRAY_SIZE(array)-1`.
-#define CATSKILL_EMBED(...) { 0 }
+#define CATBOOT_EMBED(...) { 0 }
 
 // ansi escape codes for terminal color and style
 #define ANSI(code) "\33[" code "m"
diff --git a/boot/scripts/embed.sh b/boot/scripts/embed.sh
index baf2e38..98a0c9d 100755
--- a/boot/scripts/embed.sh
+++ b/boot/scripts/embed.sh
@@ -2,7 +2,7 @@
 
 # embed.sh
 # a simple script to handle embedding binary file contents into invocations
-# of the CATSKILL_EMBED macro within catboot compiler source files.
+# of the CATBOOT_EMBED macro within catboot compiler source files.
 # invoked with `./embed.sh source-1.c source-2.c header.h`
 # outputs a file structure within the ./build/ directory matching the source
 # directory, with the correct replacements done.
@@ -23,9 +23,9 @@ process_file() {
 
     cp "$source_file" "$output_file"
 
-    local embed_regex='CATSKILL_EMBED\("([^)]+)"\)' # const byte data[] = CATSKILL_EMBED("./file");
-    local comment_regex='^\s*//' # // the macro CATSKILL_EMBED does...
-    local define_regex='^\s*#define' # #define CATSKILL_EMBED {0}
+    local embed_regex='CATBOOT_EMBED\("([^)]+)"\)' # const byte data[] = CATBOOT_EMBED("./file");
+    local comment_regex='^\s*//' # // the macro CATBOOT_EMBED does...
+    local define_regex='^\s*#define' # #define CATBOOT_EMBED {0}
 
     while IFS= read -r line; do
         # skip lines which are just comments mentioning the macro and