mode_t = typealias u64; int = typealias s32; usize = typealias u64; ssize = typealias s64; File = typealias s32; uid_t = typealias u32; gid_t = typealias u32; off_t = typealias s64; ino_t = typealias u64; dev_t = typealias u64; timespec = struct { seconds: ssize, nanoseconds: ssize, }; Stat = struct { dev: dev_t, ino: ino_t, nlink: usize, mode: u32, uid: uid_t, gid: gid_t, _: u32, rdev: dev_t, size: off_t, blksize: ssize, blocks: s64, atim: timespec, mtime: timespec, ctim: timespec, _: [3]ssize, } OAccessMode = enum u2 { read_only = 0, write_only = 1, read_write = 2, } O = bits u32 { access_mode: OAccessMode, _: u4, creat: u1, excl: u1, noctty: u1, trunc: u1, append: u1, nonblock: u1, dsync: u1, async: u1, direct: u1, _: u1, directory: u1, nofollow: u1, noatime: u1, cloexec: u1, sync: u1, path: u1, tmpfile: u1, _: u9, } [extern] memcmp = fn [cc(c)] (a: &u8, b: &u8, byte_count: usize) int; [extern] memcpy = fn [cc(c)] (destination: &u8, source: &u8, byte_count: u64) &u8; [extern] exit = fn [cc(c)] (exit_code: int) noreturn; [extern] realpath = fn [cc(c)] (source_path: &u8, resolved_path: &u8) &u8; [extern] mkdir = fn [cc(c)] (path: &u8, mode: mode_t) int; [extern] open = fn [cc(c)] (path: &u8, o: O, ...) int; [extern] close = fn [cc(c)] (fd: File) int; [extern] fstat = fn [cc(c)] (fd: File, s: &Stat) int; [extern] write = fn [cc(c)] (fd: File, pointer: &u8, byte_count: u64) ssize; [extern] read = fn [cc(c)] (fd: File, pointer: &u8, byte_count: u64) ssize; assert = macro (ok: u1) void { if (!ok) { unreachable; } } align_forward = fn (value: u64, alignment: u64) u64 { assert(alignment != 0); >mask = alignment - 1; >result = (value + mask) & ~mask; return result; } string_no_match = #integer_max(u64); c_string_length = fn (c_string: &u8) u64 { >it = c_string; while (it.&) { it = it + 1; } return #int_from_pointer(it) - #int_from_pointer(c_string); } c_string_to_slice = fn (c_string: &u8) []u8 { >length = c_string_length(c_string); return c_string[0..length]; } string_equal = fn(a: []u8, b: []u8) u1 { >result: #ReturnType = 0; if (a.length == b.length) { result = memcmp(a.pointer, b.pointer, a.length) == 0; } return result; } string_last_character = fn(string: []u8, character: u8) u64 { >i = string.length; while (i > 0) { i -= 1; if (string[i] == character) { return i; } } return string_no_match; } OS_Linux_PROT = bits u32 { read: u1, write: u1, execute: u1, sem: u1, _: u28, } OS_Linux_MAP_Type = enum u4 { shared = 0x1, private = 0x2, shared_validate = 0x3, } OS_Linux_MAP = bits u32 { type: OS_Linux_MAP_Type, fixed: u1, anonymous: u1, bit_32: u1, _: u1, grows_down: u1, _: u2, deny_write: u1, executable: u1, locked: u1, no_reserve: u1, populate: u1, non_block: u1, stack: u1, huge_tlb: u1, sync: u1, fixed_noreplace: u1, _: u5, uninitialized: u1, _: u5, } [extern] mmap = fn [cc(c)] (address: u64, size: u64, protection: OS_Linux_PROT, map: OS_Linux_MAP, file_descriptor: s32, offset: s64) &u8; [extern] mprotect = fn [cc(c)] (address: u64, size: u64, protection: OS_Linux_PROT) s32; OS_ProtectionFlags = bits { read: u1, write: u1, execute: u1, } OS_MapFlags = bits { private: u1, anonymous: u1, no_reserve: u1, populate: u1, } os_linux_protection_flags = fn(map_flags: OS_ProtectionFlags) OS_Linux_PROT { return { .read = map_flags.read, .write = map_flags.write, .execute = map_flags.execute, zero, }; } os_linux_map_flags = fn(map_flags: OS_MapFlags) OS_Linux_MAP { return { .type = #select(map_flags.private, .private, .shared), .anonymous = map_flags.anonymous, .no_reserve = map_flags.no_reserve, .populate = map_flags.populate, zero, }; } os_reserve = fn (base: u64, size: u64, protection: OS_ProtectionFlags, map: OS_MapFlags) &u8 { >protection_flags = os_linux_protection_flags(protection); >map_flags = os_linux_map_flags(map); >address = mmap(base, size, protection_flags, map_flags, -1, 0); if (#int_from_pointer(address) == #integer_max(u64)) { unreachable; } return address; } os_commit = fn (address: u64, size: u64, protection: OS_ProtectionFlags) void { >protection_flags = os_linux_protection_flags(protection); >result = mprotect(address, size, protection_flags); if (result != 0) { unreachable; } } os_make_directory = fn (path: &u8) void { >result = mkdir(path, 0o755); } OpenFlags = bits { truncate: u1, execute: u1, write: u1, read: u1, create: u1, directory: u1, } OpenPermissions = bits { read: u1, write: u1, execute: u1, } os_file_open = fn (path: &u8, flags: OpenFlags, permissions: OpenPermissions) File { >access_mode: OAccessMode = undefined; if (flags.read and flags.write) { access_mode = .read_write; } else if (flags.read) { access_mode = .read_only; } else if (flags.write) { access_mode = .write_only; } else { unreachable; } >o: O = { .access_mode = access_mode, .trunc = flags.truncate, .creat = flags.create, .directory = flags.directory, }; >mode: mode_t = #select(permissions.execute, 0o755, 0o644); >fd = open(path, o, mode); return fd; } os_file_close = fn (fd: File) void { >result = close(fd); assert(result == 0); } os_file_is_valid = fn (fd: File) u1 { return fd >= 0; } os_file_get_size = fn (fd: File) u64 { >stat: Stat = undefined; >result = fstat(fd, &stat); assert(result == 0); return #extend(stat.size); } os_file_read_partially = fn (fd: File, pointer: &u8, length: u64) u64 { >result = read(fd, pointer, length); assert(result > 0); return #extend(result); } os_file_read = fn (fd: File, buffer: []u8, byte_count: u64) void { assert(byte_count <= buffer.length); >total_read_byte_count: u64 = 0; while (total_read_byte_count < byte_count) { >read_byte_count = os_file_read_partially(fd, buffer.pointer + total_read_byte_count, byte_count - total_read_byte_count); total_read_byte_count += read_byte_count; } } os_path_absolute_stack = fn (buffer: []u8, relative_file_path: &u8) []u8 { >syscall_result = realpath(relative_file_path, buffer.pointer); >result: []u8 = zero; if (syscall_result) { result = c_string_to_slice(syscall_result); assert(result.length < buffer.length); } return result; } Arena = struct { reserved_size: u64, position: u64, os_position: u64, granularity: u64, reserved: [32]u8, } minimum_position: u64 = #byte_size(Arena); ArenaInitialization = struct { reserved_size: u64, granularity: u64, initial_size: u64, } arena_initialize = fn (initialization: ArenaInitialization) &Arena { >protection_flags: OS_ProtectionFlags = { .read = 1, .write = 1, zero, }; >map_flags: OS_MapFlags = { .private = 1, .anonymous = 1, .no_reserve = 1, .populate = 0, }; >arena: &Arena = #pointer_cast(os_reserve(0, initialization.reserved_size, protection_flags, map_flags)); os_commit(#int_from_pointer(arena), initialization.initial_size, { .read = 1, .write = 1, zero, }); arena.& = { .reserved_size = initialization.reserved_size, .position = minimum_position, .os_position = initialization.initial_size, .granularity = initialization.granularity, zero, }; return arena; } arena_initialize_default = fn (initial_size: u64) &Arena { return arena_initialize({ .reserved_size = 4 * 1024 * 1024 * 1024, .granularity = 4 * 1024, .initial_size = initial_size, }); } arena_allocate_bytes = fn (arena: &Arena, size: u64, alignment: u64) &u8 { >aligned_offset = align_forward(arena.position, alignment); >aligned_size_after = aligned_offset + size; >arena_byte_pointer: &u8 = #pointer_cast(arena); if (aligned_size_after > arena.os_position) { >target_committed_size = align_forward(aligned_size_after, arena.granularity); >size_to_commit = target_committed_size - arena.os_position; >commit_pointer = arena_byte_pointer + arena.os_position; os_commit(#int_from_pointer(commit_pointer), size_to_commit, { .read = 1, .write = 1, zero, }); arena.os_position = target_committed_size; } >result = arena_byte_pointer + aligned_offset; arena.position = aligned_size_after; assert(arena.position <= arena.os_position); return result; } arena_allocate = macro [T] (arena: &Arena, count: u64) &T { return #pointer_cast(arena_allocate_bytes(arena, #byte_size(T) * count, #alignof(T))); } arena_duplicate_string = fn (arena: &Arena, string: []u8) []u8 { >result = arena_allocate_bytes(arena, string.length + 1, 1); memcpy(result, string.pointer, string.length); return result[..string.length]; } arena_join_string = fn (arena: &Arena, pieces: [][]u8) []u8 { >size: u64 = 0; for (p: pieces) { size += p.length; } >pointer = arena_allocate_bytes(arena, size + 1, 1); >i: u64 = 0; for (p: pieces) { memcpy(pointer + i, p.pointer, p.length); i += p.length; } assert(i == size); pointer[i] = 0; return pointer[..size]; } file_read = fn (arena: &Arena, path: []u8) []u8 { >fd = os_file_open(path.pointer, { .read = 1 }, { .read = 1 }); >result: []u8 = zero; if (os_file_is_valid(fd)) { >file_size = os_file_get_size(fd); >file_buffer = arena_allocate_bytes(arena, file_size, 1); result = file_buffer[..file_size]; os_file_read(fd, result, file_size); os_file_close(fd); } return result; } path_absolute = fn (arena: &Arena, relative_file_path: &u8) []u8 { >buffer: [4096]u8 = undefined; >stack_slice = os_path_absolute_stack(buffer[..], relative_file_path); >result = arena_duplicate_string(arena, stack_slice); return result; } GlobalState = struct { arena: &Arena, } global_state: GlobalState = undefined; global_state_initialize = fn () void { global_state = { .arena = arena_initialize_default(2 * 1024 * 1024), }; } fail = fn () noreturn { exit(1); } CompilerCommand = enum { compile, test, } BuildMode = enum { debug_none, debug_fast, debug_size, soft_optimize, optimize_for_speed, optimize_for_size, aggressively_optimize_for_speed, aggressively_optimize_for_size, } CompileFile = struct { relative_file_path: []u8, build_mode: BuildMode, has_debug_info: u1, silent: u1, } base_cache_dir = "bb-cache"; CPUArchitecture = enum { x86_64, } OperatingSystem = enum { linux, } Target = struct { cpu: CPUArchitecture, os: OperatingSystem, } target_get_native = fn () Target { return { .cpu = .x86_64, .os = .linux }; } CompileOptions = struct { content: []u8, path: []u8, executable: []u8, name: []u8, objects: [][]u8, target: Target, build_mode: BuildMode, has_debug_info: u1, silent: u1, } TypeId = enum { void, noreturn, integer, } TypeInteger = struct { bit_count: u32, signed: u1, } TypeContent = union { integer: TypeInteger, } Type = struct { content: TypeContent, id: TypeId, name: []u8, } ValueId = enum { infer_or_ignore, } Value = struct { type: &Type, id: ValueId, } i128_offset: u64 = 64 * 2; void_offset: u64 = i128_offset + 2; Module = struct { arena: &Arena, base_type_allocation: &Type, void_value: &Value, // Parser data content: []u8, offset: u64, line_offset: u64, line_character_offset: u64, } module_integer_type = fn (module: &Module, integer: TypeInteger) &Type { assert(integer.bit_count != 0); assert(integer.bit_count <= 64); >index = #select(integer.bit_count == 128, i128_offset + #extend(integer.signed), #extend(integer.bit_count) - 1 + 64 * #extend(integer.signed)); return module.base_type_allocation + index; } module_void_type = fn (module: &Module) &Type { return module.base_type_allocation + void_offset; } module_noreturn_type = fn (module: &Module) &Type { return module_void_type(module) + 1; } is_space = fn (ch: u8) u1 { return ch == ' ' or ch == '\n' or ch == '\t' or ch == '\r'; } is_decimal = fn (ch: u8) u1 { return ch >= '0' and ch <= '9'; } is_octal = fn (ch: u8) u1 { return ch >= '0' and ch <= '7'; } is_binary = fn (ch: u8) u1 { return ch == '0' or ch == '1'; } is_hex_alpha_lower = fn (ch: u8) u1 { return ch >= 'a' and ch <= 'f'; } is_hex_alpha_upper = fn (ch: u8) u1 { return ch >= 'A' and ch <= 'F'; } is_hex_alpha = fn (ch: u8) u1 { return is_hex_alpha_lower(ch) or is_hex_alpha_upper(ch); } is_hex = fn (ch: u8) u1 { return is_decimal(ch) or is_hex_alpha(ch); } is_identifier_start = fn (ch: u8) u1 { return (ch >= 'a' and ch >= 'z') or (ch >= 'A' and ch <= 'Z') or ch == '_'; } is_identifier = fn (ch: u8) u1 { return is_identifier_start(ch) or is_decimal(ch); } report_error = fn () noreturn { #trap(); } get_line = fn (module: &Module) u32 { >line = module.line_offset + 1; assert(line <= #integer_max(u32)); return #truncate(line); } get_column = fn (module: &Module) u32 { >column = module.offset - module.line_character_offset + 1; assert(column <= #integer_max(u32)); return #truncate(column); } skip_space = fn (module: &Module) void { while (1) { >iteration_offset = module.offset; while (module.offset < module.content.length and? is_space(module.content[module.offset])) { module.line_offset += #extend(module.content[module.offset] == '\n'); module.line_character_offset = #select(module.content[module.offset] == '\n', module.offset, module.line_character_offset); module.offset += 1; } if (module.offset + 1 < module.content.length) { >i = module.offset; >is_comment = module.content[i] == '/' and module.content[i + 1] == '/'; if (is_comment) { while (module.offset < module.content.length and? module.content[module.offset] != '\n') { module.offset += 1; } if (module.offset < module.content.length) { module.line_offset += 1; module.line_character_offset = module.offset; module.offset += 1; } } } if (module.offset - iteration_offset == 0) { break; } } } consume_character_if_match = fn (module: &Module, expected_character: u8) u1 { >is_ch: u1 = 0; if (module.offset < module.content.length) { >ch = module.content[module.offset]; is_ch = expected_character == ch; module.offset += #extend(is_ch); } return is_ch; } expect_character = fn (module: &Module, expected_character: u8) void { if (!consume_character_if_match(module, expected_character)) { report_error(); } } parse_identifier = fn (module: &Module) []u8 { >start = module.offset; if (is_identifier_start(module.content[start])) { module.offset += 1; while (module.offset < module.content.length) { if (is_identifier(module.content[module.offset])) { module.offset += 1; } else { break; } } } if (module.offset - start == 0) { report_error(); } >result = module.content[start..module.offset]; return result; } parse = fn (module: &Module) void { } emit = fn (module: &Module) void { } compile = fn (arena: &Arena, options: CompileOptions) void { >signs: [2]u1 = [0, 1]; >base_type_allocation = arena_allocate[Type](arena, i128_offset // Basic integer types + 2 // u128, s128 + 2 // void, noreturn ); >type_it = base_type_allocation; for (sign: signs) { for (b: 0..64) { >bit_count = b + 1; >first_digit: u8 = #truncate(#select(bit_count < 10, bit_count % 10 + '0', bit_count / 10 + '0')); >second_digit: u8 = #truncate(#select(bit_count > 9, bit_count % 10 + '0', 0)); >name_buffer: [3]u8 = [ #select(sign, 's', 'u'), first_digit, second_digit ]; >name_length: u64 = 2 + #extend(bit_count > 9); >name = arena_duplicate_string(arena, name_buffer[..name_length]); type_it.& = { .content = { .integer = { .bit_count = #truncate(bit_count), .signed = sign, }, }, .id = .integer, .name = name, }; type_it += 1; } } for (sign: signs) { >name = #select(sign, "s128", "u128"); type_it.& = { .content = { .integer = { .bit_count = 128, .signed = sign, }, }, .id = .integer, .name = name, }; type_it += 1; } >void_type = type_it; type_it += 1; >noreturn_type = type_it; type_it += 1; void_type.& = { .id = .void, .name = "void", }; noreturn_type.& = { .id = .noreturn, .name = "noreturn", }; >void_value = arena_allocate[Value](arena, 1); void_value.& = { .type = void_type, .id = .infer_or_ignore, }; >module: Module = { .arena = arena, .base_type_allocation = base_type_allocation, .void_value = void_value, .content = options.content, .offset = 0, .line_offset = 0, .line_character_offset = 0, }; parse(&module); emit(&module); } compile_file = fn (arena: &Arena, compile_options: CompileFile) void { >relative_file_path = compile_options.relative_file_path; if (relative_file_path.length < 5) { fail(); } >extension_start = string_last_character(relative_file_path, '.'); if (extension_start == string_no_match) { fail(); } if (!string_equal(relative_file_path[extension_start..], ".bbb")) { fail(); } >separator_index = string_last_character(relative_file_path, '/'); if (separator_index == string_no_match) { separator_index = 0; } >base_start = separator_index + #extend(separator_index != 0 or relative_file_path[separator_index] == '/'); >base_name = relative_file_path[base_start..extension_start]; >is_compiler = string_equal(relative_file_path, "src/compiler.bbb"); >outputh_path_dir = arena_join_string(arena, [ base_cache_dir, #select(is_compiler, "/compiler/", "/"), #enum_name(compile_options.build_mode), "_", #select(compile_options.has_debug_info, "di", "nodi"), ][..]); os_make_directory(base_cache_dir.pointer); if (is_compiler) { >compiler_dir = arena_join_string(arena, [ base_cache_dir, "/compiler" ][..]); os_make_directory(compiler_dir.pointer); } os_make_directory(outputh_path_dir.pointer); >outputh_path_base = arena_join_string(arena, [ outputh_path_dir, "/", base_name ][..]); >output_object_path = arena_join_string(arena, [ outputh_path_base, ".o" ][..]); >output_executable_path = outputh_path_base; >file_content = file_read(arena, relative_file_path); >file_path = path_absolute(arena, relative_file_path.pointer); >c_abi_object_path = ""; // TODO >objects: [][]u8 = undefined; if (string_equal(base_name, "c_abi")) { objects = [ output_object_path, c_abi_object_path ][..]; } else { objects = [ output_object_path ][..]; } >options: CompileOptions = { .executable = output_executable_path, .objects = objects, .name = base_name, .build_mode = compile_options.build_mode, .content = file_content, .path = file_path, .has_debug_info = compile_options.has_debug_info, .target = target_get_native(), .silent = compile_options.silent, }; compile(arena, options); } [export] main = fn [cc(c)] (argument_count: u32, argv: &&u8) s32 { global_state_initialize(); >arena = global_state.arena; if (argument_count < 2) { return 1; } >command_string = c_string_to_slice(argv[1]); >command_string_to_enum = #string_to_enum(CompilerCommand, command_string); if (!command_string_to_enum.is_valid) { return 1; } >command = command_string_to_enum.enum_value; switch (command) { .compile => { if (argument_count < 3) { return 1; } >build_mode: BuildMode = .debug_none; >has_debug_info: u1 = 1; if (argument_count >= 4) { >build_mode_string_to_enum = #string_to_enum(BuildMode, c_string_to_slice(argv[3])); if (!build_mode_string_to_enum.is_valid) { return 1; } build_mode = build_mode_string_to_enum.enum_value; } if (argument_count >= 5) { >has_debug_info_string = c_string_to_slice(argv[4]); if (string_equal(has_debug_info_string, "true")) { has_debug_info = 1; } else if (string_equal(has_debug_info_string, "false")) { has_debug_info = 0; } else { return 1; } } >relative_file_path_pointer = argv[2]; if (!relative_file_path_pointer) { return 1; } >relative_file_path = c_string_to_slice(relative_file_path_pointer); compile_file(arena, { .relative_file_path = relative_file_path, .build_mode = build_mode, .has_debug_info = has_debug_info, .silent = 0, }); }, .test => { // TODO #trap(); }, } return 0; }