#include "lib.h" #define build_dir "build" #define nest_dir "nest" typedef enum OptimizationMode : u8 { O0, O1, O2, O3, Os, Oz, OPTIMIZATION_COUNT, } OptimizationMode; declare_slice(OptimizationMode); typedef enum Compiler : u8 { gcc, clang, COMPILER_COUNT, } Compiler; declare_slice(Compiler); global const Compiler default_compiler = clang; typedef enum Linkage: u8 { LINKAGE_DYNAMIC, LINKAGE_STATIC, LINKAGE_COUNT, } Linkage; declare_slice(Linkage); STRUCT(CompileOptions) { char* out_path; char* in_path; OptimizationMode optimization_mode:3; u8 debug_info:1; u8 error_on_warning:1; Compiler compiler:1; Linkage linkage:1; }; decl_vbp(char); fn u8 is_debug(OptimizationMode optimization_mode, u8 debug_info) { return (optimization_mode == O0) & (debug_info != 0); } fn void compile_c(Arena* arena, CompileOptions options, char** envp) { VirtualBufferP(char) argument_stack = {}; auto* args = &argument_stack; char* compiler; switch (options.compiler) { case gcc: compiler = "/usr/bin/gcc"; break; case clang: #if _WIN32 compiler = "clang"; #elif defined( __APPLE__) compiler = "/opt/homebrew/opt/llvm/bin/clang"; #else compiler = "/usr/bin/clang"; #endif break; case COMPILER_COUNT: unreachable(); } *vb_add(args, 1) = compiler; // *vb_add(args, 1) = "-E"; *vb_add(args, 1) = options.in_path; *vb_add(args, 1) = "-o"; *vb_add(args, 1) = options.out_path; if (options.debug_info) { *vb_add(args, 1) = "-g"; } switch (options.optimization_mode) { case O0: *vb_add(args, 1) = "-O0"; break; case O1: *vb_add(args, 1) = "-O1"; break; case O2: *vb_add(args, 1) = "-O2"; break; case O3: *vb_add(args, 1) = "-O3"; break; case Os: *vb_add(args, 1) = "-Os"; break; case Oz: *vb_add(args, 1) = "-Oz"; break; case OPTIMIZATION_COUNT: unreachable(); } *vb_add(args, 1) = "-march=native"; if (options.error_on_warning) { *vb_add(args, 1) = "-Werror"; } char* general_options[] = { "-std=gnu2x", "-Wall", "-Wextra", "-Wpedantic", "-Wconversion", "-Wno-nested-anon-types", "-Wno-keyword-macro", "-Wno-gnu-auto-type", "-Wno-auto-decl-extensions", "-Wno-gnu-empty-initializer", "-Wno-fixed-enum-extension", "-Wno-gnu-binary-literal", "-pedantic", "-fno-exceptions", "-fno-stack-protector", "-ferror-limit=1", }; memcpy(vb_add(args, array_length(general_options)), general_options, sizeof(general_options)); if (!is_debug(options.optimization_mode, options.debug_info)) { *vb_add(args, 1) = "-DNDEBUG=1"; } if (options.linkage == LINKAGE_STATIC) { char* static_options[] = { "-ffreestanding", "-nostdlib", "-static", "-DSTATIC", "-lgcc" }; memcpy(vb_add(args, array_length(static_options)), static_options, sizeof(static_options)); } // *vb_add(args, 1) = "-DSILENT"; if (options.compiler == clang) { *vb_add(args, 1) = "-MJ"; *vb_add(args, 1) = build_dir "/" "compile_commands.json"; } *vb_add(args, 1) = 0; run_command(arena, (CStringSlice) { .pointer = args->pointer, .length = args->length }, envp); } typedef enum CompilerBackend { COMPILER_BACKEND_INTERPRETER, COMPILER_BACKEND_C, COMPILER_BACKEND_MACHINE, COMPILER_BACKEND_COUNT, } CompilerBackend; declare_slice(CompilerBackend); fn void compile_and_run(Arena* arena, CompileOptions options, char** envp, CompilerBackend compiler_backend, u8 debug, char* nest_source_path) { compile_c(arena, options, envp); CStringSlice args = {}; char* compiler_backend_string; switch (compiler_backend) { case COMPILER_BACKEND_C: compiler_backend_string = "c"; break; case COMPILER_BACKEND_INTERPRETER: compiler_backend_string = "i"; break; case COMPILER_BACKEND_MACHINE: compiler_backend_string = "m"; break; case COMPILER_BACKEND_COUNT: unreachable(); } #define common_compile_and_run_args \ options.out_path, \ nest_source_path, \ compiler_backend_string, \ 0, if (debug) { #if _WIN32 args = (CStringSlice) array_to_slice(((char*[]){ "C:\\Users\\David\\Downloads\\remedybg_0_4_0_7\\remedybg.exe", "-g", common_compile_and_run_args })); #elif defined(__linux__) args = (CStringSlice) array_to_slice(((char*[]){ "/home/david/source/gf/gf2", "-ex", "set auto-solib-add off", "-ex", "r", "--args", common_compile_and_run_args })); #elif defined(__APPLE__) args = (CStringSlice) array_to_slice(((char*[]){ "/usr/bin/lldb", "-o", "run", "--", common_compile_and_run_args })); #endif } else { args = (CStringSlice) array_to_slice(((char*[]){ common_compile_and_run_args })); } run_command(arena, args, envp); } typedef enum Command : u8 { COMMAND_DEBUG, COMMAND_RUN_TESTS, COMMAND_COMPILE, COMMAND_COUNT, } Command; STRUCT(TestOptions) { Slice(Linkage) linkages; Slice(OptimizationMode) optimization_modes; Slice(String) test_paths; Slice(CompilerBackend) compiler_backends; }; fn String linkage_name(Linkage linkage) { switch (linkage) { case LINKAGE_STATIC: return strlit("static"); case LINKAGE_DYNAMIC: return strlit("dynamic"); case LINKAGE_COUNT: unreachable(); } } fn String optimization_name(OptimizationMode optimization_mode) { switch (optimization_mode) { case O0: return strlit("O0"); case O1: return strlit("O1"); case O2: return strlit("O2"); case O3: return strlit("O3"); case Os: return strlit("Os"); case Oz: return strlit("Oz"); case OPTIMIZATION_COUNT: unreachable(); } } global const auto compiler_source_path = "bootstrap/main.c"; fn void run_tests(Arena* arena, TestOptions const * const test_options, char** envp) { CompileOptions compile_options = {}; compile_options.compiler = default_compiler; compile_options.debug_info = 1; compile_options.in_path = compiler_source_path; for (u32 linkage_i = 0; linkage_i < test_options->linkages.length; linkage_i += 1) { compile_options.linkage = test_options->linkages.pointer[linkage_i]; auto linkage_string = linkage_name(compile_options.linkage); for (u32 optimization_i = 0; optimization_i < test_options->optimization_modes.length; optimization_i += 1) { compile_options.optimization_mode = test_options->optimization_modes.pointer[optimization_i]; auto optimization_string = optimization_name(compile_options.optimization_mode); print("\n===========================\n"); print("TESTS (linkage={s}, optimization={s})\n", linkage_string, optimization_string); print("===========================\n\n"); String compiler_path_split[] = { strlit("./" build_dir "/" "nest"), strlit("_"), optimization_string, strlit("_"), linkage_string, #if _WIN32 strlit(".exe"), #endif }; String compiler_path = arena_join_string(arena, ((Slice(String)) array_to_slice(compiler_path_split))); compile_options.out_path = string_to_c(compiler_path); compile_c(arena, compile_options, envp); print("\n===========================\n"); print("COMPILER BUILD [OK]\n"); print("===========================\n\n"); for (u32 test_i = 0; test_i < test_options->test_paths.length; test_i += 1) { String test_path = test_options->test_paths.pointer[test_i]; char* test_path_c = string_to_c(test_path); auto test_dir = string_no_extension(test_path); auto test_name = string_base(test_dir); for (u32 engine_i = 0; engine_i < test_options->compiler_backends.length; engine_i += 1) { CompilerBackend compiler_backend = test_options->compiler_backends.pointer[engine_i]; char* compiler_backend_string; switch (compiler_backend) { case COMPILER_BACKEND_C: compiler_backend_string = "c"; break; case COMPILER_BACKEND_INTERPRETER: compiler_backend_string = "i"; break; case COMPILER_BACKEND_MACHINE: compiler_backend_string = "m"; break; case COMPILER_BACKEND_COUNT: unreachable(); } char* arguments[] = { compile_options.out_path, test_path_c, compiler_backend_string, 0, }; run_command(arena, (CStringSlice) array_to_slice(arguments), envp); if (compiler_backend != COMPILER_BACKEND_INTERPRETER) { String path_split[] = { strlit("./" nest_dir "/"), test_name, #if _WIN32 strlit(".exe"), #endif }; String out_program = arena_join_string(arena, ((Slice(String)) array_to_slice(path_split))); char* run_arguments[] = { string_to_c(out_program), 0, }; run_command(arena, (CStringSlice) array_to_slice(run_arguments), envp); } } } } } } fn void entry_point(int argc, char* argv[], char* envp[]) { if (argc < 2) { print("Expected some arguments\n"); fail(); } Arena* arena = arena_init_default(KB(64)); CompilerBackend preferred_compiler_backend = COMPILER_BACKEND_COUNT; Command command = COMMAND_COUNT; u8 test_every_config = 0; String source_file_path = {}; for (int i = 1; i < argc; i += 1) { char* c_argument = argv[i]; auto argument = cstr(c_argument); if (s_equal(argument, strlit("i"))) { preferred_compiler_backend = COMPILER_BACKEND_INTERPRETER; } else if (s_equal(argument, strlit("c"))) { preferred_compiler_backend = COMPILER_BACKEND_C; } else if (s_equal(argument, strlit("m"))) { preferred_compiler_backend = COMPILER_BACKEND_MACHINE; } else if (s_equal(argument, strlit("test"))) { command = COMMAND_RUN_TESTS; } else if (s_equal(argument, strlit("debug"))) { command = COMMAND_DEBUG; } else if (s_equal(argument, strlit("compile"))) { command = COMMAND_COMPILE; } else if (s_equal(argument, strlit("all"))) { test_every_config = 1; } } auto index = 2 - (command == COMMAND_COUNT); if (argc > index) { auto* c_argument = argv[index]; auto argument = cstr(c_argument); String expected_starts[] = { strlit("tests/"), strlit("tests\\"), strlit("./tests/"), strlit(".\\tests\\"), strlit("src/"), strlit("src\\"), strlit("./src/"), strlit(".\\src\\"), }; for (u32 i = 0; i < array_length(expected_starts); i += 1) { auto expected_start = expected_starts[i]; if (expected_start.length < argument.length) { // TODO: make our own function if (strncmp(c_argument, string_to_c(expected_start), expected_start.length) == 0) { source_file_path = argument; break; } } } } if (command == COMMAND_COUNT && !source_file_path.pointer) { print("Expected a command\n"); fail(); } if (command == COMMAND_COUNT) { command = COMMAND_COMPILE; } if ((command == COMMAND_DEBUG) | ((command == COMMAND_RUN_TESTS) & (test_every_config == 0))) { if (preferred_compiler_backend == COMPILER_BACKEND_COUNT) { preferred_compiler_backend = COMPILER_BACKEND_MACHINE; } } switch (command) { case COMMAND_DEBUG: if (!source_file_path.pointer) { fail(); } // Test always with dynamic linkage because it's more trustworthy Linkage linkage = LINKAGE_DYNAMIC; compile_and_run(arena, (CompileOptions) { .in_path = compiler_source_path, .out_path = linkage == LINKAGE_DYNAMIC ? ( build_dir "/" "nest_O0_dynamic" #if _WIN32 ".exe" #endif ) : ( build_dir "/" "nest_O0_static" #if _WIN32 ".exe" #endif ), .compiler = default_compiler, .debug_info = 1, .error_on_warning = 0, .optimization_mode = O0, .linkage = linkage, }, envp, preferred_compiler_backend, 1, string_to_c(source_file_path)); break; case COMMAND_RUN_TESTS: { Linkage all_linkages[] = { LINKAGE_DYNAMIC, #ifdef __linux__ LINKAGE_STATIC #endif }; OptimizationMode all_optimization_modes[] = { O0, O1, O2, O3, Os, Oz }; // static_assert(array_length(all_optimization_modes) == OPTIMIZATION_COUNT); String every_single_test[] = { strlit("tests/first.nat"), // strlit("tests/add_sub.nat"), // strlit("tests/mul.nat"), // strlit("tests/div.nat"), // strlit("tests/and.nat"), // strlit("tests/or.nat"), // strlit("tests/xor.nat"), // strlit("tests/return_var.nat"), // strlit("tests/return_mod_scope.nat"), // strlit("tests/shift_left.nat"), // strlit("tests/shift_right.nat"), // strlit("tests/thousand_simple_functions.nat"), // strlit("tests/simple_arg.nat"), // strlit("tests/comparison.nat"), }; CompilerBackend all_compiler_backends[] = { // COMPILER_BACKEND_INTERPRETER, // COMPILER_BACKEND_C, COMPILER_BACKEND_MACHINE, }; Slice(Linkage) linkage_selection; Slice(OptimizationMode) optimization_selection; Slice(CompilerBackend) compiler_backend_selection; if (test_every_config) { #ifdef __linux__ linkage_selection = (Slice(Linkage)) array_to_slice(all_linkages); #else linkage_selection = (Slice(Linkage)) { .pointer = &all_linkages[0], .length = 1 }; #endif optimization_selection = (Slice(OptimizationMode)) array_to_slice(all_optimization_modes); compiler_backend_selection = (Slice(CompilerBackend)) array_to_slice(all_compiler_backends); } else { linkage_selection = (Slice(Linkage)) { .pointer = &all_linkages[0], .length = 1 }; optimization_selection = (Slice(OptimizationMode)) { .pointer = &all_optimization_modes[0], .length = 1 }; compiler_backend_selection = (Slice(CompilerBackend)) { .pointer = &preferred_compiler_backend, .length = 1 }; } Slice(String) test_selection; if (source_file_path.pointer) { test_selection = (Slice(String)) { .pointer = &source_file_path, .length = 1 }; } else { test_selection = (Slice(String)) array_to_slice(every_single_test); } run_tests(arena, &(TestOptions) { .linkages = linkage_selection, .optimization_modes = optimization_selection, .test_paths = test_selection, .compiler_backends = compiler_backend_selection, }, envp); } break; case COMMAND_COMPILE: compile_c(arena, (CompileOptions) { .in_path = compiler_source_path, .out_path = build_dir "/" "nest_O0_static", .compiler = default_compiler, .debug_info = 1, .error_on_warning = 0, .optimization_mode = O0, // #if defined(__linux__) // .linkage = LINKAGE_STATIC, // #else .linkage = LINKAGE_DYNAMIC, // #endif }, envp); break; case COMMAND_COUNT: unreachable(); } }