report_error = fn () noreturn { #trap(); } 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] dup2 = fn [cc(c)] (old_fd: s32, new_fd: s32) s32; [extern] pipe = fn [cc(c)] (fd: &[2]s32) s32; [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; [extern] fork = fn [cc(c)] () s32; [extern] execve = fn [cc(c)] (name: &u8, arguments: &&u8, environment: &&u8) s32; [extern] waitpid = fn [cc(c)] (pid: s32, wait_status: &u32, options: s32) s32; 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); result[string.length] = 0; 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]; } exit_status = fn (s: u32) u8 { return #truncate((s & 0xff00) >> 8); } term_sig = fn (s: u32) u32 { return s & 0x7f; } stop_sig = fn (s: u32) u32 { return #extend(exit_status(s)); } if_exited = fn (s: u32) u1 { return term_sig(s) == 0; } if_stopped = fn (s: u32) u1 { >v: u16 = #truncate(((s & 0xffff) * 0x10001) >> 8); return v > 0x7f00; } if_signaled = fn (s: u32) u1 { return (s & 0xffff) - 1 < 0xff; } TerminationKind = enum { unknown, exit, signal, stop, } Execution = struct { streams: [2][]u8, termination_kind: TerminationKind, termination_code: u32, } StreamPolicy = enum { inherit, pipe, ignore, } ExecutionOptions = struct { policies: [2]StreamPolicy, null_fd: s32, null_fd_valid: u1, } os_execute = fn (arena: &Arena, arguments: []&u8, environment: &&u8, options: ExecutionOptions) Execution { assert(arguments.pointer[arguments.length] == zero); >result: Execution = zero; >null_file_descriptor: s32 = -1; if (options.null_fd_valid) { null_file_descriptor = options.null_fd; assert(null_file_descriptor >= 0); } else if (options.policies[0] == .ignore or options.policies[1] == .ignore) { null_file_descriptor = open("/dev/null", { .access_mode = .write_only, zero }); assert(null_file_descriptor >= 0); } >pipes: [2][2]s32 = undefined; for (i: 0..2) { if (options.policies[i] == .pipe) { if (pipe(&pipes[i]) == -1) { #trap(); } } } >pid = fork(); switch (pid) { -1 => { #trap(); }, 0 => { for (i: 0..2) { >fd: s32 = #truncate(i + 1); switch (options.policies[i]) { .inherit => {}, .pipe => { close(pipes[i][0]); dup2(pipes[i][1], fd); close(pipes[i][1]); }, .ignore => { dup2(null_file_descriptor, fd); close(null_file_descriptor); }, } } >result = execve(arguments[0], arguments.pointer, environment); if (result != -1) { unreachable; } #trap(); }, else => { for (i: 0..2) { if (options.policies[i] == .pipe) { close(pipes[i][1]); } } // TODO: better allocation strategy >allocation_size: u64 = 1024 * 1024; >allocation: []u8 = zero; >is_pipe0 = options.policies[0] == .pipe; >is_pipe1 = options.policies[1] == .pipe; if (is_pipe0 or is_pipe1) { >element_count: u64 = 0; element_count += #extend(is_pipe0); element_count += #extend(is_pipe1); allocation = arena_allocate_slice[u8](arena, allocation_size * element_count); } >offset: u64 = 0; for (i: 0..2) { if (options.policies[i] == .pipe) { >buffer = allocation[offset..offset + allocation_size]; >byte_count = read(pipes[i][0], buffer.pointer, buffer.length); assert(byte_count >= 0); result.streams[i] = buffer[..#extend(byte_count)]; close(pipes[i][0]); offset += allocation_size; } } >status: u32 = 0; >waitpid_result = waitpid(pid, &status, 0); if (waitpid_result == pid) { if (if_exited(status)) { result.termination_kind = .exit; result.termination_code = #extend(exit_status(status)); } else if (if_signaled(status)) { result.termination_kind = .signal; result.termination_code = term_sig(status); } else if (if_stopped(status)) { result.termination_kind = .stop; result.termination_code = stop_sig(status); } else { result.termination_kind = .unknown; } if (!options.null_fd_valid and null_file_descriptor > 0) { close(null_file_descriptor); } } else if (waitpid_result == -1) { #trap(); } else { #trap(); } }, } return result; } 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; LLVMDebugRecord = 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. */ } LLVMThreadLocalMode = enum u32 { none = 0, general_dynamic = 1, local_dynamic = 2, initial_exec = 3, local_exec = 4, } LLVMUnnamedAddress = enum u32 { none = 0, local = 1, global = 2, } LLVMCallingConvention = enum u32 { 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 } LLVMVerifyFailureAction = enum u32 { abort, print, return, } 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, library_directories: [][]u8, library_names: [][]u8, library_paths: [][]u8, link_libc: u1, link_libcpp: u1, 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; MacroDeclaration = struct; MacroInstantiation = 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, } TypeArray = struct { element_type: &Type, element_count: u64, next: &Type, } TypeEnumArray = struct { enum_type: &Type, element_type: &Type, next: &Type, } EnumField = struct { name: []u8, value: u64, } TypeEnum = struct { fields: []EnumField, backing_type: &Type, enum_to_string_function: &LLVMValue, string_to_enum_function: &LLVMValue, string_to_enum_struct_type: &Type, name_array: &Global, line: u32, } Field = struct { name: []u8, type: &Type, offset: u64, // Could either be bytes or bits, depending on the aggregate type line: u32, } TypeStruct = struct { fields: []Field, byte_size: u64, byte_alignment: u32, line: u32, is_slice: u1, next: &Type, } TypeBits = struct { fields: []Field, backing_type: &Type, line: u32, is_implicit_backing_type: u1, } TypeAlias = struct { type: &Type, scope: &Scope, line: u32, } UnionField = struct { type: &Type, name: []u8, line: u32, } TypeUnion = struct { fields: []UnionField, byte_size: u64, biggest_field: u64, byte_alignment: u32, line: u32, } TypeContent = union { integer: TypeInteger, function: TypeFunction, pointer: TypePointer, array: TypeArray, enum_array: TypeEnumArray, enum: TypeEnum, struct: TypeStruct, bits: TypeBits, alias: TypeAlias, union: TypeUnion, } 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; }, .enum => { return type_is_signed(type.content.enum.backing_type); }, .pointer => { return 0; }, .bits => { >backing_type = type.content.bits.backing_type; return type_is_signed(backing_type); }, .alias => { return type_is_signed(type.content.alias.type); }, 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; }, } } type_is_aggregate_type_for_abi = fn (type: &Type) u1 { >evaluation_kind = get_evaluation_kind(type); >is_member_function_pointer_type: u1 = 0; // TODO return evaluation_kind != .scalar or is_member_function_pointer_type; } is_illegal_vector_type = fn (type: &Type) u1 { switch (type.id) { .vector => { #trap(); } else => { return 0; }, } } is_arbitrary_bit_integer = fn (type: &Type) u1 { switch (type.id) { .integer => { >bit_count = type.content.integer.bit_count; switch (bit_count) { 8, 16, 32, 64, 128 => { return 0; }, else => { return 1; }, } }, .unresolved => { unreachable; }, .bits => { return is_arbitrary_bit_integer(type.content.bits.backing_type); }, .enum => { return is_arbitrary_bit_integer(type.content.enum.backing_type); }, else => { return 0; }, } } 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; }, .pointer => { return 8; }, .array => { >element_type = type.content.array.element_type; >element_count = type.content.array.element_count; assert(element_count != 0); >element_size = get_byte_size(element_type); >result = element_size * element_count; return result; }, .enum => { >byte_size = get_byte_size(type.content.enum.backing_type); return byte_size; }, .struct => { return type.content.struct.byte_size; }, .bits => { >result = get_byte_size(type.content.bits.backing_type); return result; }, .union => { >result = type.content.union.byte_size; return result; }, 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; }, .pointer => { return 8; }, .array => { >element_type = type.content.array.element_type; return get_byte_alignment(element_type); }, .enum => { >backing_type = type.content.enum.backing_type; return get_byte_alignment(backing_type); }, .struct => { >alignment = type.content.struct.byte_alignment; return alignment; }, .bits => { >backing_type = type.content.bits.backing_type; >alignment = get_byte_alignment(backing_type); return alignment; }, .union => { return type.content.union.byte_alignment; }, .alias => { return get_byte_alignment(type.content.alias.type); }, else => { #trap(); }, } } get_bit_size = fn (type: &Type) u64 { switch (type.id) { .integer => { return type.content.integer.bit_count; }, .array => { return get_bit_size(type.content.array.element_type); }, .enum => { return get_bit_size(type.content.enum.backing_type); }, .pointer => { return 64; }, .struct => { >byte_size = get_byte_size(type); return byte_size * 8; }, .alias => { >bit_size = get_bit_size(type.content.alias.type); return bit_size; }, else => { #trap(); }, } } get_byte_allocation_size = fn (type: &Type) u64 { >size = get_byte_size(type); >alignment = get_byte_alignment(type); >result = align_forward(size, #extend(alignment)); return result; } 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; }, .bits => { >backing_type = type.content.bits.backing_type; return is_promotable_integer_type_for_abi(backing_type); }, 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, unary, binary, string_literal, variable, local, unary_type, macro, call, array_initialization, array_expression, slice_expression, enum_literal, trap, va_start, has_debug_info, field_access, argument, aggregate_initialization, zero, va_arg, unreachable, undefined, select, string_to_enum, macro_instantiation, } 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, } UnaryId = enum { minus, plus, ampersand, exclamation, tilde, enum_name, extend, truncate, pointer_cast, int_from_enum, int_from_pointer, va_end, bitwise_not, dereference, pointer_from_int, enum_from_int, } unary_is_boolean = fn (id: UnaryId) u1 { switch (id) { .exclamation => { return 1; }, else => { return 0; } } } ValueUnary = struct { value: &Value, id: UnaryId, } UnaryTypeId = enum { align_of, byte_size, enum_values, integer_max, } ValueUnaryType = struct { type: &Type, id: UnaryTypeId, } BinaryId = enum { add, sub, mul, div, rem, bitwise_and, bitwise_or, bitwise_xor, shift_left, shift_right, compare_equal, compare_not_equal, compare_greater, compare_less, compare_greater_equal, compare_less_equal, logical_and, logical_or, logical_and_shortcircuit, logical_or_shortcircuit, max, min, } binary_is_boolean = fn (id: BinaryId) u1 { switch (id) { .add, .sub, .mul, .div, .rem, .bitwise_and, .bitwise_or, .bitwise_xor, .shift_left, .shift_right, .max, .min, => { return 0; }, .compare_equal, .compare_not_equal, .compare_less, .compare_less_equal, .compare_greater, .compare_greater_equal, .logical_and, .logical_or, .logical_and_shortcircuit, .logical_or_shortcircuit, => { return 1; }, } } binary_is_shortcircuiting = fn (id: BinaryId) u1 { switch (id) { .logical_and_shortcircuit, .logical_or_shortcircuit, => { return 1; }, else => { return 0; }, } } ValueBinary = struct { left: &Value, right: &Value, id: BinaryId, } ValueCall = struct { callable: &Value, arguments: []&Value, function_type: &Type, } ValueArrayInitialization = struct { values: []&Value, is_constant: u1, } ValueArrayExpression = struct { array_like: &Value, index: &Value, } ValueSliceExpression = struct { array_like: &Value, start: &Value, end: &Value, } ValueFieldAccess = struct { aggregate: &Value, field_name: []u8, } ValueVaArg = struct { va_list: &Value, type: &Type, } AggregateInitializationElement = struct { name: []u8, value: &Value, line: u32, column: u32, } ValueAggregateInitialization = struct { elements: []AggregateInitializationElement, scope: &Scope, is_constant: u1, is_zero: u1, } ValueSelect = struct { condition: &Value, true_value: &Value, false_value: &Value, } ValueStringToEnum = struct { type: &Type, string: &Value, } ConstantArgumentId = enum { value, type, } ConstantArgumentContent = union { value: &Value, type: &Type, } ConstantArgument = struct { name: []u8, content: ConstantArgumentContent, id: ConstantArgumentId, } MacroInstantiation = struct { declaration: &MacroDeclaration, instantiation_function: &Global, declaration_arguments: []Argument, instantiation_arguments: []&Value, constant_arguments: []ConstantArgument, return_type: &Type, block: &Block, scope: Scope, line: u32, column: u32, return_alloca: &LLVMValue, return_block: &LLVMBasicBlock, } ValueContent = union { constant_integer: ValueConstantInteger, function: ValueFunction, unary: ValueUnary, binary: ValueBinary, variable: &Variable, unary_type: ValueUnaryType, call: ValueCall, array_initialization: ValueArrayInitialization, array_expression: ValueArrayExpression, slice_expression: ValueSliceExpression, enum_literal: []u8, field_access: ValueFieldAccess, string_literal: []u8, va_arg: ValueVaArg, aggregate_initialization: ValueAggregateInitialization, select: ValueSelect, string_to_enum: ValueStringToEnum, macro: &MacroDeclaration, macro_instantiation: MacroInstantiation, } ValueKind = enum { right, left, } Value = struct { content: ValueContent, type: &Type, id: ValueId, kind: ValueKind, llvm: &LLVMValue, } value_is_constant = fn (value: &Value) u1 { switch (value.id) { .constant_integer, .enum_literal, .string_literal, => { return 1; }, .unary => { return value_is_constant(value.content.unary.value); }, .binary => { return value_is_constant(value.content.binary.left) and value_is_constant(value.content.binary.right); }, .field_access, => { return 0; }, .variable => { >variable = value.content.variable; return value.kind == .left and variable.scope.kind == .global; }, .array_expression => { return 0; }, .slice_expression => { return 0; }, .call => { return 0; }, .aggregate_initialization => { assert(value.type != zero); return value.content.aggregate_initialization.is_constant; }, .array_initialization => { assert(value.type != zero); return value.content.array_initialization.is_constant; }, .select => { return 0; }, else => { #trap(); } } } i128_offset: u64 = 64 * 2; void_offset: u64 = i128_offset + 2; MacroDeclaration = struct { arguments: []Argument, constant_arguments: []ConstantArgument, return_type: &Type, block: &Block, name: []u8, scope: Scope, next: &MacroDeclaration, } macro_declaration_is_generic = fn (declaration: &MacroDeclaration) u1 { return declaration.constant_arguments.length != 0; } [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] LLVMGetDefaultTargetTriple = fn [cc(c)] () &u8; [extern] LLVMGetHostCPUName = fn [cc(c)] () &u8; [extern] LLVMGetHostCPUFeatures = 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 = LLVMGetDefaultTargetTriple(); host_cpu_model = LLVMGetHostCPUName(); host_cpu_features = LLVMGetHostCPUFeatures(); 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, } LLVMICmpPredicate = enum u32 { eq = 32, ne = 33, ugt = 34, uge = 35, ult = 36, ule = 37, sgt = 38, sge = 39, slt = 40, sle = 41, } [extern] LLVMContextCreate = fn [cc(c)] () &LLVMContext; [extern] LLVMModuleCreateWithNameInContext = fn (name: &u8, context: &LLVMContext) &LLVMModule; [extern] LLVMCreateBuilderInContext = fn (context: &LLVMContext) &LLVMBuilder; [extern] LLVMTypeOf = fn (value: &LLVMValue) &LLVMType; [extern] LLVMGlobalGetValueType = fn (value: &LLVMValue) &LLVMType; [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] LLVMArrayType2 = fn [cc(c)] (element_type: &LLVMType, element_count: u64) &LLVMType; [extern] LLVMStructTypeInContext = fn [cc(c)] (context: &LLVMContext, type_pointer: &&LLVMType, type_count: u32, is_packed: s32) &LLVMType; [extern] LLVMStoreSizeOfType = fn [cc(c)] (target_data_layout: &LLVMTargetDataLayout, type: &LLVMType) u64; [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] LLVMDIBuilderCreateArrayType = fn [cc(c)] (di_builder: &LLVMDIBuilder, bit_size: u64, bit_alignment: u32, element_type: &LLVMMetadata, subscript_pointer: &LLVMMetadata, subscript_count: u32) &LLVMMetadata; [extern] LLVMDIBuilderCreateEnumerator = fn [cc(c)] (di_builder: &LLVMDIBuilder, name_pointer: &u8, name_length: u64, value: u64, sign_extend: s32) &LLVMMetadata; [extern] LLVMDIBuilderCreateEnumerationType = fn [cc(c)] (di_builder: &LLVMDIBuilder, scope: &LLVMMetadata, name_pointer: &u8, name_length: u64, file: &LLVMMetadata, line: u32, bit_size: u64, bit_alignment: u32, field_pointer: &&LLVMMetadata, field_count: u64, backing_type: &LLVMMetadata) &LLVMMetadata; [extern] LLVMDIBuilderCreateReplaceableCompositeType = fn [cc(c)] (di_builder: &LLVMDIBuilder, tag: u32, name_pointer: &u8, name_length: u64, scope: &LLVMMetadata, file: &LLVMMetadata, line: u32, runtime_language: u32, bit_size: u64, bit_alignment: u32, flags: LLVMDIFlags, unique_identifier_pointer: &u8, unique_identifier_length: u64) &LLVMMetadata; [extern] LLVMDIBuilderCreateMemberType = fn [cc(c)] (di_builder: &LLVMDIBuilder, scope: &LLVMMetadata, name_pointer: &u8, name_length: u64, file: &LLVMMetadata, line: u32, bit_size: u64, bit_alignment: u32, bit_offset: u64, flags: LLVMDIFlags, type: &LLVMMetadata) &LLVMMetadata; [extern] LLVMDIBuilderCreateBitFieldMemberType = fn [cc(c)] (di_builder: &LLVMDIBuilder, scope: &LLVMMetadata, name_pointer: &u8, name_length: u64, file: &LLVMMetadata, line: u32, bit_size: u64, bit_offset: u64, storage_bit_offset: u64, flags: LLVMDIFlags, type: &LLVMMetadata) &LLVMMetadata; [extern] LLVMDIBuilderCreateStructType = fn [cc(c)] (di_builder: &LLVMDIBuilder, scope: &LLVMMetadata, name_pointer: &u8, name_length: u64, file: &LLVMMetadata, line: u32, bit_size: u64, bit_alignment: u32, flags: LLVMDIFlags, derived_from: &LLVMMetadata, element_pointer: &&LLVMMetadata, element_count: u32, runtime_language: u32, vtable_holder: &LLVMMetadata, unique_identifier_pointer: &u8, unique_identifier_length: u64) &LLVMMetadata; [extern] LLVMDIBuilderCreateUnionType = fn [cc(c)] (di_builder: &LLVMDIBuilder, scope: &LLVMMetadata, name_pointer: &u8, name_length: u64, file: &LLVMMetadata, line: u32, bit_size: u64, bit_alignment: u32, flags: LLVMDIFlags, element_pointer: &&LLVMMetadata, element_count: u32, runtime_language: u32, unique_identifier_pointer: &u8, unique_identifier_length: u64) &LLVMMetadata; [extern] LLVMDIBuilderCreateTypedef = fn [cc(c)] (di_builder: &LLVMDIBuilder, type: &LLVMMetadata, name_pointer: &u8, name_length: u64, file: &LLVMMetadata, line: u32, scope: &LLVMMetadata, alignment: u32) &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] LLVMDIBuilderCreateGlobalVariableExpression = 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, local_to_unit: s32, expression: &LLVMMetadata, declaration: &LLVMMetadata, bit_alignment: u32) &LLVMMetadata; [extern] LLVMGlobalSetMetadata = fn [cc(c)] (global: &LLVMValue, kind: u32, metadata: &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] LLVMDIBuilderCreateAutoVariable = fn [cc(c)] (di_builder: &LLVMDIBuilder, scope: &LLVMMetadata, name_pointer: &u8, name_length: u64, file: &LLVMMetadata, line: u32, type: &LLVMMetadata, always_preserve: s32, flags: LLVMDIFlags, bit_alignment: u32) &LLVMMetadata; [extern] LLVMDIBuilderCreateParameterVariable = fn [cc(c)] (di_builder: &LLVMDIBuilder, scope: &LLVMMetadata, name_pointer: &u8, name_length: u64, argument_index: u32, file: &LLVMMetadata, line: u32, type: &LLVMMetadata, always_preserve: s32, flags: LLVMDIFlags) &LLVMMetadata; [extern] LLVMDIBuilderInsertDeclareRecordAtEnd = fn [cc(c)] (di_builder: &LLVMDIBuilder, storage: &LLVMValue, local_variable: &LLVMMetadata, expression: &LLVMMetadata, debug_location: &LLVMMetadata, basic_block: &LLVMBasicBlock) &LLVMDebugRecord; [extern] LLVMDIBuilderCreateExpression = fn [cc(c)] (di_builder: &LLVMDIBuilder, address: &u64, length: u64) &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] llvm_subprogram_replace_type = fn [cc(c)] (subprogram: &LLVMMetadata, subroutine_type: &LLVMMetadata) void; [extern] LLVMLookupIntrinsicID = fn [cc(c)] (name_pointer: &u8, name_length: u64) LLVMIntrinsicId; [extern] LLVMGetIntrinsicDeclaration = fn [cc(c)] (module: &LLVMModule, intrinsic_id: LLVMIntrinsicId, argument_type_pointer: &&LLVMType, argument_type_count: u64) &LLVMValue; [extern] LLVMIntrinsicGetType = fn [cc(c)] (context: &LLVMContext, intrinsic_id: LLVMIntrinsicId, argument_type_pointer: &&LLVMType, argument_type_count: u64) &LLVMType; [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] LLVMAddCallSiteAttribute = fn [cc(c)] (value: &LLVMValue, index: u32, attribute: &LLVMAttribute) void; [extern] LLVMGetParams = fn [cc(c)] (function: &LLVMValue, parameter_pointer: &&LLVMValue) void; [extern] LLVMAppendBasicBlockInContext = fn [cc(c)] (context: &LLVMContext, function: &LLVMValue, name: &u8) &LLVMBasicBlock; [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] LLVMSetAlignment = fn [cc(c)] (value: &LLVMValue, alignment: u32) void; [extern] LLVMBuildAlloca = fn [cc(c)] (builder: &LLVMBuilder, type: &LLVMType, name: &u8) &LLVMValue; llvm_create_alloca = fn (builder: &LLVMBuilder, type: &LLVMType, alignment: u32, name: []u8) &LLVMValue { assert(name.pointer[name.length] == 0); >alloca = LLVMBuildAlloca(builder, type, name.pointer); LLVMSetAlignment(alloca, alignment); return alloca; } [extern] LLVMAddGlobal = fn [cc(c)] (module: &LLVMModule, type: &LLVMType, name: &u8) &LLVMValue; [extern] LLVMSetGlobalConstant = fn [cc(c)] (global: &LLVMValue, is_constant: s32) void; [extern] LLVMSetLinkage = fn [cc(c)] (value: &LLVMValue, linkage_type: LLVMLinkage) void; [extern] LLVMSetInitializer = fn [cc(c)] (global: &LLVMValue, value: &LLVMValue) void; [extern] LLVMSetThreadLocalMode = fn [cc(c)] (global: &LLVMValue, thread_local_mode: LLVMThreadLocalMode) void; [extern] LLVMSetExternallyInitialized = fn [cc(c)] (global: &LLVMValue, externally_initialized: s32) void; [extern] LLVMSetUnnamedAddress = fn [cc(c)] (global: &LLVMValue, unnamed_address: LLVMUnnamedAddress) void; llvm_create_global_variable = fn (module: &LLVMModule, type: &LLVMType, is_constant: u1, linkage: LLVMLinkage, initial_value: &LLVMValue, name: []u8, thread_local_mode: LLVMThreadLocalMode, externally_initialized: u1, alignment: u32, unnamed_address: LLVMUnnamedAddress) &LLVMValue { assert(name.pointer[name.length] == 0); >global = LLVMAddGlobal(module, type, name.pointer); LLVMSetGlobalConstant(global, #extend(is_constant)); LLVMSetLinkage(global, linkage); LLVMSetInitializer(global, initial_value); LLVMSetThreadLocalMode(global, thread_local_mode); LLVMSetExternallyInitialized(global, #extend(externally_initialized)); LLVMSetUnnamedAddress(global, unnamed_address); LLVMSetAlignment(global, alignment); return global; } [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] LLVMBuildGEP2 = fn [cc(c)] (builder: &LLVMBuilder, type: &LLVMType, pointer: &LLVMValue, index_pointer: &&LLVMValue, index_length: u32, name: &u8) &LLVMValue; [extern] LLVMBuildInBoundsGEP2 = fn [cc(c)] (builder: &LLVMBuilder, type: &LLVMType, pointer: &LLVMValue, index_pointer: &&LLVMValue, index_length: u32, name: &u8) &LLVMValue; [extern] LLVMBuildStructGEP2 = fn [cc(c)] (builder: &LLVMBuilder, type: &LLVMType, pointer: &LLVMValue, index: u32, name: &u8) &LLVMValue; [extern] LLVMBuildInsertValue = fn [cc(c)] (builder: &LLVMBuilder, aggregate: &LLVMValue, element: &LLVMValue, index: u32, name: &u8) &LLVMValue; [extern] LLVMBuildExtractValue = fn [cc(c)] (builder: &LLVMBuilder, aggregate: &LLVMValue, index: u32, name: &u8) &LLVMValue; [extern] LLVMBuildRet = fn [cc(c)] (builder: &LLVMBuilder, value: &LLVMValue) &LLVMValue; [extern] LLVMBuildRetVoid = fn [cc(c)] (builder: &LLVMBuilder) &LLVMValue; [extern] LLVMBuildUnreachable = fn [cc(c)] (builder: &LLVMBuilder) &LLVMValue; [extern] LLVMBuildBr = fn [cc(c)] (builder: &LLVMBuilder, target_block: &LLVMBasicBlock) &LLVMValue; [extern] LLVMBuildCondBr = fn [cc(c)] (builder: &LLVMBuilder, condition: &LLVMValue, taken_block: &LLVMBasicBlock, not_taken_block: &LLVMBasicBlock) &LLVMValue; [extern] LLVMBuildSwitch = fn [cc(c)] (builder: &LLVMBuilder, value: &LLVMValue, else: &LLVMBasicBlock, case_count: u32) &LLVMValue; [extern] LLVMAddCase = fn [cc(c)] (switch_instruction: &LLVMValue, value: &LLVMValue, block: &LLVMBasicBlock) void; [extern] LLVMBuildPhi = fn [cc(c)] (builder: &LLVMBuilder, type: &LLVMType, name: &u8) &LLVMValue; [extern] LLVMAddIncoming = fn [cc(c)] (phi: &LLVMValue, values: &&LLVMValue, blocks: &&LLVMBasicBlock, count: u32) void; [extern] LLVMBuildSelect = fn [cc(c)] (builder: &LLVMBuilder, condition: &LLVMValue, true_value: &LLVMValue, false_value: &LLVMValue, name: &u8) &LLVMValue; [extern] LLVMBuildIntToPtr = fn [cc(c)] (builder: &LLVMBuilder, value: &LLVMValue, type: &LLVMType, name: &u8) &LLVMValue; [extern] LLVMBuildPtrToInt = fn [cc(c)] (builder: &LLVMBuilder, value: &LLVMValue, type: &LLVMType, name: &u8) &LLVMValue; [extern] LLVMBuildIntCast2 = fn [cc(c)] (builder: &LLVMBuilder, value: &LLVMValue, type: &LLVMType, signed: s32, name: &u8) &LLVMValue; [extern] LLVMBuildNeg = fn [cc(c)] (builder: &LLVMBuilder, value: &LLVMValue, name: &u8) &LLVMValue; [extern] LLVMBuildSExt = fn [cc(c)] (builder: &LLVMBuilder, value: &LLVMValue, type: &LLVMType, name: &u8) &LLVMValue; [extern] LLVMBuildZExt = fn [cc(c)] (builder: &LLVMBuilder, value: &LLVMValue, type: &LLVMType, name: &u8) &LLVMValue; [extern] LLVMBuildTrunc = fn [cc(c)] (builder: &LLVMBuilder, value: &LLVMValue, type: &LLVMType, name: &u8) &LLVMValue; [extern] LLVMBuildNot = fn [cc(c)] (builder: &LLVMBuilder, value: &LLVMValue, name: &u8) &LLVMValue; [extern] LLVMBuildICmp = fn [cc(c)] (builder: &LLVMBuilder, predicate: LLVMICmpPredicate, left: &LLVMValue, right: &LLVMValue, name: &u8) &LLVMValue; [extern] LLVMBuildAdd = fn [cc(c)] (builder: &LLVMBuilder, left: &LLVMValue, right: &LLVMValue, name: &u8) &LLVMValue; [extern] LLVMBuildSub = fn [cc(c)] (builder: &LLVMBuilder, left: &LLVMValue, right: &LLVMValue, name: &u8) &LLVMValue; [extern] LLVMBuildMul = fn [cc(c)] (builder: &LLVMBuilder, left: &LLVMValue, right: &LLVMValue, name: &u8) &LLVMValue; [extern] LLVMBuildAnd = fn [cc(c)] (builder: &LLVMBuilder, left: &LLVMValue, right: &LLVMValue, name: &u8) &LLVMValue; [extern] LLVMBuildOr = fn [cc(c)] (builder: &LLVMBuilder, left: &LLVMValue, right: &LLVMValue, name: &u8) &LLVMValue; [extern] LLVMBuildXor = fn [cc(c)] (builder: &LLVMBuilder, left: &LLVMValue, right: &LLVMValue, name: &u8) &LLVMValue; [extern] LLVMBuildShl = fn [cc(c)] (builder: &LLVMBuilder, left: &LLVMValue, right: &LLVMValue, name: &u8) &LLVMValue; [extern] LLVMBuildAShr = fn [cc(c)] (builder: &LLVMBuilder, left: &LLVMValue, right: &LLVMValue, name: &u8) &LLVMValue; [extern] LLVMBuildLShr = fn [cc(c)] (builder: &LLVMBuilder, left: &LLVMValue, right: &LLVMValue, name: &u8) &LLVMValue; [extern] LLVMBuildSDiv = fn [cc(c)] (builder: &LLVMBuilder, left: &LLVMValue, right: &LLVMValue, name: &u8) &LLVMValue; [extern] LLVMBuildUDiv = fn [cc(c)] (builder: &LLVMBuilder, left: &LLVMValue, right: &LLVMValue, name: &u8) &LLVMValue; [extern] LLVMBuildSRem = fn [cc(c)] (builder: &LLVMBuilder, left: &LLVMValue, right: &LLVMValue, name: &u8) &LLVMValue; [extern] LLVMBuildURem = fn [cc(c)] (builder: &LLVMBuilder, left: &LLVMValue, right: &LLVMValue, name: &u8) &LLVMValue; [extern] LLVMBuildCall2 = fn [cc(c)] (builder: &LLVMBuilder, function_type: &LLVMType, function_value: &LLVMValue, argument_pointer: &&LLVMValue, argument_count: u32, name: &u8) &LLVMValue; [extern] LLVMBuildMemCpy = fn [cc(c)] (builder: &LLVMBuilder, destination: &LLVMValue, destination_alignment: u32, source: &LLVMValue, source_alignment: u32, size: &LLVMValue) &LLVMValue; [extern] LLVMBuildMemSet = fn [cc(c)] (builder: &LLVMBuilder, pointer: &LLVMValue, value: &LLVMValue, byte_count: &LLVMValue, alignment: u32) &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] LLVMGetPoison = fn [cc(c)] (type: &LLVMType) &LLVMValue; [extern] LLVMConstNull = fn [cc(c)] (type: &LLVMType) &LLVMValue; [extern] LLVMConstInt = fn [cc(c)] (type: &LLVMType, value: u64, is_signed: s32) &LLVMValue; [extern] LLVMConstNeg = fn [cc(c)] (value: &LLVMValue) &LLVMValue; [extern] LLVMConstArray2 = fn [cc(c)] (element_type: &LLVMType, value_pointer: &&LLVMValue, value_count: u64) &LLVMValue; [extern] LLVMConstStringInContext2 = fn [cc(c)] (context: &LLVMContext, pointer: &u8, length: u64, dont_null_terminate: s32) &LLVMValue; [extern] LLVMConstNamedStruct = fn [cc(c)] (type: &LLVMType, constant_pointer: &&LLVMValue, constant_count: u32) &LLVMValue; [extern] LLVMConstStructInContext = fn [cc(c)] (context: &LLVMContext, constant_pointer: &&LLVMValue, constant_count: u32, packed: s32) &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] LLVMGetFirstInstruction = fn [cc(c)] (basic_block: &LLVMBasicBlock) &LLVMValue; [extern] LLVMReplaceAllUsesWith = fn [cc(c)] (old_value: &LLVMValue, new_value: &LLVMValue) void; [extern] LLVMMetadataReplaceAllUsesWith = fn [cc(c)] (old: &LLVMMetadata, new: &LLVMMetadata) void; [extern] LLVMDeleteBasicBlock = fn [cc(c)] (basic_block: &LLVMBasicBlock) void; [extern] LLVMIsABranchInst = fn [cc(c)] (value: &LLVMValue) &LLVMValue; [extern] LLVMIsAConstant = fn [cc(c)] (value: &LLVMValue) s32; [extern] LLVMIsConditional = fn [cc(c)] (value: &LLVMValue) s32; [extern] LLVMGetSuccessor = fn [cc(c)] (value: &LLVMValue, index: u32) &LLVMBasicBlock; [extern] llvm_find_return_value_dominating_store = fn [cc(c)] (builder: &LLVMBuilder, return_alloca: &LLVMValue, element_type: &LLVMType) &LLVMValue; [extern] LLVMVerifyModule = fn [cc(c)] (module: &LLVMModule, action: LLVMVerifyFailureAction, message_pointer: &&u8) s32; [extern] LLVMPrintModuleToString = fn [cc(c)] (module: &LLVMModule) &u8; llvm_module_to_string = fn (module: &LLVMModule) []u8 { >module_string = c_string_to_slice(LLVMPrintModuleToString(module)); return module_string; } [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] LLVMAddFunction = fn [cc(c)] (module: &LLVMModule, name: &u8, function_type: &LLVMType) &LLVMValue; [extern] LLVMGetNamedFunction = fn [cc(c)] (module: &LLVMModule, name: &u8) &LLVMValue; llvm_module_create_function = fn (module: &LLVMModule, function_type: &LLVMType, linkage_type: LLVMLinkage, name: []u8) &LLVMValue { assert(name.pointer[name.length] == 0); >function = LLVMAddFunction(module, name.pointer, function_type); LLVMSetLinkage(function, linkage_type); return function; } [extern] LLVMSetFunctionCallConv = fn [cc(c)] (function: &LLVMValue, calling_convention: LLVMCallingConvention) void; [extern] LLVMSetInstructionCallConv = fn [cc(c)] (call: &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; LLDResult = struct { stdout: []u8, stderr: []u8, success: u1, } [extern] lld_elf_link = fn [cc(c)] (argument_pointer: &&u8, argument_count: u64, exit_early: u1, disable_output: u1) LLDResult; 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, first_enum_array_type: &Type, first_function_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, library_directories: [][]u8, library_names: [][]u8, library_paths: [][]u8, link_libc: u1, link_libcpp: u1, 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, } StatementSwitchDiscriminantId = enum { single, range, } StatementSwitchDiscriminantContent = union { single: &Value, range: [2]&Value, } StatementSwitchDiscriminant = struct { content: StatementSwitchDiscriminantContent, id: StatementSwitchDiscriminantId, } StatementSwitchClause = struct { values: []StatementSwitchDiscriminant, block: &Block, basic_block: &LLVMBasicBlock, } 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, } scope_to_for = fn (scope: &Scope) &StatementFor { assert(scope.kind == .for_each); return #field_parent_pointer(scope, "scope"); } scope_to_block = fn (scope: &Scope) &Block { assert(scope.kind == .local); return #field_parent_pointer(scope, "scope"); } scope_to_function = fn (scope: &Scope) &ValueFunction { assert(scope.kind == .function); return #field_parent_pointer(scope, "scope"); } scope_to_module = fn (scope: &Scope) &Module { assert(scope.kind == .global); return #field_parent_pointer(scope, "scope"); } new_local = fn (module: &Module, scope: &Scope) &Local { >result = arena_allocate[Local](module.arena, 1); result.& = zero; switch (scope.kind) { .local => { >block = scope_to_block(scope); if (block.last_local) { block.last_local.next = result; block.last_local = result; } else { block.first_local = result; block.last_local = result; } }, .for_each => { >for_each = scope_to_for(scope); if (for_each.last_local) { for_each.last_local.next = result; } else { for_each.first_local = result; } for_each.last_local = result; } else => { report_error(); } } return result; } 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; } uint1 = fn (module: &Module) &Type { return integer_type(module, { .bit_count = 1, .signed = 0 }); } uint8 = fn (module: &Module) &Type { return integer_type(module, { .bit_count = 8, .signed = 0 }); } uint32 = fn (module: &Module) &Type { return integer_type(module, { .bit_count = 32, .signed = 0 }); } uint64 = fn (module: &Module) &Type { return integer_type(module, { .bit_count = 64, .signed = 0 }); } sint32 = fn (module: &Module) &Type { return integer_type(module, { .bit_count = 32, .signed = 1 }); } 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; } left_bracket: u8 = '['; right_bracket: u8 = ']'; left_parenthesis: u8 = '('; right_parenthesis: u8 = ')'; left_brace: u8 = '{'; right_brace: u8 = '}'; format_integer_decimal = fn (buffer: []u8, v: u64) u64 { >byte_count: u64 = 0; >value = v; if (value != 0) { >reverse_buffer: [64]u8 = undefined; >reverse_index: u64 = 0; while (value != 0) { >digit_value: u8 = #truncate(value % 10); >ascii_character = digit_value + '0'; value /= 10; reverse_buffer[reverse_index] = ascii_character; reverse_index += 1; } while (reverse_index != 0) { reverse_index -= 1; buffer[byte_count] = reverse_buffer[reverse_index]; byte_count += 1; } } else { buffer[0] = '0'; byte_count = 1; } return byte_count; } array_name = fn (module: &Module, element_type: &Type, element_count: u64) []u8 { >buffer: [512]u8 = undefined; >buffer_slice = buffer[..]; >i: u64 = 0; buffer[i] = left_bracket; i += 1; i += format_integer_decimal(buffer_slice[i..], element_count); buffer[i] = right_bracket; i += 1; >element_name = element_type.name; memcpy(&buffer[i], element_name.pointer, element_name.length); i += element_name.length; >name = arena_duplicate_string(module.arena, buffer_slice[..i]); return name; } get_array_type = fn (module: &Module, element_type: &Type, element_count: u64) &Type { assert(element_type != zero); assert(element_count != 0); >array_type = module.first_array_type; while (array_type) { assert(array_type.id == .array); >candidate_element_type = array_type.content.array.element_type; >candidate_element_count = array_type.content.array.element_count; if (candidate_element_type == element_type and candidate_element_count == element_count) { return array_type; } >next = array_type.content.array.next; if (!next) { break; } array_type = next; } >last_array_type = array_type; >result = new_type(module, { .content = { .array = { .element_type = element_type, .element_count = element_count, zero, }, }, .id = .array, .name = array_name(module, element_type, element_count), .scope = element_type.scope, zero, }); if (last_array_type != zero) { last_array_type.content.array.next = result; } else { module.first_array_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); } 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; } escape_character = fn (ch: u8) u8 { switch (ch) { 'n' => { return '\n'; }, 't' => { return '\t'; }, 'r' => { return '\r'; }, '\'' => { return '\''; }, '\\' => { return '\\'; }, else => { report_error(); }, } } parse_string_literal = fn (module: &Module) []u8 { expect_character(module, '"'); >start = module.offset; >escape_character_count: u64 = 0; while (1) { >ch = module.content[module.offset]; if (ch == '"') { break; } escape_character_count += #extend(ch == '\\'); module.offset += 1; } >end = module.offset; >length = end - start - escape_character_count; >pointer = arena_allocate_bytes(module.arena, length + 1, 1); >string_literal = pointer[..length]; >source_i = start; >i: u64 = 0; while (source_i < end) { >ch = module.content[source_i]; if (ch == '\\') { source_i += 1; ch = module.content[source_i]; string_literal[i] = escape_character(ch); } else { string_literal[i] = ch; } source_i += 1; i += 1; } expect_character(module, '"'); return string_literal; } parse_name = fn (module: &Module) []u8 { >result: []u8 = undefined; if (module.content[module.offset] == '"') { result = parse_string_literal(module); } else { result = parse_identifier(module); } 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, } 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, } type_is_slice = fn (type: &Type) u1 { return type.id == .struct and type.content.struct.is_slice; } get_slice_type = fn (module: &Module, element_type: &Type) &Type { >slice_type = module.first_slice_type; while (slice_type) { assert(type_is_slice(slice_type)); assert(slice_type.content.struct.fields.length == 2); >pointer_type = slice_type.content.struct.fields[0].type; assert(pointer_type.id == .pointer); >candidate_element_type = pointer_type.content.pointer.element_type; if (candidate_element_type == element_type) { return slice_type; } >next = slice_type.content.struct.next; if (!next) { break; } slice_type = next; } >last_slice_type = slice_type; >fields = arena_allocate_slice[Field](module.arena, 2); fields[0] = { .name = "pointer", .type = get_pointer_type(module, element_type), .offset = 0, .line = 0, }; fields[1] = { .name = "length", .type = uint64(module), .offset = 8, .line = 0, }; >slice_name = arena_join_string(module.arena, [ "[]", element_type.name ][..]); >result = new_type(module, { .content = { .struct = { .fields = fields, .byte_size = 16, .byte_alignment = 8, .line = 0, .is_slice = 1, zero, }, }, .id = .struct, .name = slice_name, .scope = element_type.scope, zero, }); if (last_slice_type) { last_slice_type.content.struct.next = result; } else { module.first_slice_type = result; } return result; } get_anonymous_struct_pair = fn (module: &Module, low: &Type, high: &Type) &Type { >pair = module.first_pair_struct_type; while (pair) { assert(pair.id == .struct); assert(pair.content.struct.fields.length == 2); if (pair.content.struct.fields[0].type == low and pair.content.struct.fields[1].type == high) { return pair; } >next = pair.content.struct.next; if (!next) { break; } pair = next; } >high_alignment = get_byte_alignment(high); >alignment = #max(get_byte_alignment(low), high_alignment); >high_offset = align_forward(get_byte_size(low), #extend(alignment)); >byte_size = align_forward(high_offset + get_byte_size(high), #extend(alignment)); assert(low.scope != zero); assert(high.scope != zero); >scope = #select(low.scope.kind == .global, high.scope, low.scope); >fields = arena_allocate_slice[Field](module.arena, 2); fields[0] = { .name = "low", .type = low, .offset = 0, .line = 0, }; fields[1] = { .name = "high", .type = high, .offset = high_offset, .line = 0, }; >struct_type = new_type(module, { .content = { .struct = { .fields = fields, .byte_size = byte_size, .byte_alignment = alignment, zero, }, }, .id = .struct, .name = "", .scope = scope, zero, }); if (pair) { assert(module.first_pair_struct_type != zero); pair.content.struct.next = struct_type; } else { assert(!module.first_pair_struct_type); module.first_pair_struct_type = struct_type; } return struct_type; } get_by_value_argument_pair = fn (module: &Module, low: &Type, high: &Type) &Type { >low_size = get_byte_allocation_size(low); >high_alignment = get_byte_alignment(high); >high_start = align_forward(low_size, #extend(high_alignment)); assert(high_start != 0 and high_start <= 8); if (high_start != 8) { #trap(); } >result = get_anonymous_struct_pair(module, low, high); return result; } 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, build_mode, byte_size, enum_from_int, enum_name, enum_values, extend, field_parent_pointer, has_debug_info, integer_max, int_from_enum, int_from_pointer, max, min, 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, } 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_value = fn (module: &Module, scope: &Scope, builder: ValueBuilder) &Value; parse_type = fn (module: &Module, scope: &Scope) &Type; FunctionHeaderArgument = struct { name: []u8, line: u32, } FunctionHeaderParsing = struct { type: &Type, arguments: []FunctionHeaderArgument, attributes: FunctionAttributes, } FunctionKeyword = enum { cc, } resolve_alias = fn (module: &Module, type: &Type) &Type { >result: &Type = zero; switch (type.id) { .void, .noreturn, .integer, .enum, .function, .bits, .union, .opaque, .forward_declaration, => { 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); } }, .array => { >element_type = type.content.array.element_type; >resolved_element_type = resolve_alias(module, element_type); if (element_type == resolved_element_type) { result = type; } else { result = get_array_type(module, resolved_element_type, type.content.array.element_count); } }, .struct => { >is_slice = type.content.struct.is_slice; if (is_slice) { >old_element_type = type.content.struct.fields[0].type.content.pointer.element_type; >element_type = resolve_alias(module, old_element_type); if (old_element_type == element_type) { result = type; } else { result = get_slice_type(module, element_type); } } else { result = type; } }, .alias => { result = resolve_alias(module, type.content.alias.type); }, else => { #trap(); }, } assert(result != zero); return result; } type_function_base_compare = fn (module: &Module, a: TypeFunctionBase, b: TypeFunctionBase) u1 { >same_return_type = a.semantic_return_type == b.semantic_return_type; >same_calling_convention = a.calling_convention == b.calling_convention; >same_is_variable_argument = a.is_variable_argument == b.is_variable_argument; >same_argument_length = a.semantic_argument_types.length == b.semantic_argument_types.length; >same_argument_types = same_argument_length; if (same_argument_length) { for (i: 0..a.semantic_argument_types.length) { >a_type = a.semantic_argument_types[i]; >b_type = b.semantic_argument_types[i]; >is_same_argument_type = a_type == b_type; same_argument_types = same_argument_types and is_same_argument_type; } } >result = same_return_type and same_calling_convention and same_is_variable_argument and same_argument_types; return result; } get_function_type = fn (module: &Module, base: TypeFunctionBase) &Type { base.semantic_return_type = resolve_alias(module, base.semantic_return_type); for (i: 0..base.semantic_argument_types.length) { base.semantic_argument_types[i] = resolve_alias(module, base.semantic_argument_types[i]); } >last_function_type = module.first_function_type; while (last_function_type) { assert(last_function_type.id == .function); if (type_function_base_compare(module, base, last_function_type.content.function.base)) { return last_function_type; } >next = last_function_type.content.function.next; if (!next) { break; } last_function_type = next; } >result = new_type(module, { .content = { .function = { .base = base, zero, }, }, .id = .function, .name = "", .scope = &module.scope, zero, }); if (last_function_type) { assert(module.first_function_type != zero); last_function_type.content.function.next = result; } else { assert(!module.first_function_type); module.first_function_type = result; } return result; } parse_function_header = fn (module: &Module, scope: &Scope, mandate_argument_names: u1) FunctionHeaderParsing { >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_keyword_s2e = #string_to_enum(FunctionKeyword, function_identifier); if (!function_keyword_s2e.is_valid) { report_error(); } skip_space(module); >function_keyword = function_keyword_s2e.enum_value; switch (function_keyword) { .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, '.')) { expect_character(module, '.'); expect_character(module, '.'); skip_space(module); expect_character(module, right_parenthesis); is_variable_argument = 1; break; } if (consume_character_if_match(module, right_parenthesis)) { break; } >line = get_line(module); argument_line_buffer[semantic_argument_count] = line; >argument_name: []u8 = zero; if (mandate_argument_names) { argument_name = arena_duplicate_string(module.arena, parse_identifier(module)); skip_space(module); expect_character(module, ':'); skip_space(module); } semantic_argument_name_buffer[semantic_argument_count] = argument_name; >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) { argument_types = arena_allocate_slice[&Type](module.arena, semantic_argument_count); memcpy(#pointer_cast(argument_types.pointer), #pointer_cast(&semantic_argument_type_buffer), semantic_argument_count * #byte_size(&Type)); } >function_type = get_function_type(module, { .semantic_return_type = return_type, .semantic_argument_types = argument_types, .calling_convention = calling_convention, .is_variable_argument = is_variable_argument, }); >arguments: []FunctionHeaderArgument = zero; if (mandate_argument_names) { arguments = arena_allocate_slice[FunctionHeaderArgument](module.arena, semantic_argument_count); for (i: 0..semantic_argument_count) { arguments[i] = { .name = semantic_argument_name_buffer[i], .line = argument_line_buffer[i], }; } } return { .type = function_type, .arguments = arguments, .attributes = function_attributes, }; } TypeIntrinsic = enum { ReturnType, } 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 => { return void_type(module); }, .noreturn => { return noreturn_type(module); }, .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 { >it_scope = scope; while (it_scope) { >type = it_scope.types.first; while (type) { if (string_equal(identifier, type.name)) { return type; } type = type.next; } it_scope = it_scope.parent; } report_error(); } } } else if (start_character == '&') { module.offset += 1; skip_space(module); >element_type = parse_type(module, scope); >pointer_type = get_pointer_type(module, element_type); return pointer_type; } else if (start_character == left_bracket) { module.offset += 1; skip_space(module); >is_slice = consume_character_if_match(module, right_bracket); if (is_slice) { skip_space(module); >element_type = parse_type(module, scope); >slice_type = get_slice_type(module, element_type); return slice_type; } else { >checkpoint = get_checkpoint(module); >length_inferred: u1 = 0; if (consume_character_if_match(module, '_')) { skip_space(module); length_inferred = consume_character_if_match(module, ']'); } >length_value: &Value = zero; >element_count: u64 = 0; >resolved: u1 = 0; if (!length_inferred) { set_checkpoint(module, checkpoint); length_value = parse_value(module, scope, zero); assert(length_value != zero); if (!value_is_constant(length_value)) { report_error(); } switch (length_value.id) { .constant_integer => { element_count = length_value.content.constant_integer.value; if (element_count == 0) { report_error(); } resolved = 1; }, else => { report_error(); }, } skip_space(module); expect_character(module, right_bracket); } skip_space(module); >element_type = parse_type(module, scope); if (length_inferred) { assert(!length_value); >result = new_type(module, { .content = { .array = { .element_type = element_type, .element_count = 0, zero, }, }, .id = .array, .name = "", .scope = element_type.scope, zero, }); return result; } else { if (!resolved) { report_error(); } assert(element_count != 0); >array_type = get_array_type(module, element_type, element_count); return array_type; } } } else if (start_character == '#') { module.offset += 1; >identifier = parse_identifier(module); >intrinsic_s2e = #string_to_enum(TypeIntrinsic, identifier); if (!intrinsic_s2e.is_valid) { report_error(); } >intrinsic = intrinsic_s2e.enum_value; switch (intrinsic) { .ReturnType => { >return_type = module.current_function.variable.type.content.function.base.semantic_return_type; return return_type; }, } } 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; } 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 = zero; 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, }; }, '#' => { module.offset += 1; if (is_identifier_start(module.content[module.offset])) { >identifier = parse_identifier(module); >value_intrinsic_s2e = #string_to_enum(ValueIntrinsic, identifier); if (value_intrinsic_s2e.is_valid) { >value_intrinsic = value_intrinsic_s2e.enum_value; token = { .content = { .value_intrinsic = value_intrinsic, }, .id = .value_intrinsic, }; } else { report_error(); } } else { report_error(); } }, '<' => { >next_ch = module.content[start_index + 1]; >id: TokenId = undefined; switch (next_ch) { '<' => { id = #select(module.content[start_index + 2] == '=', .assign_shift_left, .shift_left); }, '=' => { id = .compare_less_equal; }, else => { id = .compare_less; }, } >add: u64 = undefined; switch (id) { .assign_shift_left => { add = 3; }, .shift_left, .compare_less_equal => { add = 2; }, .compare_less => { add = 1; }, else => { unreachable; }, } module.offset += add; token = { .id = id, zero, }; }, '>' => { >next_ch = module.content[start_index + 1]; >id: TokenId = undefined; switch (next_ch) { '>' => { id = #select(module.content[start_index + 2] == '=', .assign_shift_right, .shift_right); }, '=' => { id = .compare_greater_equal; }, else => { id = .compare_greater; }, } >add: u64 = undefined; switch (id) { .assign_shift_right => { add = 3; }, .shift_right, .compare_greater_equal => { add = 2; }, .compare_greater => { add = 1; }, else => { unreachable; }, } module.offset += add; token = { .id = id, zero, }; }, '=' => { >next_ch = module.content[start_index + 1]; >is_compare_equal = next_ch == '='; >id: TokenId = #select(is_compare_equal, .compare_equal, .assign); module.offset += #extend(is_compare_equal) + 1; token = { .id = id, zero, }; }, '.' => { >id: TokenId = undefined; >next_ch = module.content[start_index + 1]; switch (next_ch) { else => { id = .dot; }, '&' => { id = .pointer_dereference; }, '.' => { switch (module.content[start_index + 2]) { '.' => { id = .triple_dot; }, else => { id = .double_dot; }, } }, } >add: u64 = undefined; switch (id) { .dot => { add = 1; }, .double_dot, .pointer_dereference => { add = 2; }, .triple_dot => { add = 3; }, else => { unreachable; }, } module.offset += add; token = { .id = id, zero, }; }, '"' => { >string_literal = parse_string_literal(module); token = { .content = { .string_literal = string_literal, }, .id = .string_literal, }; }, '\'' => { module.offset += 1; >ch: u8 = undefined; if (module.content[module.offset] == '\\') { module.offset += 1; ch = escape_character(module.content[module.offset]); } else { ch = module.content[module.offset]; if (ch == '\'') { report_error(); } } module.offset += 1; expect_character(module, '\''); token = { .content = { .integer = { .value = #extend(ch), .kind = .character_literal, }, }, .id = .integer, }; }, '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, }; }, '+', '-', '*', '/', '%', '&', '|', '^', '!', => { >next_ch = module.content[start_index + 1]; >id: TokenId = undefined; if (next_ch == '=') { switch (start_character) { '+' => { id = .assign_plus; }, '-' => { id = .assign_dash; }, '*' => { id = .assign_asterisk; }, '/' => { id = .assign_forward_slash; }, '%' => { id = .assign_percentage; }, '&' => { id = .assign_ampersand; }, '|' => { id = .assign_bar; }, '^' => { id = .assign_caret; }, '!' => { id = .compare_not_equal; }, else => { unreachable; } } } else { switch (start_character) { '+' => { id = .plus; }, '-' => { id = .dash; }, '*' => { id = .asterisk; }, '/' => { id = .forward_slash; }, '%' => { id = .percentage; }, '&' => { id = .ampersand; }, '|' => { id = .bar; }, '^' => { id = .caret; }, '!' => { id = .exclamation; }, else => { unreachable; } } } token.id = id; module.offset += #extend(next_ch == '=') + 1; }, else => { if (is_identifier_start(start_character)) { >identifier = parse_identifier(module); >value_keyword_s2e = #string_to_enum(ValueKeyword, identifier); if (value_keyword_s2e.is_valid) { >value_keyword = value_keyword_s2e.enum_value; token = { .content = { .value_keyword = value_keyword, }, .id = .value_keyword, }; } else { >advance = identifier.pointer[identifier.length] == '?'; identifier.length += #extend(advance); module.offset += #extend(advance); >operator_keyword_s2e = #string_to_enum(OperatorKeyword, identifier); if (operator_keyword_s2e.is_valid) { >operator_keyword = operator_keyword_s2e.enum_value; token = { .content = { .operator_keyword = operator_keyword, }, .id = .operator_keyword, }; } else { identifier.length -= #extend(advance); module.offset -= #extend(advance); token = { .content = { .identifier = identifier, }, .id = .identifier, }; } } } else { report_error(); } } } assert(start_index != module.offset); return token; } parse_precedence = fn (module: &Module, scope: &Scope, builder: ValueBuilder) &Value; scope_to_macro_declaration = fn (scope: &Scope) &MacroDeclaration { assert(scope.kind == .macro_declaration); return #field_parent_pointer(scope, "scope"); } scope_to_macro_instantiation = fn (scope: &Scope) &MacroInstantiation { assert(scope.kind == .macro_instantiation); return #field_parent_pointer(scope, "scope"); } reference_identifier = fn (module: &Module, current_scope: &Scope, identifier: []u8, kind: ValueKind) &Value { assert(!string_equal(identifier, "")); assert(!string_equal(identifier, "_")); >variable: &Variable = zero; >scope = current_scope; while (scope != zero and variable == zero) { switch (scope.kind) { .global => { assert(module == scope_to_module(scope)); >global = module.first_global; while (global != zero) { if (string_equal(identifier, global.variable.name)) { variable = &global.variable; break; } global = global.next; } >macro_declaration = module.first_macro_declaration; while (macro_declaration) { if (string_equal(identifier, macro_declaration.name)) { >result = new_value(module); result.& = { .content = { .macro = macro_declaration, }, .id = .macro, zero, }; return result; } macro_declaration = macro_declaration.next; } }, .function => { assert(scope.parent != zero); >function = scope_to_function(scope); for (&argument: function.arguments) { if (string_equal(identifier, argument.variable.name)) { variable = &argument.variable; break; } } }, .local => { assert(scope.parent != zero); assert(scope.parent.kind != .global); >block = scope_to_block(scope); >local = block.first_local; while (local != zero) { assert(local.next == zero or block.last_local != local); if (string_equal(identifier, local.variable.name)) { variable = &local.variable; break; } local = local.next; } }, .for_each => { assert(scope.parent != zero); >for_each = scope_to_for(scope); >local = for_each.first_local; while (local) { if (string_equal(identifier, local.variable.name)) { variable = &local.variable; break; } local = local.next; } }, .macro_declaration => { assert(scope.parent != zero); >macro_declaration = scope_to_macro_declaration(scope); for (&constant_argument: macro_declaration.constant_arguments) { if (string_equal(identifier, constant_argument.name)) { #trap(); } } for (&argument: macro_declaration.arguments) { if (string_equal(identifier, argument.variable.name)) { variable = &argument.variable; break; } } }, .macro_instantiation => { assert(scope.parent != zero); >macro_instantiation = scope_to_macro_instantiation(scope); for (&argument: macro_instantiation.declaration_arguments) { if (string_equal(identifier, argument.variable.name)) { variable = &argument.variable; break; } } }, } scope = scope.parent; } if (variable != zero) { >result = new_value(module); result.& = { .content = { .variable = variable, }, .id = .variable, .kind = kind, zero, }; return result; } else { report_error(); } } parse_aggregate_initialization = fn (module: &Module, scope: &Scope, builder: ValueBuilder, end_ch: u8) &Value { skip_space(module); >element_buffer: [64]AggregateInitializationElement = undefined; >field_count: u64 = 0; >is_zero: u1 = 0; while (1) { skip_space(module); if (consume_character_if_match(module, end_ch)) { break; } >field_index = field_count; >checkpoint = get_checkpoint(module); if (consume_character_if_match(module, '.')) { >name = parse_identifier(module); skip_space(module); expect_character(module, '='); skip_space(module); >line = get_line(module); >column = get_column(module); >value = parse_value(module, scope, zero); skip_space(module); consume_character_if_match(module, ','); element_buffer[field_index] = { .name = name, .value = value, .line = line, .column = column, }; } else { >token = tokenize(module); is_zero = token.id == .value_keyword and token.content.value_keyword == .zero; if (is_zero) { skip_space(module); if (consume_character_if_match(module, ',')) { skip_space(module); } expect_character(module, right_brace); break; } else { report_error(); } } field_count += 1; } >elements = arena_allocate_slice[AggregateInitializationElement](module.arena, field_count); memcpy(#pointer_cast(elements.pointer), #pointer_cast(&element_buffer), field_count * #byte_size(AggregateInitializationElement)); >result = new_value(module); result.& = { .content = { .aggregate_initialization = { .elements = elements, .scope = scope, .is_constant = 0, .is_zero = is_zero, }, }, .id = .aggregate_initialization, .kind = builder.kind, zero, }; return result; } 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, }; }, .dash, .ampersand, .exclamation, .tilde => { assert(!builder.left); >id: UnaryId = undefined; switch (token.id) { .dash => { id = .minus; }, .ampersand => { id = .ampersand; }, .exclamation => { id = .exclamation; }, .tilde => { id = .bitwise_not; }, else => { unreachable; }, } >unary_builder = builder; unary_builder.precedence = .prefix; unary_builder.token = zero; unary_builder.kind = #select(token.id == .ampersand, .left, builder.kind); >unary_value = parse_precedence(module, scope, unary_builder); result = new_value(module); result.& = { .content = { .unary = { .value = unary_value, .id = id, }, }, .id = .unary, .kind = .right, zero, }; }, .identifier => { result = reference_identifier(module, scope, token.content.identifier, builder.kind); }, .value_intrinsic => { >intrinsic = token.content.value_intrinsic; result = new_value(module); switch (intrinsic) { .enum_from_int, .enum_name, .extend, .int_from_enum, .int_from_pointer, .truncate, .pointer_cast, .pointer_from_int, .va_end, => { >id: UnaryId = undefined; switch (intrinsic) { .enum_from_int => { id = .enum_from_int; }, .enum_name => { id = .enum_name; }, .extend => { id = .extend; }, .int_from_enum => { id = .int_from_enum; }, .int_from_pointer => { id = .int_from_pointer; }, .truncate => { id = .truncate; }, .pointer_cast => { id = .pointer_cast; }, .pointer_from_int => { id = .pointer_from_int; }, .va_end => { id = .va_end; }, else => { unreachable; }, } skip_space(module); expect_character(module, left_parenthesis); skip_space(module); >argument = parse_value(module, scope, zero); expect_character(module, right_parenthesis); result.& = { .content = { .unary = { .value = argument, .id = id, }, }, .id = .unary, zero, }; }, .align_of, .byte_size, .enum_values, .integer_max, => { skip_space(module); expect_character(module, left_parenthesis); skip_space(module); >type = parse_type(module, scope); expect_character(module, right_parenthesis); >id: UnaryTypeId = undefined; switch (intrinsic) { .align_of => { id = .align_of; }, .byte_size => { id = .byte_size; }, .enum_values => { id = .enum_values; }, .integer_max => { id = .integer_max; }, else => { unreachable; }, } result.& = { .content = { .unary_type = { .type = type, .id = id, }, }, .id = .unary_type, zero, }; }, .select => { skip_space(module); expect_character(module, left_parenthesis); skip_space(module); >condition = parse_value(module, scope, zero); expect_character(module, ','); skip_space(module); >true_value = parse_value(module, scope, zero); expect_character(module, ','); skip_space(module); >false_value = parse_value(module, scope, zero); skip_space(module); expect_character(module, right_parenthesis); result.& = { .content = { .select = { .condition = condition, .true_value = true_value, .false_value = false_value, }, }, .id = .select, zero, }; }, .string_to_enum => { skip_space(module); expect_character(module, left_parenthesis); skip_space(module); >type = parse_type(module, scope); skip_space(module); expect_character(module, ','); skip_space(module); >string_value = parse_value(module, scope, zero); skip_space(module); expect_character(module, right_parenthesis); result.& = { .content = { .string_to_enum = { .type = type, .string = string_value, }, }, .id = .string_to_enum, zero, }; }, // No argument intrinsic call .trap, .va_start, .has_debug_info, => { skip_space(module); expect_character(module, left_parenthesis); skip_space(module); expect_character(module, right_parenthesis); >id: ValueId = undefined; switch (intrinsic) { .trap => { id = .trap; }, .va_start => { id = .va_start; }, .has_debug_info => { id = .has_debug_info; }, else => { unreachable; }, } result.& = { .id = id, zero, }; }, // (value, T) .va_arg => { skip_space(module); expect_character(module, left_parenthesis); skip_space(module); >va_list = parse_value(module, scope, zero); skip_space(module); expect_character(module, ','); skip_space(module); >type = parse_type(module, scope); skip_space(module); expect_character(module, right_parenthesis); result.& = { .content = { .va_arg = { .va_list = va_list, .type = type, }, }, .id = .va_arg, zero, }; }, // TODO .va_copy => { #trap(); }, .max, .min => { #trap(); }, .build_mode => { #trap(); }, .field_parent_pointer => { #trap(); }, } }, .left_bracket => { >element_count: u64 = 0; >value_buffer: [64]&Value = undefined; skip_space(module); >checkpoint = get_checkpoint(module); >is_aggregate_initialization: u1 = 0; if (consume_character_if_match(module, '.')) { >identifier = parse_identifier(module); skip_space(module); is_aggregate_initialization = consume_character_if_match(module, '='); if (!is_aggregate_initialization) { if (!consume_character_if_match(module, ',')) { report_error(); } } } set_checkpoint(module, checkpoint); if (is_aggregate_initialization) { #trap(); } else { while (1) { skip_space(module); if (consume_character_if_match(module, right_bracket)) { break; } >value = parse_value(module, scope, zero); value_buffer[element_count] = value; element_count += 1; consume_character_if_match(module, ','); } >values = arena_allocate_slice[&Value](module.arena, element_count); memcpy(#pointer_cast(values.pointer), #pointer_cast(&value_buffer), element_count * #byte_size(&Value)); result = new_value(module); result.& = { .content = { .array_initialization = { .values = values, .is_constant = 0, // This is analyzed later }, }, .id = .array_initialization, zero, }; } }, .dot => { >identifier = parse_identifier(module); result = new_value(module); result.& = { .content = { .enum_literal = identifier, }, .id = .enum_literal, zero, }; }, .left_parenthesis => { result = parse_value(module, scope, { .kind = builder.kind, zero }); expect_character(module, right_parenthesis); }, .string_literal => { result = new_value(module); result.& = { .content = { .string_literal = token.content.string_literal, }, .id = .string_literal, zero, }; }, .left_brace => { result = parse_aggregate_initialization(module, scope, builder, right_brace); }, .value_keyword => { result = new_value(module); >value_keyword = token.content.value_keyword; >id: ValueId = undefined; switch (value_keyword) { .undefined => { id = .undefined; }, .unreachable => { id = .unreachable; }, .zero => { id = .zero; }, } result.& = { .id = id, 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 => { >operator_keyword = token.content.operator_keyword; switch (operator_keyword) { .and, ."and?", => { return .boolean_and; }, .or, ."or?", => { return .boolean_or; }, } }, .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_call_arguments = fn (module: &Module, scope: &Scope) []&Value { >arguments: []&Value = zero; >argument_count: u64 = 0; >argument_buffer: [64]&Value = undefined; while (1) { skip_space(module); if (consume_character_if_match(module, right_parenthesis)) { break; } >argument = parse_value(module, scope, zero); >argument_index = argument_count; argument_buffer[argument_index] = argument; skip_space(module); consume_character_if_match(module, ','); argument_count += 1; } if (argument_count != 0) { arguments = arena_allocate_slice[&Value](module.arena, argument_count); memcpy(#pointer_cast(arguments.pointer), #pointer_cast(&argument_buffer), argument_count * #byte_size(&Value)); } return arguments; } parse_right = fn (module: &Module, scope: &Scope, builder: ValueBuilder) &Value { >left = builder.left; assert(left != zero); >token = builder.token; >result: &Value = zero; switch (token.id) { .plus, .dash, .asterisk, .forward_slash, .percentage, .ampersand, .bar, .caret, .shift_left, .shift_right, .compare_equal, .compare_not_equal, .compare_less, .compare_less_equal, .compare_greater, .compare_greater_equal, .operator_keyword, => { >precedence = get_token_precedence(token); assert(precedence != .assignment); >id: BinaryId = undefined; switch (token.id) { .operator_keyword => { >operator_keyword = token.content.operator_keyword; switch (operator_keyword) { .and => { id = .logical_and; }, .or => { id = .logical_or; }, ."and?" => { id = .logical_and_shortcircuit; }, ."or?" => { id = .logical_or_shortcircuit; }, } }, .plus => { id = .add; }, .dash => { id = .sub; }, .asterisk => { id = .mul; }, .forward_slash => { id = .div; }, .percentage => { id = .rem; }, .ampersand => { id = .bitwise_and; }, .bar => { id = .bitwise_or; }, .caret => { id = .bitwise_xor; }, .shift_left => { id = .shift_left; }, .shift_right => { id = .shift_right; }, .compare_equal => { id = .compare_equal; }, .compare_not_equal => { id = .compare_not_equal; }, .compare_less => { id = .compare_less; }, .compare_less_equal => { id = .compare_less_equal; }, .compare_greater => { id = .compare_greater; }, .compare_greater_equal => { id = .compare_greater_equal; }, else => { unreachable; }, } >right_precedence: Precedence = #enum_from_int(#int_from_enum(precedence) + 1); >right_builder = builder; right_builder.precedence = right_precedence; right_builder.token = zero; right_builder.left = zero; >right = parse_precedence(module, scope, right_builder); result = new_value(module); result.& = { .content = { .binary = { .left = left, .right = right, .id = id, }, }, .id = .binary, .kind = .right, zero, }; }, .pointer_dereference => { result = new_value(module); result.& = { .content = { .unary = { .value = left, .id = .dereference, }, }, .id = .unary, .kind = .right, zero, }; }, .left_parenthesis => { result = new_value(module); switch (left.id) { .macro => { >declaration = left.content.macro; if (macro_declaration_is_generic(declaration)) { report_error(); } >instantiation_line = get_line(module); >instantiation_column = get_column(module); >arguments = parse_call_arguments(module, scope); result.& = { .content = { .macro_instantiation = { .declaration = declaration, .instantiation_function = module.current_function, .declaration_arguments = zero, .instantiation_arguments = arguments, .constant_arguments = zero, .return_type = declaration.return_type, .scope = { .parent = scope, .line = declaration.scope.line, .column = declaration.scope.column, .kind = .macro_instantiation, zero, }, .line = instantiation_line, .column = instantiation_column, zero, }, }, .id = .macro_instantiation, zero, }; }, else => { >arguments = parse_call_arguments(module, scope); result.& = { .content = { .call = { .callable = left, .arguments = arguments, zero, }, }, .id = .call, .kind = .right, zero, }; }, } }, .left_bracket => { skip_space(module); result = new_value(module); if (left.id == .macro) { >declaration = left.content.macro; if (!macro_declaration_is_generic(declaration)) { report_error(); } >instantiation_line = get_line(module); >instantiation_column = get_column(module); >original_constant_argument_count = declaration.constant_arguments.length; >constant_arguments = arena_allocate_slice[ConstantArgument](module.arena, original_constant_argument_count); >constant_argument_count: u64 = 0; while (1) { skip_space(module); if (consume_character_if_match(module, right_bracket)) { break; } >constant_argument_index = constant_argument_count; if (constant_argument_index == original_constant_argument_count) { report_error(); } >constant_argument = declaration.constant_arguments[constant_argument_index]; switch (constant_argument.id) { .value => { #trap(); }, .type => { >argument_type = parse_type(module, scope); constant_arguments[constant_argument_index] = { .name = constant_argument.name, .content = { .type = argument_type, }, .id = .type, }; }, } constant_argument_count += 1; skip_space(module); consume_character_if_match(module, ','); } skip_space(module); expect_character(module, left_parenthesis); if (original_constant_argument_count != constant_argument_count) { report_error(); } >instantiation_arguments = parse_call_arguments(module, scope); result.& = { .content = { .macro_instantiation = { .declaration = declaration, .instantiation_function = module.current_function, .declaration_arguments = zero, .instantiation_arguments = instantiation_arguments, .constant_arguments = constant_arguments, .return_type = declaration.return_type, .scope = { .parent = scope, .line = declaration.scope.line, .column = declaration.scope.column, .kind = .macro_instantiation, zero, }, .line = instantiation_line, .column = instantiation_column, zero, }, }, .id = .macro_instantiation, zero, }; } else { left.kind = .left; >start_value: &Value = zero; >start = !(module.content[module.offset] == '.' and module.content[module.offset + 1] == '.'); if (start) { start_value = parse_value(module, scope, zero); } >is_array = consume_character_if_match(module, right_bracket); if (is_array) { if (!start_value) { report_error(); } >index = start_value; result.& = { .content = { .array_expression = { .array_like = left, .index = index, }, }, .id = .array_expression, .kind = builder.kind, zero, }; } else { expect_character(module, '.'); expect_character(module, '.'); >end_value: &Value = zero; if (!consume_character_if_match(module, right_bracket)) { end_value = parse_value(module, scope, zero); expect_character(module, right_bracket); } result.& = { .content = { .slice_expression = { .array_like = left, .start = start_value, .end = end_value, }, }, .id = .slice_expression, zero, }; } } }, .dot => { left.kind = .left; skip_space(module); >identifier = parse_identifier(module); result = new_value(module); result.& = { .content = { .field_access = { .aggregate = left, .field_name = identifier, }, }, .id = .field_access, .kind = builder.kind, zero, }; }, else => { report_error(); }, } assert(result != zero); return result; } 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_block = fn (module: &Module, parent_scope: &Scope) &Block; 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) { '>' => { module.offset += 1; skip_space(module); >local_name = arena_duplicate_string(module.arena, parse_identifier(module)); skip_space(module); >local_type: &Type = zero; if (consume_character_if_match(module, ':')) { skip_space(module); local_type = parse_type(module, scope); skip_space(module); } expect_character(module, '='); >initial_value = parse_value(module, scope, zero); >local = new_local(module, scope); local.& = { .variable = { .type = local_type, .scope = scope, .name = local_name, .line = statement_line, .column = statement_column, zero, }, .initial_value = initial_value, zero, }; statement.content = { .local = local, }; statement.id = .local; }, '#' => { statement.content = { .expression = parse_value(module, scope, zero), }; statement.id = .expression; }, left_brace => { >block = parse_block(module, scope); statement.content = { .block = block, }; statement.id = .block; require_semicolon = 0; }, 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 => { skip_space(module); expect_character(module, left_parenthesis); skip_space(module); >condition = parse_value(module, scope, zero); skip_space(module); expect_character(module, right_parenthesis); skip_space(module); >if_statement = parse_statement(module, scope); skip_space(module); >is_else: u1 = 0; >else_statement: &Statement = zero; if (is_identifier_start(module.content[module.offset])) { >checkpoint = get_checkpoint(module); >identifier = parse_identifier(module); is_else = string_equal(identifier, "else"); if (is_else) { skip_space(module); else_statement = parse_statement(module, scope); } else { set_checkpoint(module, checkpoint); } } require_semicolon = 0; statement.content = { .if = { .condition = condition, .if = if_statement, .else = else_statement, }, }; statement.id = .if; }, .for => { skip_space(module); expect_character(module, left_parenthesis); skip_space(module); >parent_scope = scope; statement.content = { .for = { .scope = { .parent = parent_scope, .line = statement_line, .column = statement_column, .kind = .for_each, zero, }, zero, }, }; statement.id = .for; >scope = &statement.content.for.scope; >left_value_buffer: [64]ValueKind = undefined; >left_value_count: u64 = 0; while (1) { skip_space(module); >is_left = module.content[module.offset] == '&'; module.offset += #extend(is_left); >for_local_line = get_line(module); >for_local_column = get_column(module); if (is_identifier_start(module.content[module.offset])) { >local_name = arena_duplicate_string(module.arena, parse_identifier(module)); >local = new_local(module, scope); local.& = { .variable = { .scope = scope, .name = local_name, .line = for_local_line, .column = for_local_column, zero, }, zero, }; >kind: ValueKind = #select(is_left, .left, .right); left_value_buffer[left_value_count] = kind; left_value_count += 1; } else { #trap(); } skip_space(module); if (!consume_character_if_match(module, ',')) { expect_character(module, ':'); break; } } skip_space(module); >right_value_buffer: [64]&Value = undefined; >right_value_count: u64 = 0; skip_space(module); right_value_buffer[right_value_count] = parse_value(module, scope, zero); right_value_count += 1; skip_space(module); >token = tokenize(module); >for_kind: StatementForKind = undefined; switch (token.id) { .double_dot => { if (left_value_count != 1) { report_error(); } right_value_buffer[right_value_count] = parse_value(module, scope, zero); right_value_count += 1; expect_character(module, right_parenthesis); for_kind = .range; }, .right_parenthesis => { right_value_buffer[0].kind = .left; for_kind = .slice; }, else => { report_error(); }, } statement.content.for.kind = for_kind; if (for_kind == .slice and left_value_count != right_value_count) { report_error(); } >left_values = arena_allocate_slice[ValueKind](module.arena, left_value_count); memcpy(#pointer_cast(left_values.pointer), #pointer_cast(&left_value_buffer), left_value_count * #byte_size(ValueKind)); >right_values = arena_allocate_slice[&Value](module.arena, right_value_count); memcpy(#pointer_cast(right_values.pointer), #pointer_cast(&right_value_buffer), right_value_count * #byte_size(&Value)); statement.content.for.kinds = left_values; statement.content.for.iteratables = right_values; skip_space(module); >predicate = parse_statement(module, scope); statement.content.for.predicate = predicate; skip_space(module); require_semicolon = 0; }, .while => { skip_space(module); expect_character(module, left_parenthesis); skip_space(module); >condition = parse_value(module, scope, zero); skip_space(module); expect_character(module, right_parenthesis); skip_space(module); >block = parse_block(module, scope); require_semicolon = 0; statement.content = { .while = { .condition = condition, .block = block, }, }; statement.id = .while; }, .switch => { skip_space(module); expect_character(module, left_parenthesis); skip_space(module); >discriminant = parse_value(module, scope, zero); skip_space(module); expect_character(module, right_parenthesis); skip_space(module); expect_character(module, left_brace); >clause_buffer: [64]StatementSwitchClause = undefined; >clause_count: u64 = 0; while (1) { skip_space(module); >is_else: u1 = 0; if (is_identifier_start(module.content[module.offset])) { >else_checkpoint = get_checkpoint(module); >i = parse_identifier(module); is_else = string_equal(i, "else"); if (!is_else) { set_checkpoint(module, else_checkpoint); } } >clause_values: []StatementSwitchDiscriminant = zero; if (is_else) { skip_space(module); expect_character(module, '='); expect_character(module, '>'); } else { >case_buffer: [64]StatementSwitchDiscriminant = undefined; >case_count: u64 = 0; while (1) { >first_case_value = parse_value(module, scope, zero); skip_space(module); >checkpoint = get_checkpoint(module); >token = tokenize(module); >clause_discriminant: StatementSwitchDiscriminant = undefined; switch (token.id) { .triple_dot => { >last_case_value = parse_value(module, scope, zero); clause_discriminant = { .content = { .range = [ first_case_value, last_case_value ], }, .id = .range, }; }, else => { if (token.id != .comma) { set_checkpoint(module, checkpoint); } clause_discriminant = { .content = { .single = first_case_value, }, .id = .single, }; }, } switch (clause_discriminant.id) { .single => { assert(clause_discriminant.content.single != zero); }, .range => { assert(clause_discriminant.content.range[0] != zero); assert(clause_discriminant.content.range[1] != zero); }, } case_buffer[case_count] = clause_discriminant; case_count += 1; skip_space(module); if (consume_character_if_match(module, '=')) { expect_character(module, '>'); break; } } clause_values = arena_allocate_slice[StatementSwitchDiscriminant](module.arena, case_count); memcpy(#pointer_cast(clause_values.pointer), #pointer_cast(&case_buffer), case_count * #byte_size(StatementSwitchDiscriminant)); } skip_space(module); >clause_block = parse_block(module, scope); clause_buffer[clause_count] = { .values = clause_values, .block = clause_block, zero, }; clause_count += 1; consume_character_if_match(module, ','); skip_space(module); if (consume_character_if_match(module, right_brace)) { break; } } >clauses = arena_allocate_slice[StatementSwitchClause](module.arena, clause_count); memcpy(#pointer_cast(clauses.pointer), #pointer_cast(&clause_buffer), clause_count * #byte_size(StatementSwitchClause)); require_semicolon = 0; statement.content = { .switch = { .discriminant = discriminant, .clauses = clauses, }, }; statement.id = .switch; }, .break => { statement.id = .break; }, .continue => { statement.id = .continue; }, } } else { set_checkpoint(module, checkpoint); >left = parse_value(module, scope, { .kind = .left, zero }); skip_space(module); if (consume_character_if_match(module, ';')) { require_semicolon = 0; statement.content = { .expression = left, }; statement.id = .expression; } else { >token = tokenize(module); >id: StatementAssignmentId = undefined; switch (token.id) { .assign => { id = .assign; }, .assign_plus => { id = .assign_add; }, .assign_dash => { id = .assign_sub; }, .assign_asterisk => { id = .assign_mul; }, .assign_forward_slash => { id = .assign_div; }, .assign_percentage => { id = .assign_rem; }, .assign_shift_left => { id = .assign_shift_left; }, .assign_shift_right => { id = .assign_shift_right; }, .assign_ampersand => { id = .assign_and; }, .assign_bar => { id = .assign_or; }, .assign_caret => { id = .assign_xor; }, else => { unreachable; }, } skip_space(module); >right = parse_value(module, scope, zero); statement.content = { .assignment = { .left = left, .right = right, .id = id, }, }; statement.id = .assignment; } } } 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; } enum_bit_count = fn (highest_value: u64) u64 { >needed_bit_count: u64 = 64 - #leading_zeroes(highest_value); needed_bit_count = #select(needed_bit_count != 0, needed_bit_count, 1); return needed_bit_count; } 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 = arena_duplicate_string(module.arena, parse_identifier(module)); >global_forward_declaration: &Global = zero; >last_global = module.first_global; while (last_global) { if (string_equal(global_name, last_global.variable.name)) { global_forward_declaration = last_global; if (last_global.variable.storage.id != .forward_declared_function) { report_error(); } // TODO: only forbid extern, not exports if (last_global.linkage == .external) { report_error(); } break; } if (!last_global.next) { break; } last_global = last_global.next; } >type_it = module.scope.types.first; >type_forward_declaration: &Type = zero; while (type_it) { if (string_equal(global_name, type_it.name)) { if (type_it.id == .forward_declaration) { type_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; if (global_forward_declaration != zero and global_keyword != .fn) { report_error(); } switch (global_keyword) { .bits => { >is_implicit_type = module.content[module.offset] == left_brace; >backing_type: &Type = zero; if (!is_implicit_type) { backing_type = parse_type(module, scope); } skip_space(module); expect_character(module, left_brace); >field_buffer: [64]Field = undefined; >field_bit_offset: u64 = 0; >field_count: u64 = 0; while (1) { skip_space(module); if (consume_character_if_match(module, right_brace)) { break; } >field_line = get_line(module); >field_name = parse_identifier(module); skip_space(module); expect_character(module, ':'); skip_space(module); >field_type = parse_type(module, scope); >field_bit_count = get_bit_size(field_type); skip_space(module); consume_character_if_match(module, ','); field_buffer[field_count] = { .name = field_name, .type = field_type, .offset = field_bit_offset, .line = field_line, }; field_bit_offset += field_bit_count; field_count += 1; } consume_character_if_match(module, ';'); >fields = arena_allocate_slice[Field](module.arena, field_count); memcpy(#pointer_cast(fields.pointer), #pointer_cast(&field_buffer), field_count * #byte_size(Field)); >needed_bit_count = #max(next_power_of_two(field_bit_offset), 8); if (needed_bit_count > #integer_max(u32)) { report_error(); } >bit_count = needed_bit_count; if (!backing_type) { backing_type = integer_type(module, { .bit_count = bit_count, .signed = 0 }); } if (backing_type.id != .integer) { report_error(); } >backing_type_bit_size = get_bit_size(backing_type); if (backing_type_bit_size > 64) { report_error(); } new_type(module, { .content = { .bits = { .fields = fields, .backing_type = backing_type, .line = global_line, .is_implicit_backing_type = is_implicit_type, }, }, .id = .bits, .name = global_name, .scope = &module.scope, zero, }); }, .enum => { >is_implicit_type = module.content[module.offset] == left_brace; >backing_type: &Type = zero; if (!is_implicit_type) { backing_type = parse_type(module, scope); } skip_space(module); expect_character(module, left_brace); >name_buffer: [64][]u8 = undefined; >int_value_buffer: [64]u64 = undefined; >field_count: u64 = 0; >is_resolved: u1 = 1; while (1) { skip_space(module); if (consume_character_if_match(module, right_brace)) { break; } >field_index = field_count; field_count += 1; >field_name = parse_name(module); name_buffer[field_index] = field_name; skip_space(module); >field_integer_value = field_index; if (consume_character_if_match(module, '=')) { skip_space(module); >field_value = parse_value(module, scope, zero); if (is_resolved) { if (value_is_constant(field_value)) { switch (field_value.id) { .constant_integer => { field_integer_value = field_value.content.constant_integer.value; }, else => { report_error(); }, } } else { // TODO: ?? report_error(); } } else { // TODO: ?? report_error(); } } else { if (!is_resolved) { report_error(); } } int_value_buffer[field_index] = field_integer_value; skip_space(module); consume_character_if_match(module, ','); } if (is_resolved) { >fields = arena_allocate_slice[EnumField](module.arena, field_count); >highest_value: u64 = 0; for (i: 0..field_count) { >value = int_value_buffer[i]; highest_value = #max(highest_value, value); fields[i] = { .name = name_buffer[i], .value = value, }; } >needed_bit_count = enum_bit_count(highest_value); if (!backing_type) { backing_type = integer_type(module, { .bit_count = needed_bit_count, .signed = 0 }); } >enum_type = new_type(module, { .content = { .enum = { .fields = fields, .backing_type = backing_type, .line = global_line, zero, }, }, .id = .enum, .name = global_name, .scope = &module.scope, zero, }); } else { report_error(); } }, .fn => { >mandate_argument_names: u1 = 1; >function_header = parse_function_header(module, scope, mandate_argument_names); >function_type = function_header.type; >function_attributes = function_header.attributes; >semantic_argument_types = function_type.content.function.base.semantic_argument_types; >pointer_to_function_type = get_pointer_type(module, function_type); >global: &Global = zero; if (global_forward_declaration) { global = global_forward_declaration; if (global_forward_declaration.variable.type != function_type) { report_error(); } assert(global_forward_declaration.variable.storage.type == pointer_to_function_type); global.variable.name = global_name; global.variable.line = global_line; global.variable.column = global_column; } else { >storage = new_value(module); storage.& = { .type = pointer_to_function_type, .id = .forward_declared_function, // TODO: .kind = .left // ???? zero, }; global = new_global(module); global.& = { .variable = { .storage = storage, .type = function_type, .scope = scope, .name = global_name, .line = global_line, .column = global_column, }, .initial_value = zero, .linkage = #select(is_export or is_extern, .external, .internal), zero, }; } if (!consume_character_if_match(module, ';')) { module.current_function = global; >arguments = arena_allocate_slice[Argument](module.arena, semantic_argument_types.length); for (i: 0..semantic_argument_types.length) { >argument = &arguments[i]; >header_argument = &function_header.arguments[i]; >name = header_argument.name; >line = header_argument.line; >type = semantic_argument_types[i]; argument.& = { .variable = { .storage = zero, .type = type, .scope = &global.variable.storage.content.function.scope, .name = name, .line = line, .column = 0, }, .index = #truncate(i + 1), }; } global.variable.storage.content.function = { .arguments = arguments, .scope = { .parent = scope, .line = global_line, .column = global_column, .kind = .function, zero, }, .attributes = function_attributes, zero, }; global.variable.storage.id = .function; global.variable.storage.content.function.block = parse_block(module, &global.variable.storage.content.function.scope); module.current_function = zero; } }, .macro => { >constant_argument_buffer: [64]ConstantArgument = undefined; >constant_argument_count: u64 = 0; >is_generic = consume_character_if_match(module, left_bracket); >macro_declaration = arena_allocate[MacroDeclaration](module.arena, 1); macro_declaration.& = { .name = global_name, .scope = { .parent = scope, .line = global_line, .column = global_column, .kind = .macro_declaration, zero, }, zero, }; if (is_generic) { while (1) { skip_space(module); if (consume_character_if_match(module, right_bracket)) { break; } >argument_name = arena_duplicate_string(module.arena, parse_identifier(module)); skip_space(module); >has_value = consume_character_if_match(module, ':'); >constant_argument_index = constant_argument_count; if (has_value) { #trap(); } else { >ty = new_type(module, { .id = .unresolved, .name = argument_name, .scope = ¯o_declaration.scope, zero, }); constant_argument_buffer[constant_argument_index] = { .name = argument_name, .content = { .type = ty, }, .id = .type, }; } constant_argument_count += 1; } skip_space(module); // END OF SCOPE if (constant_argument_count == 0) { report_error(); } } else { assert(constant_argument_count == 0); } expect_character(module, left_parenthesis); macro_declaration.constant_arguments = arena_allocate_slice[ConstantArgument](module.arena, constant_argument_count); memcpy(#pointer_cast(macro_declaration.constant_arguments.pointer), #pointer_cast(&constant_argument_buffer), constant_argument_count * #byte_size(ConstantArgument)); if (module.first_macro_declaration) { assert(module.first_macro_declaration != zero); module.last_macro_declaration.next = macro_declaration; } else { assert(!module.first_macro_declaration); module.first_macro_declaration = macro_declaration; } module.last_macro_declaration = macro_declaration; module.current_macro_declaration = macro_declaration; >scope = ¯o_declaration.scope; >argument_buffer: [64]Argument = undefined; >argument_count: u64 = 0; while (1) { skip_space(module); if (consume_character_if_match(module, right_parenthesis)) { break; } >argument_index = argument_count; >argument_line = get_line(module); >argument_column = get_column(module); >argument_name = arena_duplicate_string(module.arena, parse_identifier(module)); skip_space(module); expect_character(module, ':'); skip_space(module); >argument_type = parse_type(module, scope); >argument = &argument_buffer[argument_index]; argument.& = { .variable = { .type = argument_type, .scope = scope, .name = argument_name, .line = argument_line, .column = argument_column, zero, }, .index = #truncate(argument_index + 1), }; argument_count += 1; skip_space(module); consume_character_if_match(module, ','); } skip_space(module); >return_type = parse_type(module, scope); macro_declaration.return_type = return_type; >arguments = arena_allocate_slice[Argument](module.arena, argument_count); memcpy(#pointer_cast(arguments.pointer), #pointer_cast(&argument_buffer), argument_count * #byte_size(Argument)); macro_declaration.arguments = arguments; skip_space(module); >block = parse_block(module, scope); macro_declaration.block = block; // END OF SCOPE module.current_macro_declaration = zero; }, .opaque => { #trap(); }, .struct => { skip_space(module); >struct_type: &Type = undefined; if (type_forward_declaration) { struct_type = type_forward_declaration; } else { struct_type = new_type(module, { .id = .forward_declaration, .name = global_name, .scope = &module.scope, zero, }); } if (consume_character_if_match(module, left_brace)) { >field_buffer: [256]Field = undefined; >byte_size: u64 = 0; >byte_alignment: u32 = 1; >field_count: u64 = 0; while (1) { skip_space(module); if (consume_character_if_match(module, right_brace)) { break; } >field_index = field_count; >field_line = get_line(module); >field_name = parse_identifier(module); skip_space(module); expect_character(module, ':'); skip_space(module); >field_type = parse_type(module, scope); >field_byte_size = get_byte_size(field_type); >field_byte_alignment = get_byte_alignment(field_type); // Align struct size by field alignment >field_byte_offset = align_forward(byte_size, #extend(field_byte_alignment)); field_buffer[field_index] = { .name = field_name, .type = field_type, .offset = field_byte_offset, .line = field_line, }; byte_size = field_byte_offset + field_byte_size; byte_alignment = #max(byte_alignment, field_byte_alignment); skip_space(module); consume_character_if_match(module, ','); field_count += 1; } byte_size = align_forward(byte_size, #extend(byte_alignment)); assert(byte_size % #extend(byte_alignment) == 0); skip_space(module); consume_character_if_match(module, ';'); >fields = arena_allocate_slice[Field](module.arena, field_count); memcpy(#pointer_cast(fields.pointer), #pointer_cast(&field_buffer), field_count * #byte_size(Field)); struct_type.content = { .struct = { .fields = fields, .byte_size = byte_size, .byte_alignment = byte_alignment, .line = global_line, .is_slice = 0, .next = zero, }, }; struct_type.id = .struct; } else { if (type_forward_declaration) { report_error(); } expect_character(module, ';'); } }, .typealias => { >aliased_type = parse_type(module, scope); if (!consume_character_if_match(module, ';')) { report_error(); } >alias_type = new_type(module, { .content = { .alias = { .type = aliased_type, .scope = scope, .line = global_line, }, }, .id = .alias, .name = global_name, .scope = scope, zero, }); }, .union => { skip_space(module); expect_character(module, left_brace); >union_type: &Type = undefined; if (type_forward_declaration) { union_type = type_forward_declaration; } else { union_type = new_type(module, { .id = .forward_declaration, .name = global_name, .scope = &module.scope, zero, }); } >field_count: u64 = 0; >biggest_field: u64 = 0; >alignment: u32 = 1; >byte_size: u64 = 0; >field_buffer: [64]UnionField = undefined; while (1) { skip_space(module); if (consume_character_if_match(module, right_brace)) { break; } >field_index = field_count; field_count += 1; >field_line = get_line(module); >field_name = parse_identifier(module); skip_space(module); expect_character(module, ':'); skip_space(module); >field_type = parse_type(module, scope); >field_alignment = get_byte_alignment(field_type); >field_size = get_byte_size(field_type); field_buffer[field_index] = { .type = field_type, .name = field_name, .line = field_line, }; biggest_field = #select(field_size > byte_size, field_index, biggest_field); alignment = #max(alignment, field_alignment); byte_size = #max(byte_size, field_size); skip_space(module); consume_character_if_match(module, ','); } skip_space(module); consume_character_if_match(module, ';'); >fields = arena_allocate_slice[UnionField](module.arena, field_count); memcpy(#pointer_cast(fields.pointer), #pointer_cast(&field_buffer), field_count * #byte_size(UnionField)); >biggest_size = get_byte_size(fields[biggest_field].type); assert(biggest_size == byte_size); union_type.content.union = { .fields = fields, .byte_size = byte_size, .byte_alignment = alignment, .line = global_line, .biggest_field = biggest_field, }; union_type.id = .union; }, } } else { set_checkpoint(module, checkpoint); } } if (!is_global_keyword) { >initial_value = parse_value(module, scope, zero); skip_space(module); expect_character(module, ';'); >global_storage = new_value(module); global_storage.& = { .id = .global, zero, }; >global = new_global(module); global.& = { .variable = { .storage = global_storage, .type = global_type, .scope = scope, .name = global_name, .line = global_line, .column = global_column, }, .initial_value = initial_value, .linkage = .internal, // TODO: linkage zero, }; } } } 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; }, .array => { >element_type = type.content.array.element_type; >element_count = type.content.array.element_count; assert(element_count != 0); resolve_type_in_place_memory(module, element_type); >array_type = LLVMArrayType2(element_type.llvm.memory, element_count); result = array_type; }, .enum => { >backing_type = type.content.enum.backing_type; resolve_type_in_place_abi(module, backing_type); result = backing_type.llvm.abi; }, .struct => { >llvm_type_buffer: [64]&LLVMType = undefined; >fields = type.content.struct.fields; for (i: 0..fields.length) { >field = &fields[i]; >field_type = field.type; resolve_type_in_place_memory(module, field_type); llvm_type_buffer[i] = field_type.llvm.memory; } >is_packed: u1 = 0; result = LLVMStructTypeInContext(module.llvm.context, &llvm_type_buffer[0], #truncate(fields.length), #extend(is_packed)); >llvm_size = LLVMStoreSizeOfType(module.llvm.target_data_layout, result); >size = get_byte_size(type); assert(llvm_size == size); }, .bits => { >backing_type = type.content.bits.backing_type; resolve_type_in_place_abi(module, backing_type); result = backing_type.llvm.abi; >llvm_size = LLVMStoreSizeOfType(module.llvm.target_data_layout, result); >size = get_byte_size(type); assert(llvm_size == size); }, .union => { >biggest_type = type.content.union.fields[type.content.union.biggest_field].type; resolve_type_in_place_memory(module, biggest_type); result = LLVMStructTypeInContext(module.llvm.context, &biggest_type.llvm.memory, 1, 0); >llvm_size = LLVMStoreSizeOfType(module.llvm.target_data_layout, result); >size = get_byte_size(type); assert(llvm_size == size); }, .alias => { >aliased = type.content.alias.type; resolve_type_in_place_abi(module, aliased); result = aliased.llvm.abi; }, 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)); }, .enum => { >backing_type = type.content.enum.backing_type; resolve_type_in_place_memory(module, backing_type); result = backing_type.llvm.memory; }, .bits => { >backing_type = type.content.bits.backing_type; resolve_type_in_place_memory(module, backing_type); result = backing_type.llvm.memory; }, .union => { >biggest_type = type.content.union.fields[type.content.union.biggest_field].type; resolve_type_in_place_memory(module, biggest_type); result = LLVMStructTypeInContext(module.llvm.context, &biggest_type.llvm.memory, 1, 0); >llvm_size = LLVMStoreSizeOfType(module.llvm.target_data_layout, result); >size = get_byte_size(type); assert(llvm_size == size); }, .alias => { >aliased = type.content.alias.type; resolve_type_in_place_memory(module, aliased); result = aliased.llvm.memory; }, else => { #trap(); }, } assert(result != zero); type.llvm.memory = result; } } align_integer_type = fn (module: &Module, type: &Type) &Type { >bit_count = get_bit_size(type); >abi_bit_count = align_bit_count(bit_count); >is_signed = type_is_signed(type); >result = integer_type(module, { .bit_count = abi_bit_count, .signed = is_signed }); return 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); } }, .array => { >element_type = type.content.array.element_type; >element_count = type.content.array.element_count; assert(element_count != 0); resolve_type_in_place_debug(module, element_type); >bit_alignment = get_byte_alignment(type) * 8; >array_type = LLVMDIBuilderCreateArrayType(module.llvm.di_builder, get_bit_size(type), bit_alignment, element_type.llvm.debug, zero, 0); result = array_type; }, .enum => { >backing_type = type.content.enum.backing_type; resolve_type_in_place_debug(module, backing_type); >field_buffer: [64]&LLVMMetadata = undefined; >fields = type.content.enum.fields; for (i: 0..fields.length) { >field = &fields[i]; >enum_field = LLVMDIBuilderCreateEnumerator(module.llvm.di_builder, field.name.pointer, field.name.length, field.value, #extend(!type_is_signed(backing_type))); field_buffer[i] = enum_field; } >debug_aligned_type = align_integer_type(module, backing_type); resolve_type_in_place_debug(module, debug_aligned_type); result = LLVMDIBuilderCreateEnumerationType(module.llvm.di_builder, module.scope.llvm, type.name.pointer, type.name.length, module.llvm.file, type.content.enum.line, get_bit_size(type), get_byte_alignment(type) * 8, &field_buffer[0], fields.length, debug_aligned_type.llvm.debug); }, .struct => { >flags: LLVMDIFlags = zero; >runtime_language: u32 = 0; >byte_size = get_byte_size(type); >alignment = get_byte_alignment(type); >name = type.name; >forward_declaration = LLVMDIBuilderCreateReplaceableCompositeType(module.llvm.di_builder, module.llvm.debug_tag, name.pointer, name.length, module.scope.llvm, module.llvm.file, type.content.struct.line, runtime_language, byte_size * 8, alignment * 8, flags, name.pointer, name.length); type.llvm.debug = forward_declaration; module.llvm.debug_tag += 1; >llvm_type_buffer: [64]&LLVMMetadata = undefined; >fields = type.content.struct.fields; for (i: 0..fields.length) { >field = &fields[i]; >field_type = field.type; resolve_type_in_place_debug(module, field_type); >member_type = LLVMDIBuilderCreateMemberType(module.llvm.di_builder, module.scope.llvm, field.name.pointer, field.name.length, module.llvm.file, field.line, get_bit_size(field_type), get_byte_alignment(field_type) * 8, field.offset * 8, flags, field_type.llvm.debug); llvm_type_buffer[i] = member_type; } >derived_from: &LLVMMetadata = zero; >runtime_language: u32 = 0; >vtable_holder: &LLVMMetadata = zero; >struct_type = LLVMDIBuilderCreateStructType(module.llvm.di_builder, module.scope.llvm, name.pointer, name.length, module.llvm.file, type.content.struct.line, byte_size * 8, alignment * 8, flags, derived_from, &llvm_type_buffer[0], #truncate(fields.length), runtime_language, vtable_holder, name.pointer, name.length); LLVMMetadataReplaceAllUsesWith(forward_declaration, struct_type); result = struct_type; }, .bits => { >llvm_type_buffer: [64]&LLVMMetadata = undefined; >fields = type.content.bits.fields; >backing_type = type.content.bits.backing_type; >flags: LLVMDIFlags = zero; for (i: 0..fields.length) { >field = &fields[i]; >field_type = field.type; resolve_type_in_place_debug(module, field_type); >bit_offset: u64 = 0; >member_type = LLVMDIBuilderCreateBitFieldMemberType(module.llvm.di_builder, module.scope.llvm, field.name.pointer, field.name.length, module.llvm.file, field.line, get_bit_size(field_type), bit_offset, field.offset, flags, backing_type.llvm.debug); llvm_type_buffer[i] = member_type; } >size = get_byte_size(type) * 8; >alignment = get_byte_alignment(type) * 8; >derived_from: &LLVMMetadata = zero; >runtime_language: u32 = 0; >vtable_holder: &LLVMMetadata = zero; >struct_type = LLVMDIBuilderCreateStructType(module.llvm.di_builder, module.scope.llvm, type.name.pointer, type.name.length, module.llvm.file, type.content.bits.line, size, alignment, flags, zero, &llvm_type_buffer[0], #truncate(fields.length), runtime_language, vtable_holder, type.name.pointer, type.name.length); result = struct_type; }, .union => { >flags: LLVMDIFlags = zero; >runtime_language: u32 = 0; >byte_size = get_byte_size(type); >alignment = get_byte_alignment(type); >forward_declaration = LLVMDIBuilderCreateReplaceableCompositeType(module.llvm.di_builder, module.llvm.debug_tag, type.name.pointer, type.name.length, module.scope.llvm, module.llvm.file, type.content.union.line, runtime_language, byte_size * 8, alignment * 8, flags, type.name.pointer, type.name.length); module.llvm.debug_tag += 1; type.llvm.debug = forward_declaration; >llvm_type_buffer: [64]&LLVMMetadata = undefined; >fields = type.content.union.fields; for (i: 0..fields.length) { >field = &fields[i]; >field_type = field.type; resolve_type_in_place_debug(module, field_type); >offset: u64 = 0; >member_type = LLVMDIBuilderCreateMemberType(module.llvm.di_builder, module.scope.llvm, field.name.pointer, field.name.length, module.llvm.file, field.line, get_byte_size(field_type) * 8, get_byte_alignment(field_type) * 8, offset, flags, field_type.llvm.debug); llvm_type_buffer[i] = member_type; } >runtime_language: u32 = 0; >union_type = LLVMDIBuilderCreateUnionType(module.llvm.di_builder, module.scope.llvm, type.name.pointer, type.name.length, module.llvm.file, type.content.union.line, byte_size * 8, alignment * 8, flags, &llvm_type_buffer[0], #truncate(fields.length), runtime_language, type.name.pointer, type.name.length); LLVMMetadataReplaceAllUsesWith(forward_declaration, union_type); result = union_type; }, .alias => { >aliased = type.content.alias.type; resolve_type_in_place_debug(module, aliased); >alignment = get_byte_alignment(aliased); result = LLVMDIBuilderCreateTypedef(module.llvm.di_builder, aliased.llvm.debug, type.name.pointer, type.name.length, module.llvm.file, type.content.alias.line, type.content.alias.scope.llvm, alignment * 8); }, else => { #trap(); }, } assert(result != zero); type.llvm.debug = result; } } } AbiSystemVClass = enum { none, integer, sse, sse_up, x87, x87_up, complex_x87, memory, } // AMD64-ABI 3.2.3p2: Rule 4. Each field of an object is // classified recursively so that always two fields are // considered. The resulting class is calculated according to // the classes of the fields in the eightbyte: // // (a) If both classes are equal, this is the resulting class. // // (b) If one of the classes is NO_CLASS, the resulting class is // the other class. // // (c) If one of the classes is MEMORY, the result is the MEMORY // class. // // (d) If one of the classes is INTEGER, the result is the // INTEGER. // // (e) If one of the classes is X87, X87UP, COMPLEX_X87 class, // MEMORY is used as class. // // (f) Otherwise class SSE is used. // Accum should never be memory (we should have returned) or // ComplexX87 (because this cannot be passed in a structure). abi_system_v_merge_class = fn (accumulator: AbiSystemVClass, field: AbiSystemVClass) AbiSystemVClass { assert(accumulator != .memory and accumulator != .complex_x87); if (accumulator == field or field == .none) { return accumulator; } if (field == .memory) { return .memory; } if (accumulator == .integer or field == .integer) { return .integer; } if (field == .x87 or field == .x87_up or field == .complex_x87 or accumulator == .x87 or accumulator == .x87_up) { return .memory; } return .sse; } abi_system_v_classify_post_merge = fn (aggregate_size: u64, classes: [2]AbiSystemVClass) [2]AbiSystemVClass { >result = classes; if (result[1] == .memory) { result[0] = .memory; } if (result[1] == .x87_up) { #trap(); } if (aggregate_size > 16 and (result[0] != .sse or result[1] != .sse_up)) { result[0] = .memory; } if (result[1] == .sse_up and result[0] == .sse) { result[0] = .sse; } return result; } AbiSystemVClassifyArgument = struct { base_offset: u64, is_variable_argument: u1, 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; }, .pointer => { result[current_index] = .integer; }, .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(); } }, .struct, .union, => { >byte_size = get_byte_size(type); if (byte_size <= 64) { >has_variable_array: u1 = 0; // TODO if (!has_variable_array) { result[current_index] = .none; switch (type.id) { .struct => { >fields = type.content.struct.fields; for (&field: fields) { >offset = options.base_offset + field.offset; >member_type = field.type; >member_size = get_byte_size(member_type); >member_alignment: u64 = #extend(get_byte_alignment(member_type)); >native_vector_size: u64 = 16; >gt_16 = byte_size > 16 and ((type.id != .union and byte_size != member_size) or byte_size > native_vector_size); >padding = offset % member_alignment != 0; if (gt_16 or padding) { result[0] = .memory; result = abi_system_v_classify_post_merge(byte_size, result); return result; } >member_classes = abi_system_v_classify_type(member_type, { .base_offset = offset, .is_variable_argument = options.is_variable_argument, .register_call = options.register_call, }); for (i: 0..member_classes.length) { result[i] = abi_system_v_merge_class(result[i], member_classes[i]); } if (result[0] == .memory or result[1] == .memory) { break; } } result = abi_system_v_classify_post_merge(byte_size, result); }, .union => { #trap(); } else => { unreachable; }, } } } }, .array => { >byte_size = get_byte_size(type); if (byte_size <= 64) { if (options.base_offset % #extend(get_byte_alignment(type)) == 0) { >element_type = type.content.array.element_type; >element_size = get_byte_size(element_type); result[current_index] = .none; >vector_size: u64 = 16; if (byte_size > 16 and (byte_size != get_byte_size(element_type) or byte_size > vector_size)) { unreachable; } else { >offset = options.base_offset; >element_count = type.content.array.element_count; for (i: 0..element_count) { >element_classes = abi_system_v_classify_type(element_type, { .base_offset = offset, .is_variable_argument = options.is_variable_argument, zero, }); offset += element_size; result[0] = abi_system_v_merge_class(result[0], element_classes[0]); result[1] = abi_system_v_merge_class(result[1], element_classes[1]); if (result[0] == .memory or result[1] == .memory) { break; } } result = abi_system_v_classify_post_merge(byte_size, result); assert(result[1] != .sse or result[0] != .sse); } } } }, .bits => { return abi_system_v_classify_type(type.content.bits.backing_type, options); }, 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; >fields = type.content.struct.fields; for (&field: fields) { >field_offset = field.offset; if (field_offset >= end) { break; } >field_start = #select(field_offset < start, start - field_offset, 0); if (!contains_no_user_data(field.type, field_start, end - field_offset)) { result = 0; break; } } }, .array, .enum_array => { result = 1; >element_type: &Type = zero; >element_count: u64 = 0; switch (type.id) { .array => { element_type = type.content.array.element_type; element_count = type.content.array.element_count; }, .enum_array => { #trap(); }, else => { unreachable; }, } assert(element_type != zero); assert(element_count != 0); >element_size = get_byte_size(element_type); for (i: 0..element_count) { >offset = i * element_size; if (offset >= end) { break; } >element_start = #select(offset < start, start - offset, 0); if (!contains_no_user_data(element_type, element_start, end - offset)) { result = 0; break; } } }, else => {}, } } return result; } get_member_at_offset = fn (struct_type: &Type, offset: u64) &Field { assert(struct_type.id == .struct); >result: &Field = zero; >byte_size = get_byte_size(struct_type); if (byte_size > offset) { >offset_it: u64 = 0; >fields = struct_type.content.struct.fields; for (&field: fields) { if (offset_it > offset) { break; } result = field; assert(offset_it == field.offset); offset_it = align_forward(offset_it + get_byte_size(field.type), #extend(get_byte_alignment(field.type))); } assert(result != zero); } 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; } } } }, .pointer => { if (offset == 0) { return type; } else { #trap(); } }, .struct => { >field = get_member_at_offset(type, offset); if (field) { >field_type = field.type; switch (field_type.id) { .integer, .enum, => { field_type = align_integer_type(module, field_type); }, else => {}, } return abi_system_v_get_integer_type_at_offset(module, field_type, offset - field.offset, source_type, source_offset); } else { unreachable; } }, .bits => { >backing_type = type.content.bits.backing_type; return abi_system_v_get_integer_type_at_offset(module, backing_type, offset, #select(source_type == type, backing_type, source_type), source_offset); }, .enum => { #trap(); }, .array => { #trap(); }, else => { #trap(); }, } >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_get_ignore = fn (module: &Module, semantic_type: &Type) AbiInformation { resolve_type_in_place(module, semantic_type); return { .semantic_type = semantic_type, .flags = { .kind = .ignore, zero, }, zero, }; } AbiSystemVExtendOptions = struct { semantic_type: &Type, type: &Type, sign: u1, } abi_system_v_get_extend = fn (options: AbiSystemVExtendOptions) AbiInformation { assert(is_integral_or_enumeration_type(options.semantic_type)); >result: AbiInformation = { .semantic_type = options.semantic_type, .flags = { .kind = .extend, zero, }, zero, }; abi_set_coerce_to_type(&result, #select(options.type != zero, options.type, options.semantic_type)); abi_set_padding_type(&result, zero); abi_set_direct_offset(&result, 0); abi_set_direct_alignment(&result, 0); result.flags.sign_extension = options.sign; return result; } AbiSystemVIndirectOptions = struct { semantic_type: &Type, padding_type: &Type, alignment: u32, not_by_value: u1, // This is the inverse of `by_value` because we want `by_value` to `true` by default realign: u1, } abi_system_v_get_indirect = fn (indirect: AbiSystemVIndirectOptions) AbiInformation { >result: AbiInformation = { .semantic_type = indirect.semantic_type, .attributes = { .indirect = { .alignment = 0, .address_space = 0, }, }, .flags = { .kind = .indirect, zero, }, zero, }; abi_set_indirect_align(&result, indirect.alignment); abi_set_indirect_by_value(&result, !indirect.not_by_value); abi_set_indirect_realign(&result, indirect.realign); abi_set_sret_after_this(&result, 0); abi_set_padding_type(&result, indirect.padding_type); return result; } abi_system_v_get_indirect_result = fn (module: &Module, type: &Type, free_gpr: u32) AbiInformation { if (!type_is_aggregate_type_for_abi(type) and !is_illegal_vector_type(type) and !is_arbitrary_bit_integer(type)) { if (is_promotable_integer_type_for_abi(type)) { #trap(); } else { return abi_system_v_get_direct(module, { .semantic_type = type, .type = type, zero, }); } } else { >alignment = #max(get_byte_alignment(type), 8); >size = get_byte_size(type); if (free_gpr == 0 and alignment != 8 and size <= 8) { return abi_system_v_get_direct(module, { .semantic_type = type, .type = integer_type(module, { .bit_count = size * 8, .signed = 0 }), zero, }); } else { return abi_system_v_get_indirect({ .semantic_type = type, .alignment = alignment, zero, }); } } } NaturalAlignIndirect = struct { semantic_type: &Type, padding_type: &Type, not_by_value: u1, // by_value = true by default realign: u1, } abi_system_v_get_natural_align_indirect = fn (natural: NaturalAlignIndirect) AbiInformation { >alignment = get_byte_alignment(natural.semantic_type); return abi_system_v_get_indirect({ .semantic_type = natural.semantic_type, .padding_type = natural.padding_type, .alignment = alignment, .not_by_value = natural.not_by_value, .realign = natural.realign, }); } abi_system_v_get_indirect_return_result = fn (type: &Type) AbiInformation { if (type_is_aggregate_type_for_abi(type)) { return abi_system_v_get_natural_align_indirect({ .semantic_type = type, zero, }); } else { #trap(); } } 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) { return abi_system_v_get_ignore(module, semantic_return_type); } 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)) { return abi_system_v_get_extend({ .semantic_type = semantic_return_type, .sign = type_is_signed(semantic_return_type), zero, }); } } }, .memory => { return abi_system_v_get_indirect_return_result(semantic_return_type); }, 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) { low_type = get_by_value_argument_pair(module, low_type, high_type); } >result = abi_system_v_get_direct(module, { .semantic_type = semantic_return_type, .type = low_type, zero, }); return result; } AbiSystemVClassifyArgumentTypeOptions = struct { available_gpr: u32, is_named_argument: u1, register_call: u1, } AbiSystemVClassifyArgumentTypeResult = struct { abi: AbiInformation, needed_registers: AbiRegisterCountSystemV, } abi_system_v_classify_argument_type = fn (module: &Module, semantic_argument_type: &Type, options: AbiSystemVClassifyArgumentTypeOptions) AbiSystemVClassifyArgumentTypeResult { >classes = abi_system_v_classify_type(semantic_argument_type, { .base_offset = 0, .is_variable_argument = !options.is_named_argument, .register_call = options.register_call, }); >needed_registers: AbiRegisterCountSystemV = zero; >low_type: &Type = zero; switch (classes[0]) { .none => { unreachable; }, .integer => { needed_registers.gpr += 1; low_type = abi_system_v_get_integer_type_at_offset(module, semantic_argument_type, 0, semantic_argument_type, 0); if (classes[1] == .none and low_type.id == .integer) { // TODO: if enumerator? if (is_integral_or_enumeration_type(semantic_argument_type) and? is_promotable_integer_type_for_abi(semantic_argument_type)) { return { .abi = abi_system_v_get_extend({ .semantic_type = semantic_argument_type, .sign = type_is_signed(semantic_argument_type), zero, }), .needed_registers = needed_registers, }; } } }, .memory => { return { .abi = abi_system_v_get_indirect_result(module, semantic_argument_type, options.available_gpr), .needed_registers = needed_registers, }; }, else => { unreachable; }, } >high_type: &Type = zero; switch (classes[1]) { .none => {}, .integer => { needed_registers.gpr += 1; high_type = abi_system_v_get_integer_type_at_offset(module, semantic_argument_type, 8, semantic_argument_type, 8); if (classes[0] == .none) { #trap(); } }, else => { unreachable; }, } >result_type = low_type; if (high_type) { result_type = get_by_value_argument_pair(module, low_type, high_type); } return { .abi = abi_system_v_get_direct(module, { .semantic_type = semantic_argument_type, .type = result_type, zero, }), .needed_registers = needed_registers, }; } AbiSystemVClassifyArgumentOptions = struct { type: &Type, abi_start: u16, is_named_argument: u1, register_call: u1, } abi_system_v_classify_argument = fn (module: &Module, available_registers: &AbiRegisterCountSystemV, llvm_abi_argument_type_buffer: []&LLVMType, abi_argument_type_buffer: []&Type, options: AbiSystemVClassifyArgumentOptions) AbiInformation { >semantic_argument_type = options.type; if (options.register_call) { #trap(); } >result = abi_system_v_classify_argument_type(module, semantic_argument_type, { .available_gpr = available_registers.gpr, .is_named_argument = options.is_named_argument, .register_call = options.register_call, }); >abi = result.abi; >needed_registers = result.needed_registers; >argument_abi: AbiInformation = undefined; if (available_registers.gpr >= needed_registers.gpr and available_registers.sse >= needed_registers.sse) { available_registers.gpr -= needed_registers.gpr; available_registers.sse -= needed_registers.sse; argument_abi = abi; } else { argument_abi = abi_system_v_get_indirect_result(module, semantic_argument_type, available_registers.gpr); } if (abi_get_padding_type(&argument_abi)) { #trap(); } argument_abi.abi_start = options.abi_start; >count: u16 = 0; >abi_start: u64 = #extend(argument_abi.abi_start); switch (argument_abi.flags.kind) { .direct, .extend, => { >coerce_to_type = abi_get_coerce_to_type(&argument_abi); resolve_type_in_place(module, coerce_to_type); >is_flattened_struct = argument_abi.flags.kind == .direct and abi_get_can_be_flattened(&argument_abi) and coerce_to_type.id == .struct; if (is_flattened_struct) { >fields = coerce_to_type.content.struct.fields; for (i: 0..fields.length) { >field = &fields[i]; >field_type = field.type; llvm_abi_argument_type_buffer[abi_start + i] = field_type.llvm.abi; abi_argument_type_buffer[abi_start + i] = field_type; } count = #truncate(coerce_to_type.content.struct.fields.length); } else { llvm_abi_argument_type_buffer[abi_start] = coerce_to_type.llvm.abi; abi_argument_type_buffer[abi_start] = coerce_to_type; count = 1; } }, .indirect => { >indirect_type = get_pointer_type(module, argument_abi.semantic_type); >abi_index = argument_abi.abi_start; abi_argument_type_buffer[abi_index] = indirect_type; resolve_type_in_place(module, indirect_type); llvm_abi_argument_type_buffer[abi_index] = indirect_type.llvm.abi; count = 1; }, else => { unreachable; }, } assert(count != 0); argument_abi.abi_count = count; return argument_abi; } 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, abi.semantic_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_create_alloca(module.llvm.builder, abi_type.llvm.memory, 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; } GEPOptions = struct { type: &LLVMType, pointer: &LLVMValue, indices: []&LLVMValue, not_inbounds: u1, } create_gep = fn (module: &Module, options: GEPOptions) &LLVMValue { assert(options.indices.length == 1 or options.indices.length == 2); >gep_function = #select(options.not_inbounds, &LLVMBuildGEP2, &LLVMBuildInBoundsGEP2); >gep = gep_function(module.llvm.builder, options.type, options.pointer, options.indices.pointer, #truncate(options.indices.length), ""); return gep; } 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_block = fn (module: &Module, block: &Block) void; analyze_type = fn (module: &Module, value: &Value, expected_type: &Type, analysis: TypeAnalysis) void; emit_value = fn (module: &Module, value: &Value, type_kind: TypeKind, expect_constant: u1) void; emit_assignment = fn (module: &Module, left_llvm: &LLVMValue, left_type: &Type, right: &Value) void; analyze_value = fn (module: &Module, value: &Value, expected_type: &Type, type_kind: TypeKind, must_be_constant: u1) void { analyze_type(module, value, expected_type, { .must_be_constant = must_be_constant, zero }); emit_value(module, value, type_kind, must_be_constant); } analyze_binary_type = fn (module: &Module, left: &Value, right: &Value, is_boolean: u1, expected_type: &Type, must_be_constant: u1) void { >left_constant = value_is_constant(left); >right_constant = value_is_constant(right); if (!expected_type) { if (left_constant != zero and right_constant) { if (left.type == zero and right.type == zero) { >string_literal = left.id == .string_literal and right.id == .string_literal; if (string_literal) { #trap(); } else { report_error(); } } } } if (is_boolean or expected_type == zero) { if (left_constant) { analyze_type(module, right, zero, { .must_be_constant = must_be_constant, zero }); analyze_type(module, left, right.type, { .must_be_constant = must_be_constant, zero }); } else { analyze_type(module, left, zero, { .must_be_constant = must_be_constant, zero }); analyze_type(module, right, left.type, { .must_be_constant = must_be_constant, zero }); } } else if (!is_boolean and expected_type != zero) { analyze_type(module, left, expected_type, { .must_be_constant = must_be_constant, zero }); analyze_type(module, right, expected_type, { .must_be_constant = must_be_constant, zero }); } else { report_error(); } assert(left.type != zero); assert(right.type != zero); } get_va_list_type = fn (module: &Module) &Type { >va_list_type = module.va_list_type; if (!va_list_type) { >u32_type = uint32(module); // TODO: this is fake >void_pointer = get_pointer_type(module, uint8(module)); >fields = arena_allocate_slice[Field](module.arena, 4); fields[0] = { .name = "gp_offset", .type = u32_type, .offset = 0, zero }; fields[1] = { .name = "fp_offset", .type = u32_type, .offset = 4, zero }; fields[2] = { .name = "overflow_arg_area", .type = void_pointer, .offset = 8, zero }; fields[3] = { .name = "reg_save_area", .type = void_pointer, .offset = 16, zero }; >va_list_struct = new_type(module, { .content = { .struct = { .fields = fields, .byte_size = 24, .byte_alignment = 16, zero, }, }, .id = .struct, .name = "va_list", .scope = &module.scope, zero, }); va_list_type = get_array_type(module, va_list_struct, 1); module.va_list_type = va_list_type; } return va_list_type; } get_enum_name_array_global = fn (module: &Module, enum_type: &Type) &Global { assert(enum_type.id == .enum); if (!enum_type.content.enum.name_array) { >fields = enum_type.content.enum.fields; >u8_type = uint8(module); >u64_type = uint64(module); resolve_type_in_place(module, u8_type); resolve_type_in_place(module, u64_type); >name_constant_buffer: [64]&LLVMValue = undefined; for (i: 0..fields.length) { >field = &fields[i]; >is_constant: u1 = 1; >null_terminate: u1 = 1; >initial_value = LLVMConstStringInContext2(module.llvm.context, field.name.pointer, field.name.length, #extend(!null_terminate)); >alignment: u32 = 1; >name_global = llvm_create_global_variable(module.llvm.module, LLVMArrayType2(u8_type.llvm.abi, field.name.length + #extend(null_terminate)), is_constant, .internal, initial_value, arena_join_string(module.arena, [ "string.", enum_type.name, ".", field.name ][..]), .none, 0, alignment, .global); >constants: [_]&LLVMValue = [ name_global, LLVMConstInt(u64_type.llvm.abi, field.name.length, 0), ]; >slice_constant = LLVMConstStructInContext(module.llvm.context, &constants[0], constants.length, 0); name_constant_buffer[i] = slice_constant; } >slice_type = get_slice_type(module, u8_type); >array_element_count = fields.length; >name_array = LLVMConstArray2(slice_type.llvm.abi, &name_constant_buffer[0], array_element_count); >name_array_type = LLVMArrayType2(slice_type.llvm.abi, array_element_count); >is_constant: u1 = 1; >name_array_variable = llvm_create_global_variable(module.llvm.module, name_array_type, is_constant, .internal, name_array, "name.array.enum", .none, 0, get_byte_alignment(slice_type), .global); >global_type = get_array_type(module, slice_type, array_element_count); resolve_type_in_place(module, global_type); >storage_type = get_pointer_type(module, global_type); resolve_type_in_place(module, storage_type); >global_storage = new_value(module); global_storage.& = { .type = storage_type, .id = .global, .kind = .left, .llvm = name_array_variable, zero, }; >global = new_global(module); global.& = { .variable = { .storage = global_storage, .type = global_type, .scope = &module.scope, .name = arena_join_string(module.arena, [ "name.array.enum.", enum_type.name ][..]), zero, }, .linkage = .internal, zero, }; global.emitted = 1; enum_type.content.enum.name_array = global; } return enum_type.content.enum.name_array; } resolve_type = fn (module: &Module, type: &Type) &Type { >result: &Type = zero; switch (type.id) { .unresolved => { assert(!module.current_macro_declaration); >instantiation = module.current_macro_instantiation; if (!instantiation) { report_error(); } >declaration = instantiation.declaration; >declaration_arguments = declaration.constant_arguments; >instantiation_arguments = instantiation.constant_arguments; assert(declaration_arguments.length == instantiation_arguments.length); for (i: 0..declaration_arguments.length) { >declaration_argument = &declaration_arguments[i]; >instantiation_argument = &instantiation_arguments[i]; assert(instantiation_argument.id == declaration_argument.id); if (declaration_argument.id == .type and type == declaration_argument.content.type) { assert(string_equal(declaration_argument.name, instantiation_argument.name)); result = instantiation_argument.content.type; break; } } if (!result) { report_error(); } }, .void, .noreturn, .integer, => { result = type; }, .pointer => { >old_element_type = type.content.pointer.element_type; >element_type = resolve_type(module, old_element_type); result = type; if (element_type != old_element_type) { result = get_pointer_type(module, element_type); } }, .struct => { result = type; if (type_is_slice(type)) { >old_pointer_type = type.content.struct.fields[0].type; assert(old_pointer_type.id == .pointer); >old_element_type = old_pointer_type.content.pointer.element_type; >element_type = resolve_type(module, old_element_type); if (element_type != old_element_type) { result = get_slice_type(module, element_type); } } }, else => { #trap(); }, } assert(result != zero); return result; } BlockCopy = struct { source: &Block, destination: &Block, } copy_block = fn (module: &Module, parent_scope: &Scope, copy: BlockCopy) void; clone_value = fn (module: &Module, scope: &Scope, old_value: &Value) &Value { assert(old_value != zero); >result: &Value = zero; if (old_value.id == .variable) { result = reference_identifier(module, scope, old_value.content.variable.name, old_value.kind); } else { result = new_value(module); result.& = old_value.&; switch (old_value.id) { .variable => { unreachable; }, .binary => { >left = clone_value(module, scope, old_value.content.binary.left); >right = clone_value(module, scope, old_value.content.binary.right); result.content = { .binary = { .left = left, .right = right, .id = old_value.content.binary.id, }, }; }, .unary => { >unary_value = clone_value(module, scope, old_value.content.unary.value); result.content = { .unary = { .value = unary_value, .id = old_value.content.unary.id, }, }; }, .unreachable => {}, .slice_expression => { >array_like = clone_value(module, scope, old_value.content.slice_expression.array_like); >start = old_value.content.slice_expression.start; >end = old_value.content.slice_expression.end; if (start) { start = clone_value(module, scope, start); } if (end) { end = clone_value(module, scope, end); } result.content = { .slice_expression = { .array_like = array_like, .start = start, .end = end, }, }; }, else => { #trap(); }, } } assert(result != zero); return result; } clone_statement = fn (module: &Module, scope: &Scope, old_statement: &Statement) &Statement { >new_statement = arena_allocate[Statement](module.arena, 1); new_statement.& = zero; >old_statement_id = old_statement.id; new_statement.id = old_statement_id; // TODO: is this right? new_statement.line = old_statement.line; new_statement.column = old_statement.column; switch (old_statement_id) { .return => { >old_return_value = old_statement.content.return; >return_value: &Value = zero; if (old_return_value) { return_value = clone_value(module, scope, old_return_value); } new_statement.content = { .return = return_value, }; }, .if => { >condition = clone_value(module, scope, old_statement.content.if.condition); >if_statement = clone_statement(module, scope, old_statement.content.if.if); >else_statement = old_statement.content.if.else; if (else_statement) { else_statement = clone_statement(module, scope, else_statement); } new_statement.content = { .if = { .condition = condition, .if = if_statement, .else = else_statement, }, }; }, .block => { >block = arena_allocate[Block](module.arena, 1); copy_block(module, scope, { .source = old_statement.content.block, .destination = block, }); new_statement.content = { .block = block, }; }, .expression => { >value = clone_value(module, scope, old_statement.content.expression); new_statement.content = { .expression = value, }; }, .local => { >local_old = old_statement.content.local; >local_new = new_local(module, scope); assert(!local_old.variable.storage); >initial_value = clone_value(module, scope, local_old.initial_value); >local_type = local_old.variable.type; if (local_type) { local_type = resolve_type(module, local_type); } local_new.& = { .variable = { .type = local_type, .scope = scope, .name = local_old.variable.name, .line = local_old.variable.line, .column = local_old.variable.column, zero, }, .initial_value = initial_value, zero, }; new_statement.content = { .local = local_new, }; }, else => { #trap(); }, } return new_statement; } copy_block = fn (module: &Module, parent_scope: &Scope, copy: BlockCopy) void { >source = copy.source; >destination = copy.destination; destination.& = zero; >scope = &destination.scope; scope.& = source.scope; scope.parent = parent_scope; assert(!scope.llvm); >last_statement: &Statement = zero; >old_statement = source.first_statement; while (old_statement) { >statement = clone_statement(module, scope, old_statement); assert(!statement.next); if (last_statement) { last_statement.next = statement; } else { destination.first_statement = statement; } last_statement = statement; old_statement = old_statement.next; } } 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); }, .binary => { >left = value.content.binary.left; >right = value.content.binary.right; >id = value.content.binary.id; >is_boolean = binary_is_boolean(id); analyze_binary_type(module, left, right, is_boolean, expected_type, analysis.must_be_constant); check_types(module, left.type, right.type); value_type = #select(is_boolean, uint1(module), left.type); }, .unary => { >unary_id = value.content.unary.id; >unary_value = value.content.unary.value; switch (unary_id) { .extend => { if (!expected_type) { report_error(); } >extended_value = unary_value; analyze_type(module, extended_value, zero, { .must_be_constant = analysis.must_be_constant, zero }); >source = extended_value.type; assert(source != zero); >source_bit_size = get_bit_size(source); >expected_bit_size = get_bit_size(expected_type); if (source_bit_size > expected_bit_size) { report_error(); } else if (source_bit_size == expected_bit_size and type_is_signed(source) == type_is_signed(expected_type)) { report_error(); } value_type = expected_type; }, .truncate => { if (!expected_type) { report_error(); } analyze_type(module, unary_value, zero, { .must_be_constant = analysis.must_be_constant, zero }); >expected_bit_size = get_bit_size(expected_type); >source_bit_size = get_bit_size(unary_value.type); if (expected_bit_size >= source_bit_size) { report_error(); } value_type = expected_type; }, .dereference => { analyze_type(module, unary_value, zero, { .must_be_constant = analysis.must_be_constant, zero }); if (value.kind == .left) { report_error(); } >pointer_type = unary_value.type; assert(pointer_type.id == .pointer); >dereference_type = pointer_type.content.pointer.element_type; typecheck(module, expected_type, dereference_type); value_type = dereference_type; }, .int_from_enum => { analyze_type(module, unary_value, zero, { .must_be_constant = analysis.must_be_constant, zero }); >value_enum_type = unary_value.type; if (value_enum_type.id != .enum) { report_error(); } >backing_type = value_enum_type.content.enum.backing_type; typecheck(module, expected_type, backing_type); value_type = backing_type; }, .int_from_pointer => { analyze_type(module, unary_value, zero, { .must_be_constant = analysis.must_be_constant, zero }); >value_enum_type = unary_value.type; if (value_enum_type.id != .pointer) { report_error(); } value_type = uint64(module); typecheck(module, expected_type, value_type); }, .pointer_cast => { if (!expected_type) { report_error(); } if (expected_type.id != .pointer) { report_error(); } analyze_type(module, unary_value, zero, { .must_be_constant = analysis.must_be_constant, zero }); >value_pointer_type = unary_value.type; if (value_pointer_type == expected_type) { report_error(); } if (value_pointer_type.id != .pointer) { report_error(); } value_type = expected_type; }, .enum_name => { >string_type = get_slice_type(module, uint8(module)); typecheck(module, expected_type, string_type); analyze_type(module, unary_value, zero, { .must_be_constant = analysis.must_be_constant, zero }); >enum_type = unary_value.type; if (enum_type.id != .enum) { report_error(); } resolve_type_in_place(module, enum_type); >enum_to_string_function = enum_type.content.enum.enum_to_string_function; if (!enum_to_string_function) { >current_block = LLVMGetInsertBlock(module.llvm.builder); >enum_name_array_global = get_enum_name_array_global(module, enum_type); >argument_types: [_]&LLVMType = [ enum_type.llvm.abi ]; >llvm_function_type = LLVMFunctionType(string_type.llvm.memory, &argument_types[0], argument_types.length, 0); >function_name = arena_join_string(module.arena, [ "enum_to_string.", enum_type.name ][..]); >llvm_function = llvm_module_create_function(module.llvm.module, llvm_function_type, .internal, function_name); LLVMSetFunctionCallConv(llvm_function, .fast); >llvm_argument: &LLVMValue = zero; //assert(argument_types.length == 1); TODO: constant evaluation LLVMGetParams(llvm_function, &llvm_argument); >entry_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "entry"); LLVMPositionBuilderAtEnd(module.llvm.builder, entry_block); >u32_type = uint32(module); resolve_type_in_place(module, u32_type); // TODO: alloca insert point >alloca = create_alloca(module, { .type = string_type, .name = "return_value", zero, }); >return_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "return_block"); >else_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "else_block"); >enum_fields = enum_type.content.enum.fields; >switch_instruction = LLVMBuildSwitch(module.llvm.builder, llvm_argument, else_block, #truncate(enum_fields.length)); >backing_type = enum_type.llvm.abi; assert(backing_type != zero); >u64_type = uint64(module).llvm.abi; assert(u64_type != zero); for (i: 0..enum_fields.length) { >field = &enum_fields[i]; >case_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "case_block"); >case_value = LLVMConstInt(backing_type, field.value, 0); LLVMAddCase(switch_instruction, case_value, case_block); LLVMPositionBuilderAtEnd(module.llvm.builder, case_block); >case_value_result_pointer = create_gep(module, { .type = enum_name_array_global.variable.type.llvm.memory, .pointer = enum_name_array_global.variable.storage.llvm, .indices = [ LLVMConstNull(u64_type), LLVMConstInt(u64_type, i, 0) ][..], zero, }); >case_value_result = create_load(module, { .type = string_type, .pointer = case_value_result_pointer, zero, }); create_store(module, { .source = case_value_result, .destination = alloca, .type = string_type, zero, }); LLVMBuildBr(module.llvm.builder, return_block); } LLVMPositionBuilderAtEnd(module.llvm.builder, else_block); LLVMBuildUnreachable(module.llvm.builder); LLVMPositionBuilderAtEnd(module.llvm.builder, return_block); >function_result = create_load(module, { .type = string_type, .pointer = alloca, zero, }); LLVMBuildRet(module.llvm.builder, function_result); if (current_block) { LLVMPositionBuilderAtEnd(module.llvm.builder, current_block); } enum_to_string_function = llvm_function; enum_type.content.enum.enum_to_string_function = enum_to_string_function; // TODO: alloca insertion point restoration } assert(enum_to_string_function != zero); value_type = string_type; }, .pointer_from_int => { if (!expected_type) { report_error(); } if (expected_type.id != .pointer) { report_error(); } analyze_type(module, unary_value, zero, { .must_be_constant = analysis.must_be_constant, zero }); >unary_value_type = unary_value.type; if (unary_value_type.id != .integer) { report_error(); } // TODO: is this correct? if (get_bit_size(unary_value_type) != 64) { report_error(); } value_type = expected_type; }, .enum_from_int => { #trap(); }, // Generic case .minus, => { >is_boolean = unary_is_boolean(unary_id); if (is_boolean) { analyze_type(module, unary_value, zero, { .must_be_constant = analysis.must_be_constant, zero }); value_type = uint1(module); } else { analyze_type(module, unary_value, expected_type, { .must_be_constant = analysis.must_be_constant, zero }); value_type = unary_value.type; } typecheck(module, expected_type, value_type); }, // TODO: this is the default case .ampersand, .exclamation, .va_end, .bitwise_not, => { >is_boolean = unary_is_boolean(unary_id); if (is_boolean) { analyze_type(module, unary_value, zero, { .must_be_constant = analysis.must_be_constant, zero }); value_type = uint1(module); } else { analyze_type(module, unary_value, expected_type, { .must_be_constant = analysis.must_be_constant, zero }); value_type = unary_value.type; } typecheck(module, expected_type, value_type); }, else => { // TODO #trap(); }, } }, .variable => { switch (value.kind) { .left => { value_type = value.content.variable.storage.type; }, .right => { value_type = value.content.variable.type; }, } assert(value_type != zero); typecheck(module, expected_type, value_type); }, .unary_type => { >unary_type_id = value.content.unary_type.id; >unary_type = value.content.unary_type.type; // TODO: call resolve_type value.content.unary_type.type = unary_type; if (unary_type_id == .enum_values) { #trap(); } else { if (expected_type != zero) { value_type = expected_type; } else { value_type = unary_type; } assert(value_type != zero); if (value_type.id != .integer) { report_error(); } >value: u64 = undefined; >max_value = integer_max_value(value_type.content.integer.bit_count, value_type.content.integer.signed); switch (unary_type_id) { .align_of => { value = #extend(get_byte_alignment(unary_type)); }, .byte_size => { value = get_byte_size(unary_type); }, .integer_max => { if (unary_type.id != .integer) { report_error(); } value = integer_max_value(unary_type.content.integer.bit_count, unary_type.content.integer.signed); }, .enum_values => { unreachable; }, } if (value > max_value) { report_error(); } } typecheck(module, expected_type, value_type); }, .call => { >call = &value.content.call; >callable = call.callable; analyze_type(module, callable, zero, { .must_be_constant = analysis.must_be_constant, zero }); >function_type: &Type = zero; switch (callable.id) { .variable => { >variable = callable.content.variable; >variable_type = variable.type; switch (variable_type.id) { .function => { function_type = variable_type; }, .pointer => { >element_type = resolve_alias(module, variable_type.content.pointer.element_type); switch (element_type.id) { .function => { function_type = element_type; }, else => { report_error(); }, } }, else => { report_error(); }, } }, else => { report_error(); }, } assert(function_type != zero); assert(function_type.id == .function); call.function_type = function_type; >semantic_argument_types = function_type.content.function.base.semantic_argument_types; >call_arguments = call.arguments; if (function_type.content.function.base.is_variable_argument) { if (call_arguments.length < semantic_argument_types.length) { report_error(); } } else { if (call_arguments.length != semantic_argument_types.length) { report_error(); } } for (i: 0..semantic_argument_types.length) { >argument_type = semantic_argument_types[i]; >call_argument = call_arguments[i]; analyze_type(module, call_argument, argument_type, { .must_be_constant = analysis.must_be_constant, zero }); check_types(module, argument_type, call_argument.type); } for (i: semantic_argument_types.length..call_arguments.length) { >call_argument = call_arguments[i]; analyze_type(module, call_argument, zero, { .must_be_constant = analysis.must_be_constant, zero }); } >semantic_return_type = function_type.content.function.base.semantic_return_type; typecheck(module, expected_type, semantic_return_type); value_type = semantic_return_type; }, .array_initialization => { >values = value.content.array_initialization.values; if (expected_type != zero) { if (expected_type.id != .array) { report_error(); } >element_type = expected_type.content.array.element_type; if (expected_type.content.array.element_count == 0) { // TODO: use existing types? >element_count = values.length; expected_type.content.array.element_count = element_count; assert(string_equal(expected_type.name, "")); expected_type.name = array_name(module, element_type, element_count); } else { if (expected_type.content.array.element_count != values.length) { report_error(); } } >is_constant: u1 = 1; for (value: values) { analyze_type(module, value, element_type, { .must_be_constant = analysis.must_be_constant, zero }); is_constant = is_constant and value_is_constant(value); } value.content.array_initialization.is_constant = is_constant; if (value.kind == .left) // TODO: possible? { report_error(); } value_type = expected_type; } else { if (values.length == 0) { report_error(); } >expected_type: &Type = zero; >is_constant: u1 = 1; for (value: values) { analyze_type(module, value, expected_type, { .must_be_constant = analysis.must_be_constant, zero }); is_constant = is_constant and value_is_constant(value); >value_type = value.type; if (expected_type) { if (expected_type != value_type) { report_error(); } } else { assert(value_type != zero); expected_type = value_type; } } if (!expected_type) { report_error(); } >element_type = expected_type; >element_count = values.length; >array_type = get_array_type(module, element_type, element_count); value_type = array_type; if (value.kind == .left) { value_type = get_pointer_type(module, array_type); } } }, .array_expression => { >array_like = value.content.array_expression.array_like; // Override the value kind in order for `array_like` to be a l-value array_like.kind = .left; analyze_type(module, array_like, zero, { .must_be_constant = analysis.must_be_constant, zero }); assert(array_like.kind == .left); >array_like_type = array_like.type; if (array_like_type.id != .pointer) { report_error(); } >pointer_element_type = array_like_type.content.pointer.element_type; >indexing_type = #select(pointer_element_type.id == .enum_array, pointer_element_type.content.enum_array.enum_type, uint64(module)); analyze_type(module, value.content.array_expression.index, zero, { .indexing_type = indexing_type, .must_be_constant = analysis.must_be_constant }); >element_type: &Type = zero; switch (pointer_element_type.id) { .array => { element_type = pointer_element_type.content.array.element_type; }, .struct => { >slice_type = pointer_element_type; if (!slice_type.content.struct.is_slice) { report_error(); } >slice_pointer_type = slice_type.content.struct.fields[0].type; assert(slice_pointer_type.id == .pointer); element_type = slice_pointer_type.content.pointer.element_type; }, .pointer => { element_type = pointer_element_type.content.pointer.element_type; }, .enum_array => { #trap(); }, else => { unreachable; }, } assert(element_type != zero); value_type = element_type; if (value.kind == .left) { value_type = get_pointer_type(module, element_type); } typecheck(module, expected_type, value_type); }, .enum_literal => { if (!expected_type) { expected_type = analysis.indexing_type; } if (!expected_type) { report_error(); } if (expected_type.id != .enum) { report_error(); } value_type = expected_type; }, .trap => { value_type = noreturn_type(module); }, .field_access => { >aggregate = value.content.field_access.aggregate; >field_name = value.content.field_access.field_name; analyze_type(module, aggregate, zero, { .must_be_constant = analysis.must_be_constant, zero }); if (aggregate.kind == .right) { report_error(); } >aggregate_type = aggregate.type; if (aggregate_type.id != .pointer) { report_error(); } >aggregate_element_type = aggregate_type.content.pointer.element_type; >real_aggregate_type = #select(aggregate_element_type.id == .pointer, aggregate_element_type.content.pointer.element_type, aggregate_element_type); >resolved_aggregate_type = resolve_alias(module, real_aggregate_type); switch (resolved_aggregate_type.id) { .struct => { >result_field: &Field = zero; >fields = resolved_aggregate_type.content.struct.fields; for (i: 0..fields.length) { >field = &fields[i]; if (string_equal(field_name, field.name)) { result_field = field; break; } } if (!result_field) { report_error(); } >field_type = result_field.type; value_type = #select(value.kind == .left, get_pointer_type(module, field_type), field_type); }, .union => { >fields = resolved_aggregate_type.content.union.fields; >union_field: &UnionField = zero; for (&field: fields) { if (string_equal(field_name, field.name)) { union_field = field; break; } } if (!union_field) { report_error(); } >field_type = union_field.type; value_type = field_type; if (value.kind == .left) { value_type = get_pointer_type(module, value_type); } }, .bits => { if (value.kind == .left) { report_error(); } >fields = resolved_aggregate_type.content.bits.fields; >result_field: &Field = zero; for (&field: fields) { if (string_equal(field_name, field.name)) { result_field = field; break; } } if (!result_field) { report_error(); } assert(value.kind == .right); value_type = result_field.type; }, .enum_array, .array => { if (!string_equal(field_name, "length")) { report_error(); } if (expected_type) { if (expected_type.id != .integer) { report_error(); } value_type = expected_type; } else { if (resolved_aggregate_type.id == .enum_array) { >enum_type = resolved_aggregate_type.content.enum_array.enum_type; >backing_type = enum_type.content.enum.backing_type; value_type = backing_type; } else if (resolved_aggregate_type.id == .array) { value_type = uint64(module); } else { unreachable; } } // TODO: see if it fits }, .pointer => { report_error(); }, else => { report_error(); }, } assert(value_type != zero); typecheck(module, expected_type, value_type); }, .slice_expression => { >array_like = value.content.slice_expression.array_like; >start = value.content.slice_expression.start; >end = value.content.slice_expression.end; if (array_like.kind != .left) { report_error(); } analyze_type(module, array_like, zero, { .must_be_constant = analysis.must_be_constant, zero }); >pointer_type = array_like.type; if (pointer_type.id != .pointer) { report_error(); } >sliceable_type = resolve_alias(module, pointer_type.content.pointer.element_type); >element_type: &Type = zero; switch (sliceable_type.id) { .pointer => { element_type = sliceable_type.content.pointer.element_type; }, .struct => { if (!sliceable_type.content.struct.is_slice) { report_error(); } >slice_pointer_type = sliceable_type.content.struct.fields[0].type; assert(slice_pointer_type.id == .pointer); >slice_element_type = slice_pointer_type.content.pointer.element_type; element_type = slice_element_type; }, .array => { element_type = sliceable_type.content.array.element_type; }, else => { report_error(); }, } assert(element_type != zero); >slice_type = get_slice_type(module, element_type); typecheck(module, expected_type, slice_type); >index_type = uint64(module); >indices: [_]&Value = [ start, end ]; for (index: indices) { if (index) { analyze_type(module, index, index_type, { .must_be_constant = analysis.must_be_constant, zero }); if (index.type.id != .integer) { report_error(); } } } value_type = slice_type; }, .string_literal => { >u8_type = uint8(module); >pointer_type = get_pointer_type(module, u8_type); >slice_type = get_slice_type(module, u8_type); if (pointer_type == expected_type) { value_type = expected_type; } else if (slice_type == expected_type) { value_type = expected_type; } else { typecheck(module, expected_type, slice_type); value_type = slice_type; } }, .va_start => { >va_list_type = get_va_list_type(module); typecheck(module, expected_type, va_list_type); value_type = va_list_type; }, .va_arg => { analyze_type(module, value.content.va_arg.va_list, get_pointer_type(module, get_va_list_type(module)), { .must_be_constant = analysis.must_be_constant, zero }); value_type = value.content.va_arg.type; typecheck(module, expected_type, value_type); }, .aggregate_initialization => { if (!expected_type) { report_error(); } >resolved_type = resolve_alias(module, expected_type); value_type = resolved_type; assert(!value.content.aggregate_initialization.is_constant); >is_constant: u1 = 1; >elements = value.content.aggregate_initialization.elements; >is_zero = value.content.aggregate_initialization.is_zero; >field_mask: u64 = 0; // TODO: make consecutive initialization with `zero` constant // ie: // Right now 0, 1, 2, 3 => constant values, rest zeroed is constant because `declaration_index == initialization_index` // With constant initialization values 2, 3, 4 and rest zeroed, the aggregate initialization because `declaration_index != initialization_index`, that is, the first initialization index (0) does not match the declaration index (2). The same case can be applied for cases (1, 3) and (2, 4) >aggregate_type: &Type = zero; switch (value.kind) { .left => { if (resolved_type.id != .pointer) { report_error(); } aggregate_type = resolved_type.content.pointer.element_type; }, .right => { aggregate_type = resolved_type; }, } switch (aggregate_type.id) { .struct => { >fields = aggregate_type.content.struct.fields; assert(fields.length <= 64); >is_ordered: u1 = 1; >same_values_as_field = fields.length == elements.length; >is_properly_initialized = same_values_as_field or is_zero; if (zero and same_values_as_field) { report_error(); } if (!is_properly_initialized) { report_error(); } assert(elements.length <= fields.length); for (initialization_index: 0..elements.length) { >element = &elements[initialization_index]; >name = element.name; >value = element.value; >declaration_index: u64 = 0; while (declaration_index < fields.length) { >field = &fields[declaration_index]; if (string_equal(name, field.name)) { break; } declaration_index += 1; } if (declaration_index == fields.length) { report_error(); } >mask = 1 << declaration_index; >current_mask = field_mask; if (current_mask & mask) { // Repeated field report_error(); } field_mask = current_mask | mask; is_ordered = is_ordered and declaration_index == initialization_index; >field = &fields[declaration_index]; >declaration_type = field.type; analyze_type(module, value, declaration_type, { .must_be_constant = analysis.must_be_constant, zero }); is_constant = is_constant and value_is_constant(value); } value.content.aggregate_initialization.is_constant = is_constant and is_ordered; }, .bits => { >fields = aggregate_type.content.bits.fields; assert(fields.length <= 64); >same_values_as_field = fields.length == elements.length; >is_properly_initialized = same_values_as_field or is_zero; if (is_zero and same_values_as_field) { report_error(); } if (!is_properly_initialized) { report_error(); } assert(elements.length <= fields.length); for (&initialization_element: elements) { >value = initialization_element.value; >name = initialization_element.name; >declaration_index: u64 = 0; while (declaration_index < fields.length) { >field = &fields[declaration_index]; if (string_equal(name, field.name)) { break; } declaration_index += 1; } if (declaration_index == fields.length) { report_error(); } >mask = 1 << declaration_index; >current_mask = field_mask; if (current_mask & mask) { // Repeated field report_error(); } field_mask = current_mask | mask; >field = &fields[declaration_index]; >declaration_type = field.type; analyze_type(module, value, declaration_type, { .must_be_constant = analysis.must_be_constant, zero }); is_constant = is_constant and value_is_constant(value); } value.content.aggregate_initialization.is_constant = is_constant; }, .union => { if (elements.length != 1) { report_error(); } >initialization_value = elements[0].value; >initialization_name = elements[0].name; >result_field: &UnionField = zero; >fields = aggregate_type.content.union.fields; for (&field: fields) { if (string_equal(initialization_name, field.name)) { result_field = field; break; } } if (!result_field) { report_error(); } analyze_type(module, initialization_value, result_field.type, { .must_be_constant = analysis.must_be_constant, zero }); }, .enum_array => { #trap(); }, else => { report_error(); }, } }, .zero => { if (!expected_type) { report_error(); } if (expected_type.id == .void or expected_type.id == .noreturn) { report_error(); } value_type = expected_type; }, .select => { >condition = value.content.select.condition; >true_value = value.content.select.true_value; >false_value = value.content.select.false_value; analyze_type(module, condition, zero, { .must_be_constant = analysis.must_be_constant, zero }); >is_boolean: u1 = 0; analyze_binary_type(module, true_value, false_value, is_boolean, expected_type, analysis.must_be_constant); >true_type = true_value.type; >false_type = false_value.type; check_types(module, true_type, false_type); assert(true_type == false_type); >result_type = true_type; typecheck(module, expected_type, result_type); value_type = result_type; }, .unreachable => { value_type = noreturn_type(module); }, .string_to_enum => { >enum_type = value.content.string_to_enum.type; >enum_string_value = value.content.string_to_enum.string; if (enum_type.id != .enum) { report_error(); } if (!enum_type.content.enum.string_to_enum_function) { resolve_type_in_place(module, enum_type); >fields = enum_type.content.enum.fields; >array_element_count = fields.length; >insert_block = LLVMGetInsertBlock(module.llvm.builder); >u1_type = uint1(module); >u8_type = uint8(module); >u64_type = uint64(module); resolve_type_in_place(module, u1_type); resolve_type_in_place(module, u8_type); resolve_type_in_place(module, u64_type); >u64_zero = LLVMConstNull(u64_type.llvm.abi); >enum_alignment = get_byte_alignment(enum_type); >enum_size = get_byte_size(enum_type); >byte_size = align_forward(enum_size + 1, #extend(enum_alignment)); >struct_fields = arena_allocate_slice[Field](module.arena, 2); struct_fields[0] = { .name = "enum_value", .type = enum_type, zero, }; struct_fields[1] = { .name = "is_valid", .type = u1_type, .offset = enum_size, zero, }; >struct_type = new_type(module, { .content = { .struct = { .fields = struct_fields, .byte_size = byte_size, .byte_alignment = enum_alignment, zero, }, }, .id = .struct, .name = "string_to_enum", .scope = enum_type.scope, zero, }); resolve_type_in_place(module, struct_type); >argument_types: [_]&LLVMType = [ module.llvm.pointer_type, u64_type.llvm.abi ]; >llvm_function_type = LLVMFunctionType(struct_type.llvm.abi, &argument_types[0], argument_types.length, 0); >slice_struct_type = get_slice_type(module, u8_type); >function_name = arena_join_string(module.arena, [ "string_to_enum.", enum_type.name ][..]); >llvm_function = llvm_module_create_function(module.llvm.module, llvm_function_type, .internal, function_name); LLVMSetFunctionCallConv(llvm_function, .fast); >name_array_global = get_enum_name_array_global(module, enum_type); >enum_value_type = enum_type.llvm.memory; >value_constant_buffer: [64]&LLVMValue = undefined; for (i: 0..fields.length) { >field = &fields[i]; >global_value = LLVMConstInt(enum_value_type, field.value, 0); value_constant_buffer[i] = global_value; } >value_array = LLVMConstArray2(enum_value_type, &value_constant_buffer[0], array_element_count); >value_array_variable_type = LLVMArrayType2(enum_value_type, array_element_count); >is_constant: u1 = 1; >thread_local_mode: LLVMThreadLocalMode = .none; >externally_initialized: u1 = 0; >value_array_variable = llvm_create_global_variable(module.llvm.module, value_array_variable_type, is_constant, .internal, value_array, "value.array.enum", thread_local_mode, externally_initialized, enum_alignment, .global); >entry_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "entry"); >return_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "return_block"); >loop_entry_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "loop.entry"); >loop_body_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "loop.body"); >loop_exit_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "loop.exit"); LLVMPositionBuilderAtEnd(module.llvm.builder, entry_block); >arguments: [2]&LLVMValue = undefined; LLVMGetParams(llvm_function, &arguments[0]); >return_value_alloca = create_alloca(module, { .type = enum_type, .name = "retval", zero, }); >return_boolean_alloca = create_alloca(module, { .type = u8_type, .name = "retbool", zero, }); >index_alloca = create_alloca(module, { .type = u64_type, .name = "index", zero, }); create_store(module, { .source = u64_zero, .destination = index_alloca, .type = u64_type, zero, }); >slice_pointer = arguments[0]; >slice_length = arguments[1]; LLVMBuildBr(module.llvm.builder, loop_entry_block); LLVMPositionBuilderAtEnd(module.llvm.builder, loop_entry_block); >index_load = create_load(module, { .type = u64_type, .pointer = index_alloca, zero, }); >loop_compare = LLVMBuildICmp(module.llvm.builder, .ult, index_load, LLVMConstInt(u64_type.llvm.abi, array_element_count, 0), ""); LLVMBuildCondBr(module.llvm.builder, loop_compare, loop_body_block, loop_exit_block); LLVMPositionBuilderAtEnd(module.llvm.builder, loop_body_block); >body_index_load = create_load(module, { .type = u64_type, .pointer = index_alloca, zero, }); >array_element_pointer = create_gep(module, { .type = name_array_global.variable.type.llvm.memory, .pointer = name_array_global.variable.storage.llvm, .indices = [ u64_zero, body_index_load ][..], zero, }); >element_length_pointer = LLVMBuildStructGEP2(module.llvm.builder, slice_struct_type.llvm.abi, array_element_pointer, 1, ""); >element_length = create_load(module, { .type = u64_type, .pointer = element_length_pointer, zero, }); >length_comparison = LLVMBuildICmp(module.llvm.builder, .eq, slice_length, element_length, ""); >length_match_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "length.match"); >length_mismatch_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "length.mismatch"); LLVMBuildCondBr(module.llvm.builder, length_comparison, length_match_block, length_mismatch_block); LLVMPositionBuilderAtEnd(module.llvm.builder, length_match_block); >s32_type = sint32(module); resolve_type_in_place(module, s32_type); >memcmp = module.llvm.memcmp; if (!memcmp) { memcmp = LLVMGetNamedFunction(module.llvm.module, "memcmp"); if (!memcmp) { >arguments: [_]&LLVMType = [ module.llvm.pointer_type, module.llvm.pointer_type, u64_type.llvm.abi, ]; >llvm_function_type = LLVMFunctionType(s32_type.llvm.abi, &arguments[0], arguments.length, 0); >llvm_function = llvm_module_create_function(module.llvm.module, llvm_function_type, .external, "memcmp"); memcmp = llvm_function; } module.llvm.memcmp = memcmp; } assert(memcmp != zero); assert(module.llvm.memcmp != zero); >length_index_load = create_load(module, { .type = u64_type, .pointer = index_alloca, zero, }); >length_array_element_pointer = create_gep(module, { .type = name_array_global.variable.type.llvm.memory, .pointer = name_array_global.variable.storage.llvm, .indices = [ u64_zero, length_index_load ][..], zero, }); >element_pointer_pointer = LLVMBuildStructGEP2(module.llvm.builder, slice_struct_type.llvm.abi, length_array_element_pointer, 0, ""); >element_pointer = create_load(module, { .type = get_pointer_type(module, u8_type), .pointer = element_pointer_pointer, zero, }); >memcmp_arguments: [_]&LLVMValue = [ slice_pointer, element_pointer, slice_length, ]; >memcmp_return_result = LLVMBuildCall2(module.llvm.builder, LLVMGlobalGetValueType(memcmp), memcmp, &memcmp_arguments[0], memcmp_arguments.length, ""); >content_comparison = LLVMBuildICmp(module.llvm.builder, .eq, memcmp_return_result, LLVMConstNull(s32_type.llvm.abi), ""); >content_match_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "content.match"); LLVMBuildCondBr(module.llvm.builder, content_comparison, content_match_block, length_mismatch_block); LLVMPositionBuilderAtEnd(module.llvm.builder, content_match_block); >content_index_load = create_load(module, { .type = u64_type, .pointer = index_alloca, zero, }); >value_array_element_pointer = create_gep(module, { .type = value_array_variable_type, .pointer = value_array_variable, .indices = [ u64_zero, content_index_load ][..], zero, }); >enum_value_load = create_load(module, { .type = enum_type, .pointer = value_array_element_pointer, zero, }); create_store(module, { .source = enum_value_load, .destination = return_value_alloca, .type = enum_type, zero, }); create_store(module, { .source = LLVMConstInt(u8_type.llvm.abi, 1, 0), .destination = return_boolean_alloca, .type = u8_type, zero, }); LLVMBuildBr(module.llvm.builder, return_block); LLVMPositionBuilderAtEnd(module.llvm.builder, length_mismatch_block); >inc_index_load = create_load(module, { .type = u64_type, .pointer = index_alloca, zero, }); >inc = LLVMBuildAdd(module.llvm.builder, inc_index_load, LLVMConstInt(u64_type.llvm.abi, 1, 0), ""); create_store(module, { .source = inc, .destination = index_alloca, .type = u64_type, zero, }); LLVMBuildBr(module.llvm.builder, loop_entry_block); LLVMPositionBuilderAtEnd(module.llvm.builder, loop_exit_block); create_store(module, { .source = LLVMConstNull(enum_type.llvm.memory), .destination = return_value_alloca, .type = enum_type, zero, }); create_store(module, { .source = LLVMConstNull(u8_type.llvm.abi), .destination = return_boolean_alloca, .type = u8_type, zero, }); LLVMBuildBr(module.llvm.builder, return_block); LLVMPositionBuilderAtEnd(module.llvm.builder, return_block); >value_load = create_load(module, { .type = enum_type, .pointer = return_value_alloca, .kind = .memory, zero, }); >return_value = LLVMBuildInsertValue(module.llvm.builder, LLVMGetPoison(struct_type.llvm.memory), value_load, 0, ""); >bool_load = create_load(module, { .type = u8_type, .pointer = return_boolean_alloca, zero, }); return_value = LLVMBuildInsertValue(module.llvm.builder, return_value, bool_load, 1, ""); LLVMBuildRet(module.llvm.builder, return_value); // End of scope LLVMPositionBuilderAtEnd(module.llvm.builder, insert_block); enum_type.content.enum.string_to_enum_function = llvm_function; enum_type.content.enum.string_to_enum_struct_type = struct_type; } >struct_type = enum_type.content.enum.string_to_enum_struct_type; assert(struct_type != zero); typecheck(module, expected_type, struct_type); >string_type = get_slice_type(module, uint8(module)); analyze_type(module, enum_string_value, string_type, { .must_be_constant = analysis.must_be_constant, zero }); value_type = struct_type; }, .undefined => { if (!expected_type) { report_error(); } if (expected_type.id == .void or expected_type.id == .noreturn) { report_error(); } value_type = expected_type; }, .macro_instantiation => { if (module.current_macro_declaration) { report_error(); } >current_function = module.current_function; if (!current_function) { report_error(); } module.current_function = zero; >current_macro_instantiation = module.current_macro_instantiation; assert(!current_macro_instantiation); // TODO >macro_instantiation = &value.content.macro_instantiation; module.current_macro_instantiation = macro_instantiation; >declaration = macro_instantiation.declaration; >declaration_arguments = declaration.arguments; >instantiation_declaration_arguments = arena_allocate_slice[Argument](module.arena, declaration_arguments.length); macro_instantiation.declaration_arguments = instantiation_declaration_arguments; >subprogram: &LLVMMetadata = zero; if (module.has_debug_info) { >subroutine_type: &LLVMMetadata = zero; >is_local_to_unit: u1 = 1; >is_definition: u1 = 1; >flags: LLVMDIFlags = zero; >is_optimized = build_mode_is_optimized(module.build_mode); subprogram = LLVMDIBuilderCreateFunction(module.llvm.di_builder, module.scope.llvm, declaration.name.pointer, declaration.name.length, declaration.name.pointer, declaration.name.length, module.llvm.file, macro_instantiation.scope.line, subroutine_type, #extend(is_local_to_unit), #extend(is_definition), macro_instantiation.scope.line, flags, #extend(is_optimized)); } macro_instantiation.scope.llvm = subprogram; // First copy for (i: 0..declaration_arguments.length) { >instantiation_declaration_argument = &instantiation_declaration_arguments[i]; >declaration_argument = &declaration_arguments[i]; instantiation_declaration_argument.& = { .variable = { .type = declaration_argument.variable.type, .scope = ¯o_instantiation.scope, .name = declaration_argument.variable.name, .line = declaration_argument.variable.line, .column = declaration_argument.variable.column, zero, }, .index = declaration_argument.index, }; } >declaration_constant_arguments = declaration.constant_arguments; >instantiation_constant_arguments = macro_instantiation.constant_arguments; for (i: 0..declaration_constant_arguments.length) { >declaration_constant_argument = &declaration_constant_arguments[i]; >instantiation_constant_argument = &instantiation_constant_arguments[i]; assert(declaration_constant_argument.id == instantiation_constant_argument.id); instantiation_constant_argument.name = declaration_constant_argument.name; switch (declaration_constant_argument.id) { .value => { #trap(); }, .type => { >declaration_type = declaration_constant_argument.content.type; assert(declaration_type.id == .unresolved); >old_instantiation_type = instantiation_constant_argument.content.type; >instantiation_type = new_type(module, { .content = { .alias = { .type = old_instantiation_type, .scope = ¯o_instantiation.scope, .line = macro_instantiation.line, }, }, .id = .alias, .name = declaration_constant_argument.name, .scope = ¯o_instantiation.scope, zero, }); instantiation_constant_argument.content = { .type = instantiation_type, }; }, } } value_type = resolve_type(module, declaration.return_type); assert(value_type.id != .unresolved); macro_instantiation.return_type = value_type; for (&argument: macro_instantiation.declaration_arguments) { argument.variable.type = resolve_type(module, argument.variable.type); } >instantiation_arguments = macro_instantiation.instantiation_arguments; if (instantiation_arguments.length != instantiation_declaration_arguments.length) { report_error(); } if (module.has_debug_info) { for (i: 0..instantiation_arguments.length) { >instantiation_argument = instantiation_arguments[i]; >declaration_argument = &instantiation_declaration_arguments[i]; >argument_type = declaration_argument.variable.type; assert(argument_type != zero); analyze_type(module, instantiation_argument, argument_type, { .must_be_constant = analysis.must_be_constant, zero }); } >type_buffer: [64]&LLVMMetadata = undefined; >type_count = instantiation_arguments.length + 1; resolve_type_in_place_debug(module, value_type); type_buffer[0] = value_type.llvm.debug; for (i: 0..instantiation_declaration_arguments.length) { >declaration_argument = &instantiation_declaration_arguments[i]; >type = declaration_argument.variable.type; resolve_type_in_place_debug(module, type); type_buffer[i + 1] = type.llvm.debug; } LLVMSetCurrentDebugLocation2(module.llvm.builder, zero); >flags: LLVMDIFlags = zero; >subroutine_type = LLVMDIBuilderCreateSubroutineType(module.llvm.di_builder, module.llvm.file, &type_buffer[0], #truncate(type_count), flags); assert(macro_instantiation.scope.llvm != zero); llvm_subprogram_replace_type(subprogram, subroutine_type); } assert(!macro_instantiation.block); macro_instantiation.block = arena_allocate[Block](module.arena, 1); copy_block(module, ¯o_instantiation.scope, { .source = declaration.block, .destination = macro_instantiation.block, }); resolve_type_in_place(module, value_type); typecheck(module, expected_type, value_type); if (!module.has_debug_info) { for (i: 0..instantiation_arguments.length) { >instantiation_argument = instantiation_arguments[i]; >declaration_argument = &instantiation_declaration_arguments[i]; >argument_type = declaration_argument.variable.type; assert(argument_type != zero); analyze_type(module, instantiation_argument, argument_type, { .must_be_constant = analysis.must_be_constant, zero }); } } // END OF SCOPE module.current_macro_instantiation = current_macro_instantiation; module.current_function = current_function; }, 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_binary = fn (module: &Module, left: &LLVMValue, left_type: &Type, right: &LLVMValue, right_type: &Type, id: BinaryId, resolved_value_type: &Type) &LLVMValue { switch (resolved_value_type.id) { .integer => { >is_boolean = binary_is_boolean(id); >left_signed = type_is_signed(left_type); >right_signed = type_is_signed(left_type); assert(left_signed == right_signed); >signed = #select(is_boolean, left_signed, resolved_value_type.content.integer.signed); switch (id) { .max, .min => { #trap(); }, .shift_right => { if (signed) { return LLVMBuildAShr(module.llvm.builder, left, right, ""); } else { return LLVMBuildLShr(module.llvm.builder, left, right, ""); } }, .div => { if (signed) { return LLVMBuildSDiv(module.llvm.builder, left, right, ""); } else { return LLVMBuildUDiv(module.llvm.builder, left, right, ""); } }, .rem => { if (signed) { return LLVMBuildSRem(module.llvm.builder, left, right, ""); } else { return LLVMBuildURem(module.llvm.builder, left, right, ""); } }, .compare_equal, .compare_not_equal, .compare_less, .compare_less_equal, .compare_greater, .compare_greater_equal, => { assert(left_type == right_type); >predicate: LLVMICmpPredicate = undefined; switch (id) { .compare_equal => { predicate = .eq; }, .compare_not_equal => { predicate = .ne; }, .compare_less => { predicate = #select(signed, .slt, .ult); }, .compare_less_equal => { predicate = #select(signed, .sle, .ule); }, .compare_greater => { predicate = #select(signed, .sgt, .ugt); }, .compare_greater_equal => { predicate = #select(signed, .sge, .uge); }, } return LLVMBuildICmp(module.llvm.builder, predicate, left, right, ""); }, .add => { return LLVMBuildAdd(module.llvm.builder, left, right, ""); }, .sub => { return LLVMBuildSub(module.llvm.builder, left, right, ""); }, .mul => { return LLVMBuildMul(module.llvm.builder, left, right, ""); }, .logical_and, .bitwise_and => { return LLVMBuildAnd(module.llvm.builder, left, right, ""); }, .logical_or, .bitwise_or => { return LLVMBuildOr(module.llvm.builder, left, right, ""); }, .bitwise_xor => { return LLVMBuildXor(module.llvm.builder, left, right, ""); }, .shift_left => { return LLVMBuildShl(module.llvm.builder, left, right, ""); }, else => { unreachable; }, } }, .pointer => { >element_type = resolved_value_type.content.pointer.element_type; resolve_type_in_place(module, element_type); if (id != .add and id != .sub) { report_error(); } >index = right; if (id == .sub) { index = LLVMBuildNeg(module.llvm.builder, index, ""); } return create_gep(module, { .type = element_type.llvm.abi, .pointer = left, .indices = [ index ][..], zero, }); } else => { report_error(); } } } 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; } invalidate_analysis = fn (module: &Module, value: &Value) void { switch (value.id) { .variable, .constant_integer, .unary_type, => {}, .aggregate_initialization => { >elements = value.content.aggregate_initialization.elements; for (&element: elements) { invalidate_analysis(module, element.value); } }, .field_access => { invalidate_analysis(module, value.content.field_access.aggregate); }, .binary => { invalidate_analysis(module, value.content.binary.left); invalidate_analysis(module, value.content.binary.right); }, .unary => { invalidate_analysis(module, value.content.unary.value); }, } value.type = zero; } reanalyze_type_as_left_value = fn (module: &Module, value: &Value) void { >original_type = value.type; assert(original_type != zero); assert(value.kind == .right); invalidate_analysis(module, value); value.kind = .left; >expected_type = #select(value.id == .aggregate_initialization, get_pointer_type(module, original_type), zero); analyze_type(module, value, expected_type, zero); } type_is_integer_backing = fn (type: &Type) u1 { switch (type.id) { .integer, .enum, .bits, .pointer, => { return 1; }, else => { return 0; }, } } ValueTypePair = struct { value: &LLVMValue, type: &Type, } enter_struct_pointer_for_coerced_access = fn (module: &Module, source_value: &LLVMValue, source_type: &Type, destination_size: u64) ValueTypePair { >fields = source_type.content.struct.fields; assert(source_type.id == .struct and fields.length > 0); >first_field_type = fields[0].type; >first_field_size = get_byte_size(first_field_type); >source_size = get_byte_size(source_type); if (!(first_field_size < destination_size and first_field_size < source_size)) { >gep = LLVMBuildStructGEP2(module.llvm.builder, source_type.llvm.abi, source_value, 0, "coerce.dive"); if (first_field_type.id == .struct) { #trap(); } else { return { .value = gep, .type = first_field_type }; } } else { return { .value = source_value, .type = source_type }; } } create_coerced_store = fn (module: &Module, source_value: &LLVMValue, source_type: &Type, destination_value: &LLVMValue, destination_type: &Type, destination_size: u64, destination_volatile: u1) void { >source_size = get_byte_size(source_type); // TODO: this smells badly // destination_type != uint1(module) if (destination_type.id == .struct and !type_is_abi_equal(module, source_type, destination_type)) { >r = enter_struct_pointer_for_coerced_access(module, destination_value, destination_type, source_size); destination_value = r.value; destination_type = r.type; } >is_scalable: u1 = 0; if (is_scalable or source_size <= destination_size) { >destination_alignment = get_byte_alignment(destination_type); if (source_type.id == .integer and destination_type.id == .pointer and source_size == align_forward(destination_size, #extend(destination_alignment))) { #trap(); } else if (source_type.id == .struct) { >fields = source_type.content.struct.fields; for (i: 0..fields.length) { >field = &fields[i]; >gep = LLVMBuildStructGEP2(module.llvm.builder, source_type.llvm.abi, destination_value, #truncate(i), ""); >field_value = LLVMBuildExtractValue(module.llvm.builder, source_value, #truncate(i), ""); create_store(module, { .source = field_value, .destination = gep, .type = field.type, .alignment = destination_alignment, }); } } else { create_store(module, { .source = source_value, .destination = destination_value, .type = destination_type, .alignment = destination_alignment, }); } } else if (type_is_integer_backing(source_type)) { #trap(); } else { // Coercion through memory >original_destination_alignment = get_byte_alignment(destination_type); >source_alloca_alignment = #max(original_destination_alignment, get_byte_alignment(source_type)); >source_alloca = create_alloca(module, { .type = source_type, .name = "coerce", .alignment = source_alloca_alignment, }); create_store(module, { .source = source_value, .destination = source_alloca, .type = source_type, .alignment = source_alloca_alignment, }); >u64_type = uint64(module); resolve_type_in_place(module, u64_type); LLVMBuildMemCpy(module.llvm.builder, destination_value, original_destination_alignment, source_alloca, source_alloca_alignment, LLVMConstInt(u64_type.llvm.abi, destination_size, 0)); } } coerce_integer_or_pointer_to_integer_or_pointer = fn (module: &Module, source: &LLVMValue, source_type: &Type, destination_type: &Type) &LLVMValue { #trap(); } create_coerced_load = fn (module: &Module, source: &LLVMValue, source_type: &Type, destination_type: &Type) &LLVMValue { >result: &LLVMValue = zero; if (type_is_abi_equal(module, source_type, destination_type)) { #trap(); } else { >destination_size = get_byte_size(destination_type); if (source_type.id == .struct) { >src = enter_struct_pointer_for_coerced_access(module, source, source_type, destination_size); source = src.value; source_type = src.type; } if (type_is_integer_backing(source_type) and type_is_integer_backing(destination_type)) { >load = create_load(module, { .type = source_type, .pointer = source, zero, }); >result = coerce_integer_or_pointer_to_integer_or_pointer(module, load, source_type, destination_type); return result; } else { >source_size = get_byte_size(source_type); >is_source_type_scalable: u1 = 0; >is_destination_type_scalable: u1 = 0; if (!is_source_type_scalable and? !is_source_type_scalable and? source_size >= destination_size) { result = create_load(module, { .type = destination_type, .pointer = source, zero, }); } else { >scalable_vector_type: u1 = 0; if (scalable_vector_type) { #trap(); } else { // Coercion through memory >original_destination_alignment = get_byte_alignment(destination_type); >source_alignment = get_byte_alignment(source_type); >destination_alignment = #max(original_destination_alignment, source_alignment); >destination_alloca = create_alloca(module, { .type = destination_type, .name = "coerce", .alignment = destination_alignment, }); >u64_type = uint64(module); resolve_type_in_place(module, u64_type); LLVMBuildMemCpy(module.llvm.builder, destination_alloca, destination_alignment, source, source_alignment, LLVMConstInt(u64_type.llvm.abi, source_size, 0)); >load = create_load(module, { .type = destination_type, .pointer = destination_alloca, .alignment = destination_alignment, zero, }); result = load; } } } } assert(result != zero); return result; } emit_call = fn (module: &Module, value: &Value, left_llvm: &LLVMValue, left_type: &Type) &LLVMValue { assert(value.id == .call); >call = &value.content.call; >raw_function_type = call.function_type; >callable = call.callable; >call_arguments = call.arguments; >llvm_callable: &LLVMValue = zero; switch (callable.id) { .variable => { >variable = callable.content.variable; >variable_type = variable.type; >llvm_value = variable.storage.llvm; switch (variable_type.id) { .pointer => { >element_type = resolve_alias(module, variable_type.content.pointer.element_type); switch (element_type.id) { .function => { llvm_callable = create_load(module, { .type = get_pointer_type(module, raw_function_type), .pointer = llvm_value, zero, }); }, else => { report_error(); }, } }, .function => { llvm_callable = llvm_value; }, } }, else => { report_error(); }, } assert(llvm_callable != zero); >llvm_abi_argument_value_buffer: [64]&LLVMValue = undefined; >llvm_abi_argument_type_buffer: [64]&LLVMType = undefined; >abi_argument_type_buffer: [64]&Type = undefined; >argument_abi_buffer: [64]AbiInformation = undefined; >llvm_abi_argument_type_buffer_slice = llvm_abi_argument_type_buffer[..]; >abi_argument_type_buffer_slice = abi_argument_type_buffer[..]; >abi_argument_count: u16 = 0; >uses_in_alloca: u1 = 0; if (uses_in_alloca) { #trap(); } >llvm_indirect_return_value: &LLVMValue = zero; >return_abi = &raw_function_type.content.function.abi.return_abi; >return_abi_kind = return_abi.flags.kind; switch (return_abi_kind) { .indirect, .in_alloca, .coerce_and_expand, => { // TODO: handle edge cases: // - virtual function pointer thunk // - return alloca already exists >pointer: &LLVMValue = zero; >semantic_return_type = return_abi.semantic_type; if (left_llvm) { assert(left_type.id == .pointer); assert(left_type.content.pointer.element_type == semantic_return_type); pointer = left_llvm; } else { #trap(); } assert(pointer != zero); >has_sret = return_abi.flags.kind == .indirect; if (has_sret) { >void_ty = void_type(module); llvm_abi_argument_value_buffer[abi_argument_count] = pointer; abi_argument_type_buffer[abi_argument_count] = void_ty; llvm_abi_argument_type_buffer[abi_argument_count] = void_ty.llvm.abi; abi_argument_count += 1; llvm_indirect_return_value = pointer; } else if (return_abi.flags.kind == .in_alloca) { #trap(); } else { #trap(); } }, else => {}, } >available_registers = raw_function_type.content.function.abi.available_registers; >declaration_semantic_argument_types = raw_function_type.content.function.base.semantic_argument_types; for (call_argument_index: 0..call_arguments.length) { >is_named_argument = call_argument_index < declaration_semantic_argument_types.length; >semantic_call_argument_value = call_arguments[call_argument_index]; >semantic_argument_type: &Type = undefined; >argument_abi: AbiInformation = undefined; if (is_named_argument) { argument_abi = raw_function_type.content.function.abi.argument_abis[call_argument_index]; semantic_argument_type = argument_abi.semantic_type; } else { semantic_argument_type = semantic_call_argument_value.type; argument_abi = abi_system_v_classify_argument(module, &available_registers.system_v, llvm_abi_argument_type_buffer_slice, abi_argument_type_buffer_slice, { .type = resolve_alias(module, semantic_argument_type), .abi_start = abi_argument_count, .is_named_argument = 0, zero, }); } assert(semantic_argument_type != zero); resolve_type_in_place(module, semantic_argument_type); if (is_named_argument) { // TODO: better slice single statement >llvm_abi_argument_types = llvm_abi_argument_type_buffer_slice[#extend(argument_abi.abi_start)..#extend(argument_abi.abi_start + argument_abi.abi_count)]; >destination_abi_argument_types = abi_argument_type_buffer_slice[#extend(argument_abi.abi_start)..#extend(argument_abi.abi_start + argument_abi.abi_count)]; >source_abi_argument_types = raw_function_type.content.function.abi.abi_argument_types[#extend(argument_abi.abi_start)..#extend(argument_abi.abi_start + argument_abi.abi_count)]; for (i: 0..argument_abi.abi_count) { llvm_abi_argument_types[i] = source_abi_argument_types[i].llvm.abi; destination_abi_argument_types[i] = source_abi_argument_types[i]; } } argument_abi_buffer[call_argument_index] = argument_abi; if (argument_abi.padding.type != zero) { #trap(); } assert(abi_argument_count == argument_abi.abi_start); switch (argument_abi.flags.kind) { .direct, .extend, => { >coerce_to_type = abi_get_coerce_to_type(&argument_abi); resolve_type_in_place(module, coerce_to_type); if (coerce_to_type.id != .struct and argument_abi.attributes.direct.offset == 0 and type_is_abi_equal(module, semantic_argument_type, coerce_to_type)) { emit_value(module, semantic_call_argument_value, .abi, 0); >evaluation_kind = get_evaluation_kind(argument_abi.semantic_type); >v: &Value = undefined; switch (evaluation_kind) { .scalar => { v = semantic_call_argument_value; }, .aggregate => { #trap(); }, .complex => { #trap(); }, } if (!type_is_abi_equal(module, coerce_to_type, v.type)) { #trap(); } llvm_abi_argument_value_buffer[abi_argument_count] = v.llvm; abi_argument_count += 1; } else { if (coerce_to_type.id == .struct and argument_abi.flags.kind == .direct and !argument_abi.flags.can_be_flattened) { #trap(); } // TODO: fix this hack and collapse it into the generic path if (coerce_to_type == uint8(module) and semantic_argument_type == uint1(module)) { #trap(); } else { >evaluation_kind = get_evaluation_kind(semantic_argument_type); >src: &Value = zero; switch (evaluation_kind) { .scalar => { #trap(); }, .aggregate => { src = semantic_call_argument_value; }, .complex => { #trap(); }, } assert(src != zero); if (argument_abi.attributes.direct.offset != 0) { report_error(); } if (coerce_to_type.id == .struct and argument_abi.flags.kind == .direct and argument_abi.flags.can_be_flattened) { >source_type_is_scalable: u1 = 0; if (source_type_is_scalable) { #trap(); } else { if (src.kind == .right and !value_is_constant(src)) { if (!type_is_slice(src.type)) { switch (src.id) { .aggregate_initialization, .variable, .field_access, => { reanalyze_type_as_left_value(module, src); }, else => { #trap(); } } } } emit_value(module, src, .memory, 0); >destination_size = get_byte_size(coerce_to_type); >source_size = get_byte_size(argument_abi.semantic_type); >alignment = get_byte_alignment(argument_abi.semantic_type); >source = src.llvm; if (source_size < destination_size) { >alloca = create_alloca(module, { .type = argument_abi.semantic_type, .name = "coerce", .alignment = alignment, }); >u64_type = uint64(module); resolve_type_in_place(module, u64_type); LLVMBuildMemCpy(module.llvm.builder, alloca, alignment, source, alignment, LLVMConstInt(u64_type.llvm.abi, source_size, 0)); source = alloca; } assert(coerce_to_type.id == .struct); >coerce_fields = coerce_to_type.content.struct.fields; // TODO: assert(argument_abi.attributes.direct.offset == 0); switch (semantic_call_argument_value.kind) { .left => { for (i: 0..coerce_fields.length) { >field = &coerce_fields[i]; >gep = LLVMBuildStructGEP2(module.llvm.builder, coerce_to_type.llvm.memory, source, #truncate(i), ""); >maybe_undef: u1 = 0; if (maybe_undef) { #trap(); } >load = create_load(module, { .type = field.type, .pointer = gep, .alignment = alignment, zero, }); llvm_abi_argument_value_buffer[abi_argument_count] = load; abi_argument_count += 1; } }, .right => { if (type_is_abi_equal(module, coerce_to_type, semantic_argument_type)) { for (i: 0..coerce_fields.length) { llvm_abi_argument_value_buffer[abi_argument_count] = LLVMBuildExtractValue(module.llvm.builder, source, #truncate(i), ""); abi_argument_count += 1; } } else { switch (semantic_call_argument_value.id) { .aggregate_initialization => { >is_constant = semantic_call_argument_value.content.aggregate_initialization.is_constant; if (is_constant) { >linkage: LLVMLinkage = .internal; >thread_local_mode: LLVMThreadLocalMode = .none; >externally_initialized: u1 = 0; >alignment = get_byte_alignment(semantic_argument_type); >unnamed_address: LLVMUnnamedAddress = .global; >global = llvm_create_global_variable(module.llvm.module, semantic_argument_type.llvm.memory, is_constant, linkage, semantic_call_argument_value.llvm, "const.struct", thread_local_mode, externally_initialized, alignment, unnamed_address); for (i: 0..coerce_fields.length) { >gep = LLVMBuildStructGEP2(module.llvm.builder, coerce_to_type.llvm.abi, global, #truncate(i), ""); >field = &coerce_fields[i]; >maybe_undef: u1 = 0; if (maybe_undef) { #trap(); } >load = create_load(module, { .type = field.type, .pointer = gep, .alignment = alignment, zero, }); llvm_abi_argument_value_buffer[abi_argument_count] = load; abi_argument_count += 1; } } else { #trap(); } } .zero => { #trap(); }, else => { #trap(); }, } } }, } } } else { assert(argument_abi.abi_count == 1); >destination_type = coerce_to_type; >v: &LLVMValue = zero; switch (src.id) { .zero => { v = LLVMConstNull(coerce_to_type.llvm.abi); }, else => { >pointer: &LLVMValue = zero; >pointer_type: &Type = zero; if (src.type.id != .pointer) { >type = src.type; pointer_type = get_pointer_type(module, type); if (src.id != .variable) { pointer = create_alloca(module, { .type = type, .name = "my.coerce", zero, }); emit_assignment(module, pointer, pointer_type, src); } else { assert(src.id == .variable); assert(src.kind == .right); reanalyze_type_as_left_value(module, src); } } else { #trap(); } assert(pointer_type != zero); assert(pointer_type.id == .pointer); >element_type = pointer_type.content.pointer.element_type; if (!pointer) { assert(src.type.id == .pointer); assert(src.type.llvm.abi == module.llvm.pointer_type); emit_value(module, src, .memory, 0); pointer = src.llvm; } >source_type = element_type; assert(source_type == argument_abi.semantic_type); >load = create_coerced_load(module, pointer, source_type, destination_type); >is_cmse_ns_call: u1 = 0; if (is_cmse_ns_call) { #trap(); } >maybe_undef: u1 = 0; if (maybe_undef) { #trap(); } v = load; }, } assert(v != zero); llvm_abi_argument_value_buffer[abi_argument_count] = v; abi_argument_count += 1; } } } }, .indirect, .indirect_aliased, => { >evaluation_kind = get_evaluation_kind(semantic_argument_type); >do_continue: u1 = 0; if (evaluation_kind == .aggregate) { >same_address_space: u1 = 1; >abi_start: u64 = #extend(argument_abi.abi_start); assert(abi_start >= raw_function_type.content.function.abi.abi_argument_types.length or same_address_space); // TODO: handmade code, may contain bugs assert(argument_abi.abi_count == 1); >abi_argument_type = abi_argument_type_buffer[abi_start]; >semantic_argument_type = semantic_call_argument_value.type; if (abi_argument_type == semantic_argument_type) { #trap(); } else if (abi_argument_type.id == .pointer and abi_argument_type.content.pointer.element_type == semantic_argument_type) { >is_value_constant = value_is_constant(semantic_call_argument_value); if (is_value_constant) { emit_value(module, semantic_call_argument_value, .memory, 1); >is_constant: u1 = 1; >linkage: LLVMLinkage = .internal; >thread_local_mode: LLVMThreadLocalMode = .none; >externally_initialized: u1 = 0; >alignment = get_byte_alignment(semantic_argument_type); >unnamed_address: LLVMUnnamedAddress = .global; >global = llvm_create_global_variable(module.llvm.module, semantic_argument_type.llvm.memory, is_constant, linkage, semantic_call_argument_value.llvm, "indirect.const.aggregate", thread_local_mode, externally_initialized, alignment, unnamed_address); llvm_abi_argument_value_buffer[abi_argument_count] = global; abi_argument_count += 1; } else { >pointer_type = get_pointer_type(module, semantic_argument_type); switch (semantic_call_argument_value.id) { .variable => { reanalyze_type_as_left_value(module, semantic_call_argument_value); emit_value(module, semantic_call_argument_value, .memory, 0); llvm_abi_argument_value_buffer[abi_argument_count] = semantic_call_argument_value.llvm; abi_argument_count += 1; }, else => { assert(abi_argument_type.id == .pointer); assert(abi_argument_type.content.pointer.element_type == semantic_argument_type); >alloca = create_alloca(module, { .type = semantic_argument_type, .name = "indirect.struct.passing", zero, }); emit_assignment(module, alloca, pointer_type, semantic_call_argument_value); llvm_abi_argument_value_buffer[abi_argument_count] = alloca; abi_argument_count += 1; }, } } do_continue = 1; } else { #trap(); } } if (!do_continue) { #trap(); } }, .ignore => { unreachable; }, else => { #trap(); }, // TODO } assert(abi_argument_count == argument_abi.abi_start + argument_abi.abi_count); } >declaration_abi_argument_types = raw_function_type.content.function.abi.abi_argument_types; if (raw_function_type.content.function.base.is_variable_argument) { assert(declaration_abi_argument_types.length <= #extend(abi_argument_count)); } else { assert(declaration_abi_argument_types.length == #extend(abi_argument_count)); } assert(raw_function_type.llvm.abi != zero); >llvm_call = LLVMBuildCall2(module.llvm.builder, raw_function_type.llvm.abi, llvm_callable, &llvm_abi_argument_value_buffer[0], #extend(abi_argument_count), ""); >llvm_calling_convention: LLVMCallingConvention = undefined; switch (raw_function_type.content.function.base.calling_convention) { .c => { llvm_calling_convention = .c; }, } LLVMSetInstructionCallConv(llvm_call, llvm_calling_convention); >argument_abis = argument_abi_buffer[..call_arguments.length]; emit_attributes(module, llvm_call, &LLVMAddCallSiteAttribute, { .return_abi = return_abi, .argument_abis = argument_abis, .abi_argument_types = abi_argument_type_buffer[..#extend(abi_argument_count)], .abi_return_type = raw_function_type.content.function.abi.abi_return_type, .attributes = zero, }); switch (return_abi_kind) { .ignore => { assert(return_abi.semantic_type == noreturn_type(module) or return_abi.semantic_type == void_type(module)); return llvm_call; }, .direct, .extend, => { >coerce_to_type = abi_get_coerce_to_type(return_abi); if (return_abi.attributes.direct.offset == 0 and type_is_abi_equal(module, return_abi.semantic_type, coerce_to_type)) { >evaluation_kind = get_evaluation_kind(coerce_to_type); switch (evaluation_kind) { .scalar => { return llvm_call; }, .aggregate => {}, .complex => { unreachable; }, } } // TODO: if >fixed_vector_type: u1 = 0; if (fixed_vector_type) { #trap(); } >coerce_alloca: &LLVMValue = zero; if (left_llvm) { assert(left_type.id == .pointer); assert(left_type.content.pointer.element_type == return_abi.semantic_type); coerce_alloca = left_llvm; } else { coerce_alloca = create_alloca(module, { .type = return_abi.semantic_type, .name = "coerce", zero, }); } >destination_pointer = coerce_alloca; if (return_abi.attributes.direct.offset != 0) { #trap(); } >destination_type = return_abi.semantic_type; >source_value = llvm_call; >source_type = raw_function_type.content.function.abi.abi_return_type; >destination_size = get_byte_size(destination_type); >left_destination_size = destination_size - #extend(return_abi.attributes.direct.offset); >is_destination_volatile: u1 = 0; switch (return_abi.semantic_type.id) { .struct => { >fields = return_abi.semantic_type.content.struct.fields; if (fields.length > 0) { create_coerced_store(module, source_value, source_type, destination_pointer, destination_type, left_destination_size, is_destination_volatile); } else { #trap(); } }, .array => { if (get_byte_size(return_abi.semantic_type) <= 8) { create_store(module, { .source = source_value, .destination = destination_pointer, .type = source_type, zero, }); } else { create_coerced_store(module, source_value, source_type, destination_pointer, destination_type, left_destination_size, is_destination_volatile); } }, else => { unreachable; }, } assert(coerce_alloca != zero); if (left_llvm) { assert(destination_pointer == left_llvm); return destination_pointer; } else { switch (value.kind) { .right => { #trap(); }, .left => { #trap(); }, } } }, .indirect => { assert(llvm_indirect_return_value != zero); return llvm_indirect_return_value; }, else => { unreachable; }, } } emit_constant_array = fn (module: &Module, elements: []&Value, element_type: &Type) &LLVMValue { >value_buffer: [64]&LLVMValue = undefined; resolve_type_in_place(module, element_type); for (i: 0..elements.length) { >v = elements[i]; emit_value(module, v, .memory, 1); value_buffer[i] = v.llvm; } >constant_array = LLVMConstArray2(element_type.llvm.memory, &value_buffer[0], elements.length); return constant_array; } emit_intrinsic_call = fn (module: &Module, index: LLVMIntrinsicIndex, argument_types: []&LLVMType, argument_values: []&LLVMValue) &LLVMValue { >intrinsic_id = module.llvm.intrinsic_table[index]; >intrinsic_function = LLVMGetIntrinsicDeclaration(module.llvm.module, intrinsic_id, argument_types.pointer, argument_types.length); >intrinsic_function_type = LLVMIntrinsicGetType(module.llvm.context, intrinsic_id, argument_types.pointer, argument_types.length); >call = LLVMBuildCall2(module.llvm.builder, intrinsic_function_type, intrinsic_function, argument_values.pointer, #truncate(argument_values.length), ""); return call; } StructLikeFieldAccess = struct { type: &Type, struct_type: &LLVMType, field_index: u32, } emit_field_access = fn (module: &Module, value: &Value, left_llvm: &LLVMValue, left_type: &Type, type_kind: TypeKind) &LLVMValue { assert(value.id == .field_access); >aggregate = value.content.field_access.aggregate; >field_name = value.content.field_access.field_name; emit_value(module, aggregate, .memory, 0); assert(aggregate.kind == .left); >aggregate_type = aggregate.type; assert(aggregate_type.id == .pointer); >aggregate_element_type = aggregate_type.content.pointer.element_type; >real_aggregate_type = #select(aggregate_element_type.id == .pointer, aggregate_element_type.content.pointer.element_type, aggregate_element_type); >resolved_aggregate_type = resolve_alias(module, real_aggregate_type); resolve_type_in_place(module, resolved_aggregate_type); >v: &LLVMValue = undefined; if (real_aggregate_type != aggregate_element_type) { v = create_load(module, { .type = aggregate_element_type, .pointer = aggregate.llvm, zero, }); } else { v = aggregate.llvm; } switch (resolved_aggregate_type.id) { .struct, .union => { >field_access: StructLikeFieldAccess = undefined; switch (resolved_aggregate_type.id) { .struct => { >result_field: &Field = zero; >fields = resolved_aggregate_type.content.struct.fields; for (i: 0..fields.length) { >field = &fields[i]; if (string_equal(field_name, field.name)) { result_field = field; break; } } assert(result_field != zero); >field_index: u32 = #truncate(result_field - fields.pointer); field_access = { .type = resolved_aggregate_type.content.struct.fields[field_index].type, .field_index = field_index, .struct_type = resolved_aggregate_type.llvm.memory, }; }, .union => { >fields = resolved_aggregate_type.content.union.fields; >union_field: &UnionField = zero; for (&field: fields) { if (string_equal(field_name, field.name)) { union_field = field; break; } } assert(union_field != zero); >field_type = union_field.type; resolve_type_in_place(module, field_type); >struct_type = LLVMStructTypeInContext(module.llvm.context, &field_type.llvm.memory, 1, 0); field_access = { .type = field_type, .field_index = 0, .struct_type = struct_type, }; }, else => { unreachable; }, } >gep = LLVMBuildStructGEP2(module.llvm.builder, field_access.struct_type, v, field_access.field_index, ""); if (left_llvm) { #trap(); } else { switch (value.kind) { .left => { return gep; }, .right => { >load = create_load(module, { .type = field_access.type, .pointer = gep, .kind = type_kind, zero, }); return load; }, } } }, .bits => { >fields = resolved_aggregate_type.content.bits.fields; >result_field: &Field = zero; for (&field: fields) { if (string_equal(field_name, field.name)) { result_field = field; break; } } assert(result_field != zero); >field_type = result_field.type; resolve_type_in_place(module, field_type); >load = create_load(module, { .type = resolved_aggregate_type, .pointer = v, zero, }); >shift = LLVMBuildLShr(module.llvm.builder, load, LLVMConstInt(resolved_aggregate_type.llvm.abi, result_field.offset, 0), ""); >trunc = LLVMBuildTrunc(module.llvm.builder, shift, field_type.llvm.abi, ""); if (left_llvm) { #trap(); } return trunc; }, .enum_array, .array => { assert(string_equal(value.content.field_access.field_name, "length")); >array_length_type = get_llvm_type(value.type, type_kind); >array_element_count: u64 = 0; switch (resolved_aggregate_type.id) { .enum_array => { >enum_type = resolved_aggregate_type.content.enum_array.enum_type; assert(enum_type.id == .enum); array_element_count = enum_type.content.enum.fields.length; }, .array => { array_element_count = resolved_aggregate_type.content.array.element_count; }, else => { unreachable; }, } assert(array_element_count != 0); >result = LLVMConstInt(array_length_type, array_element_count, 0); return result; }, else => { unreachable; }, } } emit_slice_expresion = fn (module: &Module, value: &Value) [2]&LLVMValue { assert(value.id == .slice_expression); >value_type = value.type; assert(value_type != zero); assert(type_is_slice(value_type)); >slice_pointer_type = value_type.content.struct.fields[0].type; assert(slice_pointer_type.id == .pointer); >slice_element_type = slice_pointer_type.content.pointer.element_type; >index_type = uint64(module); resolve_type_in_place(module, index_type); >llvm_index_type = index_type.llvm.abi; >index_zero = LLVMConstInt(llvm_index_type, 0, 0); >array_like = value.content.slice_expression.array_like; >start = value.content.slice_expression.start; >end = value.content.slice_expression.end; assert(array_like.kind == .left); emit_value(module, array_like, .memory, 0); >pointer_type = array_like.type; assert(pointer_type.id == .pointer); >sliceable_type = pointer_type.content.pointer.element_type; >has_start = start != zero; if (start != zero and? start.id == .constant_integer and? start.content.constant_integer.value == 0) { has_start = 0; } if (has_start) { emit_value(module, start, .memory, 0); } if (end) { emit_value(module, end, .memory, 0); } switch (sliceable_type.id) { .pointer => { >element_type = sliceable_type.content.pointer.element_type; >pointer_load = create_load(module, { .type = sliceable_type, .pointer = array_like.llvm, zero, }); >slice_pointer = pointer_load; if (has_start) { slice_pointer = create_gep(module, { .type = element_type.llvm.memory, .pointer = pointer_load, .indices = [ start.llvm ][..], zero, }); } >slice_length = end.llvm; if (has_start) { slice_length = LLVMBuildSub(module.llvm.builder, slice_length, start.llvm, ""); } return [ slice_pointer, slice_length ]; }, .struct => { assert(type_is_slice(sliceable_type)); >slice_load = create_load(module, { .type = sliceable_type, .pointer = array_like.llvm, zero, }); >old_slice_pointer = LLVMBuildExtractValue(module.llvm.builder, slice_load, 0, ""); >slice_pointer = old_slice_pointer; if (has_start) { slice_pointer = create_gep(module, { .type = slice_element_type.llvm.memory, .pointer = old_slice_pointer, .indices = [ start.llvm ][..], zero, }); } >slice_end: &LLVMValue = undefined; if (end) { slice_end = end.llvm; } else { slice_end = LLVMBuildExtractValue(module.llvm.builder, slice_load, 1, ""); } >slice_length = slice_end; if (has_start) { slice_length = LLVMBuildSub(module.llvm.builder, slice_end, start.llvm, ""); } return [ slice_pointer, slice_length ]; }, .array => { assert(sliceable_type.content.array.element_type == slice_element_type); >slice_pointer = array_like.llvm; if (has_start) { slice_pointer = create_gep(module, { .type = sliceable_type.llvm.memory, .pointer = slice_pointer, .indices = [ index_zero, start.llvm ][..], zero, }); } >slice_length: &LLVMValue = zero; if (has_start) { #trap(); } else if (end != zero) { slice_length = end.llvm; } else { >element_count = sliceable_type.content.array.element_count; slice_length = LLVMConstInt(llvm_index_type, element_count, 0); } assert(slice_length != zero); return [ slice_pointer, slice_length ]; }, else => { unreachable; }, } } emit_slice_result = fn (module: &Module, slice_values: [2]&LLVMValue, slice_type: &LLVMType) &LLVMValue { >result = LLVMGetPoison(slice_type); result = LLVMBuildInsertValue(module.llvm.builder, result, slice_values[0], 0, ""); result = LLVMBuildInsertValue(module.llvm.builder, result, slice_values[1], 1, ""); return result; } emit_va_arg_from_memory = fn (module: &Module, va_list_pointer: &LLVMValue, va_list_struct: &Type, argument_type: &Type) &LLVMValue { assert(va_list_struct.id == .struct); >overflow_arg_area_pointer = LLVMBuildStructGEP2(module.llvm.builder, va_list_struct.llvm.abi, va_list_pointer, 2, ""); >overflow_arg_area_type = va_list_struct.content.struct.fields[2].type; >overflow_arg_area = create_load(module, { .type = overflow_arg_area_type, .pointer = overflow_arg_area_pointer, zero, }); if (get_byte_alignment(argument_type) > 8) { #trap(); } >argument_type_size = get_byte_size(argument_type); >raw_offset = align_forward(argument_type_size, 8); >u32_type = uint32(module).llvm.abi; >offset = LLVMConstInt(u32_type, raw_offset, 0); >new_overflow_arg_area = create_gep(module, { .type = u32_type, .pointer = overflow_arg_area, .indices = [ offset ][..], .not_inbounds = 1, }); create_store(module, { .source = new_overflow_arg_area, .destination = overflow_arg_area_pointer, .type = overflow_arg_area_type, zero, }); return overflow_arg_area; } 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); } emit_va_arg = fn (module: &Module, value: &Value, left_llvm: &LLVMValue, left_type: &Type, llvm_function: &LLVMValue) &LLVMValue { assert(value.id == .va_arg); >raw_va_list_type = get_va_list_type(module); >va_list_value = value.content.va_arg.va_list; emit_value(module, va_list_value, .memory, 0); >u64_type = uint64(module); resolve_type_in_place(module, u64_type); >index_zero = LLVMConstNull(u64_type.llvm.memory); >va_list_value_llvm = create_gep(module, { .type = raw_va_list_type.llvm.memory, .pointer = va_list_value.llvm, .indices = [ index_zero, index_zero ][..], zero, }); >va_arg_type = value.content.va_arg.type; >result = abi_system_v_classify_argument_type(module, va_arg_type, zero); >abi = result.abi; >needed_registers = result.needed_registers; assert(abi.flags.kind != .ignore); assert(raw_va_list_type.id == .array); >va_list_struct = raw_va_list_type.content.array.element_type; >address: &LLVMValue = zero; if (needed_registers.gpr == 0 and needed_registers.sse == 0) { address = emit_va_arg_from_memory(module, va_list_value_llvm, va_list_struct, va_arg_type); } else { >va_list_struct_llvm = va_list_struct.llvm.memory; >gpr_offset_pointer: &LLVMValue = zero; >gpr_offset: &LLVMValue = zero; if (needed_registers.gpr != 0) { gpr_offset_pointer = LLVMBuildStructGEP2(module.llvm.builder, va_list_struct_llvm, va_list_value_llvm, 0, ""); gpr_offset = create_load(module, { .type = va_list_struct.content.struct.fields[0].type, .pointer = gpr_offset_pointer, .alignment = 16, zero, }); } else { #trap(); } >raw_in_regs = 48 - needed_registers.gpr * 8; >u32_type = uint32(module); resolve_type_in_place(module, u32_type); >u32_llvm = u32_type.llvm.memory; >in_regs: &LLVMValue = zero; if (needed_registers.gpr != 0) { in_regs = LLVMConstInt(u32_llvm, #extend(raw_in_regs), 0); } else { #trap(); } if (needed_registers.gpr != 0) { in_regs = LLVMBuildICmp(module.llvm.builder, .ule, gpr_offset, in_regs, ""); } else { #trap(); } assert(in_regs != zero); >fp_offset_pointer: &LLVMValue = zero; if (needed_registers.sse != 0) { #trap(); } >fp_offset: &LLVMValue = zero; if (needed_registers.sse != 0) { #trap(); } >raw_fits_in_fp = 176 - needed_registers.sse * 16; >fits_in_fp: &LLVMValue = zero; if (needed_registers.sse != 0) { #trap(); } if (needed_registers.sse != 0 and needed_registers.gpr != 0) { #trap(); } >in_reg_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "va_arg.in_reg"); >in_mem_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "va_arg.in_mem"); >end_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "va_arg.end"); LLVMBuildCondBr(module.llvm.builder, in_regs, in_reg_block, in_mem_block); emit_block(module, in_reg_block); >reg_save_area_type = va_list_struct.content.struct.fields[3].type; >reg_save_area = create_load(module, { .type = reg_save_area_type, .pointer = LLVMBuildStructGEP2(module.llvm.builder, va_list_struct_llvm, va_list_value_llvm, 3, ""), .alignment = 16, zero, }); >register_address: &LLVMValue = zero; if (needed_registers.gpr != 0 and needed_registers.sse != 0) { #trap(); } else if (needed_registers.gpr != 0) { >t = reg_save_area_type.content.pointer.element_type; resolve_type_in_place(module, t); register_address = create_gep(module, { .type = t.llvm.abi, .pointer = reg_save_area, .indices = [ gpr_offset ][..], .not_inbounds = 1, }); } else if (needed_registers.sse == 1) { #trap(); } else if (needed_registers.sse == 2) { #trap(); } else { unreachable; } if (needed_registers.gpr != 0) { >raw_offset = needed_registers.gpr * 8; >new_offset = LLVMBuildAdd(module.llvm.builder, gpr_offset, LLVMConstInt(u32_llvm, #extend(raw_offset), 0), ""); create_store(module, { .source = new_offset, .destination = gpr_offset_pointer, .type = u32_type, zero, }); } if (needed_registers.sse != 0) { #trap(); } LLVMBuildBr(module.llvm.builder, end_block); emit_block(module, in_mem_block); >memory_address = emit_va_arg_from_memory(module, va_list_value_llvm, va_list_struct, va_arg_type); emit_block(module, end_block); >phi = LLVMBuildPhi(module.llvm.builder, module.llvm.pointer_type, ""); >values: [_]&LLVMValue = [ register_address, memory_address ]; >blocks: [_]&LLVMBasicBlock = [ in_reg_block, in_mem_block ]; LLVMAddIncoming(phi, &values[0], &blocks[0], 2); address = phi; } assert(address != zero); >evaluation_kind = get_evaluation_kind(va_arg_type); switch (evaluation_kind) { .scalar => { assert(!left_llvm); assert(!left_type); return create_load(module, { .type = va_arg_type, .pointer = address, zero, }); }, .aggregate => { if (left_llvm) { >u64_type = uint64(module); resolve_type_in_place(module, u64_type); >memcpy_size = get_byte_size(va_arg_type); >alignment = get_byte_alignment(va_arg_type); LLVMBuildMemCpy(module.llvm.builder, left_llvm, alignment, address, alignment, LLVMConstInt(u64_type.llvm.abi, memcpy_size, 0)); return left_llvm; } else { #trap(); } }, .complex => { #trap(); }, } } emit_condition = fn (module: &Module, condition: &Value) &LLVMValue { >llvm_condition = condition.llvm; >condition_type = condition.type; assert(llvm_condition != zero); assert(condition_type != zero); assert(condition_type.id == .integer or condition_type.id == .pointer); if (!(condition_type.id == .integer and condition_type.content.integer.bit_count == 1)) { llvm_condition = LLVMBuildICmp(module.llvm.builder, .ne, llvm_condition, LLVMConstNull(condition_type.llvm.abi), ""); } assert(llvm_condition != zero); return llvm_condition; } emit_string_literal = fn (module: &Module, value: &Value) [2]&LLVMValue { assert(value.id == .string_literal); >resolved_value_type = resolve_alias(module, value.type); >null_terminate: u1 = 1; >pointer = value.content.string_literal.pointer; >length = value.content.string_literal.length; >constant_string = LLVMConstStringInContext2(module.llvm.context, pointer, length, #extend(!null_terminate)); >u8_type = uint8(module); resolve_type_in_place(module, u8_type); >string_type = LLVMArrayType2(u8_type.llvm.abi, length + #extend(null_terminate)); >is_constant: u1 = 1; >thread_local_mode: LLVMThreadLocalMode = .none; >externally_initialized: u1 = 0; >alignment: u32 = 1; >global = llvm_create_global_variable(module.llvm.module, string_type, is_constant, .internal, constant_string, "conststring", thread_local_mode, externally_initialized, alignment, .global); return [ global, LLVMConstInt(uint64(module).llvm.abi, length, 0) ]; } emit_local_storage = fn (module: &Module, variable: &Variable) void { assert(!variable.storage); assert(variable.name.pointer != zero); assert(variable.name.length != zero); >value_type = variable.type; resolve_type_in_place(module, value_type); >pointer_type = get_pointer_type(module, value_type); >alloca = create_alloca(module, { .type = value_type, .name = variable.name, zero, }); >storage = new_value(module); storage.& = { .type = pointer_type, .id = .local, .llvm = alloca, zero, }; variable.storage = storage; } null_expression = fn (module: &Module) &LLVMMetadata { return LLVMDIBuilderCreateExpression(module.llvm.di_builder, zero, 0); } end_debug_local = fn (module: &Module, variable: &Variable, llvm_local: &LLVMMetadata) void { >debug_location = LLVMDIBuilderCreateDebugLocation(module.llvm.context, variable.line, variable.column, variable.scope.llvm, module.llvm.inlined_at); LLVMSetCurrentDebugLocation2(module.llvm.builder, debug_location); >basic_block = LLVMGetInsertBlock(module.llvm.builder); assert(basic_block != zero); LLVMDIBuilderInsertDeclareRecordAtEnd(module.llvm.di_builder, variable.storage.llvm, llvm_local, null_expression(module), debug_location, basic_block); } emit_local = fn (module: &Module, local: &Local) void { emit_local_storage(module, &local.variable); assert(local.variable.storage != zero); if (module.has_debug_info) { >debug_type = local.variable.type.llvm.debug; assert(debug_type != zero); >always_preserve: s32 = 1; >flags: LLVMDIFlags = zero; >scope = local.variable.scope.llvm; >bit_alignment = get_byte_alignment(local.variable.type) * 8; >name = local.variable.name; >local_variable = LLVMDIBuilderCreateAutoVariable(module.llvm.di_builder, scope, name.pointer, name.length, module.llvm.file, local.variable.line, debug_type, always_preserve, flags, bit_alignment); end_debug_local(module, &local.variable, local_variable); } } emit_argument = fn (module: &Module, argument: &Argument) void { emit_local_storage(module, &argument.variable); assert(argument.variable.storage != zero); if (module.has_debug_info) { >debug_type = argument.variable.type.llvm.debug; assert(debug_type != zero); >scope = argument.variable.scope.llvm; assert(scope != zero); >always_preserve: u1 = 1; >flags: LLVMDIFlags = zero; >argument_variable = LLVMDIBuilderCreateParameterVariable(module.llvm.di_builder, scope, argument.variable.name.pointer, argument.variable.name.length, argument.index, module.llvm.file, argument.variable.line, debug_type, #extend(always_preserve), flags); end_debug_local(module, &argument.variable, argument_variable); } } emit_macro_instantiation = fn (module: &Module, value: &Value) void { assert(value.id == .macro_instantiation); >current_function = module.current_function; if (!current_function) { report_error(); } module.current_function = zero; >old_macro_instantiation = module.current_macro_instantiation; assert(!old_macro_instantiation); >macro_instantiation = &value.content.macro_instantiation; module.current_macro_instantiation = macro_instantiation; >caller_debug_location: &LLVMMetadata = zero; if (module.has_debug_info) { assert(!module.llvm.inlined_at); caller_debug_location = LLVMDIBuilderCreateDebugLocation(module.llvm.context, macro_instantiation.line, macro_instantiation.column, macro_instantiation.scope.parent.llvm, zero); LLVMSetCurrentDebugLocation2(module.llvm.builder, caller_debug_location); } for (instantiation_argument: macro_instantiation.instantiation_arguments) { emit_value(module, instantiation_argument, .abi, 0); } >older_inlined_at = module.llvm.inlined_at; assert(!older_inlined_at); module.llvm.inlined_at = caller_debug_location; >llvm_function = current_function.variable.storage.llvm; >entry_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "macro.entry"); LLVMBuildBr(module.llvm.builder, entry_block); LLVMPositionBuilderAtEnd(module.llvm.builder, entry_block); >return_alloca: &LLVMValue = zero; >return_type = macro_instantiation.return_type; if (return_type.id != .void and return_type.id != .noreturn) { return_alloca = create_alloca(module, { .type = return_type, .name = "macro.return", zero, }); } assert(!macro_instantiation.return_alloca != zero); macro_instantiation.return_alloca = return_alloca; >return_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "macro.return_block"); assert(!macro_instantiation.return_block); macro_instantiation.return_block = return_block; >declaration_arguments = macro_instantiation.declaration_arguments; >instantiation_arguments = macro_instantiation.instantiation_arguments; assert(declaration_arguments.length == instantiation_arguments.length); for (i: 0..declaration_arguments.length) { >declaration_argument = &declaration_arguments[i]; >instantiation_argument = instantiation_arguments[i]; emit_argument(module, declaration_argument); >type = declaration_argument.variable.type; >resolved_type = resolve_alias(module, type); >evaluation_kind = get_evaluation_kind(resolved_type); >llvm_instantiation_argument = instantiation_argument.llvm; >llvm_declaration_argument = declaration_argument.variable.storage.llvm; switch (evaluation_kind) { .scalar => { create_store(module, { .source = llvm_instantiation_argument, .destination = llvm_declaration_argument, .type = type, zero, }); }, .aggregate => { #trap(); }, .complex => { #trap(); }, } } analyze_block(module, macro_instantiation.block); if (LLVMGetInsertBlock(module.llvm.builder)) { LLVMBuildBr(module.llvm.builder, return_block); } LLVMPositionBuilderAtEnd(module.llvm.builder, return_block); // END OF SCOPE module.llvm.inlined_at = older_inlined_at; module.current_macro_instantiation = old_macro_instantiation; module.current_function = current_function; } ShortcircuitingOperation = enum { and, or, } emit_value = fn (module: &Module, value: &Value, type_kind: TypeKind, expect_constant: u1) void { >must_be_constant = expect_constant or (!module.current_function and !module.current_macro_instantiation); >parent_function_global: &Global = zero; if (module.current_function) { parent_function_global = module.current_function; } else if (module.current_macro_instantiation) { parent_function_global = module.current_macro_instantiation.instantiation_function; } else { assert(must_be_constant); } >llvm_function: &LLVMValue = zero; if (parent_function_global) { llvm_function = parent_function_global.variable.storage.llvm; assert(llvm_function != zero); } assert(value.type != zero); assert(!value.llvm); >resolved_value_type = resolve_alias(module, value.type); resolve_type_in_place(module, resolved_value_type); >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)); }, .binary => { >binary_id = value.content.binary.id; >is_shortcircuiting = binary_is_shortcircuiting(binary_id); >left = value.content.binary.left; >right = value.content.binary.right; >values = [ left, right ]; if (is_shortcircuiting) { if (must_be_constant) { report_error(); } >op: ShortcircuitingOperation = undefined; switch (binary_id) { .logical_and_shortcircuit => { op = .and; }, .logical_or_shortcircuit => { op = .or; }, else => { unreachable; }, } >left = value.content.binary.left; >right = value.content.binary.right; >right_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "shorcircuit.right"); >end_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "shorcircuit.end"); >true_block: &LLVMBasicBlock = undefined; >false_block: &LLVMBasicBlock = undefined; switch (op) { .and => { true_block = right_block; false_block = end_block; }, .or => { true_block = end_block; false_block = right_block; }, } emit_value(module, left, .abi, 0); >llvm_condition = emit_condition(module, left); >current_basic_block = LLVMGetInsertBlock(module.llvm.builder); LLVMBuildCondBr(module.llvm.builder, llvm_condition, true_block, false_block); LLVMPositionBuilderAtEnd(module.llvm.builder, right_block); assert(!right.llvm); emit_value(module, right, .abi, 0); >right_llvm = right.llvm; >right_condition: &LLVMValue = zero; switch (right.type.id) { .integer => { switch (right.type.content.integer.bit_count) { 1 => { right_condition = right_llvm; }, else => { #trap(); }, } }, else => { #trap(); }, } assert(right_condition != zero); LLVMBuildBr(module.llvm.builder, end_block); LLVMPositionBuilderAtEnd(module.llvm.builder, end_block); >boolean_type = uint1(module); resolve_type_in_place(module, boolean_type); >boolean = boolean_type.llvm.abi; >incoming_left: &LLVMValue = zero; switch (op) { .and => { incoming_left = LLVMConstNull(boolean); }, .or => { incoming_left = LLVMConstInt(boolean, 1, 0); }, } >phi = LLVMBuildPhi(module.llvm.builder, boolean, ""); >values: [_]&LLVMValue = [ incoming_left, right_condition, ]; >blocks: [_]&LLVMBasicBlock = [ current_basic_block, right_block, ]; LLVMAddIncoming(phi, &values[0], &blocks[0], blocks.length); llvm_value = phi; if (type_kind == .memory) { #trap(); } } else { >llvm_values: [2]&LLVMValue = undefined; for (i: 0..llvm_values.length) { >binary_value = values[i]; emit_value(module, binary_value, .abi, must_be_constant); llvm_values[i] = binary_value.llvm; } llvm_value = emit_binary(module, llvm_values[0], values[0].type, llvm_values[1], values[1].type, binary_id, resolved_value_type); } }, .unary => { >unary_id = value.content.unary.id; >unary_value = value.content.unary.value; assert(!unary_value.llvm); >resolved_unary_type = resolve_alias(module, unary_value.type); if (unary_id == .truncate or unary_id == .enum_name) { type_kind = .abi; } emit_value(module, unary_value, type_kind, must_be_constant); >destination_type = get_llvm_type(resolved_value_type, type_kind); assert(destination_type != zero); >llvm_unary_value = unary_value.llvm; assert(llvm_unary_value != zero); switch (unary_id) { .minus => { if (value_is_constant(unary_value)) { llvm_value = LLVMConstNeg(llvm_unary_value); } else { llvm_value = LLVMBuildNeg(module.llvm.builder, llvm_unary_value, ""); } }, .extend => { assert(resolved_unary_type.id == .integer); if (resolved_unary_type.content.integer.signed) { llvm_value = LLVMBuildSExt(module.llvm.builder, llvm_unary_value, destination_type, ""); } else { llvm_value = LLVMBuildZExt(module.llvm.builder, llvm_unary_value, destination_type, ""); } }, .truncate => { if (type_kind != .abi) { assert(resolved_value_type.llvm.abi == resolved_value_type.llvm.memory); } llvm_value = LLVMBuildTrunc(module.llvm.builder, llvm_unary_value, destination_type, ""); }, .ampersand => { assert(resolved_value_type == resolved_unary_type); llvm_value = llvm_unary_value; }, .dereference => { switch (value.kind) { .right => { >pointer_type = unary_value.type; assert(pointer_type.id == .pointer); >child_type = resolve_alias(module, pointer_type.content.pointer.element_type); assert(child_type == resolved_value_type); >load = create_load(module, { .type = child_type, .pointer = unary_value.llvm, .kind = type_kind, zero, }); llvm_value = load; }, .left => { #trap(); } } }, .int_from_enum => { llvm_value = llvm_unary_value; }, .exclamation => { if (resolved_value_type == resolved_unary_type) { llvm_value = LLVMBuildNot(module.llvm.builder, llvm_unary_value, ""); } else { switch (resolved_unary_type.id) { .pointer => { llvm_value = LLVMBuildICmp(module.llvm.builder, .eq, llvm_unary_value, LLVMConstNull(resolved_unary_type.llvm.abi), ""); }, else => { report_error(); }, } } }, .int_from_pointer => { llvm_value = LLVMBuildPtrToInt(module.llvm.builder, llvm_unary_value, resolved_value_type.llvm.abi, ""); }, .pointer_from_int => { llvm_value = LLVMBuildIntToPtr(module.llvm.builder, llvm_unary_value, resolved_value_type.llvm.abi, ""); }, .pointer_cast => { llvm_value = llvm_unary_value; }, .va_end => { llvm_value = emit_intrinsic_call(module, ."llvm.va_end", [ module.llvm.pointer_type ][..], [ llvm_unary_value ][..]); }, .enum_name => { assert(type_kind == .abi); >enum_type = resolved_unary_type; assert(enum_type.id == .enum); >enum_to_string_function = enum_type.content.enum.enum_to_string_function; assert(enum_to_string_function != zero); >call = LLVMBuildCall2(module.llvm.builder, LLVMGlobalGetValueType(enum_to_string_function), enum_to_string_function, &llvm_unary_value, 1, ""); LLVMSetInstructionCallConv(call, .fast); llvm_value = call; }, .bitwise_not => { llvm_value = LLVMBuildNot(module.llvm.builder, llvm_unary_value, ""); }, else => { #trap(); }, } }, .variable => { >variable = value.content.variable; >resolved_variable_value_type = resolve_alias(module, variable.type); >resolved_variable_pointer_type = resolve_alias(module, variable.storage.type); >llvm_storage = variable.storage.llvm; switch (value.kind) { .left => { assert(resolved_variable_pointer_type == resolved_value_type); llvm_value = llvm_storage; }, .right => { assert(resolved_variable_value_type == resolved_value_type); if (must_be_constant) { if (variable.scope.kind != .global) { report_error(); } >global: &Global = #field_parent_pointer(variable, "variable"); llvm_value = global.initial_value.llvm; } else { if (get_byte_size(resolved_value_type) <= 16) { >evaluation_kind = get_evaluation_kind(resolved_value_type); switch (evaluation_kind) { .scalar, .aggregate => { llvm_value = create_load(module, { .type = resolved_value_type, .pointer = llvm_storage, .kind = type_kind, zero, }); }, .complex => { #trap(); }, } } else { #trap(); } } }, } }, .unary_type => { >unary_type_id = value.content.unary_type.id; >unary_type = value.content.unary_type.type; resolve_type_in_place(module, unary_type); >llvm_result_type = get_llvm_type(resolved_value_type, type_kind); switch (unary_type_id) { .align_of => { assert(resolved_value_type.id == .integer); llvm_value = LLVMConstInt(llvm_result_type, #extend(get_byte_alignment(unary_type)), 0); }, .byte_size => { assert(resolved_value_type.id == .integer); llvm_value = LLVMConstInt(llvm_result_type, get_byte_size(unary_type), 0); }, .integer_max => { assert(resolved_value_type.id == .integer); assert(unary_type.id == .integer); >signed = unary_type.content.integer.signed; >max_value = integer_max_value(unary_type.content.integer.bit_count, signed); llvm_value = LLVMConstInt(llvm_result_type, max_value, #extend(signed)); }, .enum_values => { #trap(); }, } }, .call => { llvm_value = emit_call(module, value, zero, zero); }, .array_initialization => { >values = value.content.array_initialization.values; if (value.content.array_initialization.is_constant) { assert(value.kind == .right); assert(resolved_value_type.id == .array); >element_type = resolved_value_type.content.array.element_type; llvm_value = emit_constant_array(module, values, element_type); } else { switch (value.kind) { .right => { #trap(); }, .left => { assert(resolved_value_type.id == .pointer); >array_type = resolved_value_type.content.pointer.element_type; assert(array_type.id == .array); >alloca = create_alloca(module, { .type = array_type, .name = "array.init", zero, }); >element_type = array_type.content.array.element_type; >pointer_to_element_type = get_pointer_type(module, element_type); >u64_type = uint64(module); resolve_type_in_place(module, u64_type); >llvm_u64_type = u64_type.llvm.abi; >u64_zero = LLVMConstNull(llvm_u64_type); >llvm_array_type = array_type.llvm.memory; for (i: 0..values.length) { >alloca_gep = create_gep(module, { .type = llvm_array_type, .pointer = alloca, .indices = [ u64_zero, LLVMConstInt(llvm_u64_type, i, 0) ][..], zero, }); >value = values[i]; emit_assignment(module, alloca_gep, pointer_to_element_type, value); } llvm_value = alloca; }, } } }, .array_expression => { >array_like = value.content.array_expression.array_like; >index = value.content.array_expression.index; if (array_like.kind == .right) { report_error(); } emit_value(module, array_like, .memory, must_be_constant); emit_value(module, index, .memory, must_be_constant); >array_like_type = array_like.type; assert(array_like_type.id == .pointer); >pointer_element_type = array_like_type.content.pointer.element_type; switch (pointer_element_type.id) { .enum_array, .array, => { >array_type = pointer_element_type; >u64_type = uint64(module); resolve_type_in_place(module, u64_type); >u64_llvm = u64_type.llvm.abi; >zero_index = LLVMConstNull(u64_llvm); >element_type: &Type = zero; >llvm_index = index.llvm; switch (array_type.id) { .array => { element_type = array_type.content.array.element_type; }, .enum_array => { #trap(); }, else => { unreachable; }, } assert(element_type != zero); assert(llvm_index != zero); >indices: [2]&LLVMValue = [ zero_index, llvm_index ]; >gep = create_gep(module, { .type = array_type.llvm.memory, .pointer = array_like.llvm, .indices = indices[..], zero, }); switch (value.kind) { .left => { llvm_value = gep; }, .right => { llvm_value = create_load(module, { .type = element_type, .pointer = gep, zero, }); }, } }, .struct => { >slice_type = pointer_element_type; assert(slice_type.content.struct.is_slice); >slice_pointer_type = slice_type.content.struct.fields[0].type; assert(slice_pointer_type.id == .pointer); >slice_element_type = slice_pointer_type.content.pointer.element_type; resolve_type_in_place(module, slice_element_type); >pointer_load = create_load(module, { .type = slice_pointer_type, .pointer = array_like.llvm, zero, }); >gep = create_gep(module, { .type = slice_element_type.llvm.memory, .pointer = pointer_load, .indices = [ index.llvm ][..], zero, }); llvm_value = gep; if (value.kind == .right) { llvm_value = create_load(module, { .type = slice_element_type, .pointer = gep, zero, }); } }, .pointer => { >element_type = pointer_element_type.content.pointer.element_type; // TODO: consider not emitting the and doing straight GEP? >pointer_load = create_load(module, { .type = pointer_element_type, .pointer = array_like.llvm, zero, }); >gep = create_gep(module, { .type = element_type.llvm.memory, .pointer = pointer_load, .indices = [ index.llvm ][..], zero, }); llvm_value = gep; if (value.kind == .right) { llvm_value = create_load(module, { .type = element_type, .pointer = gep, zero, }); } }, else => { #trap(); }, } }, .enum_literal => { assert(resolved_value_type.id == .enum); >enum_name = value.content.enum_literal; >result_field: &EnumField = zero; >fields = resolved_value_type.content.enum.fields; for (i: 0..fields.length) { >field = &fields[i]; if (string_equal(enum_name, field.name)) { result_field = field; break; } } if (!result_field) { report_error(); } >llvm_type = get_llvm_type(resolved_value_type, type_kind); llvm_value = LLVMConstInt(llvm_type, result_field.value, #extend(type_is_signed(resolved_value_type))); }, .trap => { >call = emit_intrinsic_call(module, ."llvm.trap", zero, zero); LLVMBuildUnreachable(module.llvm.builder); LLVMClearInsertionPosition(module.llvm.builder); llvm_value = call; }, .field_access => { llvm_value = emit_field_access(module, value, zero, zero, type_kind); }, .slice_expression => { >slice_values = emit_slice_expresion(module, value); llvm_value = emit_slice_result(module, slice_values, resolved_value_type.llvm.abi); }, .va_arg => { llvm_value = emit_va_arg(module, value, zero, zero, llvm_function); }, .aggregate_initialization => { >elements = value.content.aggregate_initialization.elements; >is_constant = value.content.aggregate_initialization.is_constant; >is_zero = value.content.aggregate_initialization.is_zero; switch (value.kind) { .left => { if (resolved_value_type.id != .pointer) { report_error(); } >aggregate_type = resolved_value_type.content.pointer.element_type; >alloca = create_alloca(module, { .type = aggregate_type, zero, }); >resolved_pointer_type = resolved_value_type; >old_type = value.type; // Overwrite type so asserts are not triggered value.type = aggregate_type; emit_assignment(module, alloca, resolved_pointer_type, value); value.type = old_type; llvm_value = alloca; }, .right => { switch (resolved_value_type.id) { .struct => { >fields = resolved_value_type.content.struct.fields; assert(fields.length <= 64); assert(elements.length <= 64); if (is_constant) { >constant_buffer: [64]&LLVMValue = undefined; for (i: 0..elements.length) { >value = elements[i].value; emit_value(module, value, .memory, must_be_constant); >llvm_value = value.llvm; assert(llvm_value != zero); assert(LLVMIsAConstant(llvm_value) != 0); constant_buffer[i] = llvm_value; } >constant_count = elements.length; if (is_zero) { if (elements.length == fields.length) { unreachable; } for (i: elements.length..fields.length) { >field = &fields[i]; >field_type = field.type; resolve_type_in_place(module, field_type); constant_buffer[i] = LLVMConstNull(field_type.llvm.memory); constant_count += 1; } } assert(constant_count == fields.length); llvm_value = LLVMConstNamedStruct(get_llvm_type(resolved_value_type, type_kind), &constant_buffer[0], #truncate(constant_count)); } else { // TODO: shouldn't this be a left value? report_error(); } }, .union => { #trap(); }, .bits => { >fields = resolved_value_type.content.bits.fields; >backing_type = resolved_value_type.content.bits.backing_type; resolve_type_in_place(module, backing_type); >abi_type = get_llvm_type(backing_type, type_kind); >bitfield_type = get_llvm_type(resolved_value_type, type_kind); assert(abi_type == bitfield_type); if (is_constant) { >bits_value: u64 = 0; for (&initialization_element: elements) { >value = initialization_element.value; >name = initialization_element.name; >result_field: &Field = zero; for (&field: fields) { if (string_equal(name, field.name)) { result_field = field; break; } } assert(result_field != zero); >field_value: u64 = 0; switch (value.id) { .constant_integer => { field_value = value.content.constant_integer.value; }, .enum_literal => { >enum_name = value.content.enum_literal; >value_type = value.type; assert(value_type.id == .enum); >enum_fields = value_type.content.enum.fields; >result_enum_field: &EnumField = zero; for (&enum_field : enum_fields) { if (string_equal(enum_name, enum_field.name)) { result_enum_field = enum_field; break; } } assert(result_enum_field != zero); field_value = result_enum_field.value; }, else => { report_error(); }, } bits_value |= field_value << result_field.offset; } llvm_value = LLVMConstInt(abi_type, bits_value, 0); } else { #trap(); } }, .enum_array => { #trap(); }, else => { unreachable; }, } }, } }, .zero => { llvm_value = LLVMConstNull(get_llvm_type(resolved_value_type, type_kind)); }, .select => { >condition = value.content.select.condition; >true_value = value.content.select.true_value; >false_value = value.content.select.false_value; emit_value(module, condition, .abi, must_be_constant); >llvm_condition = condition.llvm; >condition_type = condition.type; switch (condition_type.id) { .integer => { >bit_count = condition_type.content.integer.bit_count; if (bit_count != 1) { #trap(); } }, else => { #trap(); }, } emit_value(module, true_value, type_kind, must_be_constant); emit_value(module, false_value, type_kind, must_be_constant); llvm_value = LLVMBuildSelect(module.llvm.builder, llvm_condition, true_value.llvm, false_value.llvm, ""); }, .unreachable => { if (module.has_debug_info and !build_mode_is_optimized(module.build_mode)) { emit_intrinsic_call(module, ."llvm.trap", zero, zero); } llvm_value = LLVMBuildUnreachable(module.llvm.builder); LLVMClearInsertionPosition(module.llvm.builder); }, .string_to_enum => { >enum_type = value.content.string_to_enum.type; >string_value = value.content.string_to_enum.string; emit_value(module, string_value, .memory, must_be_constant); >llvm_string_value = string_value.llvm; >s2e = enum_type.content.enum.string_to_enum_function; >first_field = LLVMBuildExtractValue(module.llvm.builder, llvm_string_value, 0, ""); >second_field = LLVMBuildExtractValue(module.llvm.builder, llvm_string_value, 1, ""); >fields: [_]&LLVMValue = [ first_field, second_field, ]; >call = LLVMBuildCall2(module.llvm.builder, LLVMGlobalGetValueType(s2e), s2e, &fields[0], fields.length, ""); LLVMSetInstructionCallConv(call, .fast); llvm_value = call; }, .string_literal => { >string_literal = emit_string_literal(module, value); switch (resolved_value_type.id) { .struct => { assert(type_is_slice(resolved_value_type)); llvm_value = emit_slice_result(module, string_literal, resolved_value_type.llvm.abi); }, .pointer => { llvm_value = string_literal[0]; }, else => { unreachable; }, } }, .macro_instantiation => { emit_macro_instantiation(module, value); >macro_instantiation = &value.content.macro_instantiation; >return_type = macro_instantiation.return_type; >return_alloca = macro_instantiation.return_alloca; // TODO: make this more serious switch (return_type.id) { .void, .noreturn => { return; }, // Return early to omit the assert else => { llvm_value = create_load(module, { .type = return_type, .pointer = return_alloca, .kind = type_kind, zero, }); }, } }, else => { #trap(); }, } assert(llvm_value != zero); value.llvm = llvm_value; } emit_assignment = fn (module: &Module, left_llvm: &LLVMValue, left_type: &Type, right: &Value) void { >parent_function_global: &Global = undefined; if (module.current_function) { parent_function_global = module.current_function; } else if (module.current_macro_instantiation) { parent_function_global = module.current_macro_instantiation.instantiation_function; } else { report_error(); } >llvm_function = parent_function_global.variable.storage.llvm; assert(llvm_function != zero); 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 => { switch (right.id) { .array_initialization => { >values = right.content.array_initialization.values; assert(resolved_value_type.id == .array); >u64_type = uint64(module); resolve_type_in_place(module, u64_type); >element_type = resolved_value_type.content.array.element_type; >element_count = resolved_value_type.content.array.element_count; if (right.content.array_initialization.is_constant) { emit_value(module, right, .memory, 0); >is_constant: u1 = 1; >linkage: LLVMLinkage = .internal; >initial_value = right.llvm; >thread_local_mode: LLVMThreadLocalMode = .none; >externally_initialized: u1 = 0; >alignment = get_byte_alignment(resolved_value_type); >unnamed_address: LLVMUnnamedAddress = .global; >global = llvm_create_global_variable(module.llvm.module, resolved_value_type.llvm.memory, is_constant, linkage, initial_value, "array.init", thread_local_mode, externally_initialized, alignment, unnamed_address); >memcpy_size = get_byte_size(resolved_value_type); LLVMBuildMemCpy(module.llvm.builder, left_llvm, alignment, global, alignment, LLVMConstInt(u64_type.llvm.abi, memcpy_size, 0)); } else { #trap(); } }, .string_literal => { >string_literal = emit_string_literal(module, right); >slice_type = get_slice_type(module, uint8(module)); for (i: 0..string_literal.length) { >member_pointer = LLVMBuildStructGEP2(module.llvm.builder, slice_type.llvm.abi, left_llvm, #truncate(i), ""); >slice_member_type = slice_type.content.struct.fields[i].type; create_store(module, { .source = string_literal[i], .destination = member_pointer, .type = slice_member_type, zero, }); } }, .va_start => { assert(resolved_value_type == get_va_list_type(module)); assert(pointer_type.content.pointer.element_type == get_va_list_type(module)); >argument_types: [_]&LLVMType = [ module.llvm.pointer_type ]; >argument_values: [_]&LLVMValue = [ left_llvm ]; emit_intrinsic_call(module, ."llvm.va_start", argument_types[..], argument_values[..]); }, .va_arg => { >result = emit_va_arg(module, right, left_llvm, left_type, llvm_function); assert(result == left_llvm); }, .aggregate_initialization => { >elements = right.content.aggregate_initialization.elements; >scope = right.content.aggregate_initialization.scope; >is_constant = right.content.aggregate_initialization.is_constant; >is_zero = right.content.aggregate_initialization.is_zero; >u64_type = uint64(module); resolve_type_in_place(module, u64_type); >byte_size = get_byte_size(value_type); >byte_size_value = LLVMConstInt(u64_type.llvm.abi, byte_size, 0); >alignment = get_byte_alignment(value_type); if (is_constant) { emit_value(module, right, .memory, 1); >linkage: LLVMLinkage = .internal; >thread_local_mode: LLVMThreadLocalMode = .none; >externally_initialized: u1 = 0; >unnamed_address: LLVMUnnamedAddress = .global; >global = llvm_create_global_variable(module.llvm.module, value_type.llvm.memory, is_constant, linkage, right.llvm, "const.aggregate", thread_local_mode, externally_initialized, alignment, unnamed_address); LLVMBuildMemCpy(module.llvm.builder, left_llvm, alignment, global, alignment, byte_size_value); } else { switch (resolved_value_type.id) { .struct => { >max_field_index: u64 = 0; >field_mask: u64 = 0; >fields = resolved_value_type.content.struct.fields; assert(fields.length <= 64); if (is_zero) { >u8_type = uint8(module); resolve_type_in_place(module, u8_type); LLVMBuildMemSet(module.llvm.builder, left_llvm, LLVMConstNull(u8_type.llvm.memory), byte_size_value, alignment); } for (&element: elements) { >name = element.name; >value = element.value; >declaration_index: u64 = 0; while (declaration_index < fields.length) { >field = &fields[declaration_index]; if (string_equal(name, field.name)) { break; } declaration_index += 1; } assert(declaration_index < fields.length); if (module.has_debug_info) { >debug_location = LLVMDIBuilderCreateDebugLocation(module.llvm.context, element.line, element.column, scope.llvm, module.llvm.inlined_at); LLVMSetCurrentDebugLocation2(module.llvm.builder, debug_location); } field_mask |= 1 << declaration_index; max_field_index = #max(max_field_index, declaration_index); >field = &fields[declaration_index]; >destination_pointer = LLVMBuildStructGEP2(module.llvm.builder, resolved_value_type.llvm.memory, left_llvm, #truncate(declaration_index), ""); emit_assignment(module, destination_pointer, get_pointer_type(module, field.type), value); } }, .union => { assert(elements.length == 1); >fields = resolved_value_type.content.union.fields; >biggest_field_index = resolved_value_type.content.union.biggest_field; >biggest_field = &fields[biggest_field_index]; >biggest_field_type = biggest_field.type; >value = elements[0].value; >field_value_type = value.type; >field_type_size = get_byte_size(field_value_type); >union_size = get_byte_size(resolved_value_type); if (field_type_size < union_size) { >u8_type = uint8(module); resolve_type_in_place(module, u8_type); LLVMBuildMemSet(module.llvm.builder, left_llvm, LLVMConstNull(u8_type.llvm.memory), LLVMConstInt(u64_type.llvm.memory, union_size, 0), alignment); } else if (field_type_size > union_size) { unreachable; } >struct_type: &LLVMType = zero; if (type_is_abi_equal(module, field_value_type, biggest_field_type)) { struct_type = resolved_value_type.llvm.memory; } else { struct_type = LLVMStructTypeInContext(module.llvm.context, &field_value_type.llvm.memory, 1, 0); } assert(struct_type != zero); >destination_pointer = LLVMBuildStructGEP2(module.llvm.builder, struct_type, left_llvm, 0, ""); >field_pointer_type = get_pointer_type(module, field_value_type); emit_assignment(module, destination_pointer, field_pointer_type, value); }, else => { #trap(); }, } } }, .call => { >result = emit_call(module, right, left_llvm, left_type); assert(result == left_llvm); }, .slice_expression => { >slice_values = emit_slice_expresion(module, right); assert(type_is_slice(resolved_value_type)); >slice_pointer_type = resolved_value_type.content.struct.fields[0].type; assert(slice_pointer_type.id == .pointer); create_store(module, { .source = slice_values[0], .destination = left_llvm, .type = slice_pointer_type, zero, }); >slice_length_destination = LLVMBuildStructGEP2(module.llvm.builder, resolved_value_type.llvm.abi, left_llvm, 1, ""); create_store(module, { .source = slice_values[1], .destination = slice_length_destination, .type = uint64(module), zero, }); }, .zero => { >u8_type = uint8(module); >u64_type = uint64(module); resolve_type_in_place(module, u8_type); resolve_type_in_place(module, u64_type); >size = get_byte_size(resolved_value_type); >alignment = get_byte_alignment(resolved_value_type); // TODO: should we just have typed memset instead: `LLVMConstNull(the_type)`, 1? LLVMBuildMemSet(module.llvm.builder, left_llvm, LLVMConstNull(u8_type.llvm.memory), LLVMConstInt(u64_type.llvm.memory, size, 0), alignment); }, .variable => { >variable = right.content.variable; switch (right.kind) { .left => { #trap(); }, .right => { >u64_type = uint64(module); resolve_type_in_place(module, u64_type); >memcpy_size = get_byte_size(resolved_value_type); >alignment = get_byte_alignment(resolved_value_type); LLVMBuildMemCpy(module.llvm.builder, left_llvm, alignment, variable.storage.llvm, alignment, LLVMConstInt(u64_type.llvm.abi, memcpy_size, 0)); }, } }, .string_to_enum => { emit_value(module, right, .memory, 0); >enum_type = right.content.string_to_enum.type; >s2e_struct_type = enum_type.content.enum.string_to_enum_struct_type; create_store(module, { .source = right.llvm, .destination = left_llvm, .type = s2e_struct_type, zero, }); }, .undefined => { // TODO: something }, .select => { if (!type_is_slice(resolved_value_type)) { report_error(); } emit_value(module, right, .memory, 0); create_store(module, { .source = right.llvm, .destination = left_llvm, .type = resolved_value_type, zero, }); }, .macro_instantiation => { emit_macro_instantiation(module, right); >size = get_byte_size(resolved_value_type); >alignment = get_byte_alignment(resolved_value_type); >u64_type = uint64(module); resolve_type_in_place(module, u64_type); LLVMBuildMemCpy(module.llvm.builder, left_llvm, alignment, right.content.macro_instantiation.return_alloca, alignment, LLVMConstInt(u64_type.llvm.abi, size, 0)); }, else => { #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) { parent_function_global = module.current_macro_instantiation.instantiation_function; } 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) { >macro_instantiation = module.current_macro_instantiation; >return_type = macro_instantiation.return_type; assert(return_type != zero); analyze_type(module, return_value, return_type, zero); emit_assignment(module, macro_instantiation.return_alloca, get_pointer_type(module, return_type), return_value); LLVMBuildBr(module.llvm.builder, macro_instantiation.return_block); LLVMClearInsertionPosition(module.llvm.builder); } else { report_error(); } }, .local => { >local = statement.content.local; >expected_type = local.variable.type; assert(!local.variable.storage); analyze_type(module, local.initial_value, expected_type, zero); local.variable.type = #select(expected_type != zero, expected_type, local.initial_value.type); assert(local.variable.type != zero); emit_local(module, local); emit_assignment(module, local.variable.storage.llvm, local.variable.storage.type, local.initial_value); }, .if => { >condition = statement.content.if.condition; >taken_statement = statement.content.if.if; >not_taken_statement = statement.content.if.else; >taken_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "if.taken"); >not_taken_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "if.not_taken"); >exit_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "if.exit"); analyze_value(module, condition, zero, .abi, 0); >llvm_condition = emit_condition(module, condition); LLVMBuildCondBr(module.llvm.builder, llvm_condition, taken_block, not_taken_block); LLVMPositionBuilderAtEnd(module.llvm.builder, taken_block); analyze_statement(module, scope, taken_statement); if (LLVMGetInsertBlock(module.llvm.builder) != zero) { LLVMBuildBr(module.llvm.builder, exit_block); } LLVMPositionBuilderAtEnd(module.llvm.builder, not_taken_block); if (not_taken_statement != zero) { analyze_statement(module, scope, not_taken_statement); } if (LLVMGetInsertBlock(module.llvm.builder) != zero) { LLVMBuildBr(module.llvm.builder, exit_block); } LLVMPositionBuilderAtEnd(module.llvm.builder, exit_block); }, .block => { analyze_block(module, statement.content.block); }, .expression => { analyze_value(module, statement.content.expression, zero, .memory, 0); }, .while => { >entry_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "while.entry"); LLVMBuildBr(module.llvm.builder, entry_block); LLVMPositionBuilderAtEnd(module.llvm.builder, entry_block); >body_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "while.body"); >continue_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "while.continue"); >exit_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "while.exit"); >previous_continue_block = module.llvm.continue_block; >previous_exit_block = module.llvm.exit_block; module.llvm.continue_block = continue_block; module.llvm.exit_block = exit_block; >condition = statement.content.while.condition; >block = statement.content.while.block; if (value_is_constant(condition)) { switch (condition.id) { .constant_integer => { >value = condition.content.constant_integer.value; if (value == 0) { report_error(); } }, else => { report_error(); }, } LLVMBuildBr(module.llvm.builder, body_block); } else { analyze_value(module, condition, zero, .abi, 0); >llvm_condition = emit_condition(module, condition); LLVMBuildCondBr(module.llvm.builder, llvm_condition, body_block, exit_block); } LLVMPositionBuilderAtEnd(module.llvm.builder, body_block); analyze_block(module, block); if (LLVMGetInsertBlock(module.llvm.builder)) { LLVMBuildBr(module.llvm.builder, continue_block); } LLVMPositionBuilderAtEnd(module.llvm.builder, continue_block); LLVMBuildBr(module.llvm.builder, entry_block); if (!LLVMGetFirstUse(#pointer_cast(body_block))) { #trap(); } if (!LLVMGetFirstUse(#pointer_cast(exit_block))) { #trap(); } LLVMPositionBuilderAtEnd(module.llvm.builder, exit_block); module.llvm.continue_block = previous_continue_block; module.llvm.exit_block = previous_exit_block; }, .assignment => { >left = statement.content.assignment.left; >right = statement.content.assignment.right; >id = statement.content.assignment.id; analyze_value(module, left, zero, .memory, 0); >left_type = left.type; if (left_type.id != .pointer) { report_error(); } >element_type = left_type.content.pointer.element_type; >left_llvm = left.llvm; switch (id) { .assign => { analyze_type(module, right, element_type, zero); emit_assignment(module, left_llvm, left_type, right); }, else => { >evaluation_kind = get_evaluation_kind(element_type); assert(evaluation_kind == .scalar); >load = create_load(module, { .type = element_type, .pointer = left_llvm, .kind = .abi, zero, }); analyze_value(module, right, element_type, .abi, 0); >a = load; >b = right.llvm; >binary_id: BinaryId = undefined; switch (id) { .assign_add => { binary_id = .add; }, .assign_sub => { binary_id = .sub; }, .assign_mul => { binary_id = .mul; }, .assign_div => { binary_id = .div; }, .assign_rem => { binary_id = .rem; }, .assign_shift_left => { binary_id = .shift_left; }, .assign_shift_right => { binary_id = .shift_right; }, .assign_and => { binary_id = .bitwise_and; }, .assign_or => { binary_id = .bitwise_or; }, .assign_xor => { binary_id = .bitwise_xor; }, .assign => { unreachable; }, } >result = emit_binary(module, a, element_type, b, right.type, binary_id, element_type); create_store(module, { .source = result, .destination = left_llvm, .type = element_type, zero, }); }, } }, .switch => { >discriminant = statement.content.switch.discriminant; >clauses = statement.content.switch.clauses; >exit_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "switch.exit"); analyze_value(module, discriminant, zero, .abi, 0); >discriminant_type = discriminant.type; >invalid_clause_index: u64 = ~0; >else_clause_index = invalid_clause_index; >discriminant_case_count: u64 = 0; // TODO: more analysis switch (discriminant_type.id) { .enum, .integer => {}, else => { report_error(); }, } for (i: 0..clauses.length) { >clause = &clauses[i]; clause.basic_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, #select(clause.values.length == 0, "switch.else_case_block", "switch.case_block")); discriminant_case_count += clause.values.length; if (clause.values.length == 0) { if (else_clause_index != invalid_clause_index) { // Double else report_error(); } else_clause_index = i; } else { for (&value: clause.values) { switch (value.id) { .single => { >v = value.content.single; assert(v != zero); analyze_value(module, v, discriminant_type, .abi, 1); }, .range => { >start = value.content.range[0]; >end = value.content.range[0]; for (v: value.content.range) { analyze_value(module, v, discriminant_type, .abi, 1); } if (start.id != end.id) { report_error(); } switch (start.id) { .constant_integer => { if (start.content.constant_integer.value >= end.content.constant_integer.value) { report_error(); } }, else => { report_error(); }, } }, } } } } >else_block: &LLVMBasicBlock = undefined; if (else_clause_index != invalid_clause_index) { else_block = clauses[else_clause_index].basic_block; } else { else_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "switch.else_case_block"); } >switch_instruction = LLVMBuildSwitch(module.llvm.builder, discriminant.llvm, else_block, #truncate(discriminant_case_count)); >all_blocks_terminated: u1 = 1; for (&clause: clauses) { for (&value: clause.values) { switch (value.id) { .single => { LLVMAddCase(switch_instruction, value.content.single.llvm, clause.basic_block); }, .range => { >start = value.content.range[0]; >end = value.content.range[1]; LLVMAddCase(switch_instruction, start.llvm, clause.basic_block); assert(start.id == end.id); switch (start.id) { .constant_integer => { >start_value = start.content.constant_integer.value; >end_value = end.content.constant_integer.value; for (i: start_value + 1 .. end_value) { LLVMAddCase(switch_instruction, LLVMConstInt(start.type.llvm.abi, i, 0), clause.basic_block); } }, else => { unreachable; }, } LLVMAddCase(switch_instruction, end.llvm, clause.basic_block); }, } } LLVMPositionBuilderAtEnd(module.llvm.builder, clause.basic_block); analyze_block(module, clause.block); if (LLVMGetInsertBlock(module.llvm.builder)) { all_blocks_terminated = 0; LLVMBuildBr(module.llvm.builder, exit_block); LLVMClearInsertionPosition(module.llvm.builder); } } if (else_clause_index == invalid_clause_index) { LLVMPositionBuilderAtEnd(module.llvm.builder, else_block); if (module.has_debug_info and !build_mode_is_optimized(module.build_mode)) { emit_intrinsic_call(module, ."llvm.trap", zero, zero); } LLVMBuildUnreachable(module.llvm.builder); LLVMClearInsertionPosition(module.llvm.builder); } LLVMPositionBuilderAtEnd(module.llvm.builder, exit_block); if (all_blocks_terminated) { LLVMBuildUnreachable(module.llvm.builder); LLVMClearInsertionPosition(module.llvm.builder); } }, .for => { if (module.has_debug_info) { >lexical_block = LLVMDIBuilderCreateLexicalBlock(module.llvm.di_builder, statement.content.for.scope.parent.llvm, module.llvm.file, statement.content.for.scope.line, statement.content.for.scope.column); statement.content.for.scope.llvm = lexical_block; } >index_type = uint64(module); resolve_type_in_place(module, index_type); >index_zero = LLVMConstNull(index_type.llvm.abi); >entry_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "for_each.entry"); >body_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "for_each.body"); >continue_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "for_each.continue"); >exit_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "for_each.exit"); >previous_continue_block = module.llvm.continue_block; >previous_exit_block = module.llvm.exit_block; module.llvm.continue_block = continue_block; module.llvm.exit_block = exit_block; >kinds = statement.content.for.kinds; >iteratables = statement.content.for.iteratables; switch (statement.content.for.kind) { .slice => { // Construct loop pre-initialization assert(kinds.length == iteratables.length); >local = statement.content.for.first_local; for (i: 0..iteratables.length) { >kind = kinds[i]; >iteratable = iteratables[i]; analyze_type(module, iteratable, zero, zero); if (iteratable.kind == .right) { if (!type_is_slice(iteratable.type)) { reanalyze_type_as_left_value(module, iteratable); } } >aggregate_type: &Type = zero; if (iteratable.kind == .left and iteratable.type.id != .pointer) { if (!type_is_slice(iteratable.type)) { report_error(); } iteratable.kind = .right; } switch (iteratable.kind) { .left => { >pointer_type = iteratable.type; if (pointer_type.id != .pointer) { report_error(); } aggregate_type = pointer_type.content.pointer.element_type; }, .right => { aggregate_type = iteratable.type; assert(type_is_slice(aggregate_type)); }, } >child_type: &Type = zero; switch (aggregate_type.id) { .array => { child_type = aggregate_type.content.array.element_type; }, .struct => { if (!type_is_slice(aggregate_type)) { report_error(); } >pointer_type = aggregate_type.content.struct.fields[0].type; assert(pointer_type.id == .pointer); child_type = pointer_type.content.pointer.element_type; }, else => { unreachable; }, } assert(child_type != zero); assert(!local.variable.type); >local_type = child_type; if (kind == .left) { local_type = get_pointer_type(module, child_type); } assert(local_type != zero); local.variable.type = local_type; emit_local(module, local); emit_value(module, iteratable, .memory, 0); local = local.next; } assert(!local); >length_value: &LLVMValue = zero; // TODO: we are only taking the first length in order to operate for (value: iteratables) { >value_type = value.type; >aggregate_type = value_type; if (value.kind == .left) { assert(value_type.id == .pointer); aggregate_type = value_type.content.pointer.element_type; } assert(aggregate_type != zero); >llvm_value = value.llvm; switch (aggregate_type.id) { .array => { assert(value.kind == .left); >element_count = aggregate_type.content.array.element_count; length_value = LLVMConstInt(index_type.llvm.abi, element_count, 0); }, .struct => { assert(type_is_slice(aggregate_type)); switch (value.kind) { .right => { length_value = LLVMBuildExtractValue(module.llvm.builder, llvm_value, 1, "slice.length"); }, .left => { >gep = LLVMBuildStructGEP2(module.llvm.builder, aggregate_type.llvm.abi, llvm_value, 1, "slice.length.pointer"); >load = create_load(module, { .type = index_type, .pointer = gep, zero, }); length_value = load; }, } }, else => { unreachable; }, } break; } >index_alloca = create_alloca(module, { .type = index_type, .name = "for_each.index", zero, }); create_store(module, { .source = index_zero, .destination = index_alloca, .type = index_type, zero, }); assert(length_value != zero); // Construct loop header LLVMBuildBr(module.llvm.builder, entry_block); LLVMPositionBuilderAtEnd(module.llvm.builder, entry_block); >header_index_load = create_load(module, { .type = index_type, .pointer = index_alloca, zero, }); >index_compare = LLVMBuildICmp(module.llvm.builder, .ult, header_index_load, length_value, ""); LLVMBuildCondBr(module.llvm.builder, index_compare, body_block, exit_block); // Construct loop body LLVMPositionBuilderAtEnd(module.llvm.builder, body_block); >body_index_load = create_load(module, { .type = index_type, .pointer = index_alloca, zero, }); local = statement.content.for.first_local; for (i: 0..iteratables.length) { >kind = kinds[i]; >iteratable = iteratables[i]; >iteratable_type = iteratable.type; >iteratable_kind = iteratable.kind; >iteratable_llvm = iteratable.llvm; >aggregate_type: &Type = iteratable_type; if (iteratable_kind == .left) { assert(iteratable_type.id == .pointer); aggregate_type = iteratable_type.content.pointer.element_type; } assert(aggregate_type != zero); >element_pointer_value: &LLVMValue = zero; switch (aggregate_type.id) { .array => { assert(iteratable_kind == .left); element_pointer_value = create_gep(module, { .type = aggregate_type.llvm.memory, .pointer = iteratable_llvm, .indices = [ index_zero, body_index_load ][..], zero, }); }, .struct => { assert(type_is_slice(aggregate_type)); if (iteratable_kind == .left) { iteratable_llvm = create_load(module, { .type = aggregate_type, .pointer = iteratable_llvm, zero, }); } >extract_pointer = LLVMBuildExtractValue(module.llvm.builder, iteratable_llvm, 0, ""); >fields = aggregate_type.content.struct.fields; >pointer_type = fields[0].type; assert(pointer_type.id == .pointer); >gep_type = pointer_type.content.pointer.element_type; resolve_type_in_place(module, gep_type); >gep = create_gep(module, { .type = gep_type.llvm.memory, .pointer = extract_pointer, .indices = [ body_index_load ][..], zero, }); element_pointer_value = gep; }, else => { unreachable; }, } assert(element_pointer_value != zero); >local_type = local.variable.type; switch (kind) { .right => { >evaluation_kind = get_evaluation_kind(local_type); if (evaluation_kind == .scalar or type_is_slice(aggregate_type) or type_is_slice(local_type)) { >load = create_load(module, { .type = local_type, .pointer = element_pointer_value, zero, }); create_store(module, { .source = load, .destination = local.variable.storage.llvm, .type = local_type, zero, }); } else { #trap(); } }, .left => { create_store(module, { .source = element_pointer_value, .destination = local.variable.storage.llvm, .type = local_type, zero, }); }, } local = local.next; } assert(!local); analyze_statement(module, &statement.content.for.scope, statement.content.for.predicate); if (LLVMGetInsertBlock(module.llvm.builder)) { LLVMBuildBr(module.llvm.builder, continue_block); } LLVMPositionBuilderAtEnd(module.llvm.builder, continue_block); >continue_index_load = create_load(module, { .type = index_type, .pointer = index_alloca, zero, }); >inc = LLVMBuildAdd(module.llvm.builder, continue_index_load, LLVMConstInt(index_type.llvm.abi, 1, 0), ""); create_store(module, { .source = inc, .destination = index_alloca, .type = index_type, zero, }); LLVMBuildBr(module.llvm.builder, entry_block); LLVMPositionBuilderAtEnd(module.llvm.builder, exit_block); }, .range => { >local = statement.content.for.first_local; assert(local != zero); assert(!local.next); assert(!local.variable.type); assert(kinds.length == 1); if (iteratables.length == 2) { >start = iteratables[0]; >end = iteratables[1]; >local_type: &Type = zero; if (value_is_constant(start)) { switch (start.id) { .constant_integer => { switch (end.id) { .constant_integer => { >start_signed = start.content.constant_integer.signed; >end_signed = end.content.constant_integer.signed; >is_signed = !(!start_signed and !end_signed); local_type = integer_type(module, { .bit_count = 64, .signed = is_signed }); }, else => { analyze_type(module, end, zero, zero); >end_type = end.type; assert(end_type != zero); start.type = end_type; local_type = end_type; }, } }, else => { unreachable; }, } } else { analyze_binary_type(module, start, end, 0, zero, 0); assert(start.type == end.type); local_type = start.type; } assert(local_type != zero); for (iteratable: iteratables) { if (!iteratable.type) { analyze_type(module, iteratable, local_type, zero); } } local.variable.type = local_type; emit_local(module, local); emit_value(module, start, .memory, 0); >index_alloca = local.variable.storage.llvm; create_store(module, { .source = start.llvm, .destination = index_alloca, .type = local_type, zero, }); LLVMBuildBr(module.llvm.builder, entry_block); LLVMPositionBuilderAtEnd(module.llvm.builder, entry_block); >header_index_load = create_load(module, { .type = local_type, .pointer = index_alloca, zero, }); emit_value(module, end, .abi, 0); >length_value = end.llvm; >index_compare = LLVMBuildICmp(module.llvm.builder, .ult, header_index_load, length_value, ""); LLVMBuildCondBr(module.llvm.builder, index_compare, body_block, exit_block); LLVMPositionBuilderAtEnd(module.llvm.builder, body_block); analyze_statement(module, &statement.content.for.scope, statement.content.for.predicate); if (LLVMGetInsertBlock(module.llvm.builder)) { LLVMBuildBr(module.llvm.builder, continue_block); } LLVMPositionBuilderAtEnd(module.llvm.builder, continue_block); >continue_index_load = create_load(module, { .type = local_type, .pointer = index_alloca, zero, }); >inc = LLVMBuildAdd(module.llvm.builder, continue_index_load, LLVMConstInt(local_type.llvm.abi, 1, 0), ""); create_store(module, { .source = inc, .destination = index_alloca, .type = local_type, zero, }); LLVMBuildBr(module.llvm.builder, entry_block); LLVMPositionBuilderAtEnd(module.llvm.builder, exit_block); } else { // TODO: case for reverse range #trap(); } }, } // END OF SCOPE module.llvm.continue_block = previous_continue_block; module.llvm.exit_block = previous_exit_block; }, .break => { >exit_block = module.llvm.exit_block; if (!exit_block) { report_error(); } LLVMBuildBr(module.llvm.builder, exit_block); LLVMClearInsertionPosition(module.llvm.builder); }, .continue => { >continue_block = module.llvm.continue_block; if (!continue_block) { report_error(); } LLVMBuildBr(module.llvm.builder, continue_block); LLVMClearInsertionPosition(module.llvm.builder); }, 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; } } 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; } ArgumentBuilder = struct { buffer: [128]&u8, count: u64, } add_argument = fn (builder: &ArgumentBuilder, argument: []u8) void { assert(argument.pointer[argument.length] == 0); >index = builder.count; assert(index < builder.buffer.length); builder.buffer[index] = argument.pointer; builder.count = index + 1; } flush_arguments = fn (builder: &ArgumentBuilder) []&u8 { >argument_count = builder.count; assert(argument_count < builder.buffer.length); builder.buffer[argument_count] = zero; return builder.buffer[..argument_count]; } link = fn (module: &Module) void { >arena = module.arena; >builder: ArgumentBuilder = zero; add_argument(&builder, "ld.lld"); add_argument(&builder, "--error-limit=0"); add_argument(&builder, "-o"); add_argument(&builder, module.executable); for (library_directory: module.library_directories) { add_argument(&builder, arena_join_string(arena, [ "-L", library_directory ][..])); } >candidate_library_paths: [_][]u8 = [ "/usr/lib", "/usr/lib/x86_64-linux-gnu", ]; >i: u64 = 0; >scrt1_directory_path: []u8 = zero; >scrt1_object_path: []u8 = zero; for (directory_path: candidate_library_paths) { >object_path = arena_join_string(arena, [ directory_path, "/Scrt1.o" ][..]); >file = os_file_open(object_path.pointer, { .read = 1, zero }, zero); if (file >= 0) { os_file_close(file); scrt1_directory_path = directory_path; scrt1_object_path = object_path; break; } } if (scrt1_object_path.length == 0) { report_error(); } add_argument(&builder, arena_join_string(arena, [ "-L", scrt1_directory_path ][..])); add_argument(&builder, "-L/usr/lib/gcc/x86_64-pc-linux-gnu/15.1.1"); add_argument(&builder, "-L/usr/lib/gcc/x86_64-linux-gnu/13"); for (object: module.objects) { add_argument(&builder, object); } for (library_path: module.library_paths) { add_argument(&builder, library_path); } for (library_name: module.library_names) { add_argument(&builder, arena_join_string(arena, [ "-l", library_name ][..])); } if (module.link_libcpp) { add_argument(&builder, "-lstdc++"); } >link_libc: u1 = 1; >dynamic_linker: u1 = 1; if (dynamic_linker) { add_argument(&builder, "-dynamic-linker"); add_argument(&builder, "/usr/lib64/ld-linux-x86-64.so.2"); } if (link_libc) { add_argument(&builder, scrt1_object_path); add_argument(&builder, "-lc"); } >linker_arguments = flush_arguments(&builder); >result = lld_elf_link(linker_arguments.pointer, linker_arguments.length, 1, 0); if (!result.success) { #trap(); } } emit_debug_argument = fn (module: &Module, argument: &Argument, basic_block: &LLVMBasicBlock) void { assert(module.has_debug_info); resolve_type_in_place(module, argument.variable.type); >always_preserve: u1 = 1; >flags: LLVMDIFlags = zero; >scope = argument.variable.scope.llvm; >parameter_variable = LLVMDIBuilderCreateParameterVariable(module.llvm.di_builder, scope, argument.variable.name.pointer, argument.variable.name.length, argument.index, module.llvm.file, argument.variable.line, argument.variable.type.llvm.debug, #extend(always_preserve), flags); >inlined_at = module.llvm.inlined_at; >debug_location = LLVMDIBuilderCreateDebugLocation(module.llvm.context, argument.variable.line, argument.variable.column, scope, inlined_at); LLVMDIBuilderInsertDeclareRecordAtEnd(module.llvm.di_builder, argument.variable.storage.llvm, parameter_variable, null_expression(module), debug_location, basic_block); } 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 = LLVMModuleCreateWithNameInContext(module.name.pointer, context); >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 = zero; >cpu_model: &u8 = zero; >cpu_features: &u8 = zero; 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); LLVMTargetMachineOptionsSetFeatures(target_machine_options, cpu_features); >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, &target, &error_message); if (result != 0) { report_error(); } assert(!error_message); >target_machine = LLVMCreateTargetMachineWithOptions(target, target_triple, target_machine_options); >target_data_layout = LLVMCreateTargetDataLayout(target_machine); LLVMSetModuleDataLayout(m, target_data_layout); LLVMSetTarget(m, target_triple); 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) { switch (global.variable.storage.id) { .function, .forward_declared_function => { >global_pointer_type = global.variable.storage.type; assert(global_pointer_type.id == .pointer); >global_value_type = global_pointer_type.content.pointer.element_type; >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; }, } >register_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(register_call, 11, 6), .sse = #select(register_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; }, .ignore, .indirect, => { abi_return_type = void_type(module); }, else => { unreachable; // TODO }, } 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) { assert(!function_type.abi.return_abi.flags.sret_after_this); function_type.abi.available_registers.system_v.gpr -= 1; >indirect_type = get_pointer_type(module, function_type.abi.return_abi.semantic_type); resolve_type_in_place(module, indirect_type); >abi_index = abi_argument_type_count; abi_argument_type_buffer[abi_index] = indirect_type; llvm_abi_argument_type_buffer[abi_index] = indirect_type.llvm.abi; abi_argument_type_count += 1; } if (semantic_argument_types.length != 0) { for (i: 0..semantic_argument_types.length) { >abi = &function_type.abi.argument_abis[i]; >semantic_argument_type = function_type.base.semantic_argument_types[i]; >is_named_argument = i < semantic_argument_types.length; assert(is_named_argument); abi.& = abi_system_v_classify_argument(module, &function_type.abi.available_registers.system_v, llvm_abi_argument_type_buffer[..], abi_argument_type_buffer[..], { .type = semantic_argument_type, .abi_start = abi_argument_type_count, .is_named_argument = is_named_argument, zero, }); abi_argument_type_count += abi.abi_count; } } >abi_argument_types = arena_allocate_slice[&Type](module.arena, #extend(abi_argument_type_count)); memcpy(#pointer_cast(abi_argument_types.pointer), #pointer_cast(&abi_argument_type_buffer), #extend(#byte_size(&Type) * abi_argument_type_count)); function_type.abi.abi_argument_types = abi_argument_types; }, .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; } >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, 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 => { assert(!module.current_function); >initial_value = global.initial_value; analyze_value(module, initial_value, global.variable.type, .memory, 1); >initial_value_type = initial_value.type; if (!global.variable.type) { global.variable.type = initial_value_type; } >global_type = global.variable.type; if (global_type != initial_value_type) { report_error(); } resolve_type_in_place(module, global_type); >is_constant: u1 = 0; >linkage: LLVMLinkage = undefined; switch (global.linkage) { .internal => { linkage = .internal; }, .external => { linkage = .external; }, } >thread_local_mode: LLVMThreadLocalMode = .none; >externally_initialized: u1 = 0; >alignment = get_byte_alignment(global_type); >unnamed_address: LLVMUnnamedAddress = .none; >llvm_global = llvm_create_global_variable(module.llvm.module, global_type.llvm.memory, is_constant, linkage, initial_value.llvm, global.variable.name, thread_local_mode, externally_initialized, alignment, unnamed_address); global.variable.storage.llvm = llvm_global; global.variable.storage.type = get_pointer_type(module, global_type); if (module.has_debug_info) { >name = global.variable.name; >linkage_name = name; >local_to_unit = global.linkage == .internal; >global_debug = LLVMDIBuilderCreateGlobalVariableExpression(module.llvm.di_builder, module.scope.llvm, name.pointer, name.length, linkage_name.pointer, linkage_name.length, module.llvm.file, global.variable.line, global_type.llvm.debug, #extend(local_to_unit), null_expression(module), zero, alignment * 8); LLVMGlobalSetMetadata(llvm_global, 0, global_debug); } }, 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.emitted) { 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 = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "entry"); >return_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "return_block"); 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 => { >indirect_argument_index = function_type.abi.return_abi.flags.sret_after_this; if (function_type.abi.return_abi.flags.sret_after_this) { #trap(); } global.variable.storage.content.function.llvm.return_alloca = llvm_abi_arguments[indirect_argument_index]; if (!function_type.abi.return_abi.flags.indirect_by_value) { #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 => { >first_argument = argument_abi_arguments[0]; >coerce_to_type = abi_get_coerce_to_type(argument_abi); if (coerce_to_type.id != .struct and argument_abi.attributes.direct.offset == 0 and type_is_abi_equal(module, coerce_to_type, argument_abi.semantic_type)) { assert(argument_abi.abi_count == 1); >is_promoted: u1 = 0; >v = first_argument; if (coerce_to_type.llvm.abi != LLVMTypeOf(v)) { #trap(); } if (is_promoted) { #trap(); } // TODO: this we can get rid of because we handle all of this inside `create_alloca`, load, stores, etc if (is_arbitrary_bit_integer(argument_abi.semantic_type)) { >bit_count = get_bit_size(argument_abi.semantic_type); >abi_bit_count = align_bit_count(bit_count); >is_signed = type_is_signed(argument_abi.semantic_type); >destination_type = integer_type(module, { .bit_count = abi_bit_count, .signed = is_signed }); >alloca = create_alloca(module, { .type = destination_type, .name = argument.variable.name, zero, }); >result: &LLVMValue = undefined; if (bit_count < abi_bit_count) { >llvm_type = destination_type.llvm.memory; if (is_signed) { result = LLVMBuildSExt(module.llvm.builder, first_argument, llvm_type, ""); } else { result = LLVMBuildZExt(module.llvm.builder, first_argument, llvm_type, ""); } } else { #trap(); } create_store(module, { .source = result, .destination = alloca, .type = destination_type, zero, }); semantic_argument_storage = alloca; } else { >alloca = create_alloca(module, { .type = argument_abi.semantic_type, .name = argument.variable.name, zero, }); create_store(module, { .source = first_argument, .destination = alloca, .type = argument_abi.semantic_type, zero, }); semantic_argument_storage = alloca; } } else { >is_fixed_vector_type: u1 = 0; if (is_fixed_vector_type) { #trap(); } if (coerce_to_type.id == .struct and coerce_to_type.content.struct.fields.length > 1 and argument_abi.flags.kind == .direct and !argument_abi.flags.can_be_flattened) { >contains_homogeneous_scalable_vector_types: u1 = 0; if (contains_homogeneous_scalable_vector_types) { #trap(); } } >alloca = create_alloca(module, { .type = argument_abi.semantic_type, .name = argument.variable.name, zero, }); >pointer: &LLVMValue = undefined; >pointer_type: &Type = undefined; if (argument_abi.attributes.direct.offset > 0) { #trap(); } else { pointer = alloca; pointer_type = argument_abi.semantic_type; } if (coerce_to_type.id == .struct and coerce_to_type.content.struct.fields.length > 1 and argument_abi.flags.kind == .direct and argument_abi.flags.can_be_flattened) { >struct_size = get_byte_size(coerce_to_type); >pointer_element_size = get_byte_size(pointer_type); >is_scalable: u1 = 0; if (is_scalable) { #trap(); } else { >source_size = struct_size; >destination_size = pointer_element_size; >address_alignment = get_byte_alignment(argument_abi.semantic_type); >address: &LLVMValue = undefined; if (source_size <= destination_size) { address = alloca; } else { address = create_alloca(module, { .type = coerce_to_type, .name = "coerce", .alignment = address_alignment, }); } >fields = coerce_to_type.content.struct.fields; assert(fields.length == #extend(argument_abi.abi_count)); resolve_type_in_place(module, coerce_to_type); for (i: 0..fields.length) { >field = &fields[i]; >gep = LLVMBuildStructGEP2(module.llvm.builder, coerce_to_type.llvm.abi, address, #truncate(i), ""); create_store(module, { .source = argument_abi_arguments[i], .destination = gep, .type = fields[i].type, zero, }); } if (source_size > destination_size) { >u64_type = uint64(module); resolve_type_in_place(module, u64_type); >memcpy_size = LLVMConstInt(u64_type.llvm.abi, destination_size, 0); LLVMBuildMemCpy(module.llvm.builder, pointer, address_alignment, address, address_alignment, memcpy_size); } } } else { assert(argument_abi.abi_count == 1); >abi_argument_type = function_type.abi.abi_argument_types[argument_abi.abi_start]; >destination_size: u64 = get_byte_size(pointer_type) - #extend(argument_abi.attributes.direct.offset); >is_volatile: u1 = 0; create_coerced_store(module, argument_abi_arguments[0], abi_argument_type, pointer, pointer_type, destination_size, is_volatile); } semantic_argument_storage = alloca; } }, .indirect => { assert(argument_abi.abi_count == 1); >evaluation_kind = get_evaluation_kind(argument_abi.semantic_type); switch (evaluation_kind) { else => { if (argument_abi.flags.indirect_realign or argument_abi.flags.kind == .indirect_aliased) { #trap(); } >use_indirect_debug_address = !argument_abi.flags.indirect_by_value; if (use_indirect_debug_address) { #trap(); } >llvm_argument = argument_abi_arguments[0]; semantic_argument_storage = llvm_argument; }, .scalar => { #trap(); }, } }, else => { unreachable; }, } assert(semantic_argument_storage != zero); >storage = new_value(module); >value_type = argument.variable.type; storage.& = { .type = get_pointer_type(module, value_type), .id = .argument, .llvm = semantic_argument_storage, zero, }; argument.variable.storage = storage; if (module.has_debug_info) { emit_debug_argument(module, argument, entry_block); } } analyze_block(module, function.content.function.block); >current_basic_block = LLVMGetInsertBlock(module.llvm.builder); if (current_basic_block) { assert(!LLVMGetBasicBlockTerminator(current_basic_block)); if (!LLVMGetFirstInstruction(current_basic_block) or !LLVMGetFirstUse(#pointer_cast(current_basic_block))) { LLVMReplaceAllUsesWith(#pointer_cast(return_block), #pointer_cast(current_basic_block)); LLVMDeleteBasicBlock(return_block); } else { emit_block(module, return_block); } } 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 { >source: &LLVMValue = zero; if (function_type.abi.return_abi.attributes.direct.offset == 0) { source = return_alloca; } else { #trap(); } assert(source != zero); >source_type = function_type.abi.return_abi.semantic_type; >destination_type = coerce_to_type; >result = create_coerced_load(module, source, source_type, destination_type); return_value = result; } }, .indirect => { >evaluation_kind = get_evaluation_kind(function_type.abi.return_abi.semantic_type); switch (evaluation_kind) { .scalar => { #trap(); }, .aggregate => {}, .complex => { #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; >verify_result = LLVMVerifyModule(module.llvm.module, .return, &verification_error_message) == 0; if (!verify_result) { print(llvm_module_to_string(module.llvm.module)); print("\n==========================\nLLVM VERIFICATION ERROR\n==========================\n"); print(c_string_to_slice(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, }); if (object_generation_result != .success) { report_error(); } link(module); } 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, .library_directories = options.library_directories, .library_names = options.library_names, .library_paths = options.library_paths, .link_libc = options.link_libc, .link_libcpp = options.link_libcpp, .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) []u8 { >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"); >base_dir = arena_join_string(arena, [ base_cache_dir, "/", #enum_name(#build_mode), "_", #select(#has_debug_info(), "di", "nodi"), ][..]); >outputh_path_dir = arena_join_string(arena, [ base_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); os_make_directory(base_dir.pointer); if (is_compiler) { >compiler_dir = arena_join_string(arena, [ base_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 = [ output_object_path ][..]; >c_abi_library = "build/libc_abi.a"; >llvm_bindings_library = "build/libllvm_bindings.a"; >library_buffer: [256][]u8 = undefined; >library_directory: []u8 = zero; >library_directories: [][]u8 = zero; >library_names: [][]u8 = zero; >library_paths: [][]u8 = zero; if (is_compiler) { #trap(); } else if (string_equal(base_name, "c_abi")) { library_paths = { .pointer = &c_abi_library, .length = 1 }; } >options: CompileOptions = { .executable = output_executable_path, .objects = objects, .library_directories = library_directories, .library_names = library_names, .library_paths = library_paths, .link_libc = 1, .link_libcpp = 0, .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); return output_executable_path; } names: [_][]u8 = [ "minimal", "comments", "constant_add", "constant_and", "constant_div", "constant_mul", "constant_rem", "constant_or", "constant_sub", "constant_xor", "constant_shift_left", "constant_shift_right", "minimal_stack", "minimal_stack_arithmetic", "minimal_stack_arithmetic2", "minimal_stack_arithmetic3", "stack_negation", "stack_add", "stack_sub", "extend", "integer_max", "integer_hex", "basic_pointer", "basic_call", "basic_branch", "basic_array", "basic_enum", "basic_slice", "basic_string", "basic_varargs", "basic_while", "pointer", "pointer_cast", "u1_return", "local_type_inference", "global", "function_pointer", "extern", "byte_size", "argv", "assignment_operators", "not_pointer", "bits", "bits_no_backing_type", "bits_return_u1", "bits_zero", "comparison", "global_struct", "if_no_else", "if_no_else_void", "indirect", "indirect_struct", "indirect_varargs", "ret_c_bool", "return_type_builtin", "return_u64_u64", "select", "slice", "small_struct_ints", "struct_assignment", "struct", "struct_u64_u64", "struct_varargs", "struct_zero", "unreachable", "varargs", "c_abi0", "c_abi1", "c_med_struct_ints", "c_ret_struct_array", "c_split_struct_ints", "c_string_to_slice", "c_struct_with_array", "c_function_pointer", "basic_bool_call", "abi_enum_bool", "return_small_struct", "c_abi", "string_to_enum", "empty_if", "else_if", "else_if_complicated", "basic_shortcircuiting_if", "shortcircuiting_if", "field_access_left_assign", "for_each", "pointer_decay", "enum_name", "slice_of_slices", "type_alias", "integer_formats", "for_each_int", "bool_array", "basic_union", "break_continue", "constant_global_reference", "concat_logical_or", "strict_array_type", "pointer_struct_initialization", "slice_array_literal", "slice_only_start", "basic_macro", "generic_macro", "generic_pointer_macro", "noreturn_macro", "generic_pointer_array", "self_referential_struct", "forward_declared_type", ]; [export] main = fn [cc(c)] (argument_count: u32, argv: &&u8, envp: &&u8) s32 { global_state_initialize(); >arena = global_state.arena; >arguments = argv[..#extend(argument_count)]; if (arguments.length < 2) { return 1; } >command_string = c_string_to_slice(arguments[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 (arguments.length < 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(arguments[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(arguments[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 = arguments[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 => { if (arguments.length != 2) { report_error(); } >debug_info_array: [_]u1 = [1, 0]; for (name: names) { for (build_mode: #enum_values(BuildMode)) { for (has_debug_info: debug_info_array) { >position = arena.position; >relative_file_path = arena_join_string(arena, [ "tests/", name, ".bbb" ][..]); >executable_path = compile_file(arena, { .relative_file_path = relative_file_path, .build_mode = build_mode, .has_debug_info = has_debug_info, .silent = 1, }); >arguments: [_]&u8 = [ executable_path.pointer, zero, ]; >args = arguments[..arguments.length - 1]; >execution = os_execute(arena, args, envp, zero); >success = execution.termination_kind == .exit and execution.termination_code == 0; if (!success) { #trap(); } arena.position = position; } } } }, } return 0; }