bloat-buster/src/compiler.bbb

14103 lines
430 KiB
Plaintext

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;
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,
}
TypeContent = union
{
integer: TypeInteger,
function: TypeFunction,
pointer: TypePointer,
array: TypeArray,
enum_array: TypeEnumArray,
enum: TypeEnum,
struct: TypeStruct,
bits: TypeBits,
}
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);
},
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;
},
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;
},
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;
},
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_reference,
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,
}
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,
}
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,
}
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,
=>
{
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;
},
else =>
{
#trap();
}
}
}
i128_offset: u64 = 64 * 2;
void_offset: u64 = i128_offset + 2;
MacroDeclaration = struct
{
foo: u32,
}
MacroInstantiation = struct
{
foo: u32,
}
[extern] LLVMInitializeX86TargetInfo = fn [cc(c)] () void;
[extern] LLVMInitializeX86Target = fn [cc(c)] () void;
[extern] LLVMInitializeX86TargetMC = fn [cc(c)] () void;
[extern] LLVMInitializeX86AsmPrinter = fn [cc(c)] () void;
[extern] LLVMInitializeX86AsmParser = fn [cc(c)] () void;
[extern] LLVMInitializeX86Disassembler = fn [cc(c)] () void;
[extern] 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] 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] 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] 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] 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] 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;
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,
}
StatementSwitchClause = struct
{
values: []&Value,
block: &Block,
}
StatementSwitch = struct
{
discriminant: &Value,
clauses: []StatementSwitchClause,
}
StatementForKind = enum
{
slice,
range,
}
StatementFor = struct
{
first_local: &Local,
last_local: &Local,
kinds: ValueKind,
iteratables: &Value,
predicate: &Statement,
scope: Scope,
kind: StatementForKind,
}
StatementContent = union
{
local: &Local,
expression: &Value,
return: &Value,
assignment: StatementAssignment,
if: StatementIf,
block: &Block,
while: StatementWhile,
switch: StatementSwitch,
for: StatementFor,
}
Statement = struct
{
content: StatementContent,
next: &Statement,
line: u32,
column: u32,
id: StatementId,
}
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 =>
{
#trap();
}
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 });
}
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;
}
},
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;
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 != zero)
{
#trap();
}
},
.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 =>
{
#trap();
},
.macro_declaration =>
{
#trap();
},
.macro_instantiation =>
{
#trap();
},
}
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 =>
{
#trap();
},
// 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 =>
{
#trap();
},
.compare_equal,
.compare_not_equal,
.compare_less,
.compare_less_equal,
.compare_greater,
.compare_greater_equal,
=>
{
return .comparison;
},
.ampersand,
.bar,
.caret,
=>
{
return .bitwise;
},
.shift_left,
.shift_right,
=>
{
return .shifting;
},
.plus,
.dash,
=>
{
return .add_like;
},
.asterisk,
.forward_slash,
.percentage,
=>
{
return .div_like;
},
.pointer_dereference,
.left_parenthesis,
.left_bracket,
.dot,
=>
{
return .postfix;
},
else => { unreachable; },
}
}
parse_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 =>
{
#trap();
},
.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_reference =>
{
#trap();
},
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_reference)
{
#trap();
}
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 =>
{
#trap();
},
.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 =>
{
#trap();
},
.break =>
{
#trap();
},
.continue =>
{
#trap();
},
}
}
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 =>
{
#trap();
},
.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 =>
{
#trap();
},
.union =>
{
#trap();
},
}
}
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);
},
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;
},
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;
},
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_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;
}
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 =>
{
#trap();
},
.pointer_from_int =>
{
#trap();
},
.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,
=>
{
>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
{
#trap();
}
},
.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 =>
{
#trap();
},
.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 =>
{
#trap();
},
.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 =>
{
#trap();
},
.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);
},
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)
{
#trap();
}
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 =>
{
#trap();
},
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 =>
{
#trap();
},
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_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)
{
#trap();
}
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)
{
#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 ][..]);
},
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();
}
// TODO: field parent pointer
#trap();
}
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
{
#trap();
}
},
.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);
},
else =>
{
#trap();
},
}
assert(llvm_value != zero);
value.llvm = llvm_value;
}
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_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)
{
#trap();
}
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 =>
{
#trap();
},
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));
},
}
},
else =>
{
#trap();
},
}
},
.complex =>
{
#trap();
},
}
}
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_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;
}
analyze_block = fn (module: &Module, block: &Block) void;
analyze_statement = fn (module: &Module, scope: &Scope, statement: &Statement) void
{
>parent_function_global: &Global = undefined;
if (module.current_function)
{
parent_function_global = module.current_function;
assert(parent_function_global != zero);
}
else if (module.current_macro_instantiation)
{
#trap();
}
else
{
report_error();
}
>llvm_function = parent_function_global.variable.storage.llvm;
assert(llvm_function != zero);
>statement_location: &LLVMMetadata = zero;
if (module.has_debug_info)
{
statement_location = LLVMDIBuilderCreateDebugLocation(module.llvm.context, statement.line, statement.column, scope.llvm, module.llvm.inlined_at);
LLVMSetCurrentDebugLocation2(module.llvm.builder, statement_location);
}
switch (statement.id)
{
.return =>
{
>return_value = statement.content.return;
if (module.current_function)
{
assert(!module.current_macro_instantiation);
>function_type = &parent_function_global.variable.storage.type.content.pointer.element_type.content.function;
>return_abi = &function_type.abi.return_abi;
switch (return_abi.semantic_type.id)
{
.void =>
{
if (return_value)
{
report_error();
}
},
.noreturn =>
{
report_error();
},
else =>
{
if (!return_value)
{
report_error();
}
if (module.has_debug_info)
{
LLVMSetCurrentDebugLocation2(module.llvm.builder, statement_location);
}
>return_alloca = parent_function_global.variable.storage.content.function.llvm.return_alloca;
assert(return_alloca != zero);
>return_type = return_abi.semantic_type;
analyze_type(module, return_value, return_type, zero);
>pointer_type = get_pointer_type(module, return_type);
emit_assignment(module, return_alloca, pointer_type, return_value);
},
}
>return_block = parent_function_global.variable.storage.content.function.llvm.return_block;
assert(return_block != zero);
LLVMBuildBr(module.llvm.builder, return_block);
LLVMClearInsertionPosition(module.llvm.builder);
}
else if (module.current_macro_instantiation)
{
#trap();
}
else
{
report_error();
}
},
.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,
});
},
}
},
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)
{
continue;
}
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)
{
continue;
}
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",
];
[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;
}