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; } next_power_of_two = fn (n: u64) u64 { n -= 1; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; n |= n >> 32; n += 1; return n; } 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, zero, }; >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, #align_of(T))); } arena_allocate_slice = macro [T] (arena: &Arena, count: u64) []T { >pointer: &T = #pointer_cast(arena_allocate_bytes(arena, #byte_size(T) * count, #align_of(T))); return pointer[..count]; } 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, zero }, { .read = 1, zero }); >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); } LLVMContext = opaque; LLVMModule = opaque; LLVMBuilder = opaque; LLVMValue = opaque; LLVMType = opaque; LLVMBasicBlock = opaque; LLVMIntrinsicId = typealias u32; LLVMAttributeId = typealias u32; LLVMAttribute = opaque; LLVMMetadata = opaque; LLVMDIBuilder = opaque; LLVMTarget = opaque; LLVMTargetDataLayout = opaque; LLVMTargetMachine = opaque; LLVMTargetMachineOptions = opaque; LLVMIntrinsicIndex = enum u32 { "llvm.smax", "llvm.smin", "llvm.trap", "llvm.umax", "llvm.umin", "llvm.va_start", "llvm.va_end", "llvm.va_copy", } LLVMAttributeIndex = enum u32 { align, alwaysinline, byval, dead_on_unwind, inlinehint, inreg, naked, noalias, noinline, noreturn, nounwind, signext, sret, writable, zeroext, } LLVMLinkage = enum u32 { external, // Externally visible function available_externally, link_once_any, // Keep one copy of function when linking (inline)*/ link_once_odr, // Same, but only replaced by something link_once_odr_auto_hide, // Obsolete weak_any, // Keep one copy of function when linking (weak) */ weak_odr, // Same, but only replaced by something equivalent appending, // Special purpose, only applies to global arrays */ internal, // Rename collisions when linking (static functions) */ private, // Like Internal, but omit from symbol table */ dll_import, // Obsolete */ dll_export, // Obsolete */ external_weak,// ExternalWeak linkage description */ ghost, // Obsolete */ common, // Tentative definitions */ linker_private, // Like Private, but linker removes. */ linker_private_weak // Like LinkerPrivate, but is weak. */ } LLVMCallingConvention = enum { c = 0, fast = 8, cold = 9, ghc = 10, hi_pe = 11, any_reg = 13, preserve_most = 14, preserve_all = 15, swift = 16, cxx_fast_tls = 17, x86_stdcall = 64, x86_fastcall = 65, arm_apcs = 66, arm_aapcs = 67, arm_aapcs_vfp = 68, msp430_intr = 69, x86_this_call = 70, ptx_kernel = 71, ptx_device = 72, spir_function = 75, spir_kernel = 76, intel_oclbi = 77, x86_64_system_v = 78, win64 = 79, x86_vector_call = 80, hhvm = 81, hhvm_c = 82, x86_intr = 83, avr_intr = 84, avr_signal = 85, avr_builtin = 86, amdgpu_vs = 87, amdgpu_gs = 88, amdgpu_ps = 89, amdgpu_cs = 90, amdgpu_kernel = 91, x86_regcall = 92, amdgpu_hs = 93, msp430_builtin = 94, amdgpu_ls = 95, amdgpu_es = 96 } CompilerCommand = enum { compile, test, } BuildMode = enum { debug_none, debug, soft_optimize, optimize_for_speed, optimize_for_size, aggressively_optimize_for_speed, aggressively_optimize_for_size, } build_mode_is_optimized = fn (build_mode: BuildMode) u1 { switch (build_mode) { .debug_none, .debug => { return 0; }, else => { return 1; }, } } 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 }; } target_compare = fn (a: Target, b: Target) u1 { >is_same_cpu = a.cpu == b.cpu; >is_same_os = a.cpu == b.cpu; return is_same_cpu and is_same_os; } CompileOptions = struct { content: []u8, path: []u8, executable: []u8, name: []u8, objects: [][]u8, libraries: [][]u8, target: Target, build_mode: BuildMode, has_debug_info: u1, silent: u1, } CallingConvention = enum { c, } InlineBehavior = enum { default = 0, always_inline = 1, no_inline = 2, inline_hint = 3, } FunctionAttributes = struct { inline_behavior: InlineBehavior, naked: u1, } Type = struct; Value = struct; Local = struct; Block = struct; Module = struct; ScopeKind = enum { global, function, local, for_each, macro_declaration, macro_instantiation, } TypeList = struct { first: &Type, last: &Type, } Scope = struct { types: TypeList, parent: &Scope, llvm: &LLVMMetadata, line: u32, column: u32, kind: ScopeKind, } Variable = struct { storage: &Value, type: &Type, scope: &Scope, name: []u8, line: u32, column: u32, } Linkage = enum { internal, external, } Global = struct { variable: Variable, initial_value: &Value, next: &Global, linkage: Linkage, emitted: u1, } Local = struct { variable: Variable, initial_value: &Value, next: &Local, } Argument = struct { variable: Variable, index: u32, } TypeId = enum { void, noreturn, forward_declaration, integer, function, pointer, array, enum, struct, bits, alias, union, unresolved, vector, floating_point, enum_array, opaque, } TypeInteger = struct { bit_count: u64, signed: u1, } TypePointer = struct { element_type: &Type, next: &Type, } AbiRegisterCountSystemV = struct { gpr: u32, sse: u32, }; AbiRegisterCount = union { system_v: AbiRegisterCountSystemV, }; AbiInformationPadding = union { type: &Type, unpadded_coerce_and_expand_type: &Type, } AbiInformationDirectAttributes = struct { offset: u32, alignment: u32, } AbiInformationIndirectAttributes = struct { alignment: u32, address_space: u32, } AbiInformationAttributes = union { direct: AbiInformationDirectAttributes, indirect: AbiInformationIndirectAttributes, alloca_field_index: u32, } AbiKind = enum { ignore, direct, extend, indirect, indirect_aliased, expand, coerce_and_expand, in_alloca, } AbiFlags = struct { kind: AbiKind, padding_in_reg: u1, in_alloca_sret: u1, in_alloca_indirect: u1, indirect_by_value: u1, indirect_realign: u1, sret_after_this: u1, in_reg: u1, can_be_flattened: u1, sign_extension: u1, } AbiInformation = struct { semantic_type: &Type, coerce_to_type: &Type, padding: AbiInformationPadding, padding_argument_index: u16, attributes: AbiInformationAttributes, flags: AbiFlags, abi_start: u16, abi_count: u16, } abi_set_sret_after_this = fn (abi_information: &AbiInformation, value: u1) void { assert(abi_information.flags.kind == .indirect); abi_information.flags.sret_after_this = value; } abi_set_indirect_realign = fn (abi_information: &AbiInformation, value: u1) void { assert(abi_information.flags.kind == .indirect); abi_information.flags.indirect_realign = value; } abi_set_indirect_by_value = fn (abi_information: &AbiInformation, value: u1) void { assert(abi_information.flags.kind == .indirect); abi_information.flags.indirect_by_value = value; } abi_set_indirect_align = fn (abi_information: &AbiInformation, value: u32) void { assert(abi_information.flags.kind == .indirect); abi_information.attributes.indirect.alignment = value; } abi_can_have_coerce_to_type = fn (abi_information: &AbiInformation) u1 { switch (abi_information.flags.kind) { .direct, .extend, .coerce_and_expand => { return 1; }, else => { return 0; }, } } abi_set_coerce_to_type = fn (abi_information: &AbiInformation, type: &Type) void { assert(abi_can_have_coerce_to_type(abi_information)); abi_information.coerce_to_type = type; } abi_get_coerce_to_type = fn (abi_information: &AbiInformation) &Type { assert(abi_can_have_coerce_to_type(abi_information)); return abi_information.coerce_to_type; } abi_can_have_padding_type = fn (abi_information: &AbiInformation) u1 { switch (abi_information.flags.kind) { .direct, .extend, .indirect, .indirect_aliased, .expand => { return 1; }, else => { return 0; } } } abi_set_padding_type = fn (abi_information: &AbiInformation, type: &Type) void { assert(abi_can_have_padding_type(abi_information)); abi_information.padding = { .type = type, }; } abi_get_padding_type = fn (abi_information: &AbiInformation) &Type { return #select(abi_can_have_padding_type(abi_information), abi_information.padding.type, zero); } abi_set_direct_offset = fn (abi_information: &AbiInformation, offset: u32) void { assert(abi_information.flags.kind == .direct or abi_information.flags.kind == .extend); abi_information.attributes.direct.offset = offset; } abi_set_direct_alignment = fn (abi_information: &AbiInformation, alignment: u32) void { assert(abi_information.flags.kind == .direct or abi_information.flags.kind == .extend); abi_information.attributes.direct.alignment = alignment; } abi_set_can_be_flattened = fn (abi_information: &AbiInformation, value: u1) void { assert(abi_information.flags.kind == .direct); abi_information.flags.can_be_flattened = value; } abi_get_can_be_flattened = fn (abi_information: &AbiInformation) u1 { return abi_information.flags.can_be_flattened; } TypeFunctionBase = struct { semantic_return_type: &Type, semantic_argument_types: []&Type, calling_convention: CallingConvention, is_variable_argument: u1, } TypeFunctionAbi = struct { abi_argument_types: []&Type, abi_return_type: &Type, available_registers: AbiRegisterCount, argument_abis: []AbiInformation, return_abi: AbiInformation, } TypeFunction = struct { base: TypeFunctionBase, abi: TypeFunctionAbi, next: &Type, } TypeContent = union { integer: TypeInteger, function: TypeFunction, pointer: TypePointer, } TypeLLVM = struct { abi: &LLVMType, memory: &LLVMType, debug: &LLVMMetadata, } Type = struct { content: TypeContent, id: TypeId, name: []u8, next: &Type, scope: &Scope, llvm: TypeLLVM, } align_bit_count = fn (bit_count: u64) u64 { >aligned_bit_count = #max(next_power_of_two(bit_count), 8); assert(aligned_bit_count % 8 == 0); return aligned_bit_count; } aligned_byte_count_from_bit_count = fn (bit_count: u64) u64 { >aligned_bit_count = align_bit_count(bit_count); return aligned_bit_count / 8; } get_byte_size = fn (type: &Type) u64 { switch (type.id) { .integer => { >byte_count = aligned_byte_count_from_bit_count(type.content.integer.bit_count); assert(byte_count == 1 or byte_count == 2 or byte_count == 4 or byte_count == 8 or byte_count == 16); return byte_count; }, else => { #trap(); }, } } get_byte_alignment = fn (type: &Type) u32 { switch (type.id) { else => { #trap(); }, } } is_integral_or_enumeration_type = fn (type: &Type) u1 { switch (type.id) { .integer, .bits, .enum => { return 1; }, .array, .struct => { return 0; }, else => { unreachable; }, } } is_promotable_integer_type_for_abi = fn (type: &Type) u1 { switch (type.id) { .integer => { return type.content.integer.bit_count < 32; }, else => { #trap(); } } } resolve_type_in_place_abi = fn (module: &Module, type: &Type) void; resolve_type_in_place_memory = fn (module: &Module, type: &Type) void; resolve_type_in_place_debug = fn (module: &Module, type: &Type) void; resolve_type_in_place = fn (module: &Module, type: &Type) void { resolve_type_in_place_abi(module, type); resolve_type_in_place_memory(module, type); resolve_type_in_place_debug(module, type); } ValueId = enum { infer_or_ignore, external_function, function, constant_integer, global, } ValueConstantInteger = struct { value: u64, is_signed: u1, } ValueFunction = struct { arguments: []Argument, scope: Scope, block: &Block, attributes: FunctionAttributes, } ValueContent = union { constant_integer: ValueConstantInteger, function: ValueFunction, } ValueKind = enum { right, left, } Value = struct { content: ValueContent, type: &Type, id: ValueId, kind: ValueKind, } i128_offset: u64 = 64 * 2; void_offset: u64 = i128_offset + 2; MacroDeclaration = struct { foo: u32, } MacroInstantiation = struct { foo: u32, } [extern] LLVMInitializeX86TargetInfo = fn [cc(c)] () void; [extern] LLVMInitializeX86Target = fn [cc(c)] () void; [extern] LLVMInitializeX86TargetMC = fn [cc(c)] () void; [extern] LLVMInitializeX86AsmPrinter = fn [cc(c)] () void; [extern] LLVMInitializeX86AsmParser = fn [cc(c)] () void; [extern] LLVMInitializeX86Disassembler = fn [cc(c)] () void; [extern] llvm_default_target_triple = fn [cc(c)] () []u8; [extern] llvm_host_cpu_name = fn [cc(c)] () []u8; [extern] llvm_host_cpu_features = fn [cc(c)] () []u8; host_target_triple: []u8 = zero; host_cpu_model: []u8 = zero; host_cpu_features: []u8 = zero; llvm_targets_initialized: u1 = 0; llvm_initialize_targets = fn () void { if (!llvm_targets_initialized) { LLVMInitializeX86TargetInfo(); LLVMInitializeX86Target(); LLVMInitializeX86TargetMC(); LLVMInitializeX86AsmPrinter(); LLVMInitializeX86AsmParser(); LLVMInitializeX86Disassembler(); host_target_triple = llvm_default_target_triple(); host_cpu_model = llvm_host_cpu_name(); host_cpu_features = llvm_host_cpu_features(); llvm_targets_initialized = 1; } } LLVMCodeGenerationOptimizationLevel = enum u32 { none = 0, less = 1, default = 2, aggressive = 3, } LLVMDwarfSourceLanguage = enum u32 { C89, C, Ada83, C_plus_plus, Cobol74, Cobol85, Fortran77, Fortran90, Pascal83, Modula2, // New in DWARF v3: Java, C99, Ada95, Fortran95, PLI, ObjC, ObjC_plus_plus, UPC, D, // New in DWARF v4: Python, // New in DWARF v5: OpenCL, Go, Modula3, Haskell, C_plus_plus_03, C_plus_plus_11, OCaml, Rust, C11, Swift, Julia, Dylan, C_plus_plus_14, Fortran03, Fortran08, RenderScript, BLISS, Kotlin, Zig, Crystal, C_plus_plus_17, C_plus_plus_20, C17, Fortran18, Ada2005, Ada2012, HIP, Assembly, C_sharp, Mojo, GLSL, GLSL_ES, HLSL, OpenCL_CPP, CPP_for_OpenCL, SYCL, Ruby, Move, Hylo, Metal, // Vendor extensions: Mips_Assembler, GOOGLE_RenderScript, BORLAND_Delphi } LLVMDwarfEmissionKind = enum u32 { none, full, line_tables_only, } LLVMDwarfTypeEncoding = enum { void = 0x0, address = 0x1, boolean = 0x2, complex_float = 0x3, float = 0x4, signed = 0x5, signed_char = 0x6, unsigned = 0x7, unsigned_char = 0x8, // DWARF 3. imaginary_float = 0x9, packed_decimal = 0xa, numeric_string = 0xb, edited = 0xc, signed_fixed = 0xd, unsigned_fixed = 0xe, decimal_float = 0xf, // DWARF 4. UTF = 0x10, // DWARF 5. UCS = 0x11, ASCII = 0x12, // HP extensions. HP_float80 = 0x80, // Floating-point (80 bit). HP_complex_float80 = 0x81, // Complex floating-point (80 bit). HP_float128 = 0x82, // Floating-point (128 bit). HP_complex_float128 = 0x83, // Complex fp (128 bit). HP_floathpintel = 0x84, // Floating-point (82 bit IA64). HP_imaginary_float80 = 0x85, HP_imaginary_float128 = 0x86, HP_VAX_float = 0x88, // F or G floating. HP_VAX_float_d = 0x89, // D floating. HP_packed_decimal = 0x8a, // Cobol. HP_zoned_decimal = 0x8b, // Cobol. HP_edited = 0x8c, // Cobol. HP_signed_fixed = 0x8d, // Cobol. HP_unsigned_fixed = 0x8e, // Cobol. HP_VAX_complex_float = 0x8f, // F or G floating complex. HP_VAX_complex_float_d = 0x90, // D floating complex. } LLVMDIFlagsVisibility = enum u2 { none = 0, private = 1, protected = 2, public = 3, } LLVMDIFlagsInheritance = enum u2 { none = 0, single = 1, multiple = 2, virtual = 3, } LLVMDIFlags = bits u32 { visibility: LLVMDIFlagsVisibility, forward_declaration: u1, apple_block: u1, block_by_ref_struct: u1, virtual: u1, artificial: u1, explicit: u1, prototyped: u1, objective_c_class_complete: u1, object_pointer: u1, vector: u1, static_member: u1, lvalue_reference: u1, rvalue_reference: u1, _: u1, inheritance: LLVMDIFlagsInheritance, introduced_virtual: u1, bit_field: u1, noreturn: u1, type_pass_by_value: u1, type_pass_by_reference: u1, enum_class: u1, thunk: u1, non_trivial: u1, big_endian: u1, little_endian: u1, all_calls_described: u1, _: u3, } [extern] LLVMContextCreate = fn [cc(c)] () &LLVMContext; [extern] llvm_context_create_module = fn (context: &LLVMContext, name: []u8) &LLVMModule; [extern] LLVMCreateBuilderInContext = fn (context: &LLVMContext) &LLVMBuilder; [extern] LLVMVoidTypeInContext = fn [cc(c)] (context: &LLVMContext) &LLVMType; [extern] LLVMPointerTypeInContext = fn [cc(c)] (context: &LLVMContext, address_space: u32) &LLVMType; [extern] LLVMIntTypeInContext = fn [cc(c)] (context: &LLVMContext, bit_count: u32) &LLVMType; [extern] LLVMFunctionType = fn [cc(c)] (return_type: &LLVMType, argument_pointer: &&LLVMType, argument_count: u32, is_variable_argument: s32) &LLVMType; [extern] LLVMCreateDIBuilder = fn (module: &LLVMModule) &LLVMDIBuilder; [extern] LLVMDIBuilderCreateFile = fn (di_builder: &LLVMDIBuilder, file_pointer: &u8, file_length: u64, directory_pointer: &u8, directory_length: u64) &LLVMMetadata; [extern] LLVMDIBuilderCreateCompileUnit = fn (di_builder: &LLVMDIBuilder, language: LLVMDwarfSourceLanguage, file: &LLVMMetadata, producer_name_pointer: &u8, producer_name_length: u64, is_optimized: s32, flags_pointer: &u8, flags_length: u64, runtime_version: u32, split_name_pointer: &u8, split_name_length: u64, emission_kind: LLVMDwarfEmissionKind, dwo_id: u32, split_debug_inlining: s32, debug_info_for_profiling: s32, sysroot_pointer: &u8, sysroot_length: u64, sdk_pointer: &u8, sdk_length: u64) &LLVMMetadata; [extern] LLVMDIBuilderCreateBasicType = fn [cc(c)] (di_builder: &LLVMDIBuilder, name_pointer: &u8, name_length: u64, bit_size: u64, dwarf_encoding: LLVMDwarfTypeEncoding, flags: LLVMDIFlags) &LLVMMetadata; [extern] LLVMDIBuilderCreateSubroutineType = fn [cc(c)] (di_builder: &LLVMDIBuilder, file: &LLVMMetadata, argument_type_pointer: &&LLVMMetadata, argument_type_count: u32, flags: LLVMDIFlags) &LLVMMetadata; [extern] LLVMLookupIntrinsicID = fn [cc(c)] (name_pointer: &u8, name_length: u64) LLVMIntrinsicId; [extern] LLVMGetEnumAttributeKindForName = fn [cc(c)] (name_pointer: &u8, name_length: u64) LLVMAttributeId; [extern] LLVMCreateEnumAttribute = fn [cc(c)] (context: &LLVMContext, attribute_id: LLVMAttributeId, value: u64) &LLVMAttribute; [extern] LLVMCreateTypeAttribute = fn [cc(c)] (context: &LLVMContext, attribute_id: LLVMAttributeId, type: &LLVMType) &LLVMAttribute; [extern] LLVMAddAttributeAtIndex = fn [cc(c)] (value: &LLVMValue, index: u32, attribute: &LLVMAttribute) void; [extern] LLVMCreateTargetMachineOptions = fn () &LLVMTargetMachineOptions; [extern] LLVMTargetMachineOptionsSetCPU = fn (target_machine_options: &LLVMTargetMachineOptions, cpu: &u8) void; [extern] LLVMTargetMachineOptionsSetFeatures = fn (target_machine_options: &LLVMTargetMachineOptions, features: &u8) void; [extern] LLVMTargetMachineOptionsSetCodeGenOptLevel = fn (target_machine_options: &LLVMTargetMachineOptions, optimization_level: LLVMCodeGenerationOptimizationLevel) void; [extern] LLVMGetTargetFromTriple = fn (target_triple: &u8, target_pointer: &&LLVMTarget, error_message_pointer: &&u8) s32; [extern] LLVMCreateTargetMachineWithOptions = fn (target: &LLVMTarget, target_triple: &u8, target_machine_options: &LLVMTargetMachineOptions) &LLVMTargetMachine; [extern] LLVMCreateTargetDataLayout = fn (target_machine: &LLVMTargetMachine) &LLVMTargetDataLayout; [extern] LLVMSetModuleDataLayout = fn (module: &LLVMModule, target_data_layout: &LLVMTargetDataLayout) void; [extern] LLVMSetTarget = fn (module: &LLVMModule, target_triple: &u8) void; [extern] llvm_module_create_function = fn [cc(c)] (module: &LLVMModule, function_type: &LLVMType, linkage_type: LLVMLinkage, address_space: u32, name: []u8) &LLVMValue; [extern] LLVMSetFunctionCallConv = fn [cc(c)] (function: &LLVMValue, calling_convention: LLVMCallingConvention) void; default_address_space: u32 = 0; ModuleLLVM = struct { context: &LLVMContext, module: &LLVMModule, builder: &LLVMBuilder, di_builder: &LLVMDIBuilder, file: &LLVMMetadata, target_machine: &LLVMTargetMachine, target_data_layout: &LLVMTargetDataLayout, pointer_type: &LLVMType, void_type: &LLVMType, intrinsic_table: enum_array[LLVMIntrinsicIndex](LLVMIntrinsicId), attribute_table: enum_array[LLVMAttributeIndex](LLVMAttributeId), memcmp: &LLVMValue, inlined_at: &LLVMMetadata, continue_block: &LLVMBasicBlock, exit_block: &LLVMBasicBlock, debug_tag: u32, } Module = struct { arena: &Arena, content: []u8, offset: u64, line_offset: u64, line_character_offset: u64, first_pointer_type: &Type, first_slice_type: &Type, first_pair_struct_type: &Type, first_array_type: &Type, va_list_type: &Type, void_value: &Value, first_global: &Global, last_global: &Global, first_macro_declaration: &MacroDeclaration, last_macro_declaration: &MacroDeclaration, current_function: &Global, current_macro_declaration: &MacroDeclaration, current_macro_instantiation: &MacroInstantiation, llvm: ModuleLLVM, scope: Scope, name: []u8, path: []u8, executable: []u8, objects: [][]u8, libraries: [][]u8, target: Target, build_mode: BuildMode, has_debug_info: u1, silent: u1, } Statement = struct; StatementId = enum { local, expression, return, assignment, if, block, while, switch, for, break, continue, } StatementAssignmentId = enum { assign, assign_add, assign_sub, assign_mul, assign_div, assign_rem, assign_shift_left, assign_shift_right, assign_and, assign_or, assign_xor, } StatementAssignment = struct { left: &Value, right: &Value, id: StatementAssignmentId, } StatementIf = struct { condition: &Value, if: &Statement, else: &Statement, } Block = struct { first_local: &Local, last_local: &Local, first_statement: &Statement, scope: Scope, } StatementWhile = struct { condition: &Value, block: &Block, } StatementSwitchClause = struct { values: []&Value, block: &Block, } StatementSwitch = struct { discriminant: &Value, clauses: []StatementSwitchClause, } StatementForKind = enum { slice, range, } StatementFor = struct { first_local: &Local, last_local: &Local, kinds: ValueKind, iteratables: &Value, predicate: &Statement, scope: Scope, kind: StatementForKind, } StatementContent = union { local: &Local, expression: &Value, return: &Value, assignment: StatementAssignment, if: StatementIf, block: &Block, while: StatementWhile, switch: StatementSwitch, for: StatementFor, } Statement = struct { content: StatementContent, next: &Statement, line: u32, column: u32, id: StatementId, } new_global = fn (module: &Module) &Global { >result = arena_allocate[Global](module.arena, 1); if (module.last_global) { assert(module.first_global != zero); module.last_global.next = result; module.last_global = result; } else { assert(module.first_global == zero); module.first_global = result; module.last_global = result; } return result; } new_type = fn (module: &Module, type: Type) &Type { >result = arena_allocate[Type](module.arena, 1); result.& = type; >scope = type.scope; assert(scope != zero); if (scope.types.last) { assert(scope.types.first != zero); scope.types.last.next = result; scope.types.last = result; } else { assert(scope.types.first == zero); scope.types.first = result; scope.types.last = result; } return result; } 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), integer.bit_count - 1 + 64 * #extend(integer.signed)); >result = module.scope.types.first + index; assert(result.id == .integer); assert(result.content.integer.bit_count == integer.bit_count); assert(result.content.integer.signed == integer.signed); return result; } void_type = fn (module: &Module) &Type { return module.scope.types.first + void_offset; } noreturn_type = fn (module: &Module) &Type { return void_type(module) + 1; } get_pointer_type = fn (module: &Module, element_type: &Type) &Type { >last_pointer_type = module.first_pointer_type; while (last_pointer_type) { assert(last_pointer_type.id == .pointer); if (last_pointer_type.content.pointer.element_type == element_type) { return last_pointer_type; } if (!last_pointer_type.content.pointer.next) { break; } last_pointer_type = last_pointer_type.content.pointer.next; } >result = new_type(module, { .content = { .pointer = { .element_type = element_type, zero, }, }, .id = .pointer, .name = arena_join_string(module.arena, [ "&", element_type.name ][..]), .scope = element_type.scope, zero, }); if (last_pointer_type) { assert(module.first_pointer_type != zero); last_pointer_type.content.pointer.next = result; } else { assert(!module.first_pointer_type); module.first_pointer_type = result; } return result; } 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_hexadecimal_alpha_lower = fn (ch: u8) u1 { return ch >= 'a' and ch <= 'f'; } is_hexadecimal_alpha_upper = fn (ch: u8) u1 { return ch >= 'A' and ch <= 'F'; } is_hexadecimal_alpha = fn (ch: u8) u1 { return is_hexadecimal_alpha_lower(ch) or is_hexadecimal_alpha_upper(ch); } is_hexadecimal = fn (ch: u8) u1 { return is_decimal(ch) or is_hexadecimal_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); } Checkpoint = struct { offset: u64, line_offset: u64, line_character_offset: u64, } get_checkpoint = fn (module: &Module) Checkpoint { return { .offset = module.offset, .line_offset = module.line_offset, .line_character_offset = module.line_character_offset, }; } set_checkpoint = fn (module: &Module, checkpoint: Checkpoint) void { module.offset = checkpoint.offset; module.line_offset = checkpoint.line_offset; module.line_character_offset = checkpoint.line_character_offset; } 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; } new_value = fn (module: &Module) &Value { >value = arena_allocate[Value](module.arena, 1); return value; } GlobalAttributeKeyword = enum { export, extern, } GlobalKeyword = enum { bits, enum, fn, macro, opaque, struct, typealias, union, } FunctionTypeAttribute = enum { cc, } left_bracket: u8 = '['; right_bracket: u8 = ']'; left_parenthesis: u8 = '('; right_parenthesis: u8 = ')'; left_brace: u8 = '{'; right_brace: u8 = '}'; accumulate_decimal = fn(accumulator: u64, ch: u8) u64 { assert(is_decimal(ch)); return (accumulator * 10) + #extend(ch - '0'); } parse_decimal = fn (module: &Module) u64 { >value: u64 = 0; while (1) { >ch = module.content[module.offset]; if (!is_decimal(ch)) { break; } module.offset += 1; value = accumulate_decimal(value, ch); } return value; } parse_integer_decimal_assume_valid = fn (string: []u8) u64 { >value: u64 = 0; for (ch: string) { value = accumulate_decimal(value, ch); } return value; } accumulate_octal = fn (accumulator: u64, ch: u8) u64 { assert(is_octal(ch)); return (accumulator * 8) + #extend(ch - '0'); } parse_octal = fn (module: &Module) u64 { >value: u64 = 0; while (1) { >ch = module.content[module.offset]; if (!is_octal(ch)) { break; } module.offset += 1; value = accumulate_octal(value, ch); } return value; } accumulate_binary = fn (accumulator: u64, ch: u8) u64 { assert(is_binary(ch)); return (accumulator * 2) + #extend(ch - '0'); } parse_binary = fn (module: &Module) u64 { >value: u64 = 0; while (1) { >ch = module.content[module.offset]; if (!is_binary(ch)) { break; } module.offset += 1; value = accumulate_binary(value, ch); } return value; } TypeKeyword = enum { void, noreturn, enum_array, } parse_type = fn (module: &Module, scope: &Scope) &Type { >start_character = module.content[module.offset]; if (is_identifier_start(start_character)) { >identifier = parse_identifier(module); >type_keyword_s2e = #string_to_enum(TypeKeyword, identifier); if (type_keyword_s2e.is_valid) { >type_keyword = type_keyword_s2e.enum_value; switch (type_keyword) { .void => { #trap(); }, .noreturn => { #trap(); }, .enum_array => { #trap(); }, } } else { >is_integer_type = identifier.length > 1 and (identifier[0] == 's' or identifier[0] == 'u'); if (is_integer_type) { for (ch: identifier[1..]) { is_integer_type = is_integer_type and is_decimal(ch); } } if (is_integer_type) { >is_signed: u1 = undefined; switch (identifier[0]) { 's' => { is_signed = 1; }, 'u' => { is_signed = 0; }, else => { unreachable; }, } >bit_count = parse_integer_decimal_assume_valid(identifier[1..]); if (bit_count == 0) { report_error(); } if (bit_count > 64) { if (bit_count != 128) { report_error(); } } >result = integer_type(module, { .bit_count = bit_count, .signed = is_signed }); return result; } else { //>type = module.first_type; //while (type) //{ // if (string_equal(identifier, type.name)) // { // return type; // } // type = type.next; //} report_error(); } } } else if (start_character == '&') { #trap(); } else if (start_character == left_bracket) { #trap(); } else if (start_character == '#') { #trap(); } else { report_error(); } } accumulate_hexadecimal = fn (accumulator: u64, ch: u8) u64 { >value: u8 = undefined; if (is_decimal(ch)) { value = (ch - '0'); } else if (is_hexadecimal_alpha_upper(ch)) { value = (ch - 'A' + 10); } else if (is_hexadecimal_alpha_lower(ch)) { value = (ch - 'a' + 10); } else { unreachable; } return (accumulator * 16) + #extend(value); } parse_hexadecimal = fn (module: &Module) u64 { >value: u64 = 0; while (1) { >ch = module.content[module.offset]; if (!is_hexadecimal(ch)) { break; } module.offset += 1; value = accumulate_hexadecimal(value, ch); } return value; } TokenId = enum { none, comma, end_of_statement, integer, left_brace, left_bracket, left_parenthesis, right_brace, right_bracket, right_parenthesis, plus, dash, asterisk, forward_slash, percentage, caret, bar, ampersand, exclamation, assign_plus, assign_dash, assign_asterisk, assign_forward_slash, assign_percentage, assign_caret, assign_bar, assign_ampersand, value_keyword, operator_keyword, identifier, string_literal, value_intrinsic, shift_left, shift_right, assign_shift_left, assign_shift_right, compare_less, compare_less_equal, compare_greater, compare_greater_equal, compare_equal, compare_not_equal, dot, double_dot, triple_dot, pointer_dereference, assign, tilde, } TokenIntegerKind = enum { hexadecimal, decimal, octal, binary, character_literal, } TokenInteger = struct { value: u64, kind: TokenIntegerKind, }; ValueKeyword = enum { undefined, unreachable, zero, } ValueIntrinsic = enum { align_of, byte_size, enum_name, extend, integer_max, int_from_enum, int_from_pointer, pointer_cast, pointer_from_int, select, string_to_enum, trap, truncate, va_start, va_end, va_arg, va_copy, } OperatorKeyword = enum { and, or, "and?", "or?", } TokenContent = union { integer: TokenInteger, value_keyword: ValueKeyword, value_intrinsic: ValueIntrinsic, operator_keyword: OperatorKeyword, identifier: []u8, string_literal: []u8, } Token = struct { content: TokenContent, id: TokenId, } tokenize = fn (module: &Module) Token { skip_space(module); >start_index = module.offset; if (start_index == module.content.length) { report_error(); } >start_character = module.content[start_index]; >token: Token = undefined; switch (start_character) { ',', ';', '~', left_brace, left_parenthesis, left_bracket, right_brace, right_parenthesis, right_bracket, => { module.offset += 1; >id: TokenId = undefined; switch (start_character) { ',' => { id = .comma; }, ';' => { id = .end_of_statement; }, '~' => { id = .tilde; }, left_brace => { id = .left_brace; }, left_parenthesis => { id = .left_parenthesis; }, left_bracket => { id = .left_bracket; }, right_brace => { id = .right_brace; }, right_parenthesis => { id = .right_parenthesis; }, right_bracket => { id = .right_bracket; }, else => { unreachable; }, } token = { .id = id, zero, }; }, '#' => { #trap(); }, '<' => { #trap(); }, '>' => { #trap(); }, '=' => { #trap(); }, '.' => { #trap(); }, '"' => { #trap(); }, '\'' => { #trap(); }, '0'...'9' => { >next_ch = module.content[start_index + 1]; >token_integer_kind: TokenIntegerKind = .decimal; if (start_character == '0') { switch (next_ch) { 'x' => { token_integer_kind = .hexadecimal; }, 'd' => { token_integer_kind = .decimal; }, 'o' => { token_integer_kind = .octal; }, 'b' => { token_integer_kind = .binary; }, else => { token_integer_kind = .decimal; }, } >inferred_decimal = token_integer_kind == .decimal and next_ch != 'd'; module.offset += 2 * #extend(token_integer_kind != .decimal or !inferred_decimal); } >value: u64 = undefined; switch (token_integer_kind) { .hexadecimal => { value = parse_hexadecimal(module); }, .decimal => { value = parse_decimal(module); }, .octal => { value = parse_octal(module); }, .binary => { value = parse_binary(module); }, .character_literal => { report_error(); }, } token = { .content = { .integer = { .value = value, .kind = token_integer_kind, }, }, .id = .integer, }; }, '+', '-', '*', '/', '%', '&', '|', '^', '!', => { #trap(); }, else => { if (is_identifier_start(start_character)) { #trap(); } else { report_error(); } } } assert(start_index != module.offset); return token; } Precedence = enum { none, assignment, boolean_or, boolean_and, comparison, bitwise, shifting, add_like, div_like, prefix, aggregate_initialization, postfix, } ValueBuilder = struct { token: Token, left: &Value, precedence: Precedence, kind: ValueKind, allow_assignment_operators: u1, } parse_left = fn (module: &Module, scope: &Scope, builder: ValueBuilder) &Value { >token = builder.token; >result: &Value = zero; switch (token.id) { .integer => { >integer_value = token.content.integer.value; result = new_value(module); result.& = { .content = { .constant_integer = { .value = integer_value, .is_signed = 0, }, }, .id = .constant_integer, .kind = .right, zero, }; }, else => { report_error(); }, } assert(result != zero); return result; } get_token_precedence = fn (token: Token) Precedence { switch (token.id) { .none => { unreachable; }, .comma, .double_dot, .triple_dot, .end_of_statement, .right_brace, .right_bracket, .right_parenthesis, => { return .none; }, .assign, .assign_shift_left, .assign_shift_right, .assign_plus, .assign_dash, .assign_asterisk, .assign_forward_slash, .assign_percentage, .assign_caret, .assign_bar, .assign_ampersand, => { return .assignment; }, .operator_keyword => { #trap(); }, .compare_equal, .compare_not_equal, .compare_less, .compare_less_equal, .compare_greater, .compare_greater_equal, => { return .comparison; }, .ampersand, .bar, .caret, => { return .bitwise; }, .shift_left, .shift_right, => { return .shifting; }, .plus, .dash, => { return .add_like; }, .asterisk, .forward_slash, .percentage, => { return .div_like; }, .pointer_dereference, .left_parenthesis, .left_bracket, .dot, => { return .postfix; }, else => { unreachable; }, } } parse_right = fn (module: &Module, scope: &Scope, builder: ValueBuilder) &Value { #trap(); } parse_right_with_left = fn (module: &Module, scope: &Scope, builder: ValueBuilder) &Value { >result = builder.left; >precedence = builder.precedence; while (1) { >checkpoint = get_checkpoint(module); >token = tokenize(module); >token_precedence = get_token_precedence(token); if (token_precedence == .assignment) { token_precedence = #select(builder.allow_assignment_operators, token_precedence, .none); } if (precedence > token_precedence) { set_checkpoint(module, checkpoint); break; } >left = result; >right_builder = builder; right_builder.token = token; right_builder.precedence = .none; right_builder.left = left; >right = parse_right(module, scope, right_builder); result = right; } return result; } parse_precedence = fn (module: &Module, scope: &Scope, builder: ValueBuilder) &Value { assert(builder.token.id == .none); >token = tokenize(module); >left_builder = builder; left_builder.token = token; >left = parse_left(module, scope, left_builder); >right_builder = builder; right_builder.left = left; >result = parse_right_with_left(module, scope, right_builder); return result; } parse_value = fn (module: &Module, scope: &Scope, builder: ValueBuilder) &Value { assert(builder.precedence == .none); assert(!builder.left); builder.precedence = .assignment; >value = parse_precedence(module, scope, builder); return value; } StatementStartKeyword = enum { _, return, if, for, while, switch, break, continue, } parse_statement = fn (module: &Module, scope: &Scope) &Statement { >require_semicolon: u1 = 1; >statement_line = get_line(module); >statement_column = get_column(module); >statement = arena_allocate[Statement](module.arena, 1); statement.& = { .line = statement_line, .column = statement_column, zero, }; >statement_start_character = module.content[module.offset]; switch (statement_start_character) { '>' => { #trap(); }, '#' => { statement.content.expression = parse_value(module, scope, zero); statement.id = .expression; }, left_brace => { #trap(); }, else => { if (is_identifier_start(statement_start_character)) { >checkpoint = get_checkpoint(module); >statement_start_identifier = parse_identifier(module); skip_space(module); >statement_start_keyword_s2e = #string_to_enum(StatementStartKeyword, statement_start_identifier); if (statement_start_keyword_s2e.is_valid) { >statement_start_keyword = statement_start_keyword_s2e.enum_value; switch (statement_start_keyword) { ._ => { #trap(); }, .return => { >return_value = parse_value(module, scope, zero); statement.content.return = return_value; statement.id = .return; }, .if => { #trap(); }, .for => { #trap(); }, .while => { #trap(); }, .switch => { #trap(); }, .break => { #trap(); }, .continue => { #trap(); }, } } else { set_checkpoint(module, checkpoint); >left = parse_value(module, scope, { .kind = .left, zero }); #trap(); } } else { report_error(); } }, } if (require_semicolon) { expect_character(module, ';'); } return statement; } parse_block = fn (module: &Module, parent_scope: &Scope) &Block { >block = arena_allocate[Block](module.arena, 1); block.& = { .scope = { .parent = parent_scope, .line = get_line(module), .column = get_column(module), .kind = .local, zero, }, zero, }; >scope = &block.scope; expect_character(module, left_brace); >current_statement: &Statement = zero; while (1) { skip_space(module); if (module.offset == module.content.length) { break; } if (consume_character_if_match(module, right_brace)) { break; } >statement = parse_statement(module, scope); if (!block.first_statement) { block.first_statement = statement; } if (current_statement) { current_statement.next = statement; } assert(!statement.next); current_statement = statement; } return block; } parse = fn (module: &Module) void { >scope = &module.scope; while (1) { skip_space(module); if (module.offset == module.content.length) { break; } >is_export: u1 = 0; >is_extern: u1 = 0; >global_line = get_line(module); >global_column = get_line(module); if (consume_character_if_match(module, left_bracket)) { while (module.offset < module.content.length) { >global_attribute_keyword_string = parse_identifier(module); >global_attribute_keyword_s2e = #string_to_enum(GlobalAttributeKeyword, global_attribute_keyword_string); if (!global_attribute_keyword_s2e.is_valid) { report_error(); } >global_attribute_keyword = global_attribute_keyword_s2e.enum_value; switch (global_attribute_keyword) { .export => { is_export = 1; }, .extern => { is_extern = 1; }, } if (consume_character_if_match(module, right_bracket)) { break; } else { report_error(); } } skip_space(module); } >global_name = parse_identifier(module); >last_global = module.first_global; while (last_global) { if (string_equal(global_name, last_global.variable.name)) { report_error(); } if (!last_global.next) { break; } last_global = last_global.next; } >type_it = module.scope.types.first; >forward_declaration: &Type = zero; while (type_it) { if (string_equal(global_name, type_it.name)) { if (type_it.id == .forward_declaration) { forward_declaration = type_it; break; } else { report_error(); } } if (!type_it.next) { break; } type_it = type_it.next; } skip_space(module); >global_type: &Type = zero; if (consume_character_if_match(module, ':')) { skip_space(module); global_type = parse_type(module, scope); skip_space(module); } expect_character(module, '='); skip_space(module); >is_global_keyword: u1 = 0; if (is_identifier_start(module.content[module.offset])) { >checkpoint = get_checkpoint(module); >global_keyword_string = parse_identifier(module); skip_space(module); >global_keyword_s2e = #string_to_enum(GlobalKeyword, global_keyword_string); is_global_keyword = global_keyword_s2e.is_valid; if (is_global_keyword) { >global_keyword = global_keyword_s2e.enum_value; switch (global_keyword) { .bits => { #trap(); }, .enum => { #trap(); }, .fn => { >calling_convention: CallingConvention = .c; >function_attributes: FunctionAttributes = zero; >is_variable_argument: u1 = 0; if (consume_character_if_match(module, left_bracket)) { while (module.offset < module.content.length) { >function_identifier = parse_identifier(module); >function_type_attribute_s2e = #string_to_enum(FunctionTypeAttribute, function_identifier); if (!function_type_attribute_s2e.is_valid) { report_error(); } >function_type_attribute = function_type_attribute_s2e.enum_value; switch (function_type_attribute) { .cc => { expect_character(module, left_parenthesis); skip_space(module); >calling_convention_string = parse_identifier(module); >calling_convention_s2e = #string_to_enum(CallingConvention, calling_convention_string); if (!calling_convention_s2e.is_valid) { report_error(); } >candidate_calling_convention = calling_convention_s2e.enum_value; calling_convention = candidate_calling_convention; skip_space(module); expect_character(module, right_parenthesis); }, } skip_space(module); if (consume_character_if_match(module, right_bracket)) { break; } else { report_error(); } } } skip_space(module); expect_character(module, left_parenthesis); >semantic_argument_type_buffer: [64]&Type = undefined; >semantic_argument_name_buffer: [64][]u8 = undefined; >argument_line_buffer: [64]u32 = undefined; >semantic_argument_count: u64 = 0; while (module.offset < module.content.length) { skip_space(module); if (consume_character_if_match(module, '.')) { #trap(); } if (consume_character_if_match(module, right_parenthesis)) { break; } >line = get_line(module); argument_line_buffer[semantic_argument_count] = line; >argument_name = parse_identifier(module); semantic_argument_name_buffer[semantic_argument_count] = argument_name; skip_space(module); expect_character(module, ':'); skip_space(module); >argument_type = parse_type(module, scope); semantic_argument_type_buffer[semantic_argument_count] = argument_type; skip_space(module); consume_character_if_match(module, '.'); semantic_argument_count += 1; } skip_space(module); >return_type = parse_type(module, scope); skip_space(module); >argument_types: []&Type = zero; if (semantic_argument_count != 0) { #trap(); } >is_declaration = consume_character_if_match(module, ';'); >function_type = new_type(module, { .content = { .function = { .base = { .semantic_return_type = return_type, .semantic_argument_types = argument_types, .calling_convention = calling_convention, .is_variable_argument = is_variable_argument, }, zero, }, }, .id = .function, .name = "", .scope = &module.scope, zero, }); >storage = new_value(module); storage.& = { .id = .external_function, .type = get_pointer_type(module, function_type), zero, // TODO? kind = .left, }; >global = new_global(module); global.& = { .variable = { .storage = storage, .type = function_type, .scope = scope, .name = global_name, .line = global_line, .column = global_column, }, .linkage = #select(is_export or is_extern, .external, .internal), zero, }; if (!is_declaration) { module.current_function = global; >arguments = arena_allocate_slice[Argument](module.arena, semantic_argument_count); for (i: 0..semantic_argument_count) { >argument = &arguments[i]; >name = semantic_argument_name_buffer[i]; >type = semantic_argument_type_buffer[i]; >line = argument_line_buffer[i]; #trap(); } storage.content.function = { .arguments = arguments, .scope = { .parent = scope, .line = global_line, .column = global_column, .kind = .function, zero, }, .attributes = function_attributes, zero, }; storage.id = .function; storage.content.function.block = parse_block(module, &storage.content.function.scope); module.current_function = zero; } }, .macro => { #trap(); }, .opaque => { #trap(); }, .struct => { #trap(); }, .typealias => { #trap(); }, .union => { #trap(); }, } } else { set_checkpoint(module, checkpoint); } } if (!is_global_keyword) { #trap(); } } } resolve_alias = fn (module: &Module, type: &Type) &Type { >result: &Type = zero; switch (type.id) { .void, .integer, => { result = type; }, else => { #trap(); }, } assert(result != zero); return result; } resolve_type_in_place_abi = fn (module: &Module, type: &Type) void { if (!type.llvm.abi) { >result: &LLVMType = zero; switch (type.id) { .void, .noreturn => { result = module.llvm.void_type; }, .integer => { >bit_count = type.content.integer.bit_count; assert(bit_count <= #integer_max(u32)); result = LLVMIntTypeInContext(module.llvm.context, #truncate(bit_count)); }, else => { #trap(); }, } assert(result != zero); type.llvm.abi = result; } } resolve_type_in_place_memory = fn (module: &Module, type: &Type) void { if (!type.llvm.memory) { resolve_type_in_place_abi(module, type); >result: &LLVMType = zero; switch (type.id) { .void, .noreturn, => { result = type.llvm.abi; }, .integer => { >byte_size = get_byte_size(type); >bit_count = byte_size * 8; result = LLVMIntTypeInContext(module.llvm.context, #truncate(bit_count)); }, else => { #trap(); }, } assert(result != zero); type.llvm.memory = result; } } resolve_type_in_place_debug = fn (module: &Module, type: &Type) void { if (module.has_debug_info) { if (!type.llvm.debug) { >result: &LLVMMetadata = zero; switch (type.id) { .void, .noreturn => { >bit_size: u64 = 0; >dwarf_encoding: LLVMDwarfTypeEncoding = .void; >flags: LLVMDIFlags = { .noreturn = type.id == .noreturn, zero, }; result = LLVMDIBuilderCreateBasicType(module.llvm.di_builder, type.name.pointer, type.name.length, bit_size, dwarf_encoding, flags); }, .integer => { >bit_count = type.content.integer.bit_count; >dwarf_encoding: LLVMDwarfTypeEncoding = #select(type.content.integer.signed, .signed, .unsigned); dwarf_encoding = #select(bit_count == 1, .boolean, dwarf_encoding); >flags: LLVMDIFlags = zero; result = LLVMDIBuilderCreateBasicType(module.llvm.di_builder, type.name.pointer, type.name.length, bit_count, dwarf_encoding, flags); }, else => { #trap(); }, } assert(result != zero); type.llvm.debug = result; } } } AbiSystemVClass = enum { none, integer, sse, sse_up, x87, x87_up, complex_x87, memory, } AbiSystemVClassifyArgument = struct { base_offset: u64, is_variable_argument: u1, is_register_call: u1, } abi_system_v_classify_type = fn (type: &Type, options: AbiSystemVClassifyArgument) [2]AbiSystemVClass { >result: [2]AbiSystemVClass = zero; >is_memory = options.base_offset >= 8; >current_index: u64 = #extend(is_memory); >not_current_index: u64 = #extend(!is_memory); assert(current_index != not_current_index); result[current_index] = .memory; switch (type.id) { .void, .noreturn => { result[current_index] = .none; }, .integer => { >bit_count = type.content.integer.bit_count; if (bit_count <= 64) { result[current_index] = .integer; } else if (bit_count == 128) { #trap(); } else { report_error(); } }, else => { #trap(); }, } return result; } contains_no_user_data = fn (type: &Type, start: u64, end: u64) u1 { >byte_size = get_byte_size(type); >result = byte_size <= start; if (!result) { switch (type.id) { .struct => { result = 1; #trap(); }, .array, .enum_array => { result = 1; #trap(); }, else => {}, } } return result; } abi_system_v_get_integer_type_at_offset = fn (module: &Module, type: &Type, offset: u64, source_type: &Type, source_offset: u64) &Type { switch (type.id) { .integer => { if (offset == 0) { >bit_count = type.content.integer.bit_count; >start = source_offset + get_byte_size(type); >end = source_offset + 8; if (bit_count == 64) { return type; } if (bit_count == 32 or bit_count == 16 or bit_count == 8) { if (contains_no_user_data(source_type, start, end)) { return type; } } } }, } >source_size = get_byte_size(source_type); assert(source_size != source_offset); >byte_count = source_size - source_offset; >bit_count = #select(byte_count > 8, 64, byte_count * 8); >result = integer_type(module, { .bit_count = bit_count, .signed = 0 }); return result; } AbiSystemVDirect = struct { semantic_type: &Type, type: &Type, padding: &Type, offset: u32, alignment: u32, cannot_be_flattened: u1, } abi_system_v_get_direct = fn (module: &Module, direct: AbiSystemVDirect) AbiInformation { >result: AbiInformation = { .semantic_type = direct.semantic_type, .flags = { .kind = .direct, zero, }, zero, }; resolve_type_in_place(module, direct.semantic_type); resolve_type_in_place(module, direct.type); if (direct.padding) { resolve_type_in_place(module, direct.padding); } abi_set_coerce_to_type(&result, direct.type); abi_set_padding_type(&result, direct.padding); abi_set_direct_offset(&result, direct.offset); abi_set_direct_alignment(&result, direct.alignment); abi_set_can_be_flattened(&result, !direct.cannot_be_flattened); return result; } abi_system_v_classify_return_type = fn (module: &Module, semantic_return_type: &Type) AbiInformation { >classes = abi_system_v_classify_type(semantic_return_type, zero); assert(classes[1] != .memory or classes[0] == .memory); assert(classes[1] != .sse_up or classes[0] == .sse); >low_type: &Type = zero; switch (classes[0]) { .none => { if (classes[1] == .none) { #trap(); } else { report_error(); } }, .integer => { low_type = abi_system_v_get_integer_type_at_offset(module, semantic_return_type, 0, semantic_return_type, 0); if (classes[1] == .none and low_type.id == .integer) { if (is_integral_or_enumeration_type(semantic_return_type) and? is_promotable_integer_type_for_abi(semantic_return_type)) { #trap(); } } }, else => { #trap(); }, } >high_type: &Type = zero; switch (classes[1]) { .none => {}, .integer => { >high_offset: u64 = 8; high_type = abi_system_v_get_integer_type_at_offset(module, semantic_return_type, high_offset, semantic_return_type, high_offset); if (classes[0] == .none) { #trap(); } }, else => { #trap(); }, } if (high_type) { #trap(); } >result = abi_system_v_get_direct(module, { .semantic_type = semantic_return_type, .type = low_type, zero, }); return result; } LLVMAttributeCallback = typealias fn [cc(c)] (&LLVMValue, u32, &LLVMAttribute) void; add_enum_attribute = fn (module: &Module, attribute_index: LLVMAttributeIndex, attribute_value: u64, add_callback: &LLVMAttributeCallback, value: &LLVMValue, index: u32) &LLVMAttribute { >attribute = LLVMCreateEnumAttribute(module.llvm.context, module.llvm.attribute_table[attribute_index], attribute_value); add_callback(value, index, attribute); } add_type_attribute = fn (module: &Module, attribute_index: LLVMAttributeIndex, type: &LLVMType, add_callback: &LLVMAttributeCallback, value: &LLVMValue, index: u32) &LLVMAttribute { >attribute = LLVMCreateTypeAttribute(module.llvm.context, module.llvm.attribute_table[attribute_index], type); add_callback(value, index, attribute); } LLVMAttributes = struct { alignment: u32, sign_extend: u1, zero_extend: u1, no_alias: u1, in_reg: u1, sret: u1, writable: u1, dead_on_unwind: u1, by_value: u1, } add_value_attribute = fn (module: &Module, value: &LLVMValue, index: u32, add_callback: &LLVMAttributeCallback, semantic_type: &LLVMType, abi_type: &LLVMType, attributes: LLVMAttributes) void { assert(value != zero); assert(semantic_type != zero); assert(abi_type != zero); if (attributes.alignment) { add_enum_attribute(module, .align, #extend(attributes.alignment), add_callback, value, index); } if (attributes.sign_extend) { add_enum_attribute(module, .signext, 0, add_callback, value, index); } if (attributes.zero_extend) { add_enum_attribute(module, .zeroext, 0, add_callback, value, index); } if (attributes.no_alias) { add_enum_attribute(module, .noalias, 0, add_callback, value, index); } if (attributes.in_reg) { add_enum_attribute(module, .inreg, 0, add_callback, value, index); } if (attributes.sret) { add_type_attribute(module, .sret, semantic_type, add_callback, value, index); } if (attributes.writable) { add_enum_attribute(module, .writable, 0, add_callback, value, index); } if (attributes.dead_on_unwind) { add_enum_attribute(module, .dead_on_unwind, 0, add_callback, value, index); } if (attributes.by_value) { add_type_attribute(module, .byval, semantic_type, add_callback, value, index); } } AttributeBuildOptions = struct { return_abi: AbiInformation, argument_abis: []AbiInformation, abi_argument_types: []&Type, abi_return_type: &Type, attributes: FunctionAttributes, } emit_attributes = fn (module: &Module, value: &LLVMValue, add_callback: &LLVMAttributeCallback, options: AttributeBuildOptions) void { >return_abi = &options.return_abi; >semantic_return_type = return_abi.semantic_type; resolve_type_in_place(module, semantic_return_type); >abi_return_type = options.abi_return_type; resolve_type_in_place(module, abi_return_type); add_value_attribute(module, value, 0, add_callback, semantic_return_type.llvm.memory, abi_return_type.llvm.abi, { .alignment = 0, .sign_extend = return_abi.flags.kind == .extend and return_abi.flags.sign_extension, .zero_extend = return_abi.flags.kind == .extend and !return_abi.flags.sign_extension, .no_alias = 0, .in_reg = 0, .sret = 0, .writable = 0, .dead_on_unwind = 0, .by_value = 0, }); >total_abi_count: u64 = 0; if (return_abi.flags.kind == .indirect) { >abi = return_abi; >abi_index = abi.flags.sret_after_this; >abi_type = options.abi_argument_types[abi_index]; resolve_type_in_place(module, abi_type); add_value_attribute(module, value, #extend(abi_index + 1), add_callback, semantic_return_type.llvm.memory, abi_type.llvm.abi, { .alignment = get_byte_alignment(semantic_return_type), .sign_extend = 0, .zero_extend = 0, .no_alias = 1, .in_reg = abi.flags.in_reg, .sret = 1, .writable = 1, .dead_on_unwind = 1, .by_value = 0, }); total_abi_count += 1; } for (&abi: options.argument_abis) { resolve_type_in_place(module, abi.semantic_type); for (abi_index: abi.abi_start..abi.abi_start + abi.abi_count) { >abi_type = options.abi_argument_types[abi_index]; resolve_type_in_place(module, abi_type); add_value_attribute(module, value, #extend(abi_index + 1), add_callback, semantic_return_type.llvm.memory, abi_type.llvm.abi, { .alignment = #select(abi.flags.kind == .indirect, 8, 0), .sign_extend = abi.flags.kind == .extend and abi.flags.sign_extension, .zero_extend = abi.flags.kind == .extend and !abi.flags.sign_extension, .no_alias = 0, .in_reg = abi.flags.in_reg, .sret = 0, .writable = 0, .dead_on_unwind = 0, .by_value = abi.flags.indirect_by_value, }); total_abi_count += 1; } } assert(total_abi_count == options.abi_argument_types.length); #trap(); } ResolvedCallingConvention = enum { system_v, win64, } emit = fn (module: &Module) void { assert(!module.current_function); assert(!module.current_macro_instantiation); assert(!module.current_macro_declaration); llvm_initialize_targets(); >context = LLVMContextCreate(); >m = llvm_context_create_module(context, module.name); >builder = LLVMCreateBuilderInContext(context); >di_builder: &LLVMDIBuilder = zero; >di_file: &LLVMMetadata = zero; if (module.has_debug_info) { di_builder = LLVMCreateDIBuilder(m); >last_slash = string_last_character(module.path, '/'); if (last_slash == string_no_match) { report_error(); } >directory = module.path[..last_slash]; >file_name = module.path[last_slash + 1..]; >di_file = LLVMDIBuilderCreateFile(di_builder, file_name.pointer, file_name.length, directory.pointer, directory.length); >language: LLVMDwarfSourceLanguage = .C17; >producer_name = "bloat buster"; >is_optimized = build_mode_is_optimized(module.build_mode); >flags = ""; >emission_kind: LLVMDwarfEmissionKind = .full; >runtime_version: u32 = 0; >split_name = ""; >sysroot = ""; >sdk = ""; module.scope.llvm = LLVMDIBuilderCreateCompileUnit(di_builder, language, di_file, producer_name.pointer, producer_name.length, #extend(is_optimized), flags.pointer, flags.length, runtime_version, split_name.pointer, split_name.length, emission_kind, 0, 0, #extend(is_optimized), sysroot.pointer, sysroot.length, sdk.pointer, sdk.length); } >target_triple: []u8 = undefined; >cpu_model: []u8 = undefined; >cpu_features: []u8 = undefined; if (target_compare(module.target, target_get_native())) { target_triple = host_target_triple; cpu_model = host_cpu_model; cpu_features = host_cpu_features; } else { #trap(); } >target_machine_options = LLVMCreateTargetMachineOptions(); LLVMTargetMachineOptionsSetCPU(target_machine_options, cpu_model.pointer); LLVMTargetMachineOptionsSetFeatures(target_machine_options, cpu_features.pointer); >code_generation_optimization_level: LLVMCodeGenerationOptimizationLevel = undefined; switch (module.build_mode) { .debug_none, .debug => { code_generation_optimization_level = .none; }, .soft_optimize => { code_generation_optimization_level = .less; }, .optimize_for_speed, .optimize_for_size => { code_generation_optimization_level = .default; }, .aggressively_optimize_for_speed, .aggressively_optimize_for_size => { code_generation_optimization_level = .aggressive; }, } LLVMTargetMachineOptionsSetCodeGenOptLevel(target_machine_options, code_generation_optimization_level); >target: &LLVMTarget = zero; >error_message: &u8 = zero; >result = LLVMGetTargetFromTriple(target_triple.pointer, &target, &error_message); if (result != 0) { report_error(); } assert(!error_message); >target_machine = LLVMCreateTargetMachineWithOptions(target, target_triple.pointer, target_machine_options); >target_data_layout = LLVMCreateTargetDataLayout(target_machine); LLVMSetModuleDataLayout(m, target_data_layout); LLVMSetTarget(m, target_triple.pointer); module.llvm = { .context = context, .module = m, .builder = builder, .di_builder = di_builder, .file = di_file, .target_machine = target_machine, .target_data_layout = target_data_layout, .pointer_type = LLVMPointerTypeInContext(context, default_address_space), .void_type = LLVMVoidTypeInContext(context), zero, }; for (i: 0..module.llvm.intrinsic_table.length) { >e: LLVMIntrinsicIndex = #enum_from_int(i); >name = #enum_name(e); module.llvm.intrinsic_table[i] = LLVMLookupIntrinsicID(name.pointer, name.length); } for (i: 0..module.llvm.attribute_table.length) { >e: LLVMAttributeIndex = #enum_from_int(i); >name = #enum_name(e); >attribute_id = LLVMGetEnumAttributeKindForName(name.pointer, name.length); assert(attribute_id != 0); module.llvm.attribute_table[i] = attribute_id; } >global = module.first_global; while (global) { assert(!module.current_function); assert(!module.current_macro_instantiation); assert(!module.current_macro_declaration); if (global.emitted) { continue; } >global_pointer_type = global.variable.storage.type; assert(global_pointer_type.id == .pointer); >global_value_type = global_pointer_type.content.pointer.element_type; switch (global.variable.storage.id) { .function, .external_function => { >function_type = &global_value_type.content.function; >semantic_argument_types = function_type.base.semantic_argument_types; >semantic_return_type = function_type.base.semantic_return_type; function_type.abi.argument_abis = arena_allocate_slice[AbiInformation](module.arena, semantic_argument_types.length); >llvm_abi_argument_type_buffer: [64]&LLVMType = undefined; >resolved_calling_convention: ResolvedCallingConvention = undefined; >calling_convention = function_type.base.calling_convention; switch (calling_convention) { .c => { // TODO: switch on platform resolved_calling_convention = .system_v; }, } >is_reg_call = resolved_calling_convention == .system_v and 0; // TODO: regcall calling convention switch (resolved_calling_convention) { .system_v => { >abi_argument_type_buffer: [64]&Type = undefined; >abi_argument_type_count: u16 = 0; function_type.abi.available_registers = { .system_v = { .gpr = #select(is_reg_call, 11, 6), .sse = #select(is_reg_call, 16, 8), }, }; function_type.abi.return_abi = abi_system_v_classify_return_type(module, resolve_alias(module, semantic_return_type)); >return_abi_kind = function_type.abi.return_abi.flags.kind; >abi_return_type: &Type = undefined; switch (return_abi_kind) { .direct, .extend => { abi_return_type = function_type.abi.return_abi.coerce_to_type; }, else => { unreachable; }, } assert(abi_return_type != zero); function_type.abi.abi_return_type = abi_return_type; resolve_type_in_place(module, abi_return_type); if (return_abi_kind == .indirect) { #trap(); } if (semantic_argument_types.length != 0) { for (i: 0..semantic_argument_types.length) { #trap(); } } }, .win64 => { #trap(); }, } >llvm_function_type = LLVMFunctionType(function_type.abi.abi_return_type.llvm.abi, &llvm_abi_argument_type_buffer[0], #truncate(function_type.abi.abi_argument_types.length), #extend(function_type.base.is_variable_argument)); >subroutine_type: &LLVMMetadata = zero; if (module.has_debug_info) { >debug_argument_type_buffer: [64]&LLVMMetadata = undefined; >debug_argument_types = debug_argument_type_buffer[..function_type.abi.argument_abis.length + 1 + #extend(function_type.base.is_variable_argument)]; debug_argument_types[0] = function_type.abi.return_abi.semantic_type.llvm.debug; assert(debug_argument_types[0] != zero); // TODO: support double slicing: sliceable_value[1..][0..length] -- 2 slicing expressions >debug_argument_type_slice = debug_argument_types[1..]; debug_argument_type_slice = debug_argument_type_slice[0..function_type.abi.argument_abis.length]; for (i: 0..function_type.abi.argument_abis.length) { >abi = &function_type.abi.argument_abis[i]; >debug_argument_type = &debug_argument_type_slice[i]; debug_argument_type.& = abi.semantic_type.llvm.debug; assert(debug_argument_type.& != zero); } if (function_type.base.is_variable_argument) { >void_ty = void_type(module); assert(void_ty.llvm.debug != zero); debug_argument_types[function_type.abi.argument_abis.length + 1] = void_ty.llvm.debug; #trap(); } >flags: LLVMDIFlags = zero; subroutine_type = LLVMDIBuilderCreateSubroutineType(module.llvm.di_builder, module.llvm.file, debug_argument_types.pointer, #truncate(debug_argument_types.length), flags); } assert(global.variable.storage.type.id == .pointer); >value_type = global.variable.storage.type.content.pointer.element_type; value_type.llvm.abi = llvm_function_type; value_type.llvm.debug = subroutine_type; >llvm_linkage: LLVMLinkage = undefined; switch (global.linkage) { .internal => { llvm_linkage = .internal; }, .external => { llvm_linkage = .external; }, } >llvm_function = llvm_module_create_function(module.llvm.module, llvm_function_type, llvm_linkage, default_address_space, global.variable.name); >llvm_calling_convention: LLVMCallingConvention = undefined; switch (calling_convention) { .c => { llvm_calling_convention = .c; }, } LLVMSetFunctionCallConv(llvm_function, llvm_calling_convention); emit_attributes(module, llvm_function, &LLVMAddAttributeAtIndex, { .return_abi = function_type.abi.return_abi, .argument_abis = function_type.abi.argument_abis, .abi_argument_types = function_type.abi.abi_argument_types, .abi_return_type = function_type.abi.abi_return_type, .attributes = global.variable.storage.content.function.attributes, }); }, .global => { #trap(); }, else => { report_error(); }, } global = global.next; } #trap(); } 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; >previous: &Type = zero; 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 = bit_count, .signed = sign, }, }, .id = .integer, .name = name, zero, }; if (previous) { previous.next = type_it; } previous = type_it; type_it += 1; } } for (sign: signs) { >name = #select(sign, "s128", "u128"); type_it.& = { .content = { .integer = { .bit_count = 128, .signed = sign, }, }, .id = .integer, .name = name, zero, }; previous.next = type_it; previous = type_it; type_it += 1; } >void_type = type_it; type_it += 1; >noreturn_type = type_it; type_it += 1; previous.next = void_type; void_type.& = { .id = .void, .name = "void", .next = noreturn_type, zero, }; noreturn_type.& = { .id = .noreturn, .name = "noreturn", zero, }; >void_value = arena_allocate[Value](arena, 1); void_value.& = { .id = .infer_or_ignore, .type = void_type, zero, }; >module: Module = { .arena = arena, .content = options.content, .void_value = void_value, .name = options.name, .path = options.path, .executable = options.executable, .objects = options.objects, .libraries = options.libraries, .target = options.target, .build_mode = options.build_mode, .has_debug_info = options.has_debug_info, .silent = options.silent, zero, }; module.scope.types.first = base_type_allocation; module.scope.types.last = noreturn_type; 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; objects = [ output_object_path ][..]; >options: CompileOptions = { .executable = output_executable_path, .objects = objects, .libraries = zero, .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; }