2024-07-14 20:44:27 +02:00

6430 lines
174 KiB
C++

#include <stdint.h>
#include <stdarg.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/mman.h>
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int8_t s8;
typedef int16_t s16;
typedef int32_t s32;
typedef int64_t s64;
typedef float f32;
typedef double f64;
typedef u64 Hash;
#define fn static
#define method __attribute__((visibility("internal")))
#define global static
#define assert(x) if (__builtin_expect(!(x), 0)) { trap(); }
#define forceinline __attribute__((always_inline))
#define expect(x, b) __builtin_expect(x, b)
#define breakpoint() __builtin_debugtrap()
#define trap() __builtin_trap()
#define array_length(arr) sizeof(arr) / sizeof((arr)[0])
#define KB(n) ((n) * 1024)
#define MB(n) ((n) * 1024 * 1024)
#define GB(n) ((u64)(n) * 1024 * 1024 * 1024)
#define TB(n) ((u64)(n) * 1024 * 1024 * 1024 * 1024)
#define unused(x) (void)(x)
#ifdef DEMAND_LIBC
#if DEMAND_LIBC
#define LINK_LIBC 1
#else
#define LINK_LIBC 0
#endif
#else
#define LINK_LIBC 0
#endif
#if __APPLE__
global auto constexpr page_size = KB(16);
#else
global auto constexpr page_size = KB(4);
#endif
#define may_be_unused __attribute__((unused))
global constexpr auto brace_open = '{';
global constexpr auto brace_close = '}';
global constexpr auto parenthesis_open = '(';
global constexpr auto parenthesis_close = ')';
global constexpr auto bracket_open = '[';
global constexpr auto bracket_close = ']';
// Lehmer's generator
// https://lemire.me/blog/2019/03/19/the-fastest-conventional-random-number-generator-that-can-pass-big-crush/
__uint128_t rn_state;
fn u64 generate_random_number()
{
rn_state *= 0xda942042e4dd58b5;
return rn_state >> 64;
}
fn u64 round_up_to_next_power_of_2(u64 n)
{
n -= 1;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
n |= n >> 32;
n += 1;
return n;
}
extern "C" void* memcpy(void* __restrict dst, void* __restrict src, u64 size)
{
auto* destination = (u8*)dst;
auto* source = (u8*)src;
for (u64 i = 0; i < size; i += 1)
{
destination[i] = source[i];
}
return dst;
}
extern "C" void* memset(void* dst, u8 n, u64 size)
{
auto* destination = (u8*)dst;
for (u64 i = 0; i < size; i += 1)
{
destination[i] = n;
}
return dst;
}
fn int memcmp(const void* left, const void* right, u64 n)
{
const u8 *l=(const u8*)left, *r=(const u8*)right;
for (; n && *l == *r; n--, l++, r++);
return n ? *l - *r : 0;
}
template<typename T>
forceinline fn u8 mem_equal_range(T* a, T* b, u64 count)
{
return memcmp(a, b, count * sizeof(T)) == 0;
}
template<typename T>
struct Slice
{
T* pointer;
u64 length;
method T& operator[](u64 index)
{
assert(index < length);
return pointer[index];
}
fn Slice from_pointer_range(T* start, T* end)
{
assert(end >= start);
return {
.pointer = start,
.length = u64(end - start),
};
}
method Slice slice(u64 start, u64 end)
{
return {
.pointer = pointer + start,
.length = end - start,
};
}
method forceinline u8 equal(Slice other)
{
if (length == other.length)
{
return mem_equal_range(pointer, other.pointer, length);
}
else
{
return 0;
}
}
method forceinline T* begin()
{
return pointer;
}
method forceinline T* end()
{
return pointer + length;
}
method forceinline void copy_in(Slice other)
{
assert(length == other.length);
memcpy(pointer, other.pointer, sizeof(T) * other.length);
}
method T* find(T item)
{
T* result = 0;
for (T& i : *this)
{
if (i == item)
{
result = &i;
break;
}
}
return result;
}
method u32 index(T* item)
{
return item - pointer;
}
method s32 find_index(T item)
{
if (auto* result = find(item))
{
auto result_index = index(result);
return result_index;
}
else
{
return -1;
}
}
// Gotta implement this just because C++
method u8 operator==(Slice other)
{
u8 result = 0;
if (other.length == length)
{
if (other.pointer != pointer)
{
u64 i;
for (i = 0; i < length; i += 1)
{
if ((*this)[i] != other[i])
{
break;
}
}
result = i == length;
}
else
{
result = 1;
}
}
return result;
}
};
template <typename T>
forceinline fn T min(T a, T b)
{
return a < b ? a : b;
}
template <typename T>
forceinline fn T max(T a, T b)
{
return a > b ? a : b;
}
using String = Slice<u8>;
#define strlit(s) String{ .pointer = (u8*)s, .length = sizeof(s) - 1, }
#define ch_to_str(ch) String{ .pointer = &ch, .length = 1 }
#define array_to_slice(arr) { .pointer = arr, .length = array_length(arr) }
#define case_to_name(prefix, e) case prefix::e: return strlit(#e)
fn u64 parse_decimal(String string)
{
u64 value = 0;
for (u8 ch : string)
{
assert(((ch >= '0') & (ch <= '9')));
value = (value * 10) + (ch - '0');
}
return value;
}
fn u64 safe_flag(u64 value, u64 flag)
{
u64 result = value & ((u64)0 - flag);
return result;
}
fn u8 get_next_ch_safe(String string, u64 index)
{
u64 next_index = index + 1;
u64 is_in_range = next_index < string.length;
u64 safe_index = safe_flag(next_index, is_in_range);
u8 unsafe_result = string.pointer[safe_index];
u64 safe_result = safe_flag(unsafe_result, is_in_range);
assert(safe_result < 256);
return (u8)safe_result;
}
fn u32 is_space(u8 ch, u8 next_ch)
{
u32 is_comment = (ch == '/') & (next_ch == '/');
u32 is_whitespace = ch == ' ';
u32 is_vertical_tab = ch == 0x0b;
u32 is_horizontal_tab = ch == '\t';
u32 is_line_feed = ch == '\n';
u32 is_carry_return = ch == '\r';
u32 result = (((is_vertical_tab | is_horizontal_tab) | (is_line_feed | is_carry_return)) | (is_comment | is_whitespace));
return result;
}
fn u64 is_lower(u8 ch)
{
return (ch >= 'a') & (ch <= 'z');
}
fn u64 is_upper(u8 ch)
{
return (ch >= 'A') & (ch <= 'Z');
}
fn u64 is_alphabetic(u8 ch)
{
return is_lower(ch) | is_upper(ch);
}
fn u64 is_decimal_digit(u8 ch)
{
return (ch >= '0') & (ch <= '9');
}
fn u64 is_hex_digit(u8 ch)
{
return (is_decimal_digit(ch) | ((ch == 'a' | ch == 'A') | (ch == 'b' | ch == 'B'))) | (((ch == 'c' | ch == 'C') | (ch == 'd' | ch == 'D')) | ((ch == 'e' | ch == 'E') | (ch == 'f' | ch == 'F')));
}
fn u64 is_identifier_start(u8 ch)
{
u64 alphabetic = is_alphabetic(ch);
u64 is_underscore = ch == '_';
return alphabetic | is_underscore;
}
fn u64 is_identifier_ch(u8 ch)
{
u64 identifier_start = is_identifier_start(ch);
u64 decimal = is_decimal_digit(ch);
return identifier_start | decimal;
}
template<typename T>
struct DynamicList
{
T* pointer;
u64 count;
DynamicList* next;
};
template<typename T, u64 count>
struct StaticList
{
u64 length;
StaticList* next;
T array[count];
};
global auto constexpr fnv_offset = 14695981039346656037ull;
global auto constexpr fnv_prime = 1099511628211ull;
fn Hash hash_bytes(String bytes)
{
u64 result = fnv_offset;
for (u64 i = 0; i < bytes.length; i += 1)
{
result ^= bytes.pointer[i];
result *= fnv_prime;
}
return result;
}
#if LINK_LIBC == 0
#ifdef __linux__
may_be_unused fn forceinline long syscall0(long n)
{
unsigned long ret;
__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n) : "rcx", "r11", "memory");
return ret;
}
may_be_unused fn forceinline long syscall1(long n, long a1)
{
unsigned long ret;
__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1) : "rcx", "r11", "memory");
return ret;
}
may_be_unused fn forceinline long syscall2(long n, long a1, long a2)
{
unsigned long ret;
__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2)
: "rcx", "r11", "memory");
return ret;
}
may_be_unused fn forceinline long syscall3(long n, long a1, long a2, long a3)
{
unsigned long ret;
__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2),
"d"(a3) : "rcx", "r11", "memory");
return ret;
}
may_be_unused fn forceinline long syscall4(long n, long a1, long a2, long a3, long a4)
{
unsigned long ret;
register long r10 __asm__("r10") = a4;
__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2),
"d"(a3), "r"(r10): "rcx", "r11", "memory");
return ret;
}
may_be_unused fn forceinline long syscall5(long n, long a1, long a2, long a3, long a4, long a5)
{
unsigned long ret;
register long r10 __asm__("r10") = a4;
register long r8 __asm__("r8") = a5;
__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2),
"d"(a3), "r"(r10), "r"(r8) : "rcx", "r11", "memory");
return ret;
}
may_be_unused fn forceinline long syscall6(long n, long a1, long a2, long a3, long a4, long a5, long a6)
{
unsigned long ret;
register long r10 __asm__("r10") = a4;
register long r8 __asm__("r8") = a5;
register long r9 __asm__("r9") = a6;
__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2),
"d"(a3), "r"(r10), "r"(r8), "r"(r9) : "rcx", "r11", "memory");
return ret;
}
enum class SyscallX86_64 : u64 {
read = 0,
write = 1,
open = 2,
close = 3,
stat = 4,
fstat = 5,
lstat = 6,
poll = 7,
lseek = 8,
mmap = 9,
mprotect = 10,
munmap = 11,
brk = 12,
rt_sigaction = 13,
rt_sigprocmask = 14,
rt_sigreturn = 15,
ioctl = 16,
pread64 = 17,
pwrite64 = 18,
readv = 19,
writev = 20,
access = 21,
pipe = 22,
select = 23,
sched_yield = 24,
mremap = 25,
msync = 26,
mincore = 27,
madvise = 28,
shmget = 29,
shmat = 30,
shmctl = 31,
dup = 32,
dup2 = 33,
pause = 34,
nanosleep = 35,
getitimer = 36,
alarm = 37,
setitimer = 38,
getpid = 39,
sendfile = 40,
socket = 41,
connect = 42,
accept = 43,
sendto = 44,
recvfrom = 45,
sendmsg = 46,
recvmsg = 47,
shutdown = 48,
bind = 49,
listen = 50,
getsockname = 51,
getpeername = 52,
socketpair = 53,
setsockopt = 54,
getsockopt = 55,
clone = 56,
fork = 57,
vfork = 58,
execve = 59,
exit = 60,
wait4 = 61,
kill = 62,
uname = 63,
semget = 64,
semop = 65,
semctl = 66,
shmdt = 67,
msgget = 68,
msgsnd = 69,
msgrcv = 70,
msgctl = 71,
fcntl = 72,
flock = 73,
fsync = 74,
fdatasync = 75,
truncate = 76,
ftruncate = 77,
getdents = 78,
getcwd = 79,
chdir = 80,
fchdir = 81,
rename = 82,
mkdir = 83,
rmdir = 84,
creat = 85,
link = 86,
unlink = 87,
symlink = 88,
readlink = 89,
chmod = 90,
fchmod = 91,
chown = 92,
fchown = 93,
lchown = 94,
umask = 95,
gettimeofday = 96,
getrlimit = 97,
getrusage = 98,
sysinfo = 99,
times = 100,
ptrace = 101,
getuid = 102,
syslog = 103,
getgid = 104,
setuid = 105,
setgid = 106,
geteuid = 107,
getegid = 108,
setpgid = 109,
getppid = 110,
getpgrp = 111,
setsid = 112,
setreuid = 113,
setregid = 114,
getgroups = 115,
setgroups = 116,
setresuid = 117,
getresuid = 118,
setresgid = 119,
getresgid = 120,
getpgid = 121,
setfsuid = 122,
setfsgid = 123,
getsid = 124,
capget = 125,
capset = 126,
rt_sigpending = 127,
rt_sigtimedwait = 128,
rt_sigqueueinfo = 129,
rt_sigsuspend = 130,
sigaltstack = 131,
utime = 132,
mknod = 133,
uselib = 134,
personality = 135,
ustat = 136,
statfs = 137,
fstatfs = 138,
sysfs = 139,
getpriority = 140,
setpriority = 141,
sched_setparam = 142,
sched_getparam = 143,
sched_setscheduler = 144,
sched_getscheduler = 145,
sched_get_priority_max = 146,
sched_get_priority_min = 147,
sched_rr_get_interval = 148,
mlock = 149,
munlock = 150,
mlockall = 151,
munlockall = 152,
vhangup = 153,
modify_ldt = 154,
pivot_root = 155,
_sysctl = 156,
prctl = 157,
arch_prctl = 158,
adjtimex = 159,
setrlimit = 160,
chroot = 161,
sync = 162,
acct = 163,
settimeofday = 164,
mount = 165,
umount2 = 166,
swapon = 167,
swapoff = 168,
reboot = 169,
sethostname = 170,
setdomainname = 171,
iopl = 172,
ioperm = 173,
create_module = 174,
init_module = 175,
delete_module = 176,
get_kernel_syms = 177,
query_module = 178,
quotactl = 179,
nfsservctl = 180,
getpmsg = 181,
putpmsg = 182,
afs_syscall = 183,
tuxcall = 184,
security = 185,
gettid = 186,
readahead = 187,
setxattr = 188,
lsetxattr = 189,
fsetxattr = 190,
getxattr = 191,
lgetxattr = 192,
fgetxattr = 193,
listxattr = 194,
llistxattr = 195,
flistxattr = 196,
removexattr = 197,
lremovexattr = 198,
fremovexattr = 199,
tkill = 200,
time = 201,
futex = 202,
sched_setaffinity = 203,
sched_getaffinity = 204,
set_thread_area = 205,
io_setup = 206,
io_destroy = 207,
io_getevents = 208,
io_submit = 209,
io_cancel = 210,
get_thread_area = 211,
lookup_dcookie = 212,
epoll_create = 213,
epoll_ctl_old = 214,
epoll_wait_old = 215,
remap_file_pages = 216,
getdents64 = 217,
set_tid_address = 218,
restart_syscall = 219,
semtimedop = 220,
fadvise64 = 221,
timer_create = 222,
timer_settime = 223,
timer_gettime = 224,
timer_getoverrun = 225,
timer_delete = 226,
clock_settime = 227,
clock_gettime = 228,
clock_getres = 229,
clock_nanosleep = 230,
exit_group = 231,
epoll_wait = 232,
epoll_ctl = 233,
tgkill = 234,
utimes = 235,
vserver = 236,
mbind = 237,
set_mempolicy = 238,
get_mempolicy = 239,
mq_open = 240,
mq_unlink = 241,
mq_timedsend = 242,
mq_timedreceive = 243,
mq_notify = 244,
mq_getsetattr = 245,
kexec_load = 246,
waitid = 247,
add_key = 248,
request_key = 249,
keyctl = 250,
ioprio_set = 251,
ioprio_get = 252,
inotify_init = 253,
inotify_add_watch = 254,
inotify_rm_watch = 255,
migrate_pages = 256,
openat = 257,
mkdirat = 258,
mknodat = 259,
fchownat = 260,
futimesat = 261,
fstatat64 = 262,
unlinkat = 263,
renameat = 264,
linkat = 265,
symlinkat = 266,
readlinkat = 267,
fchmodat = 268,
faccessat = 269,
pselect6 = 270,
ppoll = 271,
unshare = 272,
set_robust_list = 273,
get_robust_list = 274,
splice = 275,
tee = 276,
sync_file_range = 277,
vmsplice = 278,
move_pages = 279,
utimensat = 280,
epoll_pwait = 281,
signalfd = 282,
timerfd_create = 283,
eventfd = 284,
fallocate = 285,
timerfd_settime = 286,
timerfd_gettime = 287,
accept4 = 288,
signalfd4 = 289,
eventfd2 = 290,
epoll_create1 = 291,
dup3 = 292,
pipe2 = 293,
inotify_init1 = 294,
preadv = 295,
pwritev = 296,
rt_tgsigqueueinfo = 297,
perf_event_open = 298,
recvmmsg = 299,
fanotify_init = 300,
fanotify_mark = 301,
prlimit64 = 302,
name_to_handle_at = 303,
open_by_handle_at = 304,
clock_adjtime = 305,
syncfs = 306,
sendmmsg = 307,
setns = 308,
getcpu = 309,
process_vm_readv = 310,
process_vm_writev = 311,
kcmp = 312,
finit_module = 313,
sched_setattr = 314,
sched_getattr = 315,
renameat2 = 316,
seccomp = 317,
getrandom = 318,
memfd_create = 319,
kexec_file_load = 320,
bpf = 321,
execveat = 322,
userfaultfd = 323,
membarrier = 324,
mlock2 = 325,
copy_file_range = 326,
preadv2 = 327,
pwritev2 = 328,
pkey_mprotect = 329,
pkey_alloc = 330,
pkey_free = 331,
statx = 332,
io_pgetevents = 333,
rseq = 334,
pidfd_send_signal = 424,
io_uring_setup = 425,
io_uring_enter = 426,
io_uring_register = 427,
open_tree = 428,
move_mount = 429,
fsopen = 430,
fsconfig = 431,
fsmount = 432,
fspick = 433,
pidfd_open = 434,
clone3 = 435,
close_range = 436,
openat2 = 437,
pidfd_getfd = 438,
faccessat2 = 439,
process_madvise = 440,
epoll_pwait2 = 441,
mount_setattr = 442,
quotactl_fd = 443,
landlock_create_ruleset = 444,
landlock_add_rule = 445,
landlock_restrict_self = 446,
memfd_secret = 447,
process_mrelease = 448,
futex_waitv = 449,
set_mempolicy_home_node = 450,
cachestat = 451,
fchmodat2 = 452,
map_shadow_stack = 453,
futex_wake = 454,
futex_wait = 455,
futex_requeue = 456,
};
#endif
#endif
fn void* syscall_mmap(void* address, size_t length, int protection_flags, int map_flags, int fd, signed long offset)
{
#if LINK_LIBC
return mmap(address, length, protection_flags, map_flags, fd, offset);
#else
#ifdef __linux__
return (void*) syscall6(static_cast<long>(SyscallX86_64::mmap), (unsigned long)address, length, protection_flags, map_flags, fd, offset);
#else
#error "Unsupported operating system for static linking"
#endif
#endif
}
fn int syscall_mprotect(void *address, size_t length, int protection_flags)
{
#if LINK_LIBC
return mprotect(address, length, protection_flags);
#else
#ifdef __linux__
return syscall3(static_cast<long>(SyscallX86_64::mprotect), (unsigned long)address, length, protection_flags);
#else
return mprotect(address, length, protection_flags);
#endif
#endif
}
fn int syscall_open(const char *file_path, int flags, int mode)
{
#if LINK_LIBC
return open(file_path, flags, mode);
#else
#ifdef __linux__
return syscall3(static_cast<long>(SyscallX86_64::open), (unsigned long)file_path, flags, mode);
#else
return open(file_path, flags, mode);
#endif
#endif
}
fn int syscall_fstat(int fd, struct stat *buffer)
{
#if LINK_LIBC
return fstat(fd, buffer);
#else
#ifdef __linux__
return syscall2(static_cast<long>(SyscallX86_64::fstat), fd, (unsigned long)buffer);
#else
return fstat(fd, buffer);
#endif
#endif
}
fn ssize_t syscall_read(int fd, void* buffer, size_t bytes)
{
#if LINK_LIBC
return read(fd, buffer, bytes);
#else
#ifdef __linux__
return syscall3(static_cast<long>(SyscallX86_64::read), fd, (unsigned long)buffer, bytes);
#else
return read(fd, buffer, bytes);
#endif
#endif
}
may_be_unused fn ssize_t syscall_write(int fd, const void *buffer, size_t bytes)
{
#if LINK_LIBC
return write(fd, buffer, bytes);
#else
#ifdef __linux__
return syscall3(static_cast<long>(SyscallX86_64::write), fd, (unsigned long)buffer, bytes);
#else
return write(fd, buffer, bytes);
#endif
#endif
}
[[noreturn]] [[gnu::cold]] fn void syscall_exit(int status)
{
#if LINK_LIBC
_exit(status);
#else
#ifdef __linux__
(void)syscall1(231, status);
trap();
#else
_exit(status);
#endif
#endif
}
[[noreturn]] [[gnu::cold]] fn void fail()
{
trap();
syscall_exit(1);
}
fn void* reserve(u64 size)
{
int protection_flags = PROT_NONE;
int map_flags = MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE;
void* result = syscall_mmap(0, size, protection_flags, map_flags, -1, 0);
assert(result != MAP_FAILED);
return result;
}
fn void commit(void* address, u64 size)
{
int result = syscall_mprotect(address, size, PROT_READ | PROT_WRITE);
assert(result == 0);
}
fn u64 align_forward(u64 value, u64 alignment)
{
u64 mask = alignment - 1;
u64 result = (value + mask) & ~mask;
return result;
}
global constexpr auto silent = 0;
may_be_unused fn void print(const char* format, ...)
{
if constexpr (!silent)
{
u8 stack_buffer[4096];
va_list args;
va_start(args, format);
String buffer = { .pointer = stack_buffer, .length = array_length(stack_buffer) };
const char* it = format;
u64 buffer_i = 0;
while (*it)
{
while (*it && *it != brace_open)
{
buffer[buffer_i] = *it;
buffer_i += 1;
it += 1;
}
if (*it == brace_open)
{
it += 1;
char next_ch = *it;
if (next_ch == brace_open)
{
trap();
}
else
{
switch (next_ch)
{
case 's':
{
it += 1;
if (is_decimal_digit(*it))
{
trap();
}
else
{
String string = va_arg(args, String);
memcpy(buffer.pointer + buffer_i, string.pointer, string.length);
buffer_i += string.length;
}
} break;
case 'u':
{
it += 1;
auto* bit_count_start = it;
while (is_decimal_digit(*it))
{
it += 1;
}
auto* bit_count_end = it;
auto bit_count = parse_decimal(String::from_pointer_range((u8*)bit_count_start, (u8*)bit_count_end));
enum class IntegerFormat
{
hexadecimal,
decimal,
octal,
binary,
};
IntegerFormat format = IntegerFormat::decimal;
if (*it == ':')
{
it += 1;
switch (*it)
{
case 'x':
format = IntegerFormat::hexadecimal;
break;
case 'd':
format = IntegerFormat::decimal;
break;
case 'o':
format = IntegerFormat::octal;
break;
case 'b':
format = IntegerFormat::binary;
break;
default:
trap();
}
it += 1;
}
u64 original_value;
switch (bit_count)
{
case 8:
case 16:
case 32:
original_value = va_arg(args, u32);
break;
case 64:
original_value = va_arg(args, u64);
break;
default:
trap();
}
u64 value = original_value;
if (value)
{
switch (format)
{
case IntegerFormat::hexadecimal:
{
u8 reverse_buffer[16];
u8 reverse_index = 0;
while (value)
{
u8 digit_value = value % 16;
u8 ascii_ch = digit_value >= 10 ? (digit_value + 'a' - 10) : (digit_value + '0');
value /= 16;
reverse_buffer[reverse_index] = ascii_ch;
reverse_index += 1;
}
while (reverse_index > 0)
{
reverse_index -= 1;
buffer[buffer_i] = reverse_buffer[reverse_index];
buffer_i += 1;
}
} break;
case IntegerFormat::decimal:
{
// TODO: maybe print in one go?
u8 reverse_buffer[64];
u8 reverse_index = 0;
while (value)
{
u8 digit_value = (value % 10);
u8 ascii_ch = digit_value + '0';
value /= 10;
reverse_buffer[reverse_index] = ascii_ch;
reverse_index += 1;
}
while (reverse_index > 0)
{
reverse_index -= 1;
buffer[buffer_i] = reverse_buffer[reverse_index];
buffer_i += 1;
}
} break;
case IntegerFormat::octal:
case IntegerFormat::binary:
trap();
}
}
else
{
buffer[buffer_i] = '0';
buffer_i += 1;
}
} break;
default:
trap();
}
if (*it != brace_close)
{
fail();
}
it += 1;
}
}
}
String final_string = buffer.slice(0, buffer_i);
syscall_write(1, final_string.pointer, final_string.length);
}
}
struct Arena
{
u64 reserved_size;
u64 committed;
u64 commit_position;
u64 granularity;
u8 reserved[4 * 8] = {};
global auto constexpr minimum_granularity = page_size;
global auto constexpr middle_granularity = MB(2);
global auto constexpr default_size = GB(4);
fn Arena* init(u64 reserved_size, u64 granularity, u64 initial_size)
{
Arena* arena = (Arena*)reserve(reserved_size);
commit(arena, initial_size);
*arena = {
.reserved_size = reserved_size,
.committed = initial_size,
.commit_position = sizeof(Arena),
.granularity = granularity,
};
return arena;
}
method fn Arena* init_default(u64 initial_size)
{
return init(default_size, minimum_granularity, initial_size);
}
method void* allocate_bytes(u64 size, u64 alignment)
{
u64 aligned_offset = align_forward(commit_position, alignment);
u64 aligned_size_after = aligned_offset + size;
if (aligned_size_after > committed)
{
u64 committed_size = align_forward(aligned_size_after, granularity);
u64 size_to_commit = committed_size - committed;
void* commit_pointer = (u8*)this + committed;
commit(commit_pointer, size_to_commit);
committed = committed_size;
}
void* result = (u8*)this + aligned_offset;
commit_position = aligned_size_after;
assert(commit_position <= committed);
return result;
}
template<typename T>
method T* allocate_many(u64 count)
{
return (T*)allocate_bytes(sizeof(T) * count, alignof(T));
}
template<typename T>
method T* allocate_one()
{
return allocate_many<T>(1);
}
template<typename T>
method Slice<T> allocate_slice(u64 count)
{
return {
.pointer = allocate_many<T>(count),
.length = count,
};
}
method void reset()
{
this->commit_position = sizeof(Arena);
memset(this + 1, 0, this->committed - sizeof(Arena));
}
};
static_assert(sizeof(Arena) == 64, "Arena must be cache aligned");
template<typename Destination, typename Source>
fn forceinline Destination transmute(Source source)
{
static_assert(sizeof(Source) == sizeof(Destination));
return *(Destination*)&source;
}
fn String file_read(Arena* arena, String path)
{
String result = {};
int file_descriptor = syscall_open((char*)path.pointer, 0, 0);
assert(file_descriptor != -1);
struct stat stat_buffer;
int stat_result = syscall_fstat(file_descriptor, &stat_buffer);
assert(stat_result == 0);
u64 file_size = stat_buffer.st_size;
result = {
.pointer = (u8*)arena->allocate_bytes(file_size, 64),
.length = file_size,
};
// TODO: big files
ssize_t read_result = syscall_read(file_descriptor, result.pointer, result.length);
assert(read_result >= 0);
assert((u64)read_result == file_size);
return result;
}
fn void print(String message)
{
if constexpr (silent)
{
unused(message);
}
else
{
ssize_t result = syscall_write(1, message.pointer, message.length);
assert(result >= 0);
assert((u64)result == message.length);
}
}
template<typename T> struct PinnedArray;
fn void generic_pinned_array_ensure_capacity(PinnedArray<u8>* array, u32 additional_T, u32 size_of_T);
fn u8* generic_pinned_array_add_with_capacity(PinnedArray<u8>* array, u32 additional_T, u32 size_of_T);
global constexpr auto granularity = page_size;
global constexpr auto reserved_size = ((u64)GB(4) - granularity);
template <typename T>
struct PinnedArray
{
T* pointer;
u32 length;
u32 capacity;
// static_assert(sizeof(T) % granularity == 0);
method forceinline T& operator[](u32 index)
{
assert(index < length);
return pointer[index];
}
method forceinline void ensure_capacity(u32 additional)
{
auto generic_array = (PinnedArray<u8>*)(this);
generic_pinned_array_ensure_capacity(generic_array, additional, sizeof(T));
}
method forceinline void clear()
{
length = 0;
}
method forceinline Slice<T> add_with_capacity(u32 additional)
{
auto generic_array = (PinnedArray<u8>*)(this);
auto pointer = generic_pinned_array_add_with_capacity(generic_array, additional, sizeof(T));
return {
.pointer = (T*)pointer,
.length = additional,
};
}
method forceinline Slice<T> add(u32 additional)
{
ensure_capacity(additional);
auto slice = add_with_capacity(additional);
return slice;
}
method forceinline Slice<T> append(Slice<T> items)
{
assert(items.length <= 0xffffffff);
auto slice = add(items.length);
slice.copy_in(items);
return slice;
}
method forceinline T* add_one()
{
return add(1).pointer;
}
method forceinline T* append_one(T item)
{
T* new_item = add_one();
*new_item = item;
return new_item;
}
method forceinline T pop()
{
assert(length);
length -= 1;
return pointer[length];
}
method forceinline Slice<T> slice()
{
return {
.pointer = pointer,
.length = length,
};
}
method T remove_swap(u32 index)
{
if (index >= 0 & index < length)
{
auto original_len = length;
T item = pointer[index];
T last = pointer[length - 1];
pointer[index] = last;
pop();
assert(length == original_len - 1);
return item;
}
trap();
}
};
forceinline fn u32 generic_pinned_array_length(PinnedArray<u8>* array, u32 size_of_T)
{
u32 current_length_bytes = array->length * size_of_T;
return current_length_bytes;
}
fn void generic_pinned_array_ensure_capacity(PinnedArray<u8>* array, u32 additional_T, u32 size_of_T)
{
u32 wanted_capacity = array->length + additional_T;
if (array->capacity < array->length + additional_T)
{
if (array->capacity == 0)
{
assert(array->length == 0);
assert(array->pointer == 0);
array->pointer = static_cast<u8*>(reserve(reserved_size));
}
u64 currently_committed_size = align_forward(array->capacity * size_of_T, granularity);
u64 wanted_committed_size = align_forward(wanted_capacity * size_of_T, granularity);
void* commit_pointer = array->pointer + currently_committed_size;
u64 commit_size = wanted_committed_size - currently_committed_size;
assert(commit_size > 0);
commit(commit_pointer, commit_size);
array->capacity = wanted_committed_size / size_of_T;
}
}
fn u8* generic_pinned_array_add_with_capacity(PinnedArray<u8>* array, u32 additional_T, u32 size_of_T)
{
u32 current_length_bytes = generic_pinned_array_length(array, size_of_T);
assert(current_length_bytes < reserved_size);
u8* pointer = array->pointer + current_length_bytes;
array->length += additional_T;
return pointer;
}
template <typename K, typename V>
struct Put
{
K* key;
V* value;
};
template <typename K, typename V>
struct GetOrPut
{
Put<K, V> result;
u8 existing;
};
template <typename K, typename V>
struct PutResult
{
K* key;
V* value;
};
global constexpr auto map_initial_capacity = 16;
template <typename V>
struct StringMap
{
struct Pair
{
String key;
V value;
};
Pair* pairs;
u32 length;
u32 capacity;
fn StringMap init(Arena* arena, u32 capacity)
{
auto* pairs = arena->allocate_many<Pair>(capacity);
return {
.pairs = pairs,
.length = 0,
.capacity = capacity,
};
}
StringMap duplicate(Arena* arena)
{
auto new_map = init(arena, capacity);
new_map.length = length;
memcpy(new_map.pairs, pairs, sizeof(Pair) * new_map.capacity);
return new_map;
}
struct GetOrPut
{
Pair* pair;
u8 existing;
};
method Pair* get_pair(String key)
{
Pair* result = 0;
if (length)
{
assert(capacity);
u32 index = find_index(key);
auto* pair = &pairs[index];
if (pair->key.length)
{
result = pair;
}
}
return result;
}
method Hash find_index(String key)
{
auto hash = hash_bytes(key);
auto index = hash & (capacity - 1);
return index;
}
method V* get(String key)
{
V* result = 0;
if (auto* pair = get_pair(key))
{
result = &pair->value;
}
return result;
}
method void ensure_capacity(Arena* arena, u32 additional)
{
if (length + additional > capacity)
{
auto new_capacity = max<u32>(capacity + additional, map_initial_capacity);
auto* new_pairs = arena->allocate_many<Pair>(new_capacity);
if (length)
{
memcpy(new_pairs, pairs, capacity * sizeof(Pair));
}
pairs = new_pairs;
capacity = new_capacity;
}
}
method GetOrPut get_or_put(Arena* arena, String key, V value)
{
if (capacity == 0)
{
ensure_capacity(arena, map_initial_capacity);
}
auto index = find_index(key);
auto* candidate_pair = &pairs[index];
if (candidate_pair->key.length)
{
for (u32 i = 0; i < capacity; i += 1)
{
auto wraparound_index = (index + i) & (capacity - 1);
candidate_pair = &pairs[wraparound_index];
if (candidate_pair->key.length == 0)
{
*candidate_pair = {
.key = key,
.value = value,
};
return {
.pair = candidate_pair,
.existing = 0,
};
}
else if (candidate_pair->key.equal(key))
{
return {
.pair = candidate_pair,
.existing = 1,
};
}
}
trap();
}
else
{
ensure_capacity(arena, 1);
candidate_pair->key = key;
candidate_pair->value = value;
length += 1;
return {
.pair = candidate_pair,
.existing = 0,
};
}
}
method Pair* begin()
{
return pairs;
}
method Pair* end()
{
return pairs + capacity;
}
method void clear()
{
*this = {};
}
};
template <typename T>
struct InternPool
{
T** pointer;
u32 length;
u32 capacity;
struct InternGetOrPut
{
T* result;
u8 existing;
};
Hash get_index(Hash hash)
{
assert(capacity % 2 == 0);
auto index = hash & (capacity - 1);
assert(index <= 0xffffffff);
static_assert(sizeof(index) == 8);
return index;
}
method void ensure_capacity(Arena* arena, u32 additional)
{
u64 wanted_capacity = max<u64>(round_up_to_next_power_of_2(length + additional), 32);
if (capacity < wanted_capacity)
{
T** new_array = arena->allocate_many<T*>(wanted_capacity);
memset(new_array, 0, sizeof(T*) * wanted_capacity);
auto* old_pointer = pointer;
auto old_capacity = capacity;
length = 0;
pointer = new_array;
capacity = wanted_capacity;
// print("==========\nPUTTING\n==================\n");
for (u32 i = 0; i < old_capacity; i += 1)
{
auto* key = old_pointer[i];
if (key)
{
put_assume_capacity_assume_not_existent(key);
}
}
// print("==========\nEND PUTTING\n==================\n");
for (u32 i = 0; i < old_capacity; i += 1)
{
auto* key = old_pointer[i];
if (key)
{
// print("Getting {u64:x} (hash: {u64:x})\n", key, key->hash);
auto* result = get(key);
assert(result);
assert(result == key);
}
}
}
}
method T* get(T* key)
{
auto hash = key->hash;
assert(hash);
assert(hash == key->get_hash());
auto original_index = get_index(hash);
auto it_index = original_index;
for (u32 i = 0; i < capacity; i += 1)
{
auto index = it_index & (capacity - 1);
auto* element = &pointer[index];
auto* map_key = *element;
if (!map_key)
{
// TODO:
// Linear probing makes holes into the hash table, so we gotta skip null spots,
// otherwise the removal operation will fail
// fail();
}
else if (map_key->equal(key))
{
return map_key;
}
else
{
// TODO:
// assert(map_key->hash);
// assert(hash != map_key->hash);
}
it_index += 1;
}
return 0;
}
method Hash put_assume_not_existent(Arena* arena, T* key)
{
ensure_capacity(arena, 1);
return put_assume_capacity_assume_not_existent(key);
}
method Hash get_suitable_index(T* key, Hash original_index)
{
auto it_index = original_index;
for (u32 i = 0; i < capacity; i += 1)
{
auto index = it_index & (capacity - 1);
auto* element = &pointer[index];
auto* map_key = *element;
if (!map_key)
{
return index;
}
else if (map_key->equal(key))
{
trap();
}
else
{
// TODO:
// assert(map_key->hash);
// assert(hash != map_key->hash);
}
it_index += 1;
}
trap();
}
method Hash put_assume_capacity_assume_not_existent(T* key)
{
assert(key->hash);
auto original_index = get_index(key->hash);
auto suitable_index = get_suitable_index(key, original_index);
// print("Putting {u64:x} (hash: {u64:x}) at index {u64}\n", key, key->hash, suitable_index);
put_at_assume_capacity_not_existent(key, suitable_index);
return suitable_index;
}
method void put_at_assume_capacity_not_existent(T* key, u32 index)
{
assert(length < capacity);
pointer[index] = key;
length += 1;
}
method T* remove(T* value_to_remove)
{
auto hash = value_to_remove->hash;
assert(hash);
assert(hash == value_to_remove->get_hash());
auto original_index = get_index(hash);
auto it_index = original_index;
// print("Trying to remove {u64:x} (hash: {u64:x}, original index: {u64:x}, capacity: {u32}\n", value_to_remove, hash, original_index, capacity);
for (u32 i = 0; i < capacity; i += 1)
{
auto index = it_index & (capacity - 1);
auto* element = &pointer[index];
auto* map_key = *element;
if (!map_key)
{
// TODO:
// Linear probing makes holes into the hash table, so we gotta skip null spots,
// otherwise the removal operation will fail
// fail();
}
else if (value_to_remove->equal(map_key))
{
*element = {};
length -= 1;
return map_key;
}
else
{
// TODO:
// assert(map_key->hash);
// assert(hash != map_key->hash);
}
it_index += 1;
}
trap();
}
method InternGetOrPut get_or_put(Arena* arena, T* new_value)
{
auto hash = new_value->get_hash();
auto original_index = get_index(hash);
auto it_index = original_index;
s32 first_free_spot = -1;
for (u32 i = 0; i < capacity; i += 1)
{
auto index = it_index & (capacity - 1);
assert(index <= 0xffffffff);
static_assert(sizeof(index) == 8);
auto* element = &pointer[index];
auto* map_key = *element;
// if (hash == 0x800040e1)
// {
// print("Comparing {u64:x} with {u64:x} ({u64:x})\n", new_value, map_key, map_key ? map_key->hash : (u64)0);
// }
// TODO: This is a mess, undo
if (!map_key)
{
if (first_free_spot == -1)
{
first_free_spot = index;
}
}
else if (new_value->equal(map_key))
{
assert(hash == map_key->hash);
// print("Getting {u64:x} with hash {u64:x} at index {u64:x} (from {u64:x} with hash {u64:x})\n", map_key, map_key->hash, index, new_value, hash);
return {
.result = map_key,
.existing = 1,
};
}
else
{
// TODO:
// assert(map_key->hash);
// assert(hash != map_key->hash);
}
it_index += 1;
}
Hash index;
if (length < capacity)
{
index = first_free_spot;
index &= capacity - 1;
put_at_assume_capacity_not_existent(new_value, index);
}
else if (length == capacity)
{
index = put_assume_not_existent(arena, new_value);
}
else
{
trap();
}
// print("Adding {u64:x} with hash {u64:x} at index {u64}\n", new_value, hash, index);
return {
.result = new_value,
.existing = 0,
};
}
method void clear()
{
*this = {};
}
};
template <typename T>
using Array = PinnedArray<T>;
typedef enum FileStatus
{
FILE_STATUS_ADDED = 0,
FILE_STATUS_QUEUED = 1,
FILE_STATUS_READ = 2,
FILE_STATUS_ANALYZING = 3,
} FileStatus;
enum class DebugTypeId: u8
{
VOID,
NORETURN,
POINTER,
INTEGER,
ARRAY,
STRUCT,
UNION,
COUNT,
};
global auto constexpr type_id_bit_count = 3;
static_assert(static_cast<u8>(DebugTypeId::COUNT) < (1 << type_id_bit_count), "Type bit count for id must be respected");
global auto constexpr type_flags_bit_count = 32 - (type_id_bit_count + 1);
struct Thread;
struct NodeType
{
enum class Id: u8
{
BOTTOM = 1,
TOP,
LIVE_CONTROL,
DEAD_CONTROL,
INTEGER,
MULTIVALUE,
// TODO: this is mine. Check if it is correct:
FUNCTION,
CALL,
};
union
{
struct
{
u64 constant;
u8 is_constant;
} constant;
struct
{
Slice<NodeType*> types;
} multi;
} payload = {};
Hash hash = 0;
Id id;
method u8 is_simple()
{
switch (id)
{
case Id::BOTTOM:
case Id::TOP:
case Id::LIVE_CONTROL:
case Id::DEAD_CONTROL:
return 1;
default:
return 0;
}
}
method u8 equal(NodeType* other)
{
if (this == other)
{
return 1;
}
if (id != other->id)
{
return 0;
}
switch (id)
{
case Id::BOTTOM:
case Id::TOP:
case Id::LIVE_CONTROL:
case Id::DEAD_CONTROL:
case Id::FUNCTION:
case Id::CALL:
return 1;
case Id::INTEGER:
return (payload.constant.is_constant == other->payload.constant.is_constant) &
(payload.constant.constant == other->payload.constant.constant);
case Id::MULTIVALUE:
if (payload.multi.types.length == other->payload.multi.types.length)
{
for (u32 i = 0; i < payload.multi.types.length; i += 1)
{
if (payload.multi.types[i] != other->payload.multi.types[i])
{
return 0;
}
}
return 1;
}
else
{
return 0;
}
}
}
method u8 is_constant()
{
switch (id)
{
case Id::INTEGER:
return payload.constant.is_constant;
default:
return 0;
}
}
method NodeType* meet(Thread* thread, NodeType* other)
{
if (this == other)
{
return this;
}
if (id == other->id)
{
return x_meet(thread, other);
}
if (is_simple())
{
return x_meet(thread, other);
}
if (other->is_simple())
{
return other->x_meet(thread, this);
}
return x_meet(thread, other);
}
method NodeType* x_meet(Thread* thread, NodeType* other);
method u8 is_bot()
{
assert(id == Id::INTEGER);
return !payload.constant.is_constant & (payload.constant.constant == 1);
}
method u8 is_top()
{
assert(id == Id::INTEGER);
return !payload.constant.is_constant & (payload.constant.constant == 0);
}
method u8 is_a(Thread* thread, NodeType* other)
{
return this->meet(thread, other) == other;
}
method NodeType* dual()
{
switch (id)
{
case Id::TOP:
case Id::BOTTOM:
case Id::DEAD_CONTROL:
case Id::LIVE_CONTROL:
trap();
// return { .id = id };
default:
trap();
}
}
method NodeType* join(Thread* thread, NodeType* other)
{
if (this == other)
{
return this;
}
return dual()->meet(thread, other->dual())->dual();
}
method NodeType* intern_type(Thread* thread);
method u8 is_high_or_const()
{
switch (id)
{
case Id::TOP:
case Id::DEAD_CONTROL:
return 1;
case Id::LIVE_CONTROL:
case Id::BOTTOM:
case Id::MULTIVALUE:
return 0;
case Id::FUNCTION:
case Id::CALL:
return 0;
case Id::INTEGER:
return payload.constant.is_constant | (payload.constant.constant == 0);
}
}
method Hash get_hash()
{
if (!hash)
{
switch (id)
{
case NodeType::Id::MULTIVALUE:
{
Hash sum = 0;
// print("{u64:x} Field count: {u64}\n", this, payload.multi.types.length);
for (auto* type : payload.multi.types)
{
auto field_hash = type->get_hash();
// print("Field of {u64:x} hash {u64:x}: {u32:x}\n", this, type, field_hash);
sum ^= field_hash;
}
hash = sum;
}
break;
case NodeType::Id::INTEGER:
hash = payload.constant.constant ^ (payload.constant.is_constant ? 0 : 0x4000);
break;
default:
hash = (Hash)id;
break;
}
if (!hash)
{
hash = 0xDEADBEEF;
}
}
return hash;
}
};
struct DebugType
{
u64 size;
u64 alignment;
DebugTypeId id : type_id_bit_count;
u32 resolved: 1;
u32 flags: type_flags_bit_count;
u32 reserved = 0;
String name;
method u8 get_bit_count()
{
assert(id == DebugTypeId::INTEGER);
u32 bit_count_mask = (1 << (type_flags_bit_count - 1)) - 1;
u8 bit_count = flags & bit_count_mask;
assert(bit_count <= size * 8);
assert(bit_count <= 64);
return bit_count;
}
method NodeType* lower(Thread* thread);
};
static_assert(sizeof(DebugType) == sizeof(u64) * 5, "Type must be 24 bytes");
struct Symbol
{
enum class Id: u8
{
variable,
function,
};
enum class Linkage: u8
{
internal,
external,
};
String name;
Id id: 1;
Linkage linkage: 1;
};
typedef enum AbiInfoKind : u8
{
ABI_INFO_IGNORE,
ABI_INFO_DIRECT,
ABI_INFO_DIRECT_PAIR,
ABI_INFO_DIRECT_COERCE,
ABI_INFO_DIRECT_COERCE_INT,
ABI_INFO_DIRECT_SPLIT_STRUCT_I32,
ABI_INFO_EXPAND_COERCE,
ABI_INFO_INDIRECT,
ABI_INFO_EXPAND,
} AbiInfoKind;
global auto constexpr void_type_index = 0;
global auto constexpr noreturn_type_index = 1;
global auto constexpr opaque_pointer_type_index = 2;
// global auto constexpr f32_type_offset = 3;
// global auto constexpr f64_type_offset = 4;
global auto constexpr integer_type_offset = 5;
global auto constexpr integer_type_count = 64 * 2;
global auto constexpr builtin_type_count = integer_type_count + integer_type_offset + 1;
struct Function;
struct Node;
struct Bitset
{
PinnedArray<u64> arr;
method u8 get(u64 index)
{
auto element_index = index / (sizeof(u64) * 8);
if (element_index < arr.length)
{
auto bit_index = index % (sizeof(u64) * 8);
auto result = (arr[element_index] & (1 << bit_index)) != 0;
return result;
}
return 0;
}
method void ensure_length(u64 max)
{
auto length = (max / (sizeof(u64) * 8)) + (max % (sizeof(u64) * 8) != 0);
auto old_length = arr.length;
if (old_length < length)
{
auto new_element_count = length - old_length;
unused(arr.add(new_element_count));
for (u64 byte : arr.slice().slice(old_length, length))
{
assert(!byte);
}
}
}
method void set_assert_unset(u64 index)
{
ensure_length(index + 1);
auto element_index = index / (sizeof(u64) * 8);
auto bit_index = index % (sizeof(u64) * 8);
assert((arr[element_index] & (1 << bit_index)) == 0);
arr[element_index] |= (1 << bit_index);
}
method u8 is_empty()
{
return arr.length == 0;
}
method void clear(u64 index)
{
auto bit_index = index % (sizeof(u64) * 8);
auto element_index = index / (sizeof(u64) * 8);
auto mask = ~(1 << bit_index);
static_assert(sizeof(bit_index) == sizeof(arr.pointer[0]));
arr[element_index] &= mask;
}
method void clear()
{
memset(arr.pointer, 0, arr.capacity * sizeof(u64));
arr.clear();
}
};
struct WorkList
{
PinnedArray<Node*> nodes = {};
Bitset bitset = {};
method void add_many(Slice<Node*> nodes)
{
for (auto* node : nodes)
{
push(node);
}
}
method Node* push(Node* node);
method Node* pop()
{
Node* result = 0;
if (nodes.length)
{
u64 rng = generate_random_number();
u32 index = rng & (nodes.capacity - 1);
result = nodes[index];
nodes[index] = nodes[nodes.length - 1];
nodes.length -= 1;
bitset.clear(index);
}
return result;
}
method u8 on(Node* node);
method void clear()
{
nodes.clear();
bitset.clear();
}
};
struct Thread
{
Arena* arena;
PinnedArray<Function> functions = {};
PinnedArray<NodeType> types = {};
PinnedArray<Node> nodes = {};
InternPool<NodeType> interned_types = {};
InternPool<Node> interned_nodes = {};
WorkList worklist = {};
Bitset visited = {};
struct
{
NodeType* bottom;
NodeType* top;
NodeType* live_control;
NodeType* dead_control;
NodeType* integer_bot;
NodeType* integer_top;
NodeType* integer_zero;
NodeType* if_both;
NodeType* if_neither;
NodeType* if_true;
NodeType* if_false;
} common_types;
u64 peephole_iteration_count = 0;
u64 peephole_nop_iteration_count = 0;
u32 node_count = 0;
u8 mid_assert = 0;
method void init();
method void clear();
method u8 progress_on_list(Function* function, Node* node);
};
method NodeType* NodeType::x_meet(Thread* thread, NodeType* other)
{
switch (id)
{
case Id::BOTTOM:
case Id::TOP:
case Id::LIVE_CONTROL:
case Id::DEAD_CONTROL:
{
assert(is_simple());
if (this == thread->common_types.bottom || other == thread->common_types.top)
{
return this;
}
if ((this == thread->common_types.top) | (other == thread->common_types.bottom))
{
return other;
}
if (!other->is_simple())
{
return thread->common_types.bottom;
}
if (this == thread->common_types.live_control || other == thread->common_types.live_control)
{
return thread->common_types.live_control;
}
else
{
return thread->common_types.dead_control;
}
}
case Id::INTEGER:
if (this == thread->common_types.integer_bot)
{
return this;
}
if (other == thread->common_types.integer_bot)
{
return other;
}
if (other == thread->common_types.integer_top)
{
return this;
}
if (this == thread->common_types.integer_top)
{
return other;
}
return thread->common_types.integer_bot;
case Id::MULTIVALUE:
fail();
default:
trap();
}
}
method NodeType* DebugType::lower(Thread* thread)
{
switch (id)
{
case DebugTypeId::VOID:
trap();
case DebugTypeId::NORETURN:
trap();
case DebugTypeId::POINTER:
trap();
case DebugTypeId::INTEGER:
return thread->common_types.integer_bot;
case DebugTypeId::ARRAY:
trap();
case DebugTypeId::STRUCT:
trap();
case DebugTypeId::UNION:
trap();
case DebugTypeId::COUNT:
trap();
}
}
method NodeType* NodeType::intern_type(Thread* thread)
{
auto result = thread->interned_types.get_or_put(thread->arena, this);
return result.result;
}
struct Unit
{
// PinnedArray<File> files;
// PinnedArray<Function> functions;
// Arena* arena;
// Arena* node_arena;
// Arena* type_arena;
DebugType* builtin_types;
u64 generate_debug_information : 1;
method DebugType* get_integer_type(u8 bit_count, u8 signedness)
{
auto index = integer_type_offset + signedness * 64 + bit_count - 1;
return &builtin_types[index];
}
};
union AbiInfoPayload
{
NodeType* direct;
NodeType* direct_pair[2];
NodeType* direct_coerce;
struct
{
NodeType* type;
u32 alignment;
} indirect;
};
typedef union AbiInfoPayload AbiInfoPayload;
struct AbiInfoAttributes
{
u8 by_reg: 1;
u8 zero_extend: 1;
u8 sign_extend: 1;
u8 realign: 1;
u8 by_value: 1;
};
typedef struct AbiInfoAttributes AbiInfoAttributes;
struct AbiInfo
{
AbiInfoPayload payload;
u16 indices[2] = {};
AbiInfoAttributes attributes = {};
AbiInfoKind kind;
};
struct Function
{
struct Prototype
{
AbiInfo* argument_type_abis; // The count for this array is "original_argument_count", not "abi_argument_count"
DebugType** original_argument_types;
// TODO: are these needed?
// Node::DataType* abi_argument_types;
// u32 abi_argument_count;
DebugType* original_return_type;
AbiInfo return_type_abi;
u32 original_argument_count;
// TODO: is this needed?
// Node::DataType abi_return_type;
u8 varags:1;
};
Symbol symbol;
Node* root_node;
Node* stop_node;
Node** parameters;
Function::Prototype prototype;
// u32 node_count;
u32 uid;
u16 parameter_count;
method Node* iterate(Thread* thread);
};
struct ConstantIntData
{
u64 value;
Node* input;
};
struct File;
// This is a node in the "sea of nodes" sense:
// https://en.wikipedia.org/wiki/Sea_of_nodes
struct Node
{
enum class Id: u8
{
ROOT,
STOP,
PROJECTION,
RETURN,
IF,
CONSTANT,
SCOPE,
SYMBOL_FUNCTION,
CALL,
REGION,
REGION_LOOP,
PHI,
INTEGER_ADD,
INTEGER_SUB,
INTEGER_COMPARE_EQUAL,
INTEGER_COMPARE_NOT_EQUAL,
INTEGER_COMPARE_LESS,
INTEGER_COMPARE_LESS_EQUAL,
INTEGER_COMPARE_GREATER,
INTEGER_COMPARE_GREATER_EQUAL,
};
using Type = NodeType;
Array<Node*> inputs = {};
Array<Node*> outputs = {};
Array<Node*> dependencies = {};
Type* type = 0;
u32 uid;
Hash hash = 0;
Id id;
s32 immediate_depth = 0;
union
{
struct
{
String name;
s32 index;
} projection;
struct
{
Array<StringMap<u16>> stack;
} scope;
struct
{
Type* args;
Function* function;
} root;
Symbol* symbol;
struct
{
String label;
} phi;
struct
{
Node* immediate_dominator = 0;
} region;
struct
{
Type* type;
} constant;
} payload;
u8 padding[20 ] = {};
method forceinline Slice<Node*> get_inputs()
{
return {
.pointer = inputs.pointer,
.length = inputs.length,
};
}
method forceinline Slice<Node*> get_outputs()
{
return {
.pointer = outputs.pointer,
.length = outputs.length,
};
}
struct NodeData
{
Slice<Node*> inputs;
Id id;
};
[[nodiscard]] fn Node* add(Thread* thread, NodeData data)
{
auto node_id = thread->node_count;
thread->node_count += 1;
auto* node = thread->nodes.add_one();
*node = {
.uid = node_id,
.id = data.id,
.payload = {},
};
node->inputs.append(data.inputs);
for (Node* input : data.inputs)
{
if (input)
{
input->add_output(node);
}
}
return node;
}
method Node* add_output(Node* output)
{
outputs.append_one(output);
return this;
}
method Node* add_input(Thread* thread, Node* input)
{
unlock(thread);
inputs.append_one(input);
if (input)
{
input->add_output(this);
}
return input;
}
method Node* add_dependency(Thread* thread, Node* dependency)
{
if (thread->mid_assert)
{
return this;
}
if (dependencies.slice().find(dependency))
{
return this;
}
if (inputs.slice().find(dependency))
{
return this;
}
if (outputs.slice().find(dependency))
{
return this;
}
dependencies.append_one(dependency);
return this;
}
method Node* set_input(Thread* thread, s32 index, Node* new_input)
{
unlock(thread);
Node* old_input = inputs[index];
if (old_input == new_input)
{
return this;
}
if (new_input)
{
new_input->add_output(this);
}
if (old_input && old_input->remove_output(this))
{
old_input->kill(thread);
}
inputs[index] = new_input;
return new_input;
}
method u8 remove_output(Node* output)
{
s32 index = outputs.slice().find_index(output);
assert(index != -1);
outputs.remove_swap(index);
return outputs.length == 0;
}
method void remove_input(Thread* thread, u32 index)
{
unlock(thread);
if (Node* old_input = inputs[index])
{
if (old_input->remove_output(this))
{
old_input->kill(thread);
}
}
inputs.remove_swap(index);
}
method Node* idealize(Thread* thread, Function* function)
{
switch (id)
{
case Id::INTEGER_SUB:
if (inputs[1] == inputs[2])
{
trap();
}
else
{
return 0;
}
case Id::CONSTANT:
return 0;
case Id::STOP:
{
auto input_count = inputs.length;
for (u32 i = 0; i < inputs.length; i += 1)
{
if (inputs[i]->type == thread->common_types.dead_control)
{
remove_input(thread, i);
i -= 1;
}
}
if (input_count != inputs.length)
{
return this;
}
else
{
return 0;
}
}
case Id::PROJECTION:
{
auto* control = get_control();
if (control->type->id == NodeType::Id::MULTIVALUE)
{
auto control_types = control->type->payload.multi.types;
auto projection_index = payload.projection.index;
if (control_types[projection_index] == thread->common_types.dead_control)
{
trap();
}
// TODO: fix
// auto index = 1 - projection_index;
// assert(index >= 0);
// assert((u32)index < control_types.length);
// if (control_types[index] == thread->common_types.dead_control)
// {
// trap();
// }
}
return 0;
}
case Id::ROOT:
return 0;
case Id::IF:
if (!predicate()->type->is_high_or_const())
{
for (Node* dominator = get_immediate_dominator(), *prior = this; dominator; prior = dominator, dominator = dominator->get_immediate_dominator())
{
auto* dep = dominator->add_dependency(thread, this);
if (dep->id == Id::IF && dep->predicate()->add_dependency(thread, this) == predicate() && prior->id == Id::PROJECTION)
{
trap();
}
}
}
return 0;
case Id::INTEGER_ADD:
{
auto* left = inputs[1];
auto* right = inputs[2];
assert(!(left->type->is_constant() && right->type->is_constant()));
if (right->type->id == NodeType::Id::INTEGER && right->type->payload.constant.constant == 0)
{
return left;
}
if (left == right)
{
trap();
}
return 0;
}
case Id::REGION_LOOP:
case Id::REGION:
if (!region_in_progress())
{
// Find dead input
for (u32 i = 1; i < inputs.length; i += 1)
{
if (inputs[i]->type == thread->common_types.dead_control)
{
for (u32 output_index = 0; output_index < outputs.length; output_index += 1)
{
Node* output = outputs[output_index];
if (output->id == Id::PHI)
{
output->remove_input(thread, i);
}
}
remove_input(thread, i);
if (inputs.length == 2)
{
for (u32 output_index = 0; output_index < outputs.length; output_index += 1)
{
Node* output = outputs[output_index];
if (output->id == Id::PHI)
{
// TODO:
trap();
}
}
return inputs[1];
}
else
{
trap();
}
}
}
}
return 0;
case Id::SCOPE:
return 0;
// TODO:
case Id::SYMBOL_FUNCTION:
case Id::CALL:
return 0;
case Id::INTEGER_COMPARE_EQUAL:
case Id::INTEGER_COMPARE_NOT_EQUAL:
case Id::INTEGER_COMPARE_LESS:
case Id::INTEGER_COMPARE_LESS_EQUAL:
case Id::INTEGER_COMPARE_GREATER:
case Id::INTEGER_COMPARE_GREATER_EQUAL:
if (inputs[1] == inputs[2])
{
trap();
}
else
{
return 0;
}
case Id::PHI:
{
auto* region = phi_get_region();
if (!region->is_region())
{
return inputs[1];
}
if (region->region_in_progress() || region->inputs.length <= 1)
{
return 0;
}
Node* single_unique_input = 0;
if (!(region->id == Id::REGION_LOOP && region->loop_entry()->type == thread->common_types.dead_control))
{
Node* live = 0;
for (u32 i = 1; i < inputs.length; i += 1)
{
if (region->inputs[i]->add_dependency(thread, this)->type != thread->common_types.dead_control && inputs[i] != this)
{
if (!live || live == inputs[i])
{
live = inputs[i];
}
else
{
live = 0;
break;
}
}
}
single_unique_input = live;
}
if (single_unique_input)
{
return single_unique_input;
}
Node* operand = inputs[1];
if (operand->inputs.length == 3 && !operand->inputs[0] && !operand->is_cfg() && phi_same_operand())
{
u32 input_count = inputs.length;
auto lefts = thread->arena->allocate_slice<Node*>(input_count);
auto rights = thread->arena->allocate_slice<Node*>(input_count);
lefts[0] = inputs[0];
rights[0] = inputs[0];
for (u32 i = 1; i < input_count; i += 1)
{
lefts[i] = inputs[i]->inputs[1];
rights[i] = inputs[i]->inputs[2];
}
auto* left_phi = Node::add(thread, {
.inputs = lefts,
.id = Node::Id::PHI,
});
left_phi->payload.phi.label = payload.phi.label;
left_phi = left_phi->peephole(thread, function);
auto* right_phi = Node::add(thread, {
.inputs = rights,
.id = Node::Id::PHI,
});
right_phi->payload.phi.label = payload.phi.label;
right_phi = right_phi->peephole(thread, function);
auto* result = operand->copy(thread, left_phi, right_phi);
return result;
}
return 0;
}
case Id::RETURN:
{
if (get_control()->type->id == Node::Type::Id::DEAD_CONTROL)
{
trap();
}
else
{
return 0;
}
}
}
}
method Node* copy(Thread* thread, Node* left, Node* right)
{
switch (id)
{
case Id::INTEGER_COMPARE_EQUAL:
case Id::INTEGER_COMPARE_NOT_EQUAL:
case Id::INTEGER_COMPARE_LESS:
case Id::INTEGER_COMPARE_LESS_EQUAL:
case Id::INTEGER_COMPARE_GREATER:
case Id::INTEGER_COMPARE_GREATER_EQUAL:
case Id::INTEGER_ADD:
{
Node* inputs[] = { 0, left, right };
auto* result = Node::add(thread, {
.inputs = array_to_slice(inputs),
.id = id,
});
return result;
} break;
default:
trap();
}
}
method u8 is_cfg()
{
switch (id)
{
case Id::ROOT:
case Id::STOP:
case Id::RETURN:
case Id::REGION:
case Id::REGION_LOOP:
case Id::IF:
return 1;
case Id::PROJECTION:
return (payload.projection.index == 0) || (get_control()->id == Node::Id::IF);
default:
return 0;
}
}
method u8 phi_same_operand()
{
assert(id == Id::PHI);
auto input_class = inputs[1]->id;
for (u32 i = 2; i < inputs.length; i += 1)
{
auto other_input_class = inputs[i]->id;
if (input_class != other_input_class)
{
return 0;
}
}
return 1;
}
method u8 phi_same_inputs()
{
assert(id == Id::PHI);
auto* input = inputs[1];
for (u32 i = 2; i < inputs.length; i += 1)
{
if (input != inputs[i])
{
return 0;
}
}
return 1;
}
method u8 is_unused()
{
return outputs.length == 0;
}
method u8 is_dead()
{
return is_unused() & (inputs.length == 0) && !type;
}
method void pop_inputs(Thread* thread, u32 count)
{
unlock(thread);
for (u32 i = 0; i < count; i += 1)
{
Node* old_input = inputs.pop();
if (old_input)
{
if (old_input->remove_output(this))
{
old_input->kill(thread);
}
}
}
}
method String get_id_name()
{
switch (id)
{
case_to_name(Id, ROOT);
case_to_name(Id, STOP);
case_to_name(Id, PROJECTION);
case_to_name(Id, RETURN);
case_to_name(Id, IF);
case_to_name(Id, CONSTANT);
case_to_name(Id, SCOPE);
case_to_name(Id, SYMBOL_FUNCTION);
case_to_name(Id, CALL);
case_to_name(Id, REGION);
case_to_name(Id, REGION_LOOP);
case_to_name(Id, PHI);
case_to_name(Id, INTEGER_ADD);
case_to_name(Id, INTEGER_SUB);
case_to_name(Id, INTEGER_COMPARE_EQUAL);
case_to_name(Id, INTEGER_COMPARE_NOT_EQUAL);
case_to_name(Id, INTEGER_COMPARE_LESS);
case_to_name(Id, INTEGER_COMPARE_LESS_EQUAL);
case_to_name(Id, INTEGER_COMPARE_GREATER);
case_to_name(Id, INTEGER_COMPARE_GREATER_EQUAL);
}
}
method void kill(Thread* thread)
{
unlock(thread);
assert(is_unused());
type = 0;
while (inputs.length > 0)
{
if (Node* old_input = inputs.pop())
{
thread->worklist.push(old_input);
if (old_input->remove_output(this))
{
old_input->kill(thread);
}
}
}
assert(is_dead());
}
global auto constexpr enable_peephole = 1;
[[gnu::hot]] method Node* peephole(Thread* thread, Function* function)
{
if (!enable_peephole)
{
type = compute(thread);
return this;
}
else
{
if (Node* node = peephole_optimize(thread, function))
{
auto* new_node = node->peephole(thread, function);
auto* result = dead_code_elimination(thread, new_node);
return result;
}
else
{
return this;
}
}
}
method Node::Type* set_type(Thread* thread, Node::Type* new_type)
{
auto* old_type = type;
assert(!old_type || new_type->is_a(thread, old_type));
if (old_type != new_type)
{
type = new_type;
thread->worklist.add_many(outputs.slice());
move_dependencies_to_worklist(thread);
}
return old_type;
}
method void move_dependencies_to_worklist(Thread* thread)
{
thread->worklist.add_many(dependencies.slice());
dependencies.clear();
}
[[gnu::hot]] method Node* peephole_optimize(Thread* thread, Function* function)
{
thread->peephole_iteration_count += 1;
auto old_type = set_type(thread, compute(thread));
if (!is_constant() && type->is_high_or_const())
{
auto* constant_node = Node::add(thread, {
.inputs = { .pointer = &function->root_node, .length = 1 },
.id = Id::CONSTANT,
});
constant_node->payload.constant.type = type;
return constant_node->peephole_optimize(thread, function);
}
if (!hash)
{
auto gop = thread->interned_nodes.get_or_put(thread->arena, this);
// print("{s} node {u64:x} ({u64:x}) -> {u64:x} ({u64:x})\n", gop.existing ? strlit("Getting") : strlit("Adding"), this, this->get_hash(), gop.result, gop.result->get_hash());
if (gop.existing)
{
Node* found = gop.result;
found->set_type(thread, found->type->join(thread, type));
hash = 0;
auto* result = dead_code_elimination(thread, found);
return result;
}
}
Node* node = idealize(thread, function);
if (node)
{
return node;
}
else
{
u8 condition = old_type == type;
thread->peephole_nop_iteration_count += condition;
auto* result = condition ? 0 : this;
return result;
}
}
method Node* keep()
{
return add_output(0);
}
method Node* unkeep()
{
remove_output(0);
return this;
}
method u8 is_constant()
{
switch (id)
{
default:
return 0;
case Id::CONSTANT:
return 1;
}
}
method Node* predicate()
{
assert(id == Id::IF);
return inputs[1];
}
method Node::Type* compute(Thread* thread)
{
switch (id)
{
case Node::Id::ROOT:
return payload.root.args;
case Node::Id::STOP:
return thread->common_types.bottom;
case Node::Id::IF:
{
auto* control_node = get_control();
if (control_node->type != thread->common_types.live_control && control_node->type != thread->common_types.bottom)
{
return thread->common_types.if_neither;
}
auto* this_predicate = predicate();
auto* predicate_type = this_predicate->type;
if (predicate_type == thread->common_types.top || predicate_type == thread->common_types.integer_top)
{
return thread->common_types.if_neither;
}
if ((predicate_type->id == Node::Type::Id::INTEGER) & predicate_type->is_constant())
{
auto value = predicate_type->payload.constant.constant;
if (value)
{
return thread->common_types.if_true;
}
else
{
return thread->common_types.if_false;
}
}
return thread->common_types.if_both;
}
case Node::Id::INTEGER_ADD:
case Node::Id::INTEGER_SUB:
case Node::Id::INTEGER_COMPARE_EQUAL:
case Node::Id::INTEGER_COMPARE_NOT_EQUAL:
case Node::Id::INTEGER_COMPARE_LESS:
case Node::Id::INTEGER_COMPARE_LESS_EQUAL:
case Node::Id::INTEGER_COMPARE_GREATER:
case Node::Id::INTEGER_COMPARE_GREATER_EQUAL:
{
auto left_type = inputs[1]->type;
auto right_type = inputs[2]->type;
if ((left_type->id == Node::Type::Id::INTEGER) & (right_type->id == Node::Type::Id::INTEGER))
{
if (left_type->is_constant() & right_type->is_constant())
{
u64 result;
switch (id)
{
default:
trap();
case Id::INTEGER_ADD:
result = left_type->payload.constant.constant + right_type->payload.constant.constant;
break;
case Id::INTEGER_SUB:
result = left_type->payload.constant.constant - right_type->payload.constant.constant;
break;
case Id::INTEGER_COMPARE_EQUAL:
result = left_type->payload.constant.constant == right_type->payload.constant.constant;
break;
case Id::INTEGER_COMPARE_NOT_EQUAL:
result = left_type->payload.constant.constant != right_type->payload.constant.constant;
break;
case Id::INTEGER_COMPARE_LESS:
result = left_type->payload.constant.constant < right_type->payload.constant.constant;
break;
case Id::INTEGER_COMPARE_LESS_EQUAL:
result = left_type->payload.constant.constant <= right_type->payload.constant.constant;
break;
case Id::INTEGER_COMPARE_GREATER:
result = left_type->payload.constant.constant > right_type->payload.constant.constant;
break;
case Id::INTEGER_COMPARE_GREATER_EQUAL:
result = left_type->payload.constant.constant >= right_type->payload.constant.constant;
break;
}
return thread->types.append_one({
.payload = {
.constant = {
.constant = result,
.is_constant = 1,
},
},
.id = Node::Type::Id::INTEGER,
})->intern_type(thread);
}
}
auto* result = left_type->meet(thread, right_type);
return result;
}
case Node::Id::CONSTANT:
return payload.constant.type;
case Node::Id::PROJECTION:
{
auto* control_node = inputs[0];
if (control_node->type->id == NodeType::Id::MULTIVALUE)
{
auto type = control_node->type->payload.multi.types[this->payload.projection.index];
return type;
}
else
{
trap();
}
} break;
// TODO: change
case Node::Id::SYMBOL_FUNCTION:
trap();
// return { .id = Type::Id::FUNCTION };
case Node::Id::CALL:
{
// TODO: undo this mess
return thread->common_types.integer_bot;
}
// return { .id = Type::Id::CALL };
case Node::Id::RETURN:
{
Array<Type*> types = {};
// First INPUT: control
// Second INPUT: expression
types.append_one(inputs[0]->type);
types.append_one(inputs[1]->type);
auto* ty = thread->types.append_one({
.payload = {
.multi = {
.types = types.slice(),
},
},
.id = Node::Type::Id::MULTIVALUE,
});
auto* interned_type = ty->intern_type(thread);
// print("Ty: {u64:x} -> interned {u64:x}\n", ty, interned_type);
return interned_type;
}
case Node::Id::REGION_LOOP:
if (region_in_progress())
{
return thread->common_types.live_control;
}
else
{
auto* entry = loop_entry();
return entry->type;
}
case Node::Id::REGION:
if (region_in_progress())
{
trap();
// return { .id = Type::Id::LIVE_CONTROL };
}
else
{
auto* ty = thread->common_types.dead_control;
for (u32 i = 1; i < inputs.length; i += 1)
{
ty = ty->meet(thread, inputs[i]->type);
}
return ty;
}
case Node::Id::PHI:
{
auto* region = phi_get_region();
auto is_reg = region->is_region();
if (!is_reg)
{
trap();
}
if (region->region_in_progress())
{
return thread->common_types.bottom;
}
Type* ty = thread->common_types.top;
for (u32 i = 1; i < inputs.length; i += 1)
{
if (region->inputs[i]->add_dependency(thread, this)->type != thread->common_types.dead_control && inputs[i] != this)
{
ty = ty->meet(thread, inputs[i]->type);
}
}
return ty;
}
case Id::SCOPE:
return thread->common_types.bottom;
}
}
u8 is_region()
{
switch (id)
{
case Id::REGION: case Id::REGION_LOOP:
return 1;
default:
return 0;
}
}
method u8 is_associative()
{
switch (id)
{
case Id::INTEGER_ADD:
return 1;
default:
return 0;
}
}
method Node* associative_phi_constant(u8 should_rotate)
{
unused(should_rotate);
assert(is_associative());
trap();
}
method Node* project(Thread* thread, Node* control, s32 index, String label)
{
assert(type->id == Node::Type::Id::MULTIVALUE);
auto* projection = Node::add(thread, {
.inputs = { .pointer = &control, .length = 1 },
.id = Node::Id::PROJECTION,
});
projection->payload.projection.index = index;
projection->payload.projection.name = label;
return projection;
}
[[gnu::hot]] method Node* dead_code_elimination(Thread* thread, Node* new_node)
{
if (new_node != this && is_unused())
{
new_node->keep();
kill(thread);
new_node->unkeep();
}
return new_node;
}
method DebugType* get_debug_type(Unit* unit)
{
switch (type->id)
{
case NodeType::Id::BOTTOM:
// TODO:
return unit->get_integer_type(32, 1);
case NodeType::Id::TOP:
trap();
case NodeType::Id::LIVE_CONTROL:
case NodeType::Id::DEAD_CONTROL:
trap();
case NodeType::Id::INTEGER:
// TODO:
return unit->get_integer_type(32, 1);
case NodeType::Id::MULTIVALUE:
trap();
case NodeType::Id::FUNCTION:
trap();
case NodeType::Id::CALL:
trap();
}
}
method Node* get_control()
{
switch (id)
{
case Node::Id::SCOPE:
case Node::Id::RETURN:
case Node::Id::IF:
case Node::Id::PROJECTION:
return inputs[0];
default:
trap();
}
}
method Node* swap_inputs_1_2(Thread* thread)
{
unlock(thread);
Node* temporal = inputs[1];
inputs[1] = inputs[2];
inputs[2] = temporal;
return this;
}
method u8 all_constants()
{
if (id == Id::PHI)
{
auto* region = phi_get_region();
auto is_r = region->is_region();
if (!is_r || region->region_in_progress())
{
return 0;
}
}
for (u32 i = 1; i < inputs.length; i += 1)
{
if (!inputs[i]->type->is_constant())
{
return 0;
}
}
return 1;
}
method Node* get_immediate_dominator()
{
switch (id)
{
case Id::ROOT:
return 0;
case Id::REGION_LOOP:
return loop_entry();
case Id::REGION:
if (payload.region.immediate_dominator)
{
return payload.region.immediate_dominator;
}
else
{
if (inputs.length == 3)
{
Node* left = inputs[1]->get_immediate_dominator();
Node* right = inputs[2]->get_immediate_dominator();
while (left != right)
{
if (!left || !right)
{
return 0;
}
else
{
auto comp = left->immediate_depth - right->immediate_depth;
if (comp >= 0)
{
left = left->get_immediate_dominator();
}
if (comp <= 0)
{
right = right->get_immediate_dominator();
}
}
}
if (left)
{
immediate_depth = left->immediate_depth + 1;
payload.region.immediate_dominator = left;
return left;
}
else
{
return 0;
}
}
else
{
trap();
}
}
default:
{
Node* result = inputs[0];
if (result->immediate_depth == 0)
{
result->get_immediate_dominator();
}
if (immediate_depth == 0)
{
immediate_depth = result->immediate_depth + 1;
}
return result;
}
}
}
method u8 region_in_progress()
{
assert(is_region());
return inputs.length > 1 && !(inputs[inputs.length - 1]);
}
method u8 phi_in_progress()
{
assert(id == Id::PHI);
return !(inputs[inputs.length - 1]);
}
method Node* loop_entry()
{
assert(id == Id::REGION_LOOP);
return inputs[1];
}
method Node* loop_backedge()
{
assert(id == Id::REGION_LOOP);
return inputs[2];
}
method Node* set_control(Thread* thread, Node* node)
{
assert(id == Id::SCOPE);
return set_input(thread, 0, node);
}
method Node* phi_get_region()
{
assert(id == Id::PHI);
return inputs[0];
}
method void subsume(Thread* thread, Node* node)
{
assert(node != this);
while (outputs.length > 0)
{
Node* n = outputs.pop();
n->unlock(thread);
s32 index = n->inputs.slice().find_index(this);
assert(index != -1);
n->inputs[index] = node;
node->add_output(n);
}
kill(thread);
}
method Slice<String> scope_reverse_names(Arena* arena)
{
assert(id == Node::Id::SCOPE);
Slice<String> names = arena->allocate_slice<String>(inputs.length);
for (auto& string_map : payload.scope.stack.slice())
{
for (auto& pair : string_map)
{
auto name = pair.key;
if (name.length > 0)
{
auto index = *string_map.get(name);
names[index] = name;
}
}
}
return names;
}
method Node* scope_update_extended(Thread* thread, Function* function, String name, Node* node, s32 nesting_level)
{
assert(id == Id::SCOPE);
if (nesting_level < 0)
{
return 0;
}
// TODO: avoid recursion
auto& map = payload.scope.stack[nesting_level];
if (auto* index_ptr = map.get(name))
{
auto index = *index_ptr;
auto* old = get_inputs()[index];
if (old->id == Node::Id::SCOPE)
{
auto* loop = old;
if (loop->inputs[index]->id == Id::PHI && loop->get_control() == loop->inputs[index]->phi_get_region())
{
old = loop->inputs[index];
}
else
{
Node* phi_inputs[] = {
loop->get_control(),
loop->scope_update_extended(thread, function, name, 0, nesting_level),
0,
};
auto* phi_node = Node::add(thread, {
.inputs = array_to_slice(phi_inputs),
.id = Node::Id::PHI,
});
phi_node->payload.phi.label = name;
phi_node = phi_node->peephole(thread, function);
old = loop->set_input(thread, index, phi_node);
}
set_input(thread, index, old);
}
if (node)
{
return set_input(thread, index, node);
}
else
{
return old;
}
}
else
{
return scope_update_extended(thread, function, name, node, nesting_level - 1);
}
}
method Node* scope_update(Thread* thread, Function* function, String name, Node* node)
{
assert(id == Id::SCOPE);
return scope_update_extended(thread, function, name, node, payload.scope.stack.length - 1);
}
method void scope_end_loop(Thread* thread, Function* function, Node* back, Node* exit)
{
assert(id == Id::SCOPE);
assert(back->id == Id::SCOPE);
assert(exit->id == Id::SCOPE);
Node* control_node = get_control();
assert(control_node->id == Id::REGION_LOOP);
assert(control_node->region_in_progress());
control_node->set_input(thread, 2, back->get_control());
for (u32 i = 1; i < inputs.length; i += 1)
{
if (back->inputs[i] != this)
{
auto* phi = inputs[i];
assert(phi->id == Id::PHI);
assert(phi->phi_get_region() == get_control());
assert(!phi->inputs[2]);
phi->set_input(thread, 2, back->inputs[i]);
}
if (exit->inputs[i] == this)
{
exit->set_input(thread, i, inputs[i]);
}
}
back->kill(thread);
for (u32 i = 1; i < inputs.length; i += 1)
{
auto* node = inputs[i];
if (node->id == Id::PHI)
{
Node* input = node->peephole(thread, function);
if (input != node)
{
node->subsume(thread, input);
set_input(thread, i, input);
}
}
}
}
method Hash get_hash()
{
if (!hash)
{
Hash new_hash;
switch (id)
{
case Id::PROJECTION:
new_hash = payload.projection.index;
break;
case Id::CONSTANT:
new_hash = payload.constant.type->get_hash();
break;
default:
new_hash = 0;
break;
}
for (Node* input : inputs.slice())
{
if (input)
{
new_hash = new_hash ^ (new_hash << 17) ^ (new_hash >> 13) ^ input->uid;
}
}
if (!new_hash)
{
new_hash = 0xDEADBEEF;
}
hash = new_hash;
}
// print("Hashing node {u64:x} -> {u32:x}\n", this, hash);
return hash;
}
method void unlock(Thread* thread)
{
// print("Removing node {u64:x} (in: {u32}, out: {u32}, id: {s}, hash: {u64:x})\n", this, inputs.length, outputs.length, get_id_name(), hash);
if (hash)
{
Node* old = thread->interned_nodes.remove(this);
if (old != this)
{
// print("Tried to remove node {u64:x} ({u64:x}), but got {u64:x} instead ({u64:x})\n", this, this->hash, old, old->hash);
trap();
}
hash = 0;
}
}
method u8 equal(Node* other)
{
if (this == other)
{
return 1;
}
else if (id != other->id)
{
return 0;
}
else if (inputs.length != other->inputs.length)
{
return 0;
}
else
{
u32 input_count = inputs.length;
for (u32 i = 0; i < input_count; i += 1)
{
if (inputs[i] != other->inputs[i])
{
return 0;
}
}
switch (id)
{
case Id::PROJECTION:
return payload.projection.index == other->payload.projection.index;
case Id::PHI:
return !phi_in_progress();
case Id::CONSTANT:
return type == other->type;
case Id::REGION:
case Id::REGION_LOOP:
trap();
case Id::ROOT:
return payload.root.function == other->payload.root.function;
case Id::SCOPE:
return 1;
case Id::STOP:
case Id::RETURN:
case Id::IF:
case Id::SYMBOL_FUNCTION:
case Id::CALL:
case Id::INTEGER_ADD:
case Id::INTEGER_SUB:
case Id::INTEGER_COMPARE_EQUAL:
case Id::INTEGER_COMPARE_NOT_EQUAL:
case Id::INTEGER_COMPARE_LESS:
case Id::INTEGER_COMPARE_LESS_EQUAL:
case Id::INTEGER_COMPARE_GREATER:
case Id::INTEGER_COMPARE_GREATER_EQUAL:
trap();
}
}
}
method Node* walk(Thread* thread, Function* function, Node* (*callback)(Thread*, Function*, Node*))
{
assert(thread->visited.is_empty());
Node* node = walk_internal(thread, function, callback);
thread->visited.clear();
return node;
}
method Node* walk_internal(Thread* thread, Function* function, Node* (*callback)(Thread*, Function*, Node*))
{
if (!thread->visited.get(this->uid))
{
thread->visited.set_assert_unset(this->uid);
if (Node* node = callback(thread, function, this))
{
return node;
}
Slice<Node*> lists[] = {inputs.slice(), outputs.slice()};
for (auto list : lists)
{
for (Node* node : list)
{
if (node)
{
if (Node* result = node->walk_internal(thread, function, callback))
{
return result;
}
}
}
}
}
return 0;
}
method Node* scope_lookup(Thread* thread, Function* function, File* file, String name);
method Node* merge_scopes(Thread* thread, File* file, Function* function, Node* other);
};
Node* Function::iterate(Thread* thread)
{
assert(thread->progress_on_list(this, stop_node));
u64 count = 0;
unused(count);
while (auto* node = thread->worklist.pop())
{
if (!node->is_dead())
{
count += 1;
if (Node* x = node->peephole_optimize(thread, this))
{
if (!x->is_dead())
{
if (!x->type)
{
x->set_type(thread, x->compute(thread));
}
if (x != node || x->id != Node::Id::CONSTANT)
{
for (Node* output : node->outputs.slice())
{
thread->worklist.push(output);
}
thread->worklist.push(x);
if (x != node)
{
for (Node* input : node->inputs.slice())
{
thread->worklist.push(input);
}
node->subsume(thread, x);
}
}
node->move_dependencies_to_worklist(thread);
assert(thread->progress_on_list(this, stop_node));
}
}
}
}
return stop_node;
}
struct File
{
String path;
String source_code;
FileStatus status;
StringMap<Node> symbols = {};
};
method Node* Node::scope_lookup(Thread* thread, Function* function, File* file, String name)
{
auto* result = scope_update_extended(thread, function, name, nullptr, payload.scope.stack.length - 1);
if (file && !result)
{
result = file->symbols.get(name);
}
return result;
}
method Node* Node::merge_scopes(Thread* thread, File* file, Function* function, Node* other_scope)
{
assert(id == Node::Id::SCOPE);
assert(other_scope->id == Node::Id::SCOPE);
Node* region_inputs[] = {
0,
get_control(),
other_scope->get_control(),
};
auto* region_node = set_control(thread, Node::add(thread, {
.inputs = array_to_slice(region_inputs),
.id = Node::Id::REGION,
})->keep());
auto names = scope_reverse_names(thread->arena);
// Skip input[0] ($ctrl)
for (u32 i = 1; i < inputs.length; i += 1)
{
if (inputs[i] != other_scope->inputs[i])
{
String label = names[i];
Node* input_a = scope_lookup(thread, function, file, label);
Node* input_b = other_scope->scope_lookup(thread, function, file, label);
Node* inputs[] = {
region_node,
input_a,
input_b,
};
auto* phi_node = Node::add(thread, {
.inputs = array_to_slice(inputs),
.id = Node::Id::PHI,
});
phi_node->payload.phi.label = label;
phi_node = phi_node->peephole(thread, function);
set_input(thread, i, phi_node);
}
}
other_scope->kill(thread);
return region_node->unkeep()->peephole(thread, function);
}
static_assert(sizeof(Node) == 128);
static_assert(page_size % sizeof(Node) == 0);
global String integer_names[] =
{
strlit("u1"),
strlit("u2"),
strlit("u3"),
strlit("u4"),
strlit("u5"),
strlit("u6"),
strlit("u7"),
strlit("u8"),
strlit("u9"),
strlit("u10"),
strlit("u11"),
strlit("u12"),
strlit("u13"),
strlit("u14"),
strlit("u15"),
strlit("u16"),
strlit("u17"),
strlit("u18"),
strlit("u19"),
strlit("u20"),
strlit("u21"),
strlit("u22"),
strlit("u23"),
strlit("u24"),
strlit("u25"),
strlit("u26"),
strlit("u27"),
strlit("u28"),
strlit("u29"),
strlit("u30"),
strlit("u31"),
strlit("u32"),
strlit("u33"),
strlit("u34"),
strlit("u35"),
strlit("u36"),
strlit("u37"),
strlit("u38"),
strlit("u39"),
strlit("u40"),
strlit("u41"),
strlit("u42"),
strlit("u43"),
strlit("u44"),
strlit("u45"),
strlit("u46"),
strlit("u47"),
strlit("u48"),
strlit("u49"),
strlit("u50"),
strlit("u51"),
strlit("u52"),
strlit("u53"),
strlit("u54"),
strlit("u55"),
strlit("u56"),
strlit("u57"),
strlit("u58"),
strlit("u59"),
strlit("u60"),
strlit("u61"),
strlit("u62"),
strlit("u63"),
strlit("u64"),
strlit("s1"),
strlit("s2"),
strlit("s3"),
strlit("s4"),
strlit("s5"),
strlit("s6"),
strlit("s7"),
strlit("s8"),
strlit("s9"),
strlit("s10"),
strlit("s11"),
strlit("s12"),
strlit("s13"),
strlit("s14"),
strlit("s15"),
strlit("s16"),
strlit("s17"),
strlit("s18"),
strlit("s19"),
strlit("s20"),
strlit("s21"),
strlit("s22"),
strlit("s23"),
strlit("s24"),
strlit("s25"),
strlit("s26"),
strlit("s27"),
strlit("s28"),
strlit("s29"),
strlit("s30"),
strlit("s31"),
strlit("s32"),
strlit("s33"),
strlit("s34"),
strlit("s35"),
strlit("s36"),
strlit("s37"),
strlit("s38"),
strlit("s39"),
strlit("s40"),
strlit("s41"),
strlit("s42"),
strlit("s43"),
strlit("s44"),
strlit("s45"),
strlit("s46"),
strlit("s47"),
strlit("s48"),
strlit("s49"),
strlit("s50"),
strlit("s51"),
strlit("s52"),
strlit("s53"),
strlit("s54"),
strlit("s55"),
strlit("s56"),
strlit("s57"),
strlit("s58"),
strlit("s59"),
strlit("s60"),
strlit("s61"),
strlit("s62"),
strlit("s63"),
strlit("s64"),
};
fn void unit_initialize(Unit* unit)
{
Arena* type_arena = Arena::init(Arena::default_size, Arena::minimum_granularity, KB(64));
DebugType* builtin_types = type_arena->allocate_many<DebugType>(builtin_type_count);
*unit = {
// .arena = Arena::init(Arena::default_size, Arena::minimum_granularity, KB(4)),
// .node_arena = Arena::init(Arena::default_size, Arena::minimum_granularity, KB(64)),
// .type_arena = type_arena,
.builtin_types = builtin_types,
.generate_debug_information = 1,
};
builtin_types[void_type_index] = {
.size = 0,
.alignment = 1,
.id = DebugTypeId::VOID,
.resolved = 1,
.flags = 0,
.name = strlit("void"),
};
builtin_types[noreturn_type_index] = {
.size = 0,
.alignment = 1,
.id = DebugTypeId::NORETURN,
.resolved = 1,
.flags = 0,
.name = strlit("noreturn"),
};
builtin_types[opaque_pointer_type_index] = {
.size = 8,
.alignment = 8,
.id = DebugTypeId::POINTER,
.resolved = 1,
.flags = 0,
.name = strlit("*any"),
};
// TODO: float types
u64 i;
for (i = integer_type_offset; i < integer_type_offset + 64; i += 1)
{
u64 bit_count = i - integer_type_offset + 1;
assert(bit_count >= 1 | bit_count <= 64);
auto aligned_bit_count = round_up_to_next_power_of_2(bit_count);
auto byte_count = max<u64>(aligned_bit_count / 8, 1);
assert(byte_count <= bit_count);
assert(byte_count == 1 | byte_count == 2 | byte_count == 4 | byte_count == 8);
builtin_types[i] =
{
.size = byte_count,
.alignment = byte_count,
.id = DebugTypeId::INTEGER,
.resolved = 1,
.flags = static_cast<u32>(bit_count),
.name = integer_names[bit_count - 1],
};
}
for (; i < integer_type_offset + integer_type_count; i += 1)
{
u64 bit_count = i - (integer_type_offset + 64 - 1);
assert(bit_count >= 1 | bit_count <= 64);
auto aligned_bit_count = round_up_to_next_power_of_2(bit_count);
auto byte_count = max<u64>(aligned_bit_count / 8, 1);
assert(byte_count <= bit_count);
assert(byte_count == 1 | byte_count == 2 | byte_count == 4 | byte_count == 8);
builtin_types[i] =
{
.size = byte_count,
.alignment = byte_count,
.id = DebugTypeId::INTEGER,
.resolved = 1,
.flags = static_cast<u32>(bit_count | (1 << (type_flags_bit_count - 1))), // Signedness bit
.name = integer_names[bit_count + 63],
};
}
}
static_assert(array_length(integer_names) == 128, "Integer name array must be 128 bytes");
struct Instance
{
Arena* arena;
};
fn Unit* instance_add_unit(Instance* instance)
{
Unit* unit = instance->arena->allocate_one<Unit>();
*unit = {
};
return unit;
}
// TODO: make it into an array
fn Thread* instance_add_thread(Instance* instance)
{
auto* thread = instance->arena->allocate_one<Thread>();
*thread = {
.arena = Arena::init_default(KB(64)),
.common_types = {},
};
return thread;
}
struct Parser
{
u64 i;
u32 line;
u32 column;
[[gnu::hot]] method void skip_space(String src)
{
u64 original_i = i;
if (original_i != src.length)
{
if (is_space(src.pointer[original_i], get_next_ch_safe(src, original_i)))
{
while (i < src.length)
{
u64 index = i;
u8 ch = src.pointer[index];
u64 new_line = ch == '\n';
line += new_line;
if (new_line)
{
column = index + 1;
}
if (!is_space(ch, get_next_ch_safe(src, i)))
{
break;
}
u32 is_comment = src.pointer[index] == '/';
i += is_comment + is_comment;
if (is_comment)
{
while (i < src.length)
{
if (src.pointer[i] == '\n')
{
break;
}
i += 1;
}
continue;
}
i += 1;
}
}
}
}
[[gnu::hot]] method void expect_character(String src, u8 expected_ch)
{
u64 index = i;
if (expect(index < src.length, 1))
{
u8 ch = src.pointer[index];
u64 matches = ch == expected_ch;
expect(matches, 1);
i += matches;
if (!matches)
{
print(strlit("expected character '"));
print(ch_to_str(expected_ch));
print(strlit("', but found '"));
print(ch_to_str(ch));
print(strlit("'\n"));
fail();
}
}
else
{
print(strlit("expected character '"));
print(ch_to_str(expected_ch));
print(strlit("', but found end of file\n"));
fail();
}
}
[[gnu::hot]] method String parse_raw_identifier(String src)
{
u64 identifier_start_index = i;
u64 is_string_literal = src.pointer[identifier_start_index] == '"';
i += is_string_literal;
u8 identifier_start_ch = src.pointer[i];
u64 is_valid_identifier_start = is_identifier_start(identifier_start_ch);
i += is_valid_identifier_start;
if (expect(is_valid_identifier_start, 1))
{
while (i < src.length)
{
u8 ch = src.pointer[i];
u64 is_identifier = is_identifier_ch(ch);
expect(is_identifier, 1);
i += is_identifier;
if (!is_identifier)
{
if (expect(is_string_literal, 0))
{
expect_character(src, '"');
}
String result = src.slice(identifier_start_index, i - is_string_literal);
return result;
}
}
fail();
}
else
{
fail();
}
}
method String parse_and_check_identifier(String src)
{
String identifier = parse_raw_identifier(src);
if (expect(identifier.equal(strlit("_")), 0))
{
return {};
}
return identifier;
}
};
// fn u32 get_line(Parser* parser)
// {
// return parser->line + 1;
// }
//
// fn u32 get_column(Parser* parser)
// {
// return parser->i - parser->column + 1;
// }
fn File* add_file(Arena* arena, String file_path)
{
auto* file = arena->allocate_one<File>();
*file = {
.path = file_path,
.source_code = {},
.status = FILE_STATUS_ADDED,
};
return file;
}
fn void compiler_file_read(Arena* arena, File* file)
{
assert(file->status == FILE_STATUS_ADDED || file->status == FILE_STATUS_QUEUED);
file->source_code = file_read(arena, file->path);
file->status = FILE_STATUS_READ;
}
global constexpr auto pointer_sign = '*';
global constexpr auto end_of_statement = ';';
global constexpr auto end_of_argument = ',';
global constexpr auto function_argument_start = parenthesis_open;
global constexpr auto function_argument_end = parenthesis_close;
global constexpr auto function_attribute_start = bracket_open;
global constexpr auto function_attribute_end = bracket_close;
global constexpr auto symbol_attribute_start = bracket_open;
global constexpr auto symbol_attribute_end = bracket_close;
global constexpr auto block_start = brace_open;
global constexpr auto block_end = brace_close;
global constexpr auto local_symbol_declaration_start = '>';
global constexpr auto array_expression_start = bracket_open;
// global constexpr auto array_expression_end = bracket_close;
// global constexpr auto composite_initialization_start = brace_open;
// global constexpr auto composite_initialization_end = brace_close;
global String function_attributes[] =
{
strlit("cc"),
};
typedef enum FunctionAttribute
{
FUNCTION_ATTRIBUTE_CC,
FUNCTION_ATTRIBUTE_COUNT,
} FunctionAttribute;
static_assert(array_length(function_attributes) == FUNCTION_ATTRIBUTE_COUNT, "");
global String calling_conventions[] =
{
strlit("c"),
strlit("custom"),
};
typedef enum CallingConvention
{
CALLING_CONVENTION_C,
CALLING_CONVENTION_CUSTOM,
CALLING_CONVENTION_COUNT,
} CallingConvention;
static_assert(array_length(calling_conventions) == CALLING_CONVENTION_COUNT, "");
typedef enum GlobalSymbolAttribute
{
GLOBAL_SYMBOL_ATTRIBUTE_EXPORT,
GLOBAL_SYMBOL_ATTRIBUTE_EXTERN,
GLOBAL_SYMBOL_ATTRIBUTE_COUNT,
} GlobalSymbolAttribute;
global String global_symbol_attributes[] =
{
strlit("export"),
strlit("extern"),
};
struct GlobalSymbolAttributes
{
u8 exported: 1;
u8 external: 1;
};
static_assert(array_length(global_symbol_attributes) == GLOBAL_SYMBOL_ATTRIBUTE_COUNT, "");
Node* create_scope(Thread* thread)
{
auto* scope = Node::add(thread, {
.inputs = {},
.id = Node::Id::SCOPE,
});
scope->type = thread->common_types.bottom;
scope->payload.scope.stack = {};
return scope;
}
struct Analyzer
{
Function* function;
Node* scope;
Node* break_scope = 0;
Node* continue_scope = 0;
File* file;
method Node* set_control(Thread* thread, Node* node)
{
return scope->set_control(thread, node);
}
method void kill_control(Thread* thread)
{
set_control(thread, 0);
}
method Node* add_return(Thread* thread, Node* return_value)
{
Node* inputs[] = { get_control(), return_value };
auto* return_node = Node::add(thread, {
.inputs = array_to_slice(inputs),
.id = Node::Id::RETURN,
})->peephole(thread, function);
auto* node = function->stop_node->add_input(thread, return_node);
// Kill control
auto* dead_control = Node::add(thread, {
.inputs = { .pointer = &function->root_node, .length = 1 },
.id = Node::Id::CONSTANT,
});
dead_control->payload.constant.type = thread->common_types.dead_control;
dead_control = dead_control->peephole(thread, function);
set_control(thread, dead_control);
return node;
}
method Node* get_control()
{
return scope->get_control();
}
method Node* duplicate_scope(Thread* thread, u8 loop)
{
auto* original_scope = scope;
auto original_input_count = original_scope->inputs.length;
auto* duplicate_scope = create_scope(thread);
// // TODO: make this more efficient
for (auto& hashmap: original_scope->payload.scope.stack.slice())
{
auto duplicate_hashmap = hashmap.duplicate(thread->arena);
duplicate_scope->payload.scope.stack.append_one(duplicate_hashmap);
}
duplicate_scope->add_input(thread, get_control());
for (u32 i = 1; i < original_scope->inputs.length; i += 1)
{
duplicate_scope->add_input(thread, loop ? original_scope : original_scope->inputs[i]);
}
assert(duplicate_scope->inputs.length == original_input_count);
return duplicate_scope;
}
};
fn DebugType* analyze_type(Parser* parser, Unit* unit, String src)
{
u64 start_index = parser->i;
u8 start_ch = src.pointer[start_index];
u32 array_start = start_ch == array_expression_start;
u32 u_start = start_ch == 'u';
u32 s_start = start_ch == 's';
u32 float_start = start_ch == 'f';
u32 void_start = start_ch == 'v';
u32 pointer_start = start_ch == pointer_sign;
u32 integer_start = u_start | s_start;
u32 number_start = integer_start | float_start;
if (void_start)
{
trap();
}
else if (array_start)
{
trap();
}
else if (pointer_start)
{
trap();
}
else if (number_start)
{
u64 expected_digit_start = start_index + 1;
u64 i = expected_digit_start;
u32 decimal_digit_count = 0;
u64 top = i + 5;
while (i < top)
{
u8 ch = src.pointer[i];
u32 is_digit = is_decimal_digit(ch);
decimal_digit_count += is_digit;
if (!is_digit)
{
u32 is_alpha = is_alphabetic(ch);
if (is_alpha)
{
decimal_digit_count = 0;
}
break;
}
i += 1;
}
if (decimal_digit_count)
{
parser->i += 1;
if (integer_start)
{
u64 signedness = s_start;
u64 bit_size;
u64 current_i = parser->i;
assert(src.pointer[current_i] >= '0' & src.pointer[current_i] <= '9');
switch (decimal_digit_count) {
case 0:
fail();
case 1:
bit_size = src.pointer[current_i] - '0';
break;
case 2:
bit_size = (src.pointer[current_i] - '0') * 10 + (src.pointer[current_i + 1] - '0');
break;
default:
fail();
}
parser->i += decimal_digit_count;
assert(!is_decimal_digit(src.pointer[parser->i]));
if (bit_size)
{
auto* result = unit->get_integer_type(bit_size, signedness);
return result;
}
else
{
fail();
}
}
else if (float_start)
{
trap();
}
else
{
trap();
}
}
else
{
fail();
}
}
trap();
}
fn u64 parse_hex(String string)
{
u64 value = 0;
for (u8 ch : string)
{
u8 byte;
auto is_decimal = (ch >= '0') & (ch <= '9');
auto is_lower_hex = (ch >= 'a') & (ch <= 'f');
auto is_upper_hex = (ch >= 'A') & (ch <= 'F');
if (is_decimal)
{
byte = ch - '0';
}
else if (is_lower_hex)
{
byte = ch - 'a' + 10;
}
else if (is_upper_hex)
{
byte = ch - 'A' + 10;
}
else
{
fail();
}
value = (value << 4) | (byte & 0x0f);
}
return value;
}
[[nodiscard]] [[gnu::hot]] fn Node* analyze_expression(Analyzer* analyzer, Parser* parser, Thread* thread, String src);
[[nodiscard]] [[gnu::hot]] fn Node* analyze_primary_expression(Analyzer* analyzer, Parser* parser, Thread* thread, String src)
{
parser->skip_space(src);
auto starting_ch_index = parser->i;
u8 starting_ch = src[starting_ch_index];
auto is_digit = is_decimal_digit(starting_ch);
auto is_identifier = is_identifier_start(starting_ch);
auto is_open_parenthesis = starting_ch == parenthesis_open;
if (is_identifier)
{
String identifier = parser->parse_and_check_identifier(src);
auto* node = analyzer->scope->scope_lookup(thread, analyzer->function, analyzer->file, identifier);
if (!node)
{
fail();
}
return node;
}
else if (is_digit)
{
u64 value = 0;
if (starting_ch == '0')
{
auto follow_up_character = src[parser->i + 1];
auto is_hex_start = follow_up_character == 'x';
auto is_octal_start = follow_up_character == 'o';
auto is_bin_start = follow_up_character == 'b';
auto is_prefixed_start = is_hex_start | is_octal_start | is_bin_start;
auto follow_up_alpha = is_alphabetic(follow_up_character);
auto follow_up_digit = is_decimal_digit(follow_up_character);
auto is_valid_after_zero = is_space(follow_up_character, get_next_ch_safe(src, follow_up_character)) | (!follow_up_digit and !follow_up_alpha);
if (is_prefixed_start) {
enum class IntegerPrefix {
hexadecimal,
octal,
binary,
};
IntegerPrefix prefix;
switch (follow_up_character) {
case 'x': prefix = IntegerPrefix::hexadecimal; break;
case 'o': prefix = IntegerPrefix::octal; break;
case 'b': prefix = IntegerPrefix::binary; break;
default: fail();
};
parser->i += 2;
auto start = parser->i;
switch (prefix) {
case IntegerPrefix::hexadecimal:
{
while (is_hex_digit(src[parser->i])) {
parser->i += 1;
}
auto slice = src.slice(start, parser->i);
value = parse_hex(slice);
}
case IntegerPrefix::octal:
trap();
case IntegerPrefix::binary:
trap();
}
} else if (is_valid_after_zero) {
value = 0;
parser->i += 1;
} else {
fail();
}
}
else
{
while (is_decimal_digit(src[parser->i]))
{
parser->i += 1;
}
auto slice = src.slice(starting_ch_index, parser->i);
value = parse_decimal(slice);
}
NodeType* type;
if (value == 0)
{
type = thread->common_types.integer_zero;
}
else
{
type = thread->types.append_one({
.payload = {
.constant = {
.constant = value,
.is_constant = 1,
},
},
.id = NodeType::Id::INTEGER,
})->intern_type(thread);
}
auto* constant_int = Node::add(thread, {
.inputs = { .pointer = &analyzer->function->root_node, .length = 1 },
.id = Node::Id::CONSTANT,
});
constant_int->payload.constant.type = type;
return constant_int->peephole(thread, analyzer->function);
}
else if (is_open_parenthesis)
{
trap();
}
else
{
trap();
}
trap();
}
[[nodiscard]] fn Node* analyze_unary_expression(Analyzer* analyzer, Parser* parser, Thread* thread, String src)
{
parser->skip_space(src);
Node* result;
switch (src[parser->i])
{
case '-':
trap();
default:
result = analyze_primary_expression(analyzer, parser, thread, src);
break;
}
switch (src[parser->i])
{
// Function call
case function_argument_start:
{
parser->i += 1;
Array<Node*> argument_nodes = {};
while (1)
{
parser->skip_space(src);
if (src[parser->i] == function_argument_end)
{
break;
}
Node* argument_value = analyze_expression(analyzer, parser, thread, src)->peephole(thread, analyzer->function);
argument_nodes.append_one(argument_value);
parser->skip_space(src);
switch (src[parser->i])
{
case function_argument_end:
break;
case ',':
parser->i += 1;
break;
default:
fail();
}
}
parser->expect_character(src, function_argument_end);
// Add function definition
argument_nodes.append_one(result);
Node* call_node = Node::add(thread, {
.inputs = argument_nodes.slice(),
.id = Node::Id::CALL,
})->peephole(thread, analyzer->function);
result = call_node;
}
default:
break;
}
return result;
}
[[nodiscard]] fn Node* analyze_multiplication_expression(Analyzer* analyzer, Parser* parser, Thread* thread, String src)
{
auto* result = analyze_unary_expression(analyzer, parser, thread, src);
while (1)
{
parser->skip_space(src);
u8 op_ch = src[parser->i];
if (op_ch == '*')
{
trap();
}
else if (op_ch == '/')
{
trap();
}
else
{
break;
}
trap();
}
return result;
}
[[nodiscard]] fn Node* analyze_addition_expression(Analyzer* analyzer, Parser* parser, Thread* thread, String src)
{
auto* result = analyze_multiplication_expression(analyzer, parser, thread, src);
while (1)
{
parser->skip_space(src);
auto* left = result;
u8 op_ch = src[parser->i];
Node* inputs[] = {
0,
left,
0,
};
Node::Id new_node_id;
if (op_ch == '+')
{
new_node_id = Node::Id::INTEGER_ADD;
}
else if (op_ch == '-')
{
new_node_id = Node::Id::INTEGER_SUB;
}
else
{
break;
}
parser->i += 1;
result = Node::add(thread, {
.inputs = array_to_slice(inputs),
.id = new_node_id,
});
auto* right = analyze_addition_expression(analyzer, parser, thread, src);
result->set_input(thread, 2, right);
result = result->peephole(thread, analyzer->function);
}
return result;
}
[[nodiscard]] fn Node* analyze_comparison_expression(Analyzer* analyzer, Parser* parser, Thread* thread, String src)
{
auto* result = analyze_addition_expression(analyzer, parser, thread, src);
while (1)
{
parser->skip_space(src);
auto* left = result;
Node* inputs[] = {
0,
left,
0,
};
u8 op_ch = src[parser->i];
u8 next_ch = src[parser->i + 1];
Node::Id id;
u8 negate = 0;
if (op_ch == '=' && next_ch == '=')
{
parser->i += 1;
id = Node::Id::INTEGER_COMPARE_EQUAL;
}
else if (op_ch == '!' && next_ch == '=')
{
parser->i += 1;
id = Node::Id::INTEGER_COMPARE_NOT_EQUAL;
negate = 1;
}
else if (op_ch == '<')
{
if (next_ch == '=')
{
parser->i += 1;
id = Node::Id::INTEGER_COMPARE_LESS_EQUAL;
}
else
{
id = Node::Id::INTEGER_COMPARE_LESS;
}
}
else if (op_ch == '>')
{
if (next_ch == '=')
{
parser->i += 1;
id = Node::Id::INTEGER_COMPARE_GREATER_EQUAL;
}
else
{
id = Node::Id::INTEGER_COMPARE_GREATER;
}
}
else
{
break;
}
parser->i += 1;
result = Node::add(thread, {
.inputs = array_to_slice(inputs),
.id = id,
});
auto* right = analyze_addition_expression(analyzer, parser, thread, src);
result->set_input(thread, 2, right);
result = result->peephole(thread, analyzer->function);
if (negate)
{
trap();
}
}
return result;
}
[[nodiscard]] [[gnu::hot]] fn Node* analyze_expression(Analyzer* analyzer, Parser* parser, Thread* thread, String src)
{
return analyze_comparison_expression(analyzer, parser, thread, src);
}
fn void push_scope(Analyzer* analyzer)
{
analyzer->scope->payload.scope.stack.append_one({});
}
fn void pop_scope(Analyzer* analyzer, Thread* thread)
{
analyzer->scope->pop_inputs(thread, analyzer->scope->payload.scope.stack.pop().length);
}
fn Node* define_variable(Analyzer* analyzer, Thread* thread, String name, Node* node)
{
auto* stack = &analyzer->scope->payload.scope.stack;
assert(stack->length);
auto* last = &stack->pointer[stack->length - 1];
auto input_index = analyzer->scope->inputs.length;
if (last->get_or_put(thread->arena, name, input_index).existing)
{
trap();
return 0;
}
return analyzer->scope->add_input(thread, node);
}
fn Node* analyze_local_block(Analyzer* analyzer, Parser* parser, Unit* unit, Thread* thread, String src);
fn Node* jump_to(Analyzer* analyzer, Thread* thread, Node* target_scope)
{
auto* current_scope = analyzer->duplicate_scope(thread, 0);
// Kill current scope
auto* dead_control = Node::add(thread, {
.inputs = { .pointer = &analyzer->function->root_node, .length = 1 },
.id = Node::Id::CONSTANT,
});
dead_control->payload.constant.type = thread->common_types.dead_control;
dead_control = dead_control->peephole(thread, analyzer->function);
analyzer->set_control(thread, dead_control);
while (current_scope->payload.scope.stack.length > analyzer->break_scope->payload.scope.stack.length)
{
current_scope->payload.scope.stack.pop();
}
if (target_scope)
{
assert(target_scope->payload.scope.stack.length <= analyzer->break_scope->payload.scope.stack.length);
auto* result = target_scope->merge_scopes(thread, analyzer->file, analyzer->function, current_scope);
target_scope->set_control(thread, result);
return target_scope;
}
else
{
return current_scope;
}
}
fn Node* analyze_statement(Analyzer* analyzer, Parser* parser, Unit* unit, Thread* thread, String src)
{
auto statement_start_index = parser->i;
u8 statement_start_ch = src[statement_start_index];
Function* function = analyzer->function;
if (is_identifier_start(statement_start_ch))
{
Node* statement_node = 0;
String identifier = parser->parse_raw_identifier(src);
if (identifier.equal(strlit("return")))
{
parser->skip_space(src);
auto* return_value = analyze_expression(analyzer, parser, thread, src)->peephole(thread, function);
parser->expect_character(src, ';');
auto* return_node = analyzer->add_return(thread, return_value);
statement_node = return_node;
}
else if (identifier.equal(strlit("if")))
{
parser->skip_space(src);
parser->expect_character(src, parenthesis_open);
parser->skip_space(src);
auto* predicate_node = analyze_expression(analyzer, parser, thread, src);
parser->skip_space(src);
parser->expect_character(src, parenthesis_close);
parser->skip_space(src);
Node* if_inputs[] = {
analyzer->get_control(),
predicate_node,
};
auto* if_node = Node::add(thread, {
.inputs = array_to_slice(if_inputs),
.id = Node::Id::IF,
})->peephole(thread, function);
Node* if_true = if_node->project(thread, if_node->keep(), 0, strlit("True"))->peephole(thread, function)->keep();
Node* if_false = if_node->project(thread, if_node->unkeep(), 1, strlit("False"))->peephole(thread, function)->keep();
u32 original_input_count = analyzer->scope->inputs.length;
auto* false_scope = analyzer->duplicate_scope(thread, 0);
analyzer->set_control(thread, if_true->unkeep());
analyze_statement(analyzer, parser, unit, thread, src);
parser->skip_space(src);
auto* true_scope = analyzer->scope;
analyzer->scope = false_scope;
analyzer->set_control(thread, if_false->unkeep());
if (is_identifier_start(src[parser->i]))
{
auto before_else = parser->i;
String identifier = parser->parse_raw_identifier(src);
if (identifier.equal(strlit("else")))
{
parser->skip_space(src);
analyze_statement(analyzer, parser, unit, thread, src);
false_scope = analyzer->scope;
}
else
{
parser->i = before_else;
}
}
if ((true_scope->inputs.length != original_input_count) | (false_scope->inputs.length != original_input_count))
{
fail();
}
analyzer->scope = true_scope;
auto* merged_scope = true_scope->merge_scopes(thread, analyzer->file, analyzer->function, false_scope);
statement_node = analyzer->set_control(thread, merged_scope);
assert(statement_node);
}
else if (identifier.equal(strlit("while")))
{
parser->skip_space(src);
parser->expect_character(src, parenthesis_open);
parser->skip_space(src);
auto* old_break_scope = analyzer->break_scope;
auto* old_continue_scope = analyzer->continue_scope;
Node* loop_inputs[] = {
0,
analyzer->get_control(),
0,
};
auto* loop_node = Node::add(thread, {
.inputs = array_to_slice(loop_inputs),
.id = Node::Id::REGION_LOOP,
})->peephole(thread, function);
analyzer->set_control(thread, loop_node);
Node* head_scope = analyzer->scope->keep();
analyzer->scope = analyzer->duplicate_scope(thread, 1);
auto* predicate_node = analyze_expression(analyzer, parser, thread, src);
parser->skip_space(src);
parser->expect_character(src, parenthesis_close);
parser->skip_space(src);
Node* if_inputs[] = {
analyzer->get_control(),
predicate_node,
};
auto* if_node = Node::add(thread, {
.inputs = array_to_slice(if_inputs),
.id = Node::Id::IF,
})->peephole(thread, function);
Node* if_true = if_node->project(thread, if_node->keep(), 0, strlit("True"))->peephole(thread, function)->keep();
Node* if_false = if_node->project(thread, if_node->unkeep(), 1, strlit("False"))->peephole(thread, function);
analyzer->set_control(thread, if_false);
analyzer->break_scope = analyzer->duplicate_scope(thread, 0);
analyzer->continue_scope = 0;
analyzer->set_control(thread, if_true->unkeep());
analyze_statement(analyzer, parser, unit, thread, src);
if (analyzer->continue_scope)
{
analyzer->continue_scope = jump_to(analyzer, thread, analyzer->continue_scope);
analyzer->scope->kill(thread);
analyzer->scope = analyzer->continue_scope;
}
auto* exit_scope = analyzer->break_scope;
head_scope->scope_end_loop(thread, function, analyzer->scope, exit_scope);
head_scope->unkeep()->kill(thread);
analyzer->break_scope = old_break_scope;
analyzer->continue_scope = old_continue_scope;
analyzer->scope = exit_scope;
statement_node = exit_scope;
}
else if (identifier.equal(strlit("break")))
{
parser->skip_space(src);
parser->expect_character(src, end_of_statement);
if (!analyzer->break_scope)
{
fail();
}
analyzer->break_scope = jump_to(analyzer, thread, analyzer->break_scope);
statement_node = analyzer->break_scope;
}
else if (identifier.equal(strlit("continue")))
{
parser->skip_space(src);
parser->expect_character(src, end_of_statement);
if (!analyzer->break_scope)
{
fail();
}
analyzer->continue_scope = jump_to(analyzer, thread, analyzer->continue_scope);
statement_node = analyzer->continue_scope;
}
if (statement_node)
{
return statement_node;
}
else
{
if (auto* left_node = analyzer->scope->scope_lookup(thread, analyzer->function, analyzer->file, identifier))
{
parser->skip_space(src);
enum class LoadStoreOperation : u8
{
NONE
};
LoadStoreOperation operation;
switch (src[parser->i])
{
case '=':
operation = LoadStoreOperation::NONE;
parser->i += 1;
break;
default:
trap();
}
parser->skip_space(src);
Node* right_expression = analyze_expression(analyzer, parser, thread, src);
parser->skip_space(src);
parser->expect_character(src, ';');
switch (operation)
{
case LoadStoreOperation::NONE:
if (!analyzer->scope->scope_update(thread, function, identifier, right_expression))
{
fail();
}
break;
default:
trap();
}
return 0;
}
else
{
fail();
}
}
}
else
{
switch (statement_start_ch)
{
case local_symbol_declaration_start:
{
parser->i += 1;
parser->skip_space(src);
String name = parser->parse_and_check_identifier(src);
u8 has_local_attributes = src[parser->i] == symbol_attribute_start;
parser->i += has_local_attributes;
if (has_local_attributes)
{
// TODO: local attributes
fail();
}
parser->skip_space(src);
struct LocalResult
{
Node* node;
DebugType* type;
};
LocalResult local_result = {};
switch (src[parser->i])
{
case ':':
{
parser->i += 1;
parser->skip_space(src);
DebugType* type = analyze_type(parser, unit, src);
parser->skip_space(src);
parser->expect_character(src, '=');
parser->skip_space(src);
auto* initial_node = analyze_expression(analyzer, parser, thread, src);
if (!define_variable(analyzer, thread, name, initial_node))
{
fail();
}
local_result = {
.node = initial_node,
.type = type,
};
} break;
case '=':
{
parser->i += 1;
parser->skip_space(src);
auto* initial_node = analyze_expression(analyzer, parser, thread, src);
if (!define_variable(analyzer, thread, name, initial_node))
{
fail();
}
local_result = {
.node = initial_node,
.type = initial_node->get_debug_type(unit),
};
} break;
default: fail();
}
parser->skip_space(src);
parser->expect_character(src, ';');
return local_result.node;
} break;
case block_start:
{
return analyze_local_block(analyzer, parser, unit, thread, src);
} break;
default:
trap();
}
}
}
fn Node* analyze_local_block(Analyzer* analyzer, Parser* parser, Unit* unit, Thread* thread, String src)
{
push_scope(analyzer);
parser->expect_character(src, block_start);
while (1)
{
parser->skip_space(src);
if (src[parser->i] == block_end)
{
break;
}
analyze_statement(analyzer, parser, unit, thread, src);
}
parser->expect_character(src, block_end);
pop_scope(analyzer, thread);
return 0;
}
typedef enum SystemVClass
{
SYSTEMV_CLASS_NONE,
SYSTEMV_CLASS_MEMORY,
SYSTEMV_CLASS_INTEGER,
SYSTEMV_CLASS_SSE,
SYSTEMV_CLASS_SSEUP,
} SystemVClass;
struct SystemVClassification
{
SystemVClass v[2];
};
struct SystemVRegisterCount
{
u32 gp_registers;
u32 sse_registers;
};
fn SystemVClassification systemv_classify(DebugType* type, u64 base_offset)
{
SystemVClassification result;
u32 is_memory = base_offset >= 8;
u32 current_index = is_memory;
result.v[current_index] = SYSTEMV_CLASS_MEMORY;
result.v[!current_index] = SYSTEMV_CLASS_NONE;
switch (type->id)
{
case DebugTypeId::VOID:
trap();
case DebugTypeId::NORETURN:
trap();
case DebugTypeId::POINTER:
trap();
case DebugTypeId::INTEGER:
{
u8 bit_count = type->get_bit_count();
switch (bit_count)
{
case 8: case 16: case 32: case 64:
result.v[current_index] = SYSTEMV_CLASS_INTEGER;
break;
default:
trap();
}
} break;
case DebugTypeId::COUNT:
trap();
default:
trap();
}
return result;
}
fn u8 contains_no_user_data(DebugType* type, u64 start, u64 end)
{
unused(end);
if (type->size <= start)
{
return 1;
}
switch (type->id)
{
case DebugTypeId::ARRAY:
trap();
case DebugTypeId::STRUCT:
trap();
case DebugTypeId::UNION:
trap();
default:
return 0;
case DebugTypeId::COUNT:
trap();
}
}
fn DebugType* systemv_get_int_type_at_offset(DebugType* type, u64 offset, DebugType* source_type, u64 source_offset)
{
unused(source_type);
switch (type->id)
{
case DebugTypeId::VOID:
trap();
case DebugTypeId::NORETURN:
trap();
case DebugTypeId::POINTER:
trap();
case DebugTypeId::INTEGER:
{
u8 bit_count = type->get_bit_count();
switch (bit_count)
{
case 8: case 16: case 32: case 64:
if (offset == 0)
{
u64 start = source_offset + type->size;
u64 end = source_offset + 8;
if (contains_no_user_data(type, start, end))
{
return type;
}
trap();
}
else
{
trap();
}
default:
trap();
}
trap();
} break;
case DebugTypeId::COUNT:
trap();
case DebugTypeId::ARRAY:
trap();
case DebugTypeId::STRUCT:
trap();
case DebugTypeId::UNION:
trap();
}
}
fn Function* analyze_function(Parser* parser, Thread* thread, Unit* unit, File* file)
{
String src = file->source_code;
parser->expect_character(src, 'f');
parser->expect_character(src, 'n');
parser->skip_space(src);
u64 has_function_attributes = src.pointer[parser->i] == function_attribute_start;
parser->i += has_function_attributes;
CallingConvention calling_convention = CALLING_CONVENTION_CUSTOM;
if (has_function_attributes)
{
u64 mask = 0;
while (1)
{
parser->skip_space(src);
if (src[parser->i] == function_attribute_end)
{
break;
}
String attribute_candidate = parser->parse_raw_identifier(src);
u64 attribute_i;
for (attribute_i = 0; attribute_i < array_length(function_attributes); attribute_i += 1)
{
String function_attribute_string = function_attributes[attribute_i];
if (attribute_candidate.equal(function_attribute_string))
{
if (mask & (1 << attribute_i))
{
fail();
}
auto function_attribute = static_cast<FunctionAttribute>(attribute_i);
mask |= (1 << attribute_i);
switch (function_attribute)
{
case FUNCTION_ATTRIBUTE_CC:
{
parser->skip_space(src);
parser->expect_character(src, '(');
parser->skip_space(src);
parser->expect_character(src, '.');
String candidate_cc = parser->parse_raw_identifier(src);
parser->skip_space(src);
parser->expect_character(src, ')');
u64 cc_i;
for (cc_i = 0; cc_i < array_length(calling_conventions); cc_i += 1)
{
String calling_convention_string = calling_conventions[cc_i];
if (calling_convention_string.equal(candidate_cc))
{
calling_convention = static_cast<CallingConvention>(cc_i);
break;
}
}
if (cc_i == array_length(calling_conventions))
{
fail();
}
} break;
default:
trap();
}
break;
}
}
if (attribute_i == array_length(function_attributes))
{
fail();
}
parser->skip_space(src);
u8 after_ch = src.pointer[parser->i];
switch (after_ch)
{
case function_attribute_end: break;
default: fail();
}
}
parser->expect_character(src, function_attribute_end);
parser->skip_space(src);
}
String name = parser->parse_and_check_identifier(src);
if (!name.pointer | !name.length)
{
fail();
}
auto function_index = thread->functions.length;
auto* function = thread->functions.add_one();
auto node_id = thread->node_count;
thread->node_count += 1;
auto symbol_result = file->symbols.get_or_put(thread->arena, name, Node{
.type = {},
.uid = node_id,
.id = Node::Id::SYMBOL_FUNCTION,
.payload = {
.symbol = &function->symbol,
},
});
if (symbol_result.existing)
{
fail();
}
parser->skip_space(src);
u64 has_global_attributes = src.pointer[parser->i] == symbol_attribute_start;
parser->i += has_global_attributes;
GlobalSymbolAttributes symbol_attributes = {};
if (has_global_attributes)
{
u64 mask = 0;
while (1)
{
parser->skip_space(src);
if (src.pointer[parser->i] == symbol_attribute_end)
{
break;
}
String candidate_attribute = parser->parse_raw_identifier(src);
parser->skip_space(src);
switch (src.pointer[parser->i])
{
case symbol_attribute_end:
break;
case end_of_argument:
parser->i += 1;
break;
default:
fail();
}
u64 attribute_i;
for (attribute_i = 0; attribute_i < array_length(global_symbol_attributes); attribute_i += 1)
{
String attribute_string = global_symbol_attributes[attribute_i];
if (attribute_string.equal(candidate_attribute))
{
if (mask & (1 << attribute_i))
{
fail();
}
mask |= 1 << attribute_i;
auto attribute = static_cast<GlobalSymbolAttribute>(attribute_i);
switch (attribute)
{
case GLOBAL_SYMBOL_ATTRIBUTE_EXPORT:
symbol_attributes.exported = 1;
break;
case GLOBAL_SYMBOL_ATTRIBUTE_EXTERN:
symbol_attributes.external = 1;
break;
default:
trap();
}
break;
}
}
if (attribute_i == array_length(global_symbol_attributes))
{
fail();
}
}
parser->expect_character(src, symbol_attribute_end);
parser->skip_space(src);
}
if (symbol_attributes.exported & symbol_attributes.external)
{
fail();
}
parser->expect_character(src, function_argument_start);
Array<DebugType*> original_argument_types = {};
Array<String> argument_names = {};
while (1)
{
parser->skip_space(src);
if (src.pointer[parser->i] == function_argument_end)
{
break;
}
String argument_name = parser->parse_and_check_identifier(src);
argument_names.append_one(argument_name);
parser->skip_space(src);
parser->expect_character(src, ':');
parser->skip_space(src);
DebugType* argument_type = analyze_type(parser, unit, src);
original_argument_types.append_one(argument_type);
parser->skip_space(src);
switch (src[parser->i])
{
case function_argument_end:
break;
case end_of_argument:
parser->i += 1;
default:
fail();
}
}
parser->expect_character(src, function_argument_end);
parser->skip_space(src);
DebugType* original_return_type = analyze_type(parser, unit, src);
parser->skip_space(src);
AbiInfo return_type_abi = {};
Array<AbiInfo> argument_type_abis = {};
switch (calling_convention)
{
case CALLING_CONVENTION_C:
{
// First process the return type ABI
{
SystemVClassification return_type_classes = systemv_classify(original_return_type, 0);
assert(return_type_classes.v[1] != SYSTEMV_CLASS_MEMORY | return_type_classes.v[0] == SYSTEMV_CLASS_MEMORY);
assert(return_type_classes.v[1] != SYSTEMV_CLASS_SSEUP | return_type_classes.v[0] == SYSTEMV_CLASS_SSE);
DebugType* low_part = 0;
switch (return_type_classes.v[0])
{
case SYSTEMV_CLASS_INTEGER:
{
DebugType* result_type = systemv_get_int_type_at_offset(original_return_type, 0, original_return_type, 0);
if (return_type_classes.v[1] == SYSTEMV_CLASS_NONE & original_return_type->get_bit_count() < 32)
{
trap();
}
low_part = result_type;
} break;
default:
trap();
}
assert(low_part);
DebugType* high_part = 0;
switch (return_type_classes.v[1])
{
case SYSTEMV_CLASS_NONE:
break;
case SYSTEMV_CLASS_MEMORY:
trap();
case SYSTEMV_CLASS_INTEGER:
trap();
case SYSTEMV_CLASS_SSE:
trap();
case SYSTEMV_CLASS_SSEUP:
trap();
}
if (high_part)
{
trap();
}
else
{
// TODO:
u8 is_type = 1;
if (is_type)
{
if (low_part == original_return_type)
{
return_type_abi =
{
.payload = {
.direct = low_part->lower(thread),
},
.kind = ABI_INFO_DIRECT,
};
}
else
{
trap();
}
}
else
{
trap();
}
}
}
// Now process the ABI for argument types
// u32 abi_argument_type_count = 0;
{
SystemVRegisterCount available_registers = {
.gp_registers = 6,
.sse_registers = 8,
};
available_registers.gp_registers -= return_type_abi.kind == ABI_INFO_INDIRECT;
// TODO: return by reference
u8 return_by_reference = 0;
if (return_by_reference)
{
trap();
}
for (u32 original_argument_index = 0; original_argument_index < original_argument_types.length; original_argument_index += 1)
{
trap();
}
}
} break;
case CALLING_CONVENTION_CUSTOM:
{
return_type_abi = {
.payload = {
.direct = original_return_type->lower(thread),
},
.kind = ABI_INFO_DIRECT,
};
for (DebugType* original_argument_type : original_argument_types.slice())
{
argument_type_abis.append_one({
.payload = {
.direct = original_argument_type->lower(thread),
},
.kind = AbiInfoKind::ABI_INFO_DIRECT,
});
}
} break;
case CALLING_CONVENTION_COUNT:
trap();
break;
}
switch (symbol_attributes.external)
{
case 0:
{
switch (return_type_abi.kind)
{
case ABI_INFO_IGNORE: case ABI_INFO_DIRECT:
break;
case ABI_INFO_DIRECT_PAIR:
trap();
case ABI_INFO_DIRECT_COERCE:
trap();
case ABI_INFO_DIRECT_COERCE_INT:
trap();
case ABI_INFO_DIRECT_SPLIT_STRUCT_I32:
trap();
case ABI_INFO_EXPAND_COERCE:
trap();
case ABI_INFO_INDIRECT:
trap();
case ABI_INFO_EXPAND:
trap();
}
*function = {
.symbol = {
.name = name,
.id = Symbol::Id::function,
.linkage = symbol_attributes.external ? Symbol::Linkage::external : Symbol::Linkage::internal,
},
.root_node = 0,
.stop_node = 0,
.parameters = thread->arena->allocate_many<Node*>(argument_type_abis.length),
.prototype = {
.argument_type_abis = argument_type_abis.pointer,
.original_argument_types = original_argument_types.pointer,
.original_return_type = original_return_type,
.return_type_abi = return_type_abi,
.original_argument_count = original_argument_types.length,
.varags = 0,
},
.uid = function_index,
.parameter_count = (u16)argument_type_abis.length,
};
Array<Node::Type*> abi_argument_types = {};
Array<Node::Type*> root_arg_types = {};
root_arg_types.append_one(thread->common_types.live_control);
for (u32 i = 0; i < argument_type_abis.length; i += 1)
{
u16 start = abi_argument_types.length;
auto* abi_info = &argument_type_abis[i];
// TODO: figure out how to interact with the C ABI
switch (abi_info->kind)
{
case ABI_INFO_IGNORE:
trap();
case ABI_INFO_DIRECT:
{
auto node_type = abi_info->payload.direct;
abi_argument_types.append_one(node_type);
} break;
case ABI_INFO_DIRECT_PAIR:
trap();
case ABI_INFO_DIRECT_COERCE:
trap();
case ABI_INFO_DIRECT_COERCE_INT:
trap();
case ABI_INFO_DIRECT_SPLIT_STRUCT_I32:
trap();
case ABI_INFO_EXPAND_COERCE:
trap();
case ABI_INFO_INDIRECT:
trap();
case ABI_INFO_EXPAND:
trap();
}
u16 end = abi_argument_types.length;
abi_info->indices[0] = start;
abi_info->indices[1] = end;
}
root_arg_types.append(abi_argument_types.slice());
auto* root_type = thread->types.append_one({
.payload = {
.multi = {
.types = root_arg_types.slice(),
},
},
.id = NodeType::Id::MULTIVALUE,
})->intern_type(thread);
function->root_node = Node::add(thread, {
.inputs = {},
.id = Node::Id::ROOT,
});
function->root_node->type = root_type,
function->root_node->payload.root.args = root_type;
function->root_node->payload.root.function = function;
function->root_node->peephole(thread, function);
function->stop_node = Node::add(thread, {
.inputs = {},
.id = Node::Id::STOP,
});
Analyzer analyzer = {
.function = function,
.scope = create_scope(thread),
.file = file,
};
push_scope(&analyzer);
auto control_name = strlit("$control");
s32 next_index = 0;
Node* control_node = function->root_node->project(thread, function->root_node, next_index, control_name)->peephole(thread, function);
next_index += 1;
define_variable(&analyzer, thread, control_name, control_node);
// assert(abi_argument_type_count == 0);
// TODO: reserve memory for them
assert(argument_type_abis.length == argument_type_abis.length);
assert(argument_names.length == argument_type_abis.length);
for (u32 i = 0; i < argument_type_abis.length; i += 1)
{
auto* abi_info = &argument_type_abis[i];
auto argument_name = argument_names[i];
// TODO: figure out how to interact with the C ABI
switch (abi_info->kind)
{
case ABI_INFO_IGNORE:
trap();
case ABI_INFO_DIRECT:
{
auto* argument_node = function->root_node->project(thread, function->root_node, next_index, argument_name)->peephole(thread, function);
define_variable(&analyzer, thread, argument_name, argument_node);
next_index += 1;
} break;
case ABI_INFO_DIRECT_PAIR:
trap();
case ABI_INFO_DIRECT_COERCE:
trap();
case ABI_INFO_DIRECT_COERCE_INT:
trap();
case ABI_INFO_DIRECT_SPLIT_STRUCT_I32:
trap();
case ABI_INFO_EXPAND_COERCE:
trap();
case ABI_INFO_INDIRECT:
trap();
case ABI_INFO_EXPAND:
trap();
}
}
analyze_local_block(&analyzer, parser, unit, thread, src);
pop_scope(&analyzer, thread);
function->stop_node->peephole(thread, function);
return function;
} break;
case 1:
trap();
default:
trap();
}
}
fn void unit_file_analyze(Thread* thread, Unit* unit, File* file)
{
compiler_file_read(thread->arena, file);
Parser parser = {};
String src = file->source_code;
while (1)
{
parser.skip_space(src);
if (parser.i >= src.length)
{
break;
}
// u32 line = get_line(&parser);
// u32 column = get_column(&parser);
u64 declaration_start_index = parser.i;
u8 declaration_start_ch = src.pointer[declaration_start_index];
switch (declaration_start_ch)
{
case '>':
trap();
break;
case 'f':
if (get_next_ch_safe(src, declaration_start_index) == 'n')
{
auto* function = analyze_function(&parser, thread, unit, file);
function->iterate(thread);
}
else
{
fail();
}
break;
default:
fail();
}
}
}
method void Thread::init()
{
auto* live_control = types.append_one({
.id = NodeType::Id::LIVE_CONTROL,
})->intern_type(this);
auto* dead_control = types.append_one({
.id = NodeType::Id::DEAD_CONTROL,
})->intern_type(this);
auto if_both_types = arena->allocate_slice<NodeType*>(2);
if_both_types[0] = live_control;
if_both_types[1] = live_control;
auto if_neither_types = arena->allocate_slice<NodeType*>(2);
if_neither_types[0] = dead_control;
if_neither_types[1] = dead_control;
auto if_true_types = arena->allocate_slice<NodeType*>(2);
if_true_types[0] = live_control;
if_true_types[1] = dead_control;
auto if_false_types = arena->allocate_slice<NodeType*>(2);
if_false_types[0] = dead_control;
if_false_types[1] = live_control;
common_types = {
.bottom = types.append_one({
.id = NodeType::Id::BOTTOM,
})->intern_type(this),
.top = types.append_one({
.id = NodeType::Id::TOP,
})->intern_type(this),
.live_control = types.append_one({
.id = NodeType::Id::LIVE_CONTROL,
})->intern_type(this),
.dead_control = types.append_one({
.id = NodeType::Id::DEAD_CONTROL,
})->intern_type(this),
.integer_bot = types.append_one({
.payload = {
.constant = {
.constant = 0,
.is_constant = 0,
},
},
.id = NodeType::Id::INTEGER,
})->intern_type(this),
.integer_top = types.append_one({
.payload = {
.constant = {
.constant = 1,
.is_constant = 0,
},
},
.id = NodeType::Id::INTEGER,
})->intern_type(this),
.integer_zero = types.append_one({
.payload = {
.constant = {
.constant = 0,
.is_constant = 1,
},
},
.id = NodeType::Id::INTEGER,
})->intern_type(this),
.if_both = types.append_one({
.payload = {
.multi = {
.types = if_both_types,
},
},
.id = NodeType::Id::MULTIVALUE,
})->intern_type(this),
.if_neither = types.append_one({
.payload = {
.multi = {
.types = if_neither_types,
},
},
.id = NodeType::Id::MULTIVALUE,
})->intern_type(this),
.if_true = types.append_one({
.payload = {
.multi = {
.types = if_true_types,
},
},
.id = NodeType::Id::MULTIVALUE,
})->intern_type(this),
.if_false = types.append_one({
.payload = {
.multi = {
.types = if_false_types,
},
},
.id = NodeType::Id::MULTIVALUE,
})->intern_type(this),
};
}
method void Thread::clear()
{
functions.clear();
nodes.clear();
types.clear();
interned_nodes.clear();
interned_types.clear();
worklist.clear();
visited.clear();
common_types = {};
node_count = 0;
peephole_iteration_count = 0;
peephole_nop_iteration_count = 0;
arena->reset();
}
fn Node* progress_on_list_callback(Thread* thread, Function* function, Node* node)
{
Node* result = 0;
if (!thread->worklist.on(node))
{
Node* new_node = node->peephole_optimize(thread, function);
if (new_node)
{
result = new_node;
}
}
return result;
}
method u8 Thread::progress_on_list(Function* function, Node* stop)
{
mid_assert = 1;
auto old_iteration_count = peephole_iteration_count;
auto old_nop_iteration_count = peephole_nop_iteration_count;
Node* changed = stop->walk(this, function, &progress_on_list_callback);
peephole_iteration_count = old_iteration_count;
peephole_nop_iteration_count = old_nop_iteration_count;
mid_assert = 0;
assert(changed == 0);
return changed == 0;
}
method u8 WorkList::on(Node* node)
{
return bitset.get(node->uid);
}
method Node* WorkList::push(Node* node)
{
Node* result = 0;
if (node)
{
result = node;
u32 index = node->uid;
if (!bitset.get(index))
{
bitset.set_assert_unset(index);
nodes.append_one(node);
}
}
return result;
}
global Instance instance;
global String test_file_paths[] = {
strlit("tests/first/main.nat"),
strlit("tests/constant_prop/main.nat"),
strlit("tests/simple_variable_declaration/main.nat"),
strlit("tests/function_call_args/main.nat"),
strlit("tests/comparison/main.nat"),
strlit("tests/if/main.nat"),
strlit("tests/while/main.nat"),
strlit("tests/break_continue/main.nat"),
};
#if LINK_LIBC
int main()
#else
extern "C" void entry_point()
#endif
{
instance.arena = Arena::init(Arena::default_size, Arena::minimum_granularity, KB(4));
Thread* thread = instance_add_thread(&instance);
for (String test_file_path : test_file_paths)
{
thread->init();
print(test_file_path);
print(strlit("... "));
Unit* unit = instance_add_unit(&instance);
unit_initialize(unit);
File* file = add_file(thread->arena, test_file_path);
unit_file_analyze(thread, unit, file);
print(strlit("[\x1b[32mOK\x1b[0m]\n"));
file->symbols.clear();
thread->clear();
}
print(strlit("\x1b[32mTESTS SUCCEEDED!\x1b[0m\n"));
}