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_file_write_partially = fn (fd: File, pointer: &u8, length: u64) u64 { >result = write(fd, pointer, length); assert(result > 0); return #extend(result); } os_file_write = fn (fd: File, buffer: []u8) void { >total_written_byte_count: u64 = 0; while (total_written_byte_count < buffer.length) { >written_byte_count = os_file_write_partially(fd, buffer.pointer + total_written_byte_count, buffer.length - total_written_byte_count); total_written_byte_count += written_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; } print = fn (string: []u8) void { os_file_write(1, string); } 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; LLVMUse = 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 = "self-hosted-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, } type_is_signed = fn (type: &Type) u1 { switch (type.id) { .integer => { return type.content.integer.signed; }, else => { #trap(); }, } } EvaluationKind = enum { scalar, aggregate, complex, } get_evaluation_kind = fn (type: &Type) EvaluationKind { switch (type.id) { .integer, .pointer, .bits, .enum, => { return .scalar; }, .array, .struct, .union, .enum_array, => { return .aggregate; }, else => { unreachable; }, } } integer_max_value = fn (bit_count: u64, signed: u1) u64 { >value: u64 = #select(bit_count == 64, ~0, (1 << (bit_count - #extend(signed))) - 1); return value; } 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) { .integer => { >aligned_byte_count: u32 = #truncate(aligned_byte_count_from_bit_count(type.content.integer.bit_count)); assert(aligned_byte_count == 1 or aligned_byte_count == 2 or aligned_byte_count == 4 or aligned_byte_count == 8 or aligned_byte_count == 16); return aligned_byte_count; }, 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, forward_declared_function, function, constant_integer, global, } ValueConstantInteger = struct { value: u64, signed: u1, } ValueFunctionLLVM = struct { return_block: &LLVMBasicBlock, return_alloca: &LLVMValue, } ValueFunction = struct { arguments: []Argument, scope: Scope, block: &Block, llvm: ValueFunctionLLVM, attributes: FunctionAttributes, } ValueContent = union { constant_integer: ValueConstantInteger, function: ValueFunction, } ValueKind = enum { right, left, } Value = struct { content: ValueContent, type: &Type, id: ValueId, kind: ValueKind, llvm: &LLVMValue, } 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, } LLVMOptimizationLevel = enum u3 { O0 = 0, O1 = 1, O2 = 2, O3 = 3, Os = 4, Oz = 5, } LLVMOptimizationOptions = bits u64 { optimization_level: LLVMOptimizationLevel, debug_info: u1, loop_unrolling: u1, loop_interleaving: u1, loop_vectorization: u1, slp_vectorization: u1, merge_functions: u1, call_graph_profile: u1, unified_lto: u1, assignment_tracking: u1, verify_module: u1, reserved: u51, } LLVMCodeGenerationFileType = enum u8 { assembly = 0, object = 1, null = 2, } LLVMCodeGenerationOptions = struct { output_dwarf_file_path: []u8, output_file_path: []u8, file_type: LLVMCodeGenerationFileType, optimize_when_possible: u8, verify_module: u8, } LLVMCodeGenerationResult = enum u8 { success = 0, failed_to_create_file = 1, failed_to_emit_passes = 2, } [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] LLVMConstInt = fn [cc(c)] (type: &LLVMType, value: u64, is_signed: s32) &LLVMValue; [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] LLVMDIBuilderCreatePointerType = fn [cc(c)] (di_builder: &LLVMDIBuilder, element_type: &LLVMMetadata, bit_size: u64, bit_alignment: u32, address_space: u32, name_pointer: &u8, name_length: u64) &LLVMMetadata; [extern] LLVMDIBuilderCreateSubroutineType = fn [cc(c)] (di_builder: &LLVMDIBuilder, file: &LLVMMetadata, argument_type_pointer: &&LLVMMetadata, argument_type_count: u32, flags: LLVMDIFlags) &LLVMMetadata; [extern] LLVMDIBuilderCreateFunction = fn [cc(c)] (di_builder: &LLVMDIBuilder, scope: &LLVMMetadata, name_pointer: &u8, name_length: u64, linkage_name_pointer: &u8, linkage_name_length: u64, file: &LLVMMetadata, line: u32, type: &LLVMMetadata, is_local_to_unit: s32, is_definition: s32, scope_line: u32, flags: LLVMDIFlags, is_optimized: s32) &LLVMMetadata; [extern] LLVMDIBuilderFinalizeSubprogram = fn [cc(c)] (di_builder: &LLVMDIBuilder, subprogram: &LLVMMetadata) void; [extern] LLVMDIBuilderCreateLexicalBlock = fn [cc(c)] (di_builder: &LLVMDIBuilder, scope: &LLVMMetadata, file: &LLVMMetadata, line: u32, column: u32) &LLVMMetadata; [extern] LLVMDIBuilderCreateDebugLocation = fn [cc(c)] (context: &LLVMContext, line: u32, column: u32, scope: &LLVMMetadata, inlined_at: &LLVMMetadata) &LLVMMetadata; [extern] LLVMDIBuilderFinalize = fn [cc(c)] (di_builder: &LLVMDIBuilder) void; [extern] LLVMSetSubprogram = fn [cc(c)] (function: &LLVMValue, subprogram: &LLVMMetadata) void; [extern] LLVMGetSubprogram = fn [cc(c)] (function: &LLVMValue) &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] LLVMCreateStringAttribute = fn [cc(c)] (context: &LLVMContext, attribute_key_pointer: &u8, attribute_key_length: u64, attribute_value_pointer: &u8, attribute_value_length: u64) &LLVMAttribute; [extern] LLVMAddAttributeAtIndex = fn [cc(c)] (value: &LLVMValue, index: u32, attribute: &LLVMAttribute) void; [extern] LLVMGetParams = fn [cc(c)] (function: &LLVMValue, parameter_pointer: &&LLVMValue) void; [extern] llvm_context_create_basic_block = fn [cc(c)] (context: &LLVMContext, name: []u8, parent_function: &LLVMValue) &LLVMBasicBlock; [extern] llvm_builder_create_alloca = fn [cc(c)] (builder: &LLVMBuilder, type: &LLVMType, address_space: u32, alignment: u32, name: []u8) &LLVMValue; [extern] LLVMBuildStore = fn [cc(c)] (builder: &LLVMBuilder, source: &LLVMValue, destination: &LLVMValue) &LLVMValue; [extern] LLVMBuildLoad2 = fn [cc(c)] (builder: &LLVMBuilder, type: &LLVMType, pointer: &LLVMValue, name: &u8) &LLVMValue; [extern] LLVMPositionBuilderAtEnd = fn [cc(c)] (builder: &LLVMBuilder, basic_block: &LLVMBasicBlock) void; [extern] LLVMClearInsertionPosition = fn [cc(c)] (builder: &LLVMBuilder) void; [extern] LLVMSetCurrentDebugLocation2 = fn [cc(c)] (builder: &LLVMBuilder, debug_location: &LLVMMetadata) void; [extern] LLVMBuildRet = fn [cc(c)] (builder: &LLVMBuilder, value: &LLVMValue) &LLVMValue; [extern] LLVMBuildRetVoid = fn [cc(c)] (builder: &LLVMBuilder) &LLVMValue; [extern] LLVMBuildBr = fn [cc(c)] (builder: &LLVMBuilder, target_block: &LLVMBasicBlock) &LLVMValue; [extern] LLVMBuildIntCast2 = fn [cc(c)] (builder: &LLVMBuilder, value: &LLVMValue, type: &LLVMType, signed: s32, name: &u8) &LLVMValue; [extern] LLVMGetInsertBlock = fn [cc(c)] (builder: &LLVMBuilder) &LLVMBasicBlock; [extern] LLVMGetBasicBlockTerminator = fn [cc(c)] (basic_block: &LLVMBasicBlock) &LLVMValue; [extern] LLVMGetBasicBlockParent = fn [cc(c)] (basic_block: &LLVMBasicBlock) &LLVMValue; [extern] LLVMInstructionEraseFromParent = fn [cc(c)] (value: &LLVMValue) void; [extern] LLVMGetOperand = fn [cc(c)] (value: &LLVMValue, index: u32) &LLVMValue; [extern] LLVMGetFirstUse = fn [cc(c)] (value: &LLVMValue) &LLVMUse; [extern] LLVMGetNextUse = fn [cc(c)] (use: &LLVMUse) &LLVMUse; [extern] LLVMGetUser = fn [cc(c)] (use: &LLVMUse) &LLVMValue; [extern] LLVMIsABranchInst = fn [cc(c)] (value: &LLVMValue) &LLVMValue; [extern] LLVMIsConditional = fn [cc(c)] (value: &LLVMValue) s32; [extern] LLVMGetSuccessor = fn [cc(c)] (value: &LLVMValue, index: u32) &LLVMBasicBlock; [extern] LLVMSetAlignment = fn [cc(c)] (value: &LLVMValue, alignment: u32) void; [extern] llvm_find_return_value_dominating_store = fn [cc(c)] (builder: &LLVMBuilder, return_alloca: &LLVMValue, element_type: &LLVMType) &LLVMValue; [extern] llvm_module_verify = fn [cc(c)] (module: &LLVMModule, error_message: &[]u8) u1; [extern] llvm_module_to_string = fn [cc(c)] (module: &LLVMModule) []u8; [extern] LLVMCreateTargetMachineOptions = fn [cc(c)] () &LLVMTargetMachineOptions; [extern] LLVMTargetMachineOptionsSetCPU = fn [cc(c)] (target_machine_options: &LLVMTargetMachineOptions, cpu: &u8) void; [extern] LLVMTargetMachineOptionsSetFeatures = fn [cc(c)] (target_machine_options: &LLVMTargetMachineOptions, features: &u8) void; [extern] LLVMTargetMachineOptionsSetCodeGenOptLevel = fn [cc(c)] (target_machine_options: &LLVMTargetMachineOptions, optimization_level: LLVMCodeGenerationOptimizationLevel) void; [extern] LLVMGetTargetFromTriple = fn [cc(c)] (target_triple: &u8, target_pointer: &&LLVMTarget, error_message_pointer: &&u8) s32; [extern] LLVMCreateTargetMachineWithOptions = fn [cc(c)] (target: &LLVMTarget, target_triple: &u8, target_machine_options: &LLVMTargetMachineOptions) &LLVMTargetMachine; [extern] LLVMCreateTargetDataLayout = fn [cc(c)] (target_machine: &LLVMTargetMachine) &LLVMTargetDataLayout; [extern] LLVMSetModuleDataLayout = fn [cc(c)] (module: &LLVMModule, target_data_layout: &LLVMTargetDataLayout) void; [extern] LLVMSetTarget = fn [cc(c)] (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; [extern] llvm_module_run_optimization_pipeline = fn [cc(c)] (module: &LLVMModule, target_machine: &LLVMTargetMachine, options: &LLVMOptimizationOptions) void; [extern] llvm_module_run_code_generation_pipeline = fn [cc(c)] (module: &LLVMModule, target_machine: &LLVMTargetMachine, options: &LLVMCodeGenerationOptions) LLVMCodeGenerationResult; 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; } uint64 = fn (module: &Module) &Type { return integer_type(module, { .bit_count = 64, .signed = 0 }); } 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, .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 = .forward_declared_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; }, .pointer => { >element_type = type.content.pointer.element_type; >resolved_element_type = resolve_alias(module, element_type); if (element_type == resolved_element_type) { result = type; } else { result = get_pointer_type(module, resolved_element_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)); }, .pointer, .opaque, => { result = module.llvm.pointer_type; }, 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, .pointer, .opaque, .array, .struct, .enum_array, => { 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); }, .pointer => { >element_type = type.content.pointer.element_type; resolve_type_in_place_debug(module, element_type); result = type.llvm.debug; if (!result) { result = LLVMDIBuilderCreatePointerType(module.llvm.di_builder, element_type.llvm.debug, 64, 64, default_address_space, type.name.pointer, type.name.length); } }, 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); } add_string_attribute = fn (module: &Module, attribute_key: []u8, attribute_value: []u8, add_callback: &LLVMAttributeCallback, value: &LLVMValue, index: u32) &LLVMAttribute { >attribute = LLVMCreateStringAttribute(module.llvm.context, attribute_key.pointer, attribute_key.length, attribute_value.pointer, attribute_value.length); 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); >index: u32 = ~0; >is_noreturn = semantic_return_type == noreturn_type(module); if (is_noreturn) { add_enum_attribute(module, .noreturn, 0, add_callback, value, index); } >nounwind: u1 = 1; if (nounwind) { add_enum_attribute(module, .nounwind, 0, add_callback, value, index); } >no_inline = options.attributes.inline_behavior == .no_inline; if (no_inline) { add_enum_attribute(module, .noinline, 0, add_callback, value, index); } >always_inline = options.attributes.inline_behavior == .always_inline; if (always_inline) { add_enum_attribute(module, .alwaysinline, 0, add_callback, value, index); } if (module.has_debug_info) { add_string_attribute(module, "frame-pointer", "all", add_callback, value, index); } >is_definition = add_callback == &LLVMAddAttributeAtIndex; if (is_definition) { if (options.attributes.naked) { add_enum_attribute(module, .naked, 0, add_callback, value, index); } if (options.attributes.inline_behavior == .inline_hint) { add_enum_attribute(module, .inlinehint, 0, add_callback, value, index); } } } ResolvedCallingConvention = enum { system_v, win64, } AllocaOptions = struct { type: &Type, name: []u8, alignment: u32, } create_alloca = fn (module: &Module, options: AllocaOptions) &LLVMValue { >abi_type = options.type; resolve_type_in_place(module, abi_type); >alignment = options.alignment; if (alignment == 0) { alignment = get_byte_alignment(abi_type); } >alloca = llvm_builder_create_alloca(module.llvm.builder, abi_type.llvm.memory, default_address_space, alignment, options.name); return alloca; } StoreOptions = struct { source: &LLVMValue, destination: &LLVMValue, type: &Type, alignment: u32, } create_store = fn (module: &Module, options: StoreOptions) void { assert(options.source != zero); assert(options.destination != zero); assert(options.type != zero); >resolved_type = resolve_alias(module, options.type); resolve_type_in_place(module, resolved_type); >memory_type = resolved_type.llvm.memory; >source_value: &LLVMValue = options.source; if (resolved_type.llvm.abi != memory_type) { source_value = LLVMBuildIntCast2(module.llvm.builder, source_value, memory_type, #extend(type_is_signed(resolved_type)), ""); } >alignment = options.alignment; if (alignment == 0) { alignment = get_byte_alignment(resolved_type); } >store = LLVMBuildStore(module.llvm.builder, source_value, options.destination); LLVMSetAlignment(store, alignment); } TypeKind = enum { abi, memory, } memory_to_abi = fn (module: &Module, value: &LLVMValue, type: &Type) &LLVMValue { >result = value; if (type.llvm.memory != type.llvm.abi) { result = LLVMBuildIntCast2(module.llvm.builder, result, type.llvm.abi, #extend(type_is_signed(type)), ""); } return result; } LoadOptions = struct { type: &Type, pointer: &LLVMValue, alignment: u32, kind: TypeKind, } create_load = fn (module: &Module, options: LoadOptions) &LLVMValue { resolve_type_in_place(module, options.type); >alignment = options.alignment; if (alignment == 0) { alignment = get_byte_alignment(options.type); } >result = LLVMBuildLoad2(module.llvm.builder, options.type.llvm.memory, options.pointer, ""); LLVMSetAlignment(result, alignment); switch (options.kind) { .abi => { result = memory_to_abi(module, result, options.type); }, .memory => {}, } return result; } check_types = fn (module: &Module, expected: &Type, source: &Type) void { assert(expected != zero); assert(source != zero); if (expected != source) { >resolved_expected = resolve_alias(module, expected); >resolved_source = resolve_alias(module, source); if (resolved_expected != resolved_source) { >is_dst_p_and_source_int = resolved_expected.id == .pointer and resolved_source.id == .integer; if (!is_dst_p_and_source_int) { report_error(); } } } } typecheck = fn (module: &Module, expected: &Type, source: &Type) void { if (expected) { check_types(module, expected, source); } } TypeAnalysis = struct { indexing_type: &Type, must_be_constant: u1, } analyze_type = fn (module: &Module, value: &Value, expected_type: &Type, analysis: TypeAnalysis) void { assert(!value.type); assert(!value.llvm); if (expected_type != zero and expected_type.id == .unresolved) { #trap(); } >value_type: &Type = zero; switch (value.id) { .constant_integer => { if (!expected_type) { if (analysis.indexing_type) { expected_type = uint64(module); } } if (!expected_type) { report_error(); } resolve_type_in_place(module, expected_type); >resolved_type = resolve_alias(module, expected_type); >value_constant = value.content.constant_integer.value; >value_is_signed = value.content.constant_integer.signed; switch (resolved_type.id) { .integer => { >type_bit_count = resolved_type.content.integer.bit_count; >type_is_signed = resolved_type.content.integer.signed; if (value_is_signed) { if (type_is_signed) { report_error(); } #trap(); } else { >max_value = integer_max_value(type_bit_count, type_is_signed); if (value_constant > max_value) { report_error(); } value_type = expected_type; } }, .pointer => { value_type = uint64(module); }, else => { report_error(); }, } typecheck(module, expected_type, value_type); }, else => { #trap(); }, } assert(value_type != zero); value.type = value_type; } get_llvm_type = fn (type: &Type, kind: TypeKind) &LLVMType { switch (kind) { .abi => { return type.llvm.abi; }, .memory => { return type.llvm.memory; }, } } emit_value = fn (module: &Module, value: &Value, type_kind: TypeKind, expect_constant: u1) void { assert(value.type != zero); assert(!value.llvm); >resolved_value_type = resolve_alias(module, value.type); resolve_type_in_place(module, resolved_value_type); >must_be_constant = expect_constant or (!module.current_function and !module.current_macro_instantiation); >llvm_value: &LLVMValue = zero; switch (value.id) { .constant_integer => { >llvm_integer_type = get_llvm_type(resolved_value_type, type_kind); llvm_value = LLVMConstInt(llvm_integer_type, value.content.constant_integer.value, #extend(value.content.constant_integer.signed)); }, else => { #trap(); }, } assert(llvm_value != zero); value.llvm = llvm_value; } emit_assignment = fn (module: &Module, left_llvm: &LLVMValue, left_type: &Type, right: &Value) void { assert(right.llvm == zero); >pointer_type = left_type; >value_type = right.type; assert(pointer_type != zero); assert(value_type != zero); resolve_type_in_place(module, pointer_type); resolve_type_in_place(module, value_type); >resolved_pointer_type = resolve_alias(module, pointer_type); >resolved_value_type = resolve_alias(module, value_type); assert(resolved_pointer_type.id == .pointer); assert(resolved_pointer_type.content.pointer.element_type == resolved_value_type); >type_kind: TypeKind = .memory; >evaluation_kind = get_evaluation_kind(resolved_value_type); switch (evaluation_kind) { .scalar => { emit_value(module, right, type_kind, 0); create_store(module, { .source = right.llvm, .destination = left_llvm, .type = resolved_value_type, zero, }); }, .aggregate => { #trap(); }, .complex => { #trap(); }, } } analyze_statement = fn (module: &Module, scope: &Scope, statement: &Statement) void { >parent_function_global: &Global = undefined; if (module.current_function) { parent_function_global = module.current_function; assert(parent_function_global != zero); } else if (module.current_macro_instantiation) { #trap(); } else { report_error(); } >llvm_function = parent_function_global.variable.storage.llvm; assert(llvm_function != zero); >statement_location: &LLVMMetadata = zero; if (module.has_debug_info) { statement_location = LLVMDIBuilderCreateDebugLocation(module.llvm.context, statement.line, statement.column, scope.llvm, module.llvm.inlined_at); LLVMSetCurrentDebugLocation2(module.llvm.builder, statement_location); } switch (statement.id) { .return => { >return_value = statement.content.return; if (module.current_function) { assert(!module.current_macro_instantiation); >function_type = &parent_function_global.variable.storage.type.content.pointer.element_type.content.function; >return_abi = &function_type.abi.return_abi; switch (return_abi.semantic_type.id) { .void => { if (return_value) { report_error(); } }, .noreturn => { report_error(); }, else => { if (!return_value) { report_error(); } if (module.has_debug_info) { LLVMSetCurrentDebugLocation2(module.llvm.builder, statement_location); } >return_alloca = parent_function_global.variable.storage.content.function.llvm.return_alloca; assert(return_alloca != zero); >return_type = return_abi.semantic_type; analyze_type(module, return_value, return_type, zero); >pointer_type = get_pointer_type(module, return_type); emit_assignment(module, return_alloca, pointer_type, return_value); }, } >return_block = parent_function_global.variable.storage.content.function.llvm.return_block; assert(return_block != zero); LLVMBuildBr(module.llvm.builder, return_block); LLVMClearInsertionPosition(module.llvm.builder); } else if (module.current_macro_instantiation) { #trap(); } else { report_error(); } }, else => { #trap(); }, } } analyze_block = fn (module: &Module, block: &Block) void { if (module.has_debug_info) { >lexical_block = LLVMDIBuilderCreateLexicalBlock(module.llvm.di_builder, block.scope.parent.llvm, module.llvm.file, block.scope.line, block.scope.column); block.scope.llvm = lexical_block; } >statement = block.first_statement; while (statement) { analyze_statement(module, &block.scope, statement); statement = statement.next; } } emit_block = fn (module: &Module, basic_block: &LLVMBasicBlock) void { >current_basic_block = LLVMGetInsertBlock(module.llvm.builder); if (current_basic_block) { if (!LLVMGetBasicBlockTerminator(current_basic_block)) { LLVMBuildBr(module.llvm.builder, basic_block); } } assert(LLVMGetBasicBlockParent(basic_block) != zero); LLVMPositionBuilderAtEnd(module.llvm.builder, basic_block); } type_is_abi_equal = fn (module: &Module, a: &Type, b: &Type) u1 { resolve_type_in_place(module, a); resolve_type_in_place(module, b); >result = a == b; if (!result) { result = a.llvm.abi == b.llvm.abi; } return result; } LLVMObjectGenerate = struct { path: []u8, optimization_level: LLVMOptimizationLevel, run_optimization_passes: u1, has_debug_info: u1, } generate_object = fn (module: &LLVMModule, target_machine: &LLVMTargetMachine, options: LLVMObjectGenerate) LLVMCodeGenerationResult { if (options.run_optimization_passes) { >prefer_speed = options.optimization_level == .O2 or options.optimization_level == .O3; >options: LLVMOptimizationOptions = { .optimization_level = options.optimization_level, .debug_info = options.has_debug_info, .loop_unrolling = prefer_speed, .loop_interleaving = prefer_speed, .loop_vectorization = prefer_speed, .slp_vectorization = prefer_speed, .merge_functions = prefer_speed, .call_graph_profile = 0, .unified_lto = 0, .assignment_tracking = options.has_debug_info, .verify_module = 1, zero, }; llvm_module_run_optimization_pipeline(module, target_machine, &options); } >code_generation_options: LLVMCodeGenerationOptions = { .output_file_path = options.path, .file_type = .object, .optimize_when_possible = #extend(options.optimization_level > .O0), .verify_module = 1, zero, }; >result = llvm_module_run_code_generation_pipeline(module, target_machine, &code_generation_options); return result; } 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, .forward_declared_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); global.variable.storage.llvm = llvm_function; >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, }); >subprogram: &LLVMMetadata = zero; if (module.has_debug_info) { >name = global.variable.name; >linkage_name = name; >line = global.variable.line; >is_local_to_unit = global.linkage == .internal; >is_definition = global.variable.storage.id == .function; >scope_line = line + 1; >flags: LLVMDIFlags = zero; >is_optimized = build_mode_is_optimized(module.build_mode); subprogram = LLVMDIBuilderCreateFunction(module.llvm.di_builder, module.scope.llvm, name.pointer, name.length, linkage_name.pointer, linkage_name.length, module.llvm.file, line, subroutine_type, #extend(is_local_to_unit), #extend(is_definition), scope_line, flags, #extend(is_optimized)); LLVMSetSubprogram(llvm_function, subprogram); } switch (global.variable.storage.id) { .function => { global.variable.storage.content.function.scope.llvm = subprogram; }, .forward_declared_function => { assert(global.linkage == .external); if (module.has_debug_info) { LLVMDIBuilderFinalizeSubprogram(module.llvm.di_builder, subprogram); } }, else => { unreachable; }, } }, .global => { #trap(); }, else => { report_error(); }, } global = global.next; } global = module.first_global; while (global) { assert(!module.current_function); assert(!module.current_macro_instantiation); assert(!module.current_macro_declaration); if (global.variable.storage.id == .function) { module.current_function = global; >function = global.variable.storage; assert(function.id == .function); >pointer_type = function.type; assert(pointer_type.id == .pointer); >value_type = pointer_type.content.pointer.element_type; assert(value_type == global.variable.type); assert(value_type.id == .function); >function_type = &value_type.content.function; >semantic_argument_types = function_type.base.semantic_argument_types; >llvm_function = function.llvm; assert(llvm_function != zero); >llvm_abi_argument_buffer: [64]&LLVMValue = undefined; >llvm_abi_arguments = llvm_abi_argument_buffer[..function_type.abi.abi_argument_types.length]; LLVMGetParams(llvm_function, &llvm_abi_argument_buffer[0]); >entry_block = llvm_context_create_basic_block(module.llvm.context, "entry", llvm_function); >return_block = llvm_context_create_basic_block(module.llvm.context, "return_block", llvm_function); function.content.function.llvm.return_block = return_block; LLVMPositionBuilderAtEnd(module.llvm.builder, entry_block); LLVMSetCurrentDebugLocation2(module.llvm.builder, zero); >return_abi = &function_type.abi.return_abi; switch (return_abi.flags.kind) { .ignore => {}, .indirect => { #trap(); }, .in_alloca => { #trap(); }, else => { >alloca = create_alloca(module, { .type = return_abi.semantic_type, .name = "return_value", zero, }); function.content.function.llvm.return_alloca = alloca; }, } >arguments = function.content.function.arguments; >argument_abis = function_type.abi.argument_abis; assert(arguments.length == argument_abis.length); for (i: 0..semantic_argument_types.length) { >argument = &arguments[i]; >argument_abi = &argument_abis[i]; // TODO: double slice >argument_abi_arguments = llvm_abi_arguments[#extend(argument_abi.abi_start)..]; argument_abi_arguments = argument_abi_arguments[..#extend(argument_abi.abi_count)]; >semantic_argument_storage: &LLVMValue = zero; switch (argument_abi.flags.kind) { .direct, .extend => { #trap(); }, .indirect => { #trap(); }, else => { unreachable; }, } } analyze_block(module, function.content.function.block); >current_basic_block = LLVMGetInsertBlock(module.llvm.builder); if (current_basic_block) { assert(!LLVMGetBasicBlockTerminator(current_basic_block)); #trap(); } else { >has_single_jump_to_return_block: u1 = 0; >first_use = LLVMGetFirstUse(#pointer_cast(return_block)); >user: &LLVMValue = zero; if (first_use) { >second_use = LLVMGetNextUse(first_use); >has_one_use = first_use != zero and first_use == zero; if (has_one_use) { user = LLVMGetUser(first_use); has_single_jump_to_return_block = LLVMIsABranchInst(user) != zero and? LLVMIsConditional(user) != 0 and? LLVMGetSuccessor(user, 0) == return_block; } } if (has_single_jump_to_return_block) { #trap(); } else { emit_block(module, return_block); } } if (module.has_debug_info) { LLVMSetCurrentDebugLocation2(module.llvm.builder, zero); >subprogram = LLVMGetSubprogram(llvm_function); LLVMDIBuilderFinalizeSubprogram(module.llvm.di_builder, subprogram); } >semantic_return_type = return_abi.semantic_type; if (semantic_return_type == noreturn_type(module) or function.content.function.attributes.naked) { #trap(); } else if (semantic_return_type == void_type(module)) { LLVMBuildRetVoid(module.llvm.builder); } else { >return_value: &LLVMValue = zero; switch (return_abi.flags.kind) { .direct, .extend => { >return_alloca = function.content.function.llvm.return_alloca; >coerce_to_type = abi_get_coerce_to_type(return_abi); if (type_is_abi_equal(module, coerce_to_type, semantic_return_type) and? return_abi.attributes.direct.offset == 0) { >store = llvm_find_return_value_dominating_store(module.llvm.builder, return_alloca, semantic_return_type.llvm.abi); if (store) { return_value = LLVMGetOperand(store, 0); >alloca = LLVMGetOperand(store, 1); assert(alloca == return_alloca); LLVMInstructionEraseFromParent(store); LLVMInstructionEraseFromParent(alloca); } else { return_value = create_load(module, { .type = semantic_return_type, .pointer = return_alloca, zero, }); } } else { #trap(); } }, .indirect => { #trap(); }, } LLVMBuildRet(module.llvm.builder, return_value); } // END OF SCOPE module.current_function = zero; } global = global.next; } if (module.has_debug_info) { LLVMDIBuilderFinalize(module.llvm.di_builder); } >verification_error_message: []u8 = zero; if (!llvm_module_verify(module.llvm.module, &verification_error_message)) { print(llvm_module_to_string(module.llvm.module)); print("\n==========================\nLLVM VERIFICATION ERROR\n==========================\n"); print(verification_error_message); print("\n"); report_error(); } if (!module.silent) { print(llvm_module_to_string(module.llvm.module)); } >optimization_level: LLVMOptimizationLevel = undefined; switch (module.build_mode) { .debug_none, .debug => { optimization_level = .O0; }, .soft_optimize => { optimization_level = .O1; }, .optimize_for_speed => { optimization_level = .O2; }, .optimize_for_size => { optimization_level = .Os; }, .aggressively_optimize_for_speed => { optimization_level = .O3; }, .aggressively_optimize_for_size => { optimization_level = .Oz; }, } >object_generation_result = generate_object(module.llvm.module, module.llvm.target_machine, { .path = module.objects[0], .optimization_level = optimization_level, .run_optimization_passes = module.build_mode != .debug_none, .has_debug_info = module.has_debug_info, }); } compile = fn (arena: &Arena, options: CompileOptions) void { >module: Module = undefined; >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, .scope = &module.scope, 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, .scope = &module.scope, 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, .scope = &module.scope, zero, }; noreturn_type.& = { .id = .noreturn, .name = "noreturn", .scope = &module.scope, zero, }; >void_value = arena_allocate[Value](arena, 1); void_value.& = { .id = .infer_or_ignore, .type = void_type, zero, }; 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; }