14038 lines
428 KiB
Plaintext
14038 lines
428 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;
|
|
},
|
|
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 =>
|
|
{
|
|
#trap();
|
|
},
|
|
.enum =>
|
|
{
|
|
#trap();
|
|
},
|
|
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;
|
|
},
|
|
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 =>
|
|
{
|
|
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;
|
|
|
|
if (array_type != zero)
|
|
{
|
|
#trap();
|
|
}
|
|
|
|
>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);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
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 =>
|
|
{
|
|
#trap();
|
|
},
|
|
.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);
|
|
},
|
|
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: [][]u8 = undefined;
|
|
objects = [ output_object_path ][..];
|
|
|
|
>library_directories: [][]u8 = zero;
|
|
>library_names: [][]u8 = zero;
|
|
>library_paths: [][]u8 = zero;
|
|
|
|
if (is_compiler)
|
|
{
|
|
#trap();
|
|
}
|
|
|
|
>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",
|
|
];
|
|
|
|
[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;
|
|
}
|