From eed6fd6de94ed9c7fa8ba14e240c92f0e1c7a4fa Mon Sep 17 00:00:00 2001
From: David Gonzalez Martin <davidgm94.work@protonmail.com>
Date: Fri, 25 Oct 2024 21:37:59 -0600
Subject: [PATCH] implement linker

---
 CMakeLists.txt                              |  31 ++-
 bootstrap/bloat-buster/lld_api.cpp          |  55 +++++
 bootstrap/bloat-buster/lld_driver.c         | 239 ++++++++++++++++++++
 bootstrap/bloat-buster/llvm.cpp             |   2 +-
 bootstrap/bloat-buster/main.c               |  76 ++++---
 bootstrap/include/bloat-buster/base.h       |  10 +
 bootstrap/include/bloat-buster/lld_api.h    |  17 ++
 bootstrap/include/bloat-buster/lld_driver.h |   4 +
 bootstrap/include/std/os.h                  |   3 +
 bootstrap/include/std/string.h              |   7 +-
 bootstrap/include/std/virtual_buffer.h      |   1 +
 bootstrap/runner/runner.c                   |   4 -
 bootstrap/std/os.c                          |  25 +-
 bootstrap/std/string.c                      |  24 ++
 project.sh                                  |  23 +-
 15 files changed, 479 insertions(+), 42 deletions(-)
 create mode 100644 bootstrap/bloat-buster/lld_api.cpp
 create mode 100644 bootstrap/bloat-buster/lld_driver.c
 create mode 100644 bootstrap/include/bloat-buster/lld_api.h
 create mode 100644 bootstrap/include/bloat-buster/lld_driver.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 07287a3..e80f362 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -69,6 +69,11 @@ execute_process(
     OUTPUT_VARIABLE LLVM_LINK_MODE
     OUTPUT_STRIP_TRAILING_WHITESPACE)
 
+cmake_print_variables("LLVM libs: ${LLVM_LIBRARIES}")
+cmake_print_variables("LLVM libdirs: ${LLVM_LIBDIRS}")
+cmake_print_variables("LLVM system libs: ${LLVM_SYSTEM_LIBS}")
+cmake_print_variables("LLVM link mode: ${LLVM_LINK_MODE}")
+
 if (${LLVM_LINK_MODE} STREQUAL "shared")
     # We always ask for the system libs corresponding to static linking,
     # since on some distros LLD is only available as a static library
@@ -79,12 +84,34 @@ if (${LLVM_LINK_MODE} STREQUAL "shared")
         ERROR_QUIET # Some installations have no static libs, we just ignore the failure
         OUTPUT_STRIP_TRAILING_WHITESPACE)
     string(REPLACE " " ";" LLVM_STATIC_SYSTEM_LIBS "${LLVM_STATIC_SYSTEM_LIBS_SPACES}")
+    find_library(LLD_COFF NAMES liblldCOFF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
+    find_library(LLD_COMMON NAMES liblldCommon.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
+    find_library(LLD_ELF NAMES liblldELF.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
+    find_library(LLD_MACHO NAMES liblldMachO.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
+    find_library(LLD_MINGW NAMES liblldMinGW.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
+    find_library(LLD_WASM NAMES liblldWasm.so PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
 
     set(LLVM_LIBRARIES ${LLVM_LIBRARIES} ${LLVM_SYSTEM_LIBS} ${LLVM_STATIC_SYSTEM_LIBS})
 else()
+    find_library(LLD_COFF NAMES lldCOFF.lib lldCOFF.a liblldCOFF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
+    find_library(LLD_COMMON NAMES lldCommon.lib lldCommon.a liblldCommon.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
+    find_library(LLD_ELF NAMES lldELF.lib lldELF.a liblldELF.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
+    find_library(LLD_MACHO NAMES lldMachO.lib lldMachO.a liblldMachO.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
+    find_library(LLD_MINGW NAMES lldMinGW.lib lldMinGW.a liblldMinGW.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
+    find_library(LLD_WASM NAMES lldWasm.lib lldWasm.a liblldWasm.a PATHS ${LLVM_LIBRARY_DIRS} NO_DEFAULT_PATH)
+
     set(LLVM_LIBRARIES ${LLVM_LIBRARIES} ${LLVM_SYSTEM_LIBS})
 endif()
 
+set(lld_libs
+    ${LLD_COFF}
+    ${LLD_ELF}
+    ${LLD_COMMON}
+    ${LLD_MINGW}
+    ${LLD_MACHO}
+    ${LLD_WASM}
+)
+
 execute_process(
     COMMAND ${LLVM_CONFIG_EXE} --includedir
     OUTPUT_VARIABLE LLVM_INCLUDE_DIRS_SPACES
@@ -124,11 +151,13 @@ add_executable("${COMPILER_NAME}"
     "bootstrap/bloat-buster/main.c"
     "bootstrap/bloat-buster/pdb_image.c"
     "bootstrap/bloat-buster/llvm.cpp"
+    "bootstrap/bloat-buster/lld_driver.c"
+    "bootstrap/bloat-buster/lld_api.cpp"
 )
 
 target_compile_definitions(${COMPILER_NAME} PRIVATE ${LLVM_DEFINITIONS})
 cmake_print_variables("LLVM definitions: ${LLVM_DEFINITIONS}")
 target_include_directories(${COMPILER_NAME} PRIVATE ${LLVM_INCLUDE_DIRS})
 target_link_directories(${COMPILER_NAME} PRIVATE ${LLVM_LIBRARY_DIRS})
-target_link_libraries(${COMPILER_NAME} PRIVATE ${LIBRARY_NAME} ${LLVM_LIBRARIES})
+target_link_libraries(${COMPILER_NAME} PRIVATE ${LIBRARY_NAME} ${LLVM_LIBRARIES} ${lld_libs})
 
diff --git a/bootstrap/bloat-buster/lld_api.cpp b/bootstrap/bloat-buster/lld_api.cpp
new file mode 100644
index 0000000..964da71
--- /dev/null
+++ b/bootstrap/bloat-buster/lld_api.cpp
@@ -0,0 +1,55 @@
+#include <bloat-buster/lld_api.h>
+
+#include <llvm/ADT/ArrayRef.h>
+#include <llvm/Support/raw_ostream.h>
+#include <lld/Common/Driver.h>
+#include <std/os.h>
+
+#define lld_api_function_signature(name) bool name(llvm::ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS, llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput)
+
+#define lld_link_decl(link_name) \
+namespace link_name \
+{\
+    lld_api_function_signature(link);\
+}
+
+typedef lld_api_function_signature(LinkerFunction);
+
+namespace lld
+{
+    lld_link_decl(coff);
+    lld_link_decl(elf);
+    lld_link_decl(mingw);
+    lld_link_decl(macho);
+    lld_link_decl(wasm);
+}
+
+fn u8 lld_api_generic(LLDArguments args, LinkerFunction linker_function)
+{
+    auto arguments = llvm::ArrayRef(args.argument_pointer, args.argument_count);
+    std::string stdout_string;
+    llvm::raw_string_ostream stdout_stream(stdout_string);
+
+    std::string stderr_string;
+    llvm::raw_string_ostream stderr_stream(stderr_string);
+    u8 result = linker_function(arguments, stdout_stream, stderr_stream, args.exit_early, args.disable_output);
+    // assert(result == (stdout_string.length() == 0));
+    // assert(result == (stderr_string.length() == 0));
+
+    print_string(String{(u8*)stdout_string.data(), stdout_string.length()});
+    print_string(String{(u8*)stderr_string.data(), stderr_string.length()});
+
+    return result;
+}
+
+#define lld_api_function_impl(link_name) \
+lld_api_function_decl(link_name)\
+{\
+    return lld_api_generic(args, lld::link_name::link);\
+}
+
+lld_api_function_impl(coff)
+lld_api_function_impl(elf)
+lld_api_function_impl(mingw)
+lld_api_function_impl(macho)
+lld_api_function_impl(wasm)
diff --git a/bootstrap/bloat-buster/lld_driver.c b/bootstrap/bloat-buster/lld_driver.c
new file mode 100644
index 0000000..27bc916
--- /dev/null
+++ b/bootstrap/bloat-buster/lld_driver.c
@@ -0,0 +1,239 @@
+#include <bloat-buster/lld_driver.h>
+#include <std/virtual_buffer.h>
+#include <std/string.h>
+
+fn String linux_crt_find_path()
+{
+    auto flags = (OSFileOpenFlags) {
+        .read = 1,
+    };
+    auto permissions = (OSFilePermissions) {
+        .readable = 1,
+        .writable = 1,
+    };
+    if (os_file_descriptor_is_valid(os_file_open(strlit("/usr/lib/crti.o"), flags, permissions)))
+    {
+        return strlit("/usr/lib");
+    }
+
+    if (os_file_descriptor_is_valid(os_file_open(strlit("/usr/lib/x86_64-linux-gnu/crti.o"), flags, permissions)))
+    {
+        return strlit("/usr/lib/x86_64-linux-gnu");
+    }
+
+    todo();
+}
+
+fn String windows_msvc_find_path()
+{
+    auto flags = (OSFileOpenFlags) {
+        .read = 1,
+        .directory = 1,
+    };
+    auto permissions = (OSFilePermissions) {
+        .readable = 1,
+    };
+    String possibilities[] = {
+        strlit("C:/Program Files/Microsoft Visual Studio/2022/Enterprise"),
+        strlit("C:/Program Files/Microsoft Visual Studio/2022/Community"),
+    };
+    for (u64 i = 0; i < array_length(possibilities); i += 1)
+    {
+        auto possibility = possibilities[i];
+        auto fd = os_file_open(possibility, flags, permissions);
+
+        if (os_file_descriptor_is_valid(fd))
+        {
+            return possibility;
+        }
+    }
+
+    failed_execution();
+}
+
+fn void linux_add_crt_item(Arena* arena, VirtualBufferP(char)* args, String crt_path, String item)
+{
+    String parts[] = {
+        crt_path,
+        strlit("/"),
+        item,
+    };
+    *vb_add(args, 1) = string_to_c(arena_join_string(arena, (Slice(String))array_to_slice(parts)));
+}
+
+SliceP(char) lld_driver(Arena* arena, LinkerArguments arguments)
+{
+    VirtualBufferP(char) args = {};
+
+    char* driver;
+    switch (arguments.target.os)
+    {
+    case OPERATING_SYSTEM_LINUX:
+        driver = "ld.lld";
+        break;
+    case OPERATING_SYSTEM_MAC:
+        driver = "ld64.lld";
+        break;
+    case OPERATING_SYSTEM_WINDOWS:
+        driver = "lld-link";
+        break;
+    }
+    *vb_add(&args, 1) = driver;
+
+    if (arguments.target.os != OPERATING_SYSTEM_WINDOWS)
+    {
+        *vb_add(&args, 1) = "--error-limit=0";
+    }
+
+    switch (arguments.target.os)
+    {
+        case OPERATING_SYSTEM_WINDOWS:
+            {
+                String parts[] = {
+                    strlit("-out:"),
+                    arguments.out_path,
+                };
+                auto arg = arena_join_string(arena, (Slice(String))array_to_slice(parts));
+                *vb_add(&args, 1) = string_to_c(arg);
+            } break;
+        default:
+            {
+                *vb_add(&args, 1) = "-o";
+                *vb_add(&args, 1) = string_to_c(arguments.out_path);
+            } break;
+    }
+
+    if (arguments.target.os != OPERATING_SYSTEM_WINDOWS)
+    {
+        for (u64 i = 0; i < arguments.objects.length; i += 1)
+        {
+            *vb_add(&args, 1) = string_to_c(arguments.objects.pointer[i]);
+        }
+    }
+
+    switch (arguments.target.os)
+    {
+    case OPERATING_SYSTEM_LINUX:
+        {
+            if (arguments.link_libcpp && !arguments.link_libc)
+            {
+                failed_execution();
+            }
+
+            if (arguments.link_libc)
+            {
+                auto crt_path = linux_crt_find_path();
+
+                *vb_add(&args, 1) = "-dynamic-linker";
+
+                String dynamic_linker_filename;
+                switch (arguments.target.cpu)
+                {
+                case CPU_ARCH_X86_64:
+                    dynamic_linker_filename = strlit("ld-linux-x86-64.so.2");
+                    break;
+                case CPU_ARCH_AARCH64:
+                    dynamic_linker_filename = strlit("ld-linux-aarch64.so.1");
+                    break;
+                }
+
+                linux_add_crt_item(arena, &args, crt_path, dynamic_linker_filename);
+                linux_add_crt_item(arena, &args, crt_path, strlit("crt1.o"));
+
+                *vb_add(&args, 1) = "-L";
+                *vb_add(&args, 1) = string_to_c(crt_path);
+
+
+                *vb_add(&args, 1) = "--as-needed";
+                *vb_add(&args, 1) = "-lm";
+                *vb_add(&args, 1) = "-lpthread";
+                *vb_add(&args, 1) = "-lc";
+                *vb_add(&args, 1) = "-ldl";
+                *vb_add(&args, 1) = "-lrt";
+                *vb_add(&args, 1) = "-lutil";
+
+                linux_add_crt_item(arena, &args, crt_path, strlit("crtn.o"));
+
+                if (arguments.link_libcpp)
+                {
+                    // TODO: implement better path finding
+                    linux_add_crt_item(arena, &args, crt_path, strlit("libstdc++.so.6"));
+                }
+            }
+
+            for (u64 i = 0; i < arguments.libraries.length; i += 1)
+            {
+                auto library = arguments.libraries.pointer[i];
+                String library_pieces[] = {
+                    strlit("-l"),
+                    library,
+                };
+                auto library_argument = arena_join_string(arena, (Slice(String))array_to_slice(library_pieces));
+                *vb_add(&args, 1) = string_to_c(library_argument);
+            }
+        } break;
+    case OPERATING_SYSTEM_MAC:
+        {
+            *vb_add(&args, 1) = "-dynamic";
+            *vb_add(&args, 1) = "-platform_version";
+            *vb_add(&args, 1) = "macos";
+            *vb_add(&args, 1) = "15.0.0";
+            *vb_add(&args, 1) = "15.0.0";
+            *vb_add(&args, 1) = "-arch";
+            *vb_add(&args, 1) = "arm64";
+            *vb_add(&args, 1) = "-syslibroot";
+            *vb_add(&args, 1) = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk";
+
+            if (string_ends_with(arguments.out_path, strlit(".dylib")))
+            {
+                *vb_add(&args, 1) = "-e";
+                *vb_add(&args, 1) = "_main";
+            }
+
+            *vb_add(&args, 1) = "-lSystem";
+
+            if (arguments.link_libcpp)
+            {
+                *vb_add(&args, 1) = "-lc++";
+            }
+        } break;
+    case OPERATING_SYSTEM_WINDOWS:
+        {
+            if (arguments.link_libcpp && !arguments.link_libc)
+            {
+                failed_execution();
+            }
+
+            auto msvc_path = windows_msvc_find_path();
+
+            if (arguments.link_libc)
+            {
+                *vb_add(&args, 1) = "-defaultlib:libcmt";
+
+                {
+                    // String parts[] = {
+                    //     strlit("-libpath:"),
+                    //     msvc_path,
+                    //     strlit("/"),
+                    //     strlit("VC/Tools/MSVC/14.41.34120/lib/x64"),
+                    // };
+                    // auto arg = arena_join_string(arena, (Slice(String)) array_to_slice(parts));
+                }
+
+                if (arguments.link_libcpp)
+                {
+                    todo();
+                }
+
+                for (u64 i = 0; i < arguments.objects.length; i += 1)
+                {
+                    *vb_add(&args, 1) = string_to_c(arguments.objects.pointer[i]);
+                }
+            }
+            // clang -v main.c 
+            // "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.41.34120\\bin\\Hostx64\\x64\\link.exe" -out:a.exe -defaultlib:libcmt -defaultlib:oldnames "-libpath:C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.41.34120\\lib\\x64" "-libpath:C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.41.34120\\atlmfc\\lib\\x64" "-libpath:C:\\Program Files (x86)\\Windows Kits\\10\\Lib\\10.0.22621.0\\ucrt\\x64" "-libpath:C:\\Program Files (x86)\\Windows Kits\\10\\Lib\\10.0.22621.0\\um\\x64" "-libpath:C:\\Users\\David\\scoop\\apps\\llvm\\19.1.3\\lib\\clang\\19\\lib\\windows" -nologo "C:\\Users\\David\\AppData\\Local\\Temp\\main-706820.o"
+        } break;
+    }
+
+    return (SliceP(char)){ .pointer = args.pointer, .length = args.length };
+}
diff --git a/bootstrap/bloat-buster/llvm.cpp b/bootstrap/bloat-buster/llvm.cpp
index 01162d7..82c1313 100644
--- a/bootstrap/bloat-buster/llvm.cpp
+++ b/bootstrap/bloat-buster/llvm.cpp
@@ -100,7 +100,7 @@ namespace llvm
                 target_triple = string_ref("aarch64-apple-macosx-none");
                 break;
             case OPERATING_SYSTEM_WINDOWS:
-                target_triple = string_ref("x86_64-windows-gnu");
+                target_triple = string_ref("x86_64-pc-windows-msvc");
                 break;
         }
 
diff --git a/bootstrap/bloat-buster/main.c b/bootstrap/bloat-buster/main.c
index aa9e5b8..e9b5f1a 100644
--- a/bootstrap/bloat-buster/main.c
+++ b/bootstrap/bloat-buster/main.c
@@ -8,6 +8,8 @@
 #include <bloat-buster/base.h>
 #include <bloat-buster/pdb_image.h>
 #include <bloat-buster/llvm.h>
+#include <bloat-buster/lld_driver.h>
+#include <bloat-buster/lld_api.h>
 
 #ifdef __APPLE__
 #define clang_path "/opt/homebrew/opt/llvm/bin/clang"
@@ -38,17 +40,6 @@ STRUCT(GetOrPut(T)) \
 
 auto compiler_name = strlit("bb");
 
-fn void print_string(String message)
-{
-#ifndef SILENT
-    // TODO: check writes
-    os_file_write(os_stdout_get(), message);
-    // assert(result >= 0);
-    // assert((u64)result == message.length);
-#else
-        unused(message);
-#endif
-}
 
 STRUCT(ElfRelocation)
 {
@@ -20821,21 +20812,21 @@ may_be_unused fn String write_pe(Thread* thread, ObjectOptions options)
     auto pdb = pdb_build(thread);
 
     // TODO:
-#if _WIN32
-    auto pdb_path = arena_join_string(thread->arena, (Slice(String))array_to_slice(to_join));
-    auto fd = os_file_open(strlit("mydbg.pdb"), (OSFileOpenFlags) {
-        .write = 1,
-        .truncate = 1,
-        .create = 1,
-    }, (OSFilePermissions) {
-        .readable = 1,
-        .writable = 1,
-    });
-
-    os_file_write(fd, pdb);
-
-    os_file_close(fd);
-#endif
+// #if _WIN32
+//     auto pdb_path = arena_join_string(thread->arena, (Slice(String))array_to_slice(to_join));
+//     auto fd = os_file_open(strlit("mydbg.pdb"), (OSFileOpenFlags) {
+//         .write = 1,
+//         .truncate = 1,
+//         .create = 1,
+//     }, (OSFilePermissions) {
+//         .readable = 1,
+//         .writable = 1,
+//     });
+//
+//     os_file_write(fd, pdb);
+//
+//     os_file_close(fd);
+// #endif
 
     // Check if file matches
 #define CHECK_PE_MATCH 0
@@ -24328,6 +24319,39 @@ fn void code_generation(Thread* restrict thread, CodegenOptions options)
             {
                 // TODO: delete, this is testing
                 llvm_codegen(options, object_path);
+
+                auto lld_args = lld_driver(thread->arena, (LinkerArguments) {
+                    .target = options.target,
+                    .out_path = exe_path,
+                    .objects = (Slice(String)) { .pointer = &object_path, .length = 1 },
+                    .link_libc = 1,
+                });
+
+                LLDArguments arguments = {
+                    .disable_output = 0,
+                    .exit_early = 1,
+                    .argument_pointer = (const char**)lld_args.pointer,
+                    .argument_count = lld_args.length,
+                };
+
+                u8 result;
+                switch (options.target.os)
+                {
+                case OPERATING_SYSTEM_LINUX:
+                    result = lld_elf_link(arguments);
+                    break;
+                case OPERATING_SYSTEM_MAC:
+                    result = lld_macho_link(arguments);
+                    break;
+                case OPERATING_SYSTEM_WINDOWS:
+                    result = lld_coff_link(arguments);
+                    break;
+                }
+
+                if (!result)
+                {
+                    failed_execution();
+                }
             } break;
         case COMPILER_BACKEND_BB:
         {
diff --git a/bootstrap/include/bloat-buster/base.h b/bootstrap/include/bloat-buster/base.h
index 813c0fb..33b4f99 100644
--- a/bootstrap/include/bloat-buster/base.h
+++ b/bootstrap/include/bloat-buster/base.h
@@ -202,3 +202,13 @@ fn Target native_target_get()
 
     return target;
 }
+
+STRUCT(LinkerArguments)
+{
+    Target target;
+    String out_path;
+    Slice(String) objects;
+    Slice(String) libraries;
+    u8 link_libc:1;
+    u8 link_libcpp:1;
+};
diff --git a/bootstrap/include/bloat-buster/lld_api.h b/bootstrap/include/bloat-buster/lld_api.h
new file mode 100644
index 0000000..b27f791
--- /dev/null
+++ b/bootstrap/include/bloat-buster/lld_api.h
@@ -0,0 +1,17 @@
+#include <std/base.h>
+
+STRUCT(LLDArguments)
+{
+    const char** argument_pointer;
+    u32 argument_count;
+    u8 exit_early;
+    u8 disable_output;
+};
+
+#define lld_api_function_decl(link_name) u8 lld_ ## link_name ## _link(LLDArguments args)
+
+EXPORT lld_api_function_decl(coff);
+EXPORT lld_api_function_decl(elf);
+EXPORT lld_api_function_decl(mingw);
+EXPORT lld_api_function_decl(macho);
+EXPORT lld_api_function_decl(coff);
diff --git a/bootstrap/include/bloat-buster/lld_driver.h b/bootstrap/include/bloat-buster/lld_driver.h
new file mode 100644
index 0000000..f23c7ec
--- /dev/null
+++ b/bootstrap/include/bloat-buster/lld_driver.h
@@ -0,0 +1,4 @@
+#include <bloat-buster/base.h>
+#include <std/os.h>
+
+EXPORT SliceP(char) lld_driver(Arena* arena, LinkerArguments arguments);
diff --git a/bootstrap/include/std/os.h b/bootstrap/include/std/os.h
index 3189a07..dfab787 100644
--- a/bootstrap/include/std/os.h
+++ b/bootstrap/include/std/os.h
@@ -9,6 +9,7 @@ STRUCT(OSFileOpenFlags)
     u32 write:1;
     u32 read:1;
     u32 create:1;
+    u32 directory:1;
 };
 
 STRUCT(OSFilePermissions)
@@ -91,3 +92,5 @@ EXPORT FileDescriptor os_stdout_get();
 EXPORT void os_directory_make(String path);
 
 EXPORT void calibrate_cpu_timer();
+
+EXPORT void print_string(String string);
diff --git a/bootstrap/include/std/string.h b/bootstrap/include/std/string.h
index aac6ec5..95b54f5 100644
--- a/bootstrap/include/std/string.h
+++ b/bootstrap/include/std/string.h
@@ -1,5 +1,6 @@
 #include <std/base.h>
 
-s32 string_first_ch(String string, u8 ch);
-s64 string_last_ch(String string, u8 ch);
-u8 string_starts_with(String string, String start);
+EXPORT s32 string_first_ch(String string, u8 ch);
+EXPORT s64 string_last_ch(String string, u8 ch);
+EXPORT u8 string_starts_with(String string, String start);
+EXPORT u8 string_ends_with(String string, String end);
diff --git a/bootstrap/include/std/virtual_buffer.h b/bootstrap/include/std/virtual_buffer.h
index 07e5da9..d39993c 100644
--- a/bootstrap/include/std/virtual_buffer.h
+++ b/bootstrap/include/std/virtual_buffer.h
@@ -26,6 +26,7 @@ decl_vbp(s32);
 decl_vb(s64);
 decl_vbp(s64);
 decl_vb(String);
+decl_vbp(char);
 
 #define vb_size_of_element(vb) sizeof(*((vb)->pointer))
 #define vb_add(vb, count) (typeof((vb)->pointer)) vb_generic_add((VirtualBuffer(u8)*)(vb), (vb_size_of_element(vb)), (count))
diff --git a/bootstrap/runner/runner.c b/bootstrap/runner/runner.c
index d77d750..7b92c40 100644
--- a/bootstrap/runner/runner.c
+++ b/bootstrap/runner/runner.c
@@ -83,10 +83,6 @@ STRUCT(TestOptions)
 
 fn void run_tests(Arena* arena, String compiler_path, TestOptions const * const test_options, char** envp)
 {
-    print("\n===========================\n");
-    print("COMPILER BUILD [OK]\n");
-    print("===========================\n\n");
-
     Target target = native_target_get();
 
     for (u32 test_i = 0; test_i < test_options->test_paths.length; test_i += 1)
diff --git a/bootstrap/std/os.c b/bootstrap/std/os.c
index e8a7dd8..4a65aea 100644
--- a/bootstrap/std/os.c
+++ b/bootstrap/std/os.c
@@ -849,11 +849,10 @@ FileDescriptor os_file_open(String path, OSFileOpenFlags flags, OSFilePermission
 {
     assert(path.pointer[path.length] == 0);
 #if _WIN32
-
     DWORD dwDesiredAccess = 0;
-    dwDesiredAccess |= permissions.readable * GENERIC_READ;
-    dwDesiredAccess |= permissions.writable * GENERIC_WRITE;
-    dwDesiredAccess |= permissions.executable * GENERIC_EXECUTE;
+    dwDesiredAccess |= flags.read * GENERIC_READ;
+    dwDesiredAccess |= flags.write * GENERIC_WRITE;
+    dwDesiredAccess |= flags.executable * GENERIC_EXECUTE;
     DWORD dwShareMode = 0;
     LPSECURITY_ATTRIBUTES lpSecurityAttributes = 0;
     DWORD dwCreationDisposition = 0;
@@ -861,8 +860,10 @@ FileDescriptor os_file_open(String path, OSFileOpenFlags flags, OSFilePermission
     dwCreationDisposition |= flags.create * CREATE_ALWAYS;
     DWORD dwFlagsAndAttributes = 0;
     dwFlagsAndAttributes |= FILE_ATTRIBUTE_NORMAL;
+    dwFlagsAndAttributes |= flags.directory * FILE_FLAG_BACKUP_SEMANTICS;
     HANDLE hTemplateFile = 0;
-    auto handle = CreateFileA((char*)path.pointer, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
+
+    auto handle = CreateFileA(string_to_c(path), dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
     return handle;
 #else
     int posix_flags = 0;
@@ -1431,7 +1432,7 @@ void run_command(Arena* arena, CStringSlice arguments, char* envp[])
         DWORD exit_code;
         if (GetExitCodeProcess(process_information.hProcess, &exit_code))
         {
-            print("Process ran with exit code: {u32:x}\n", exit_code);
+            print("Process ran with exit code: 0x{u32:x}\n", exit_code);
             if (exit_code != 0)
             {
                 failed_execution();
@@ -1543,3 +1544,15 @@ void run_command(Arena* arena, CStringSlice arguments, char* envp[])
     }
 #endif
 }
+
+void print_string(String message)
+{
+#ifndef SILENT
+    // TODO: check writes
+    os_file_write(os_stdout_get(), message);
+    // assert(result >= 0);
+    // assert((u64)result == message.length);
+#else
+        unused(message);
+#endif
+}
diff --git a/bootstrap/std/string.c b/bootstrap/std/string.c
index f9e37de..a182554 100644
--- a/bootstrap/std/string.c
+++ b/bootstrap/std/string.c
@@ -61,3 +61,27 @@ u8 string_starts_with(String string, String start)
 
     return result;
 }
+
+u8 string_ends_with(String string, String end)
+{
+    u8 result = 0;
+
+    if (likely(end.length <= string.length))
+    {
+        u64 i;
+        u64 offset = string.length - end.length;
+        for (i = 0; i < end.length; i += 1)
+        {
+            auto start_ch = end.pointer[i];
+            auto string_ch = string.pointer[i + offset];
+            if (unlikely(string_ch != start_ch))
+            {
+                break;
+            }
+        }
+
+        result = i == end.length;
+    }
+
+    return result;
+}
diff --git a/project.sh b/project.sh
index fbb1fd9..b4c5c0f 100755
--- a/project.sh
+++ b/project.sh
@@ -6,6 +6,7 @@ C_COMPILER_PATH=clang
 CXX_COMPILER_PATH=clang++
 ASM_COMPILER_PATH=clang
 
+
 if [[ -z "${BIRTH_OS-}" ]]; then
     case "$OSTYPE" in
         msys*)
@@ -23,6 +24,16 @@ if [[ -z "${BIRTH_OS-}" ]]; then
     esac
 fi
 
+case "$BIRTH_OS" in
+    linux)
+        ls -las /
+        ls -las /usr
+        ls -las /usr/lib
+        ;;
+    *)
+        ;;
+esac
+
 if [[ -z "${BIRTH_ARCH-}" ]]; then
     case "$(uname -m)" in
         x86_64)
@@ -42,7 +53,7 @@ if [[ -z "${CMAKE_BUILD_TYPE-}" ]]; then
 fi
 
 if [[ -z "${CMAKE_PREFIX_PATH-}" ]]; then
-    CMAKE_PREFIX_PATH=""
+    CMAKE_PREFIX_PATH="$HOME/Downloads/llvm-$BIRTH_ARCH-$BIRTH_OS-$CMAKE_BUILD_TYPE"
 fi
 
 
@@ -59,6 +70,14 @@ case $BIRTH_OS in
         ASM_COMPILER_OPT_ARG=""
         ;;
 esac
+case $BIRTH_OS in
+    linux)
+        USE_MOLD_OPT_ARG=-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=mold"
+        ;;
+    *)
+        USE_MOLD_OPT_ARG=""
+        ;;
+esac
 
 mkdir -p $build_dir
 cmake . \
@@ -69,9 +88,11 @@ cmake . \
     -DCMAKE_C_COMPILER=$C_COMPILER_PATH \
     -DCMAKE_CXX_COMPILER=$CXX_COMPILER_PATH \
     -DCMAKE_ASM_COMPILER=$ASM_COMPILER_PATH \
+    $USE_MOLD_OPT_ARG \
     $C_COMPILER_OPT_ARG \
     $CXX_COMPILER_OPT_ARG \
     $ASM_COMPILER_OPT_ARG
+    
 cd $build_dir
 ninja -v
 cd $original_dir