#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; } }