2024-08-09 12:50:31 +02:00

531 lines
15 KiB
C

#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;
};
typedef struct CompileOptions CompileOptions;
decl_vbp(char);
fn u8 is_debug(OptimizationMode optimization_mode, u8 debug_info)
{
return (optimization_mode == O0) & (debug_info != 0);
}
fn void compile_c(const CompileOptions *const 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:
compiler = "/usr/bin/clang";
break;
case COMPILER_COUNT:
unreachable();
}
*vb_add(args, 1) = compiler;
*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();
}
if (options->error_on_warning)
{
*vb_add(args, 1) = "-Werror";
}
char* general_options[] = {
"-std=gnu2x",
"-Wall",
"-Wextra",
"-Wpedantic",
"-Wno-nested-anon-types",
"-Wno-keyword-macro",
"-Wno-gnu-auto-type",
"-Wno-auto-decl-extensions",
"-Wno-gnu-empty-initializer",
"-Wno-fixed-enum-extension",
"-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", };
memcpy(vb_add(args, array_length(static_options)), static_options, sizeof(static_options));
}
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((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(const CompileOptions* const options, char** envp, CompilerBackend compiler_backend, u8 debug, char* nest_source_path)
{
compile_c(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)
{
#ifdef __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",
"--",
common_compile_and_run_args
}));
#endif
}
else
{
args = (CStringSlice) array_to_slice(((char*[]){
common_compile_and_run_args
}));
}
run_command(args, envp);
}
typedef enum Command : u8
{
COMMAND_DEBUG,
COMMAND_RUN_TESTS,
COMMAND_COUNT,
} Command;
struct TestOptions
{
Slice(Linkage) linkages;
Slice(OptimizationMode) optimization_modes;
Slice(String) test_paths;
Slice(CompilerBackend) compiler_backends;
};
typedef struct TestOptions TestOptions;
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 = arena_join_string(arena, ((Slice(String)) array_to_slice(((String[]){
strlit(build_dir "/" "nest"),
strlit("_"),
optimization_string,
strlit("_"),
linkage_string,
}))));
compile_options.out_path = string_to_c(compiler_path);
compile_c(&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((CStringSlice) array_to_slice(arguments), envp);
if (compiler_backend != COMPILER_BACKEND_INTERPRETER)
{
String out_program = arena_join_string(arena, ((Slice(String)) array_to_slice(((String[]){
strlit(nest_dir "/"),
test_name,
}))));
char* run_arguments[] = {
string_to_c(out_program),
0,
};
run_command((CStringSlice) array_to_slice(run_arguments), envp);
}
}
}
}
}
}
int main(int argc, char* argv[], char** envp)
{
if (argc < 2)
{
print("Expected some arguments\n");
return 1;
}
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("all")))
{
test_every_config = 1;
}
}
if (argc > 2)
{
auto* c_argument = argv[2];
auto argument = cstr(c_argument);
String expected_starts[] = { strlit("tests/"), 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)
{
print("Expected a command\n");
return 1;
}
if ((command == COMMAND_DEBUG) | ((command == COMMAND_RUN_TESTS) & (test_every_config == 0)))
{
if (preferred_compiler_backend == COMPILER_BACKEND_COUNT)
{
preferred_compiler_backend = COMPILER_BACKEND_INTERPRETER;
}
}
switch (command)
{
case COMMAND_DEBUG:
if (!source_file_path.pointer)
{
fail();
}
compile_and_run(&(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, preferred_compiler_backend, 1, string_to_c(source_file_path));
break;
case COMMAND_RUN_TESTS:
{
Arena* arena = arena_init_default(KB(64));
Linkage all_linkages[] = { LINKAGE_DYNAMIC, LINKAGE_STATIC };
static_assert(array_length(all_linkages) == LINKAGE_COUNT);
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,
#ifdef __linux__
COMPILER_BACKEND_MACHINE,
#endif
};
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_COUNT:
unreachable();
}
}