From 129e5669015a7ffd78d0f665f46cc434bcf126d2 Mon Sep 17 00:00:00 2001 From: Mel Date: Tue, 27 Jan 2026 04:15:32 +0100 Subject: TinyCC (libtcc) backend for catboot Signed-off-by: Mel --- boot/build.c | 137 ++++++++++++++++++++++++++++++++++++++++++++++---- boot/catboot.c | 6 +-- boot/common.c | 4 +- boot/scripts/embed.sh | 8 +-- 4 files changed, 135 insertions(+), 20 deletions(-) (limited to 'boot') 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 + +#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 -- cgit 1.4.1