diff options
Diffstat (limited to 'boot')
| -rw-r--r-- | boot/build.c | 152 | ||||
| -rw-r--r-- | boot/catboot.c | 63 | ||||
| -rw-r--r-- | boot/catboot.h | 3 | ||||
| -rw-r--r-- | boot/common.c | 50 | ||||
| -rw-r--r-- | boot/runtime/core.c | 7 | ||||
| -rw-r--r-- | boot/runtime/stubs.c | 31 | ||||
| -rw-r--r-- | boot/transpile.c | 12 |
7 files changed, 292 insertions, 26 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, + }; +} 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"); } } |
