about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Makefile9
-rw-r--r--boot/build.c152
-rw-r--r--boot/catboot.c63
-rw-r--r--boot/catboot.h3
-rw-r--r--boot/common.c50
-rw-r--r--boot/runtime/core.c7
-rw-r--r--boot/runtime/stubs.c31
-rw-r--r--boot/transpile.c12
-rw-r--r--flake.nix25
9 files changed, 315 insertions, 37 deletions
diff --git a/Makefile b/Makefile
index d5e7069..9775895 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,12 @@
-CFLAGS ?= -std=c99 -Wall -Werror -static -g -O0
+MUSL_LIB ?= /usr/lib/musl
+MUSL_DEV ?= /usr/include/musl
+
+DEFINES ?= -DMUSL_LIB=\"$(MUSL_LIB)\" -DMUSL_DEV=\"$(MUSL_DEV)\"
+CFLAGS ?= -std=c99 -Wall -Werror -static -g -O0 $(DEFINES)
+
 .DEFAULT_GOAL := all
 
-BOOTSTRAP_SOURCES = boot/catboot.c boot/common.c boot/lex.c boot/tree.c boot/visit.c boot/parse.c
+BOOTSTRAP_SOURCES = boot/catboot.c boot/common.c boot/lex.c boot/tree.c boot/visit.c boot/parse.c boot/build.c
 BOOTSTRAP_TEST_SOURCES = boot/tests/test.c
 SOURCES = src/catskill.csk
 
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,
+    };
+}
diff --git a/boot/catboot.c b/boot/catboot.c
index 8412ffc..a0b4ca1 100644
--- a/boot/catboot.c
+++ b/boot/catboot.c
@@ -13,11 +13,13 @@
  * this compiler version will be permanently retired.
  * (although that's still very far away!! have fun! :3)
  *
- * Copyright (c) 2025, Mel G. <mel@rnrd.eu>
+ * Copyright (c) 2025-2026, Mel G. <mel@rnrd.eu>
  *
  * SPDX-License-Identifier: MPL-2.0
  */
 
+#define _POSIX_C_SOURCE 200809L
+
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -106,7 +108,10 @@ debug_parse_pass(struct Source_File source_file)
 
     struct Tree tree;
     int parser_result = parser_do_your_thing(&parser, &tree);
-    if (parser_result != 0) fprintf(stderr, "parser finished with errors\n");
+    if (parser_result != 0) {
+        log_error("parser finished with errors\n");
+        return parser_result;
+    }
 
     tree_printer(&tree);
 
@@ -125,7 +130,7 @@ debug_transpile_pass(struct Source_File source_file)
     struct Tree tree;
     int parser_result = parser_do_your_thing(&parser, &tree);
     if (parser_result != 0) {
-        fprintf(stderr, "parser finished with errors\n");
+        log_error("parser finished with errors\n");
         return parser_result;
     }
 
@@ -218,31 +223,57 @@ default_command(struct Command_Arguments* arguments)
 
     struct Tree tree;
     if (parser_do_your_thing(&parser, &tree) != 0) {
-        fprintf(stderr, "parser finished with errors\n");
+        log_error("parser finished with errors\n");
+
+        result = COMMAND_FAIL;
         goto end;
     }
 
-    FILE* output_file = nil;
-    if (arguments->output) {
-        output_file = fopen(arguments->output, "w");
-        if (!output_file) {
-            fprintf(stderr, "could not open output file: %s\n", arguments->output);
-            result = COMMAND_FAIL;
-            goto end;
-        }
-    } else {
-        output_file = stdout;
+    ascii build_directory_template[] = "/tmp/catboot-build-XXXXXX"; // TODO: /tmp/catboot/build-XXXXXX
+
+    ascii* build_directory_path = mkdtemp(build_directory_template);
+    struct String build_path = string_from_c_string(build_directory_path);
+    struct String source_path = string_append_c_str(build_path, "/input.c");
+
+    FILE* output_file = fopen(string_c_str(source_path), "w");
+    if (!output_file) {
+        log_error("could not open temporary output file: %s\n", string_c_str(source_path));
+
+        result = COMMAND_FAIL;
+        goto end;
     }
 
     struct Transpiler transpiler;
     transpiler_new(&transpiler, output_file);
 
     if (transpiler_catskill_to_c(&transpiler, &tree) != 0) {
-        fprintf(stderr, "transpiler finished with errors\n");
+        log_error("transpiler finished with errors\n");
+        fclose(output_file);
+
         result = COMMAND_FAIL;
+        goto end;
     }
+    fclose(output_file);
 
-    if (output_file != stdout) fclose(output_file);
+    struct Build_Result build_result = build_executable(build_path);
+    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, build_result.compiler_log.data);
+
+        result = COMMAND_FAIL;
+        goto end;
+    }
+
+    // copy the output executable to the desired location
+    const ascii* output_filename = arguments->output ? arguments->output : "a.out";
+
+    // simple copy command for now
+    ascii copy_command[2048];
+    snprintf(copy_command, sizeof(copy_command), "cp %s %s", string_c_str(build_result.output_path), output_filename);
+    if (system(copy_command) != 0) {
+        log_error("failed to copy executable to %s\n", output_filename);
+        result = COMMAND_FAIL;
+    }
 
 end:
     return result;
diff --git a/boot/catboot.h b/boot/catboot.h
index 621ed11..f7d3e2d 100644
--- a/boot/catboot.h
+++ b/boot/catboot.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2025, Mel G. <mel@rnrd.eu>
+ * Copyright (c) 2025-2026, Mel G. <mel@rnrd.eu>
  *
  * SPDX-License-Identifier: MPL-2.0
  */
@@ -19,3 +19,4 @@
 #include "parse.c"
 #include "visit.c"
 #include "transpile.c"
+#include "build.c"
diff --git a/boot/common.c b/boot/common.c
index e453065..095aaa6 100644
--- a/boot/common.c
+++ b/boot/common.c
@@ -3,7 +3,7 @@
  * are used throughout the bootstrap compiler.
  * allocation done purely statically.
  *
- * Copyright (c) 2025, Mel G. <mel@rnrd.eu>
+ * Copyright (c) 2025-2026, Mel G. <mel@rnrd.eu>
  *
  * SPDX-License-Identifier: MPL-2.0
  */
@@ -127,6 +127,54 @@ unreachable()
     failure("unreachable code reached");
 }
 
+// log out a message with a debug prefix.
+// does not print additional \n at the end.
+void
+log_debug(const ascii* message, ...)
+{
+    fflush(stdout);
+    fprintf(stderr, ANSI_BOLD ANSI_CYAN "debug. " ANSI_NO_BOLD);
+
+    va_list args;
+    va_start(args, message);
+    vfprintf(stderr, message, args);
+    va_end(args);
+
+    fprintf(stderr, ANSI_RESET);
+}
+
+// log out a message with a warning prefix.
+// does not print additional \n at the end.
+void
+log_warning(const ascii* message, ...)
+{
+    fflush(stdout);
+    fprintf(stderr, ANSI_BOLD ANSI_YELLOW "warning? " ANSI_NO_BOLD);
+
+    va_list args;
+    va_start(args, message);
+    vfprintf(stderr, message, args);
+    va_end(args);
+
+    fprintf(stderr, ANSI_RESET);
+}
+
+// log out a message with an error prefix.
+// does not print additional \n at the end.
+void
+log_error(const ascii* message, ...)
+{
+    fflush(stdout);
+    fprintf(stderr, ANSI_BOLD ANSI_RED "error! " ANSI_NO_BOLD);
+
+    va_list args;
+    va_start(args, message);
+    vfprintf(stderr, message, args);
+    va_end(args);
+
+    fprintf(stderr, ANSI_RESET);
+}
+
 // for each entry in a linked list.
 #define FOR_EACH(type, cursor, head) for (type cursor = head; cursor != nil; cursor = cursor->next)
 
diff --git a/boot/runtime/core.c b/boot/runtime/core.c
index c911666..f6c4ef8 100644
--- a/boot/runtime/core.c
+++ b/boot/runtime/core.c
@@ -4,13 +4,16 @@
  * used during bootstrapping, while the full
  * catskill standard library is not yet available.
  *
- * copyright (c) 2025, mel g. <mel@rnrd.eu>
+ * Copyright (c) 2025-2026, Mel G. <mel@rnrd.eu>
  *
- * spdx-license-identifier: mpl-2.0
+ * SPDX-License-Identifier: MPL-2.0
  */
 
 #pragma once
 
+// enable some posix-only functions, like `fileno`.
+#define _POSIX_C_SOURCE 200809L
+
 // common headers for both your average catskill program.
 // other libc headers can be included with the pragma
 // `| c-header "header.h"`.
diff --git a/boot/runtime/stubs.c b/boot/runtime/stubs.c
new file mode 100644
index 0000000..979020e
--- /dev/null
+++ b/boot/runtime/stubs.c
@@ -0,0 +1,31 @@
+/*
+ * stubs.c
+ * provides symbols for various software float implementations that
+ * our backend compilers really want to have somewhere.
+ * we do not use them anywhere within the transpiled source, so
+ * it's safe to let them be empty.
+ * for real implementations we should use `compiler-rt`.
+ *
+ * Copyright (c) 2026, Mel G. <mel@rnrd.eu>
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+void __addtf3(void) {}
+void __subtf3(void) {}
+void __multf3(void) {}
+void __divtf3(void) {}
+void __netf2(void) {}
+void __eqtf2(void) {}
+void __lttf2(void) {}
+void __gttf2(void) {}
+void __letf2(void) {}
+void __getf2(void) {}
+void __floatsitf(void) {}
+void __floatunsitf(void) {}
+void __fixtfsi(void) {}
+void __fixunstfsi(void) {}
+void __extendsftf2(void) {}
+void __extenddftf2(void) {}
+void __trunctfsf2(void) {}
+void __trunctfdf2(void) {}
diff --git a/boot/transpile.c b/boot/transpile.c
index 01531d9..b1171d7 100644
--- a/boot/transpile.c
+++ b/boot/transpile.c
@@ -3,7 +3,7 @@
  * takes a direct catskill syntax tree and produces a c source file,
  * without an intermediate representation.
  *
- * Copyright (c) 2025, Mel G. <mel@rnrd.eu>
+ * Copyright (c) 2025-2026, Mel G. <mel@rnrd.eu>
  *
  * SPDX-License-Identifier: MPL-2.0
  */
@@ -397,10 +397,10 @@ transpiler_visit_tree(struct Visit* visit, struct Tree* tree)
     // other headers can be included by the user
     // with the pragma `| c-header "header.h"`.
     // TODO: for now we just reference the path to it in this repo
-    // exactly, when we actually just want to fully include the string
-    // of the entire file into the transpiled output, for development it's
-    // okay for now.
-    fprintf(transpiler->output, "#include \"boot/runtime/core.c\"\n");
+    // exactly and tell the backend in `./build.c` to look for includes there,
+    // but in the real implementation we should embed all runtime files into
+    // this executable and then write them out into the temporary build directory.
+    fprintf(transpiler->output, "#include \"core.c\"\n");
 
     FOR_EACH (struct Statement*, statement, tree->top_level_statements) {
         VISIT(visit_statement, statement);
@@ -413,7 +413,7 @@ transpiler_visit_tree(struct Visit* visit, struct Tree* tree)
         if (transpiler->context.main_function_takes_args) {
             fprintf(transpiler->output, "\n#define CATSKILL_MAIN_TAKES_ARGS\n");
         }
-        fprintf(transpiler->output, "#include \"boot/runtime/runtime.c\"\n");
+        fprintf(transpiler->output, "#include \"runtime.c\"\n");
     }
 }
 
diff --git a/flake.nix b/flake.nix
index 9021e96..56134b0 100644
--- a/flake.nix
+++ b/flake.nix
@@ -27,17 +27,18 @@
     {
       devShells = eachSystem (
         { system, pkgs }:
-        with pkgs;
         let
-          stdenv = llvmPackages.stdenv;
-          shell = mkShell.override { inherit stdenv; };
+          inherit (pkgs) tinycc pkg-config gnumake lldb clang bintools;
 
-          musl-src = runCommand "musl-src" { } ''
+          stdenv = pkgs.llvmPackages.stdenv;
+          mkShell = pkgs.mkShell.override { inherit stdenv; };
+
+          musl-src = pkgs.runCommand "musl-src" { } ''
             mkdir $out
-            tar -xf ${musl.src} -C $out
+            tar -xf ${pkgs.musl.src} -C $out
           '';
 
-          musl-static = musl.overrideAttrs (attrs: {
+          musl = pkgs.musl.overrideAttrs (attrs: {
             # this HAS the be fixed in upstream nixpkgs one day, right?
             CFLAGS = attrs.CFLAGS ++ [
               "-fdebug-prefix-map=/build=${musl-src}"
@@ -47,17 +48,23 @@
           });
         in
         {
-          default = shell {
+          default = mkShell {
             buildInputs = [
-              musl-static
+              musl
+              tinycc
             ];
 
             nativeBuildInputs = [
               pkg-config
               gnumake
               lldb
-              llvmPackages.bintools
+              clang
+              bintools
             ];
+
+            MUSL_LIB = "${musl}";
+            MUSL_DEV = "${musl.dev}";
+            MUSL_SRC = "${musl-src}";
           };
         }
       );