From 3bca4c6ed17f118bdc1c29177a5698ea023221fc Mon Sep 17 00:00:00 2001 From: David Gonzalez Martin Date: Thu, 1 May 2025 08:33:46 -0600 Subject: [PATCH] wip --- CMakeLists.txt | 14 ++ build.sh | 63 +----- generate.sh | 7 + old_build.sh | 61 ++++++ src/main.cpp | 539 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 624 insertions(+), 60 deletions(-) create mode 100644 CMakeLists.txt create mode 100755 generate.sh create mode 100755 old_build.sh create mode 100644 src/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..72f1b50 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.15) +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE) +endif() +# Set C++ standard +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED YES) +project(bb) + +add_executable(bb src/main.cpp) +target_compile_definitions(bb PUBLIC + $<$:BB_DEBUG=1> + $<$>:BB_DEBUG=0> +) diff --git a/build.sh b/build.sh index 09b3c32..eead4ed 100755 --- a/build.sh +++ b/build.sh @@ -1,61 +1,4 @@ -#!/usr/bin/env bash set -eu - -MY_CWD=$PWD - -if [[ -z "${BB_CI-}" ]]; then - BB_CI=0 -fi - -if [[ -z "${BB_BUILD_TYPE-}" ]]; then - BB_BUILD_TYPE=debug -fi - -if [[ -z "${BB_ERROR_ON_WARNINGS-}" ]]; then - BB_ERROR_ON_WARNINGS=$BB_CI -fi - -if [[ -z "${BB_ERROR_LIMIT-}" ]]; then - BB_ERROR_LIMIT=$((1 - BB_CI)) -fi - -BB_COMPILE_SHADERS=0 - -BUILD_DIR=cache -LARGE_ASSET_BASE_URL=https://github.com/birth-software/bloat-buster/releases/download/large-assets -mkdir -p $BUILD_DIR - -if [[ ! -f "$BUILD_DIR/large_assembly.s" ]]; then - cd $BUILD_DIR - wget $LARGE_ASSET_BASE_URL/large_assembly.s -o large_assembly.s - cd $MY_CWD -fi - -if [[ "${BB_COMPILE_SHADERS}" == "1" ]]; then - glslangValidator -V bootstrap/std/shaders/rect.vert -o $BUILD_DIR/rect.vert.spv --quiet - glslangValidator -V bootstrap/std/shaders/rect.frag -o $BUILD_DIR/rect.frag.spv --quiet -fi - -BUILD_OUT=$BUILD_DIR/build -C_COMPILER=clang -TIME_TRACE=1 -BB_TIMETRACE=0 -GCC_ARGS= -CLANG_ARGS= -TIME_TRACE_ARG= - -if [[ $C_COMPILER == "clang"* ]]; then - CLANG_ARGS=-ferror-limit=1 - if [[ "$TIME_TRACE" == "1" ]]; then - CLANG_ARGS="$CLANG_ARGS -ftime-trace" - BB_TIMETRACE=1 - else - CLANG_ARGS="$CLANG_ARGS -ftime-trace" - fi -elif [[ $C_COMPILER == "gcc"* ]]; then - GCC_ARGS=-fmax-errors=1 -fi - -$C_COMPILER build.c -g -o $BUILD_OUT -Ibootstrap -std=gnu2x $CLANG_ARGS $GCC_ARGS -DBB_TIMETRACE=$BB_TIMETRACE -DBB_CI=$BB_CI -DBB_BUILD_TYPE=\"$BB_BUILD_TYPE\" -DBB_ERROR_ON_WARNINGS=$BB_ERROR_ON_WARNINGS -DBB_ERROR_LIMIT=$BB_ERROR_LIMIT -$BUILD_OUT $@ -exit 0 +cd build +ninja --quiet +cd .. diff --git a/generate.sh b/generate.sh new file mode 100755 index 0000000..4f76e52 --- /dev/null +++ b/generate.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -eu +rm -rf build +mkdir build +cd build +cmake .. -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_LINKER_TYPE=MOLD +cd .. diff --git a/old_build.sh b/old_build.sh new file mode 100755 index 0000000..09b3c32 --- /dev/null +++ b/old_build.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -eu + +MY_CWD=$PWD + +if [[ -z "${BB_CI-}" ]]; then + BB_CI=0 +fi + +if [[ -z "${BB_BUILD_TYPE-}" ]]; then + BB_BUILD_TYPE=debug +fi + +if [[ -z "${BB_ERROR_ON_WARNINGS-}" ]]; then + BB_ERROR_ON_WARNINGS=$BB_CI +fi + +if [[ -z "${BB_ERROR_LIMIT-}" ]]; then + BB_ERROR_LIMIT=$((1 - BB_CI)) +fi + +BB_COMPILE_SHADERS=0 + +BUILD_DIR=cache +LARGE_ASSET_BASE_URL=https://github.com/birth-software/bloat-buster/releases/download/large-assets +mkdir -p $BUILD_DIR + +if [[ ! -f "$BUILD_DIR/large_assembly.s" ]]; then + cd $BUILD_DIR + wget $LARGE_ASSET_BASE_URL/large_assembly.s -o large_assembly.s + cd $MY_CWD +fi + +if [[ "${BB_COMPILE_SHADERS}" == "1" ]]; then + glslangValidator -V bootstrap/std/shaders/rect.vert -o $BUILD_DIR/rect.vert.spv --quiet + glslangValidator -V bootstrap/std/shaders/rect.frag -o $BUILD_DIR/rect.frag.spv --quiet +fi + +BUILD_OUT=$BUILD_DIR/build +C_COMPILER=clang +TIME_TRACE=1 +BB_TIMETRACE=0 +GCC_ARGS= +CLANG_ARGS= +TIME_TRACE_ARG= + +if [[ $C_COMPILER == "clang"* ]]; then + CLANG_ARGS=-ferror-limit=1 + if [[ "$TIME_TRACE" == "1" ]]; then + CLANG_ARGS="$CLANG_ARGS -ftime-trace" + BB_TIMETRACE=1 + else + CLANG_ARGS="$CLANG_ARGS -ftime-trace" + fi +elif [[ $C_COMPILER == "gcc"* ]]; then + GCC_ARGS=-fmax-errors=1 +fi + +$C_COMPILER build.c -g -o $BUILD_OUT -Ibootstrap -std=gnu2x $CLANG_ARGS $GCC_ARGS -DBB_TIMETRACE=$BB_TIMETRACE -DBB_CI=$BB_CI -DBB_BUILD_TYPE=\"$BB_BUILD_TYPE\" -DBB_ERROR_ON_WARNINGS=$BB_ERROR_ON_WARNINGS -DBB_ERROR_LIMIT=$BB_ERROR_LIMIT +$BUILD_OUT $@ +exit 0 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..225144f --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,539 @@ +#define fn static +#define internal static +#define unused(x) (void)(x) +#define breakpoint() __builtin_debugtrap() +#define string_literal_length(s) (sizeof(s) - 1) +#define string_literal(s) ((String){ .pointer = (u8*)(s), .length = string_literal_length(s), }) +#define str(x) string_literal(x) + +#define array_length(arr) sizeof(arr) / sizeof((arr)[0]) +#define array_to_slice(arr) { .pointer = (arr), .length = array_length(arr) } +#define array_to_bytes(arr) { .pointer = (u8*)(arr), .length = sizeof(arr) } +#define backing_type(E) __underlying_type(E) + +#define unreachable_raw() __builtin_unreachable() +#define trap_raw() __builtin_trap() +#if BB_DEBUG +#define unreachable() trap_raw() +#else +#define unreachable() unreachable_raw() +#endif + +#define expect(x, b) __builtin_expect(!!(x), b) +#define likely(x) expect(x, 1) +#define unlikely(x) expect(x, 0) + +#define assert(x) (unlikely(!(x)) ? unreachable() : unused(0)) + +#define enum_case(x) x +#define enum_name(x) str(#x) + +#define ENUM(Name, BackingType, Casing) \ +enum class Name : BackingType \ +{\ + Casing(enum_case) \ +};\ +\ +String Name ## _names[] = { \ + Casing(enum_name) \ +} + +#define ENUM_NBT(Name, Casing) ENUM(Name, int, Casing) + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; +typedef unsigned long u64; + +typedef signed char s8; +typedef signed short s16; +typedef signed int s32; +typedef signed long s64; + +typedef float f32; +typedef double f64; + +internal u64 align_forward(u64 value, u64 alignment) +{ + assert(alignment != 0); + auto mask = alignment - 1; + auto result = (value + mask) & ~mask; + return result; +} + +constexpr u64 kb = 1024; +constexpr u64 mb = 1024 * 1024; +constexpr u64 gb = 1024 * 1024 * 1024; + +extern "C" [[noreturn]] void exit(s32 status); +extern "C" void *memcpy (void* __restrict destination, const void *__restrict source, u64 byte_count); +extern "C" void *memcmp (const void* a, const void *b, u64 __n); + +template +struct Slice +{ + T* pointer; + u64 length; + + T* begin() { return pointer; } + T* end() { return pointer + length; } + + T& operator[](u64 index) + { + assert(index < length); + return pointer[index]; + } + + bool equal(Slice other) + { + bool result = length == other.length; + if (result) + { + if (pointer != other.pointer) + { + result = memcmp(pointer, other.pointer, length * sizeof(T)) == 0; + } + } + + return result; + } +}; + +using String = Slice; + +template +Int string_to_enum_int(Slice names, String name) +{ + Int result = ~(Int)0; + for (Int i = 0; i < names.length; i += 1) + { + String candidate = names[i]; + if (candidate.equal(name)) + { + result = i; + break; + } + } + + return result; +} + +#define string_to_enum(E, string) (E) string_to_enum_int(array_to_slice(E ## _names), string) +#define is_enum_valid(e) ((backing_type(typeof(e)))(e) != (backing_type(typeof(e)))-1) + +fn String c_string_to_slice(const char* cstr) +{ + const auto* end = cstr; + while (*end) + { + end += 1; + } + + return { (u8*)cstr, u64(end - cstr) }; +} + +struct ProtectionFlags +{ + u8 read:1; + u8 write:1; + u8 execute:1; +}; + +struct MapFlags +{ + u8 priv:1; + u8 anonymous:1; + u8 no_reserve:1; + u8 populate:1; +}; + +struct PROT +{ + u32 read:1; + u32 write:1; + u32 execute:1; + u32 sem:1; + u32 _:28; +}; +static_assert(sizeof(PROT) == sizeof(u32)); + +struct MAP +{ + enum class Type : u32 + { + shared = 0, + priv = 1, + shared_validate = 2, + }; + + Type type:4; + u32 fixed:1; + u32 anonymous:1; + u32 bit32:1; + u32 _0: 1; + u32 grows_down:1; + u32 _1: 2; + u32 deny_write:1; + u32 executable:1; + u32 locked:1; + u32 no_reserve:1; + u32 populate:1; + u32 non_block:1; + u32 stack:1; + u32 huge_tlb:1; + u32 sync:1; + u32 fixed_no_replace:1; + u32 _2:5; + u32 uninitialized:1; + u32 _3:5; +}; +static_assert(sizeof(MAP) == sizeof(u32)); + +extern "C" s32* __errno_location(); +extern "C" void* mmap(void*, u64, PROT, MAP, s32, s64); +extern "C" s32 mprotect(void*, u64, PROT); +extern "C" s64 ptrace(s32, s32, u64, u64); +extern "C" s64 write(s32, u8*, u64); +extern "C" s64 read(s32, u8*, u64); +extern "C" s32 mkdir(const char*, u64); + +#define ErrorF(F) \ + F(success),\ + F(perm), + +ENUM(Error, u32, ErrorF); + +fn Error errno() +{ + return (Error)*__errno_location(); +} + +fn void* os_reserve(void* base, u64 size, ProtectionFlags protection, MapFlags map) +{ + auto protection_flags = PROT + { + .read = protection.read, + .write = protection.write, + .execute = protection.execute, + }; + + auto map_flags = MAP + { + .type = map.priv ? MAP::Type::priv : MAP::Type::shared, + .anonymous = map.anonymous, + .no_reserve = map.no_reserve, + .populate = map.populate, + }; + + auto* address = mmap(base, size, protection_flags, map_flags, -1, 0); + assert((u64)address != ~(u64)0); + + return address; +} + +fn void os_commit(void* address, u64 size, ProtectionFlags protection) +{ + auto protection_flags = PROT + { + .read = protection.read, + .write = protection.write, + .execute = protection.execute, + }; + auto result = mprotect(address, size, protection_flags); + assert(!result); +} + +fn u64 os_read_partially(s32 fd, u8* buffer, u64 byte_count) +{ + auto result = read(fd, buffer, byte_count); + assert(result > 0); + return (u64)result; +} + +fn void os_read(s32 fd, String buffer, u64 byte_count) +{ + assert(byte_count <= buffer.length); + u64 it_byte_count = 0; + while (it_byte_count < byte_count) + { + auto read_byte_count = os_read_partially(fd, buffer.pointer + it_byte_count, byte_count - it_byte_count); + it_byte_count += read_byte_count; + } + assert(it_byte_count == byte_count); +} + +fn u64 os_write_partially(s32 fd, u8* buffer, u64 byte_count) +{ + auto result = write(fd, buffer, byte_count); + assert(result > 0); + return (u64)result; +} + +fn void os_write(s32 fd, String content) +{ + u64 it_byte_count = 0; + while (it_byte_count < content.length) + { + auto written_byte_count = os_write_partially(fd, content.pointer + it_byte_count, content.length - it_byte_count); + it_byte_count += written_byte_count; + } + assert(it_byte_count == content.length); +} + +fn bool os_is_debugger_present() +{ + bool result = false; + if (ptrace(0, 0, 0, 0) == -1) + { + auto errno_error = errno(); + result = errno_error == Error::perm; + } + + return result; +} + +fn void make_directory(const char* path) +{ + auto result = mkdir(path, 0755); + unused(result); +} + +fn void print(String string) +{ + os_write(1, string); +} + +struct ArenaInitialization +{ + u64 reserved_size; + u64 granularity; + u64 initial_size; +}; + +struct Arena +{ + u64 reserved_size; + u64 position; + u64 os_position; + u64 granularity; + u8 reserved[32]; +}; + +constexpr u64 arena_minimum_position = sizeof(Arena); + +fn Arena& arena_initialize(ArenaInitialization i) +{ + ProtectionFlags protection_flags = { + .read = 1, + .write = 1, + }; + MapFlags map_flags = { + .priv = 1, + .anonymous = 1, + .no_reserve = 1, + }; + + auto* arena = (Arena*)os_reserve(0, i.reserved_size, protection_flags, map_flags); + os_commit(arena, i.initial_size, { .read = 1, .write = 1 }); + + *arena = { + .reserved_size = i.reserved_size, + .position = arena_minimum_position, + .os_position = i.initial_size, + .granularity = i.granularity, + }; + + return *arena; +} + +fn inline Arena& arena_initialize_default(u64 initial_size) +{ + ArenaInitialization i = { + .reserved_size = 4 * gb, + .granularity = 4 * kb, + .initial_size = initial_size, + }; + return arena_initialize(i); +} + +fn void* arena_allocate_bytes(Arena& arena, u64 size, u64 alignment) +{ + auto aligned_offset = align_forward(arena.position, alignment); + auto aligned_size_after = aligned_offset + size; + + if (aligned_size_after > arena.os_position) + { + unreachable(); + } + + auto* result = (u8*)&arena + aligned_offset; + arena.position = aligned_size_after; + assert(arena.position <= arena.os_position); + + return result; +} + +template +fn Slice arena_allocate(Arena& arena, u64 count) +{ + return { (T*)arena_allocate_bytes(arena, sizeof(T) * count, alignof(T)), count }; +} + +fn String arena_join_string(Arena& arena, Slice pieces) +{ + u64 size = 0; + for (auto piece : pieces) + { + size += piece.length; + } + + auto* pointer = (u8*)arena_allocate_bytes(arena, size + 1, 1); + u64 i = 0; + for (auto piece : pieces) + { + memcpy(pointer + i, piece.pointer, piece.length); + i += piece.length; + } + + assert(i == size); + pointer[i] = 0; + + return { pointer, size }; +} + +fn String arena_duplicate_string(Arena& arena, String string) +{ + auto memory = (u8*)arena_allocate_bytes(arena, string.length + 1, 1); + memcpy(memory, string.pointer, string.length); + memory[string.length] = 0; + return { memory, string.length}; +} + +fn void arena_restore(Arena& arena, u64 position) +{ + assert(position <= arena.position); + arena.position = position; +} + +fn void arena_reset(Arena& arena) +{ + arena.position = arena_minimum_position; +} + +struct GlobalState +{ + Arena* arena; +}; + +fn void entry_point(Slice arguments, Slice environment); + +int main(int argc, const char* argv[], const char* envp[]) +{ + auto* envp_end = envp; + while (*envp_end) + { + envp_end += 1; + } + + entry_point({argv, (u64)argc}, {envp, (u64)(envp_end - envp)}); + return 0; +} + +[[noreturn]] fn void fail() +{ + if (os_is_debugger_present()) + { + trap_raw(); + } + exit(1); +} + +[[noreturn]] fn void fail_with_message(String string) +{ + print(string); + fail(); +} + +#define CommandF(F) \ + F(compile),\ + F(test), + +ENUM(Command, u8, CommandF); + +#define BuildModeF(F) \ + F(debug_none),\ + F(debug),\ + F(soft_optimize),\ + F(optimize_for_speed),\ + F(optimize_for_size),\ + F(aggressively_optimize_for_speed),\ + F(aggressively_optimize_for_size), + +ENUM(BuildMode, u8, BuildModeF); + +fn void entry_point(Slice arguments, Slice environment) +{ + auto arena = arena_initialize_default(8 * mb); + + if (arguments.length < 2) + { + fail_with_message(str("error: Not enough arguments\n")); + } + + String command_string = c_string_to_slice(arguments[1]); + auto command = string_to_enum(Command, command_string); + + if (!is_enum_valid(command)) + { + fail_with_message(str("Invalid command!\n")); + } + + switch (command) + { + case Command::compile: + { + if (arguments.length < 3) + { + fail_with_message(str("Not enough arguments for command 'compile'\n")); + } + + auto build_mode = BuildMode::debug_none; + auto has_debug_info = true; + + if (arguments.length >= 4) + { + auto build_mode_string = c_string_to_slice(arguments[3]); + auto new_build_mode = string_to_enum(BuildMode, build_mode_string); + if (!is_enum_valid(new_build_mode)) + { + fail_with_message(str("error: Wrong build mode\n")); + } + build_mode = new_build_mode; + } + + if (arguments.length >= 5) + { + auto has_debug_info_string = c_string_to_slice(arguments[3]); + if (has_debug_info_string.equal(str("true"))) + { + has_debug_info = true; + } + else if (has_debug_info_string.equal(str("false"))) + { + has_debug_info = false; + } + else + { + fail_with_message(str("Wrong value for has_debug_info\n")); + } + } + + auto relative_file_path = c_string_to_slice(arguments[2]); + trap_raw(); + } break; + case Command::test: + { + trap_raw(); + } break; + } +}