diff --git a/bootstrap/main.c b/bootstrap/main.c
new file mode 100644
index 0000000..389d925
--- /dev/null
+++ b/bootstrap/main.c
@@ -0,0 +1,3898 @@
+#include <stdint.h>
+#include <stdarg.h>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
+#ifdef STATIC
+#define LINK_LIBC 0
+#else 
+#define LINK_LIBC 1
+#endif
+
+#if LINK_LIBC
+#include <errno.h>
+#include <string.h>
+#endif
+
+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 MIN(a, b) (((a) < (b)) ? (a) : (b))
+#define MAX(a, b) (((a) > (b)) ? (a) : (b))
+
+#define fn static
+#define method __attribute__((visibility("internal")))
+#define global static
+#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)
+#define may_be_unused __attribute__((unused))
+#if LINK_LIBC
+#define assert(x) if (__builtin_expect(!(x), 0)) { print("Assert failed! ERRNO: "); print(strerror(errno)); print("\n"); trap(); }
+#else
+#define assert(x) if (__builtin_expect(!(x), 0)) { print("Assert failed!\n"); trap(); }
+#endif
+#define static_assert(x) _Static_assert((x), "Static assert failed!")
+#define alignof(x) _Alignof(x)
+#define auto __auto_type
+
+may_be_unused fn void print(const char* format, ...);
+
+#if __APPLE__
+    const global u64 page_size = KB(16);
+#else
+    const global u64 page_size = KB(4);
+#endif
+
+
+const global u8 brace_open = '{';
+const global u8 brace_close = '}';
+
+const global u8 parenthesis_open = '(';
+const global u8 parenthesis_close = ')';
+
+const global u8 bracket_open = '[';
+const global u8 bracket_close = ']';
+
+fn u8 log2_alignment(u64 alignment)
+{
+    assert(alignment != 0);
+    assert((alignment & (alignment - 1)) == 0);
+    u64 left = (sizeof(alignment) * 8) - 1;
+    u64 right = __builtin_clzl(alignment);
+    u8 result = left - right;
+    return result;
+}
+
+// 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;
+}
+
+#if LINK_LIBC == 0
+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;
+}
+#endif
+#define slice_from_pointer_range(T, start, end) (Slice(T)) { .pointer = start, .length = (u64)(end - start), }
+
+#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 pointer_to_bytes(p) Slice<u8>{ .pointer = (u8*)(p), .length = sizeof(*p) }
+#define struct_to_bytes(s) pointer_to_bytes(&(s))
+
+#define case_to_name(prefix, e) case prefix::e: return strlit(#e)
+
+#define Slice(T) Slice_ ## T
+#define SliceP(T) SliceP_ ## T
+#define declare_slice_ex(T, StructName) struct StructName  \
+{\
+    T* pointer;\
+    u64 length;\
+};\
+typedef struct StructName StructName
+
+#define declare_slice(T) declare_slice_ex(T, Slice(T))
+#define declare_slice_p(T) declare_slice_ex(T*, SliceP(T))
+
+#define s_get(s, i) (s).pointer[i]
+#define s_get_pointer(s, i) &((s).pointer[i])
+#define s_get_slice(T, s, start, end) (Slice(T)){ .pointer = s.pointer + start, .length = end - start }
+#define s_equal(a, b) ((a).length == (b).length && memcmp((a).pointer, (b).pointer, sizeof(*((a).pointer))) == 0)
+
+declare_slice(u8);
+typedef Slice(u8) String;
+
+fn u64 parse_decimal(String string)
+{
+    u64 value = 0;
+    for (u64 i = 0; i < string.length; i += 1)
+    {
+        u8 ch = s_get(string, i);
+        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;
+}
+
+global const Hash fnv_offset = 14695981039346656037ull;
+global const u64 fnv_prime = 1099511628211ull;
+
+fn Hash hash_byte(Hash source, u8 ch)
+{
+    source ^= ch;
+    source *= fnv_prime;
+    return source;
+}
+
+fn Hash hash_bytes(String bytes)
+{
+    u64 result = fnv_offset;
+    for (u64 i = 0; i < bytes.length; i += 1)
+    {
+        result = hash_byte(result, bytes.pointer[i]);
+    }
+
+    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_close(int fd)
+{
+#if LINK_LIBC
+    return close(fd);
+#else
+#ifdef __linux__
+    return syscall1(static_cast<long>(SyscallX86_64::close), fd);
+#else
+    return close(fd);
+#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 u64 file_get_size(int fd)
+{
+    struct stat stat_buffer;
+    int stat_result = syscall_fstat(fd, &stat_buffer);
+    assert(stat_result == 0);
+    u64 size = stat_buffer.st_size;
+    return size;
+}
+
+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
+}
+
+may_be_unused fn int syscall_rmdir(const char* path)
+{
+#if LINK_LIBC
+    return rmdir(path);
+#else
+    return syscall1(static_cast<long>(SyscallX86_64::rmdir), (unsigned long)path);
+#endif
+}
+
+may_be_unused fn int syscall_unlink(const char* path)
+{
+#if LINK_LIBC
+    return unlink(path);
+#else
+    return syscall1(static_cast<long>(SyscallX86_64::unlink), (unsigned long)path);
+#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;
+}
+
+#define SILENT (0)
+
+may_be_unused fn void print(const char* format, ...)
+{
+#if SILENT == 0
+        u8 stack_buffer[4096];
+        va_list args;
+        va_start(args, format);
+        String buffer = { .pointer = stack_buffer, .length = array_length(stack_buffer) };
+        u8* it = (u8*)format;
+        u64 buffer_i = 0;
+
+        while (*it)
+        {
+            while (*it && *it != brace_open)
+            {
+                s_get(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;
+
+                                u8* bit_count_start = it;
+                                while (is_decimal_digit(*it))
+                                {
+                                    it += 1;
+                                }
+
+                                u8* bit_count_end = it;
+                                u64 bit_count = parse_decimal(slice_from_pointer_range(u8, (u8*)bit_count_start, (u8*)bit_count_end));
+
+                                typedef enum IntegerFormat : u8
+                                {
+                                    INTEGER_FORMAT_HEXADECIMAL,
+                                    INTEGER_FORMAT_DECIMAL,
+                                    INTEGER_FORMAT_OCTAL,
+                                    INTEGER_FORMAT_BINARY,
+                                } IntegerFormat;
+
+                                IntegerFormat format = INTEGER_FORMAT_DECIMAL;
+
+                                if (*it == ':')
+                                {
+                                    it += 1;
+                                    switch (*it)
+                                    {
+                                        case 'x':
+                                            format = INTEGER_FORMAT_HEXADECIMAL;
+                                            break;
+                                        case 'd':
+                                            format = INTEGER_FORMAT_DECIMAL;
+                                            break;
+                                        case 'o':
+                                            format = INTEGER_FORMAT_OCTAL;
+                                            break;
+                                        case 'b':
+                                            format = INTEGER_FORMAT_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 INTEGER_FORMAT_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;
+                                                    s_get(buffer, buffer_i) = reverse_buffer[reverse_index];
+                                                    buffer_i += 1;
+                                                }
+                                            } break;
+                                        case INTEGER_FORMAT_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;
+                                                    s_get(buffer, buffer_i) = reverse_buffer[reverse_index];
+                                                    buffer_i += 1;
+                                                }
+                                            } break;
+                                        case INTEGER_FORMAT_OCTAL:
+                                        case INTEGER_FORMAT_BINARY:
+                                            trap();
+                                    }
+                                }
+                                else
+                                {
+                                    s_get(buffer, buffer_i) = '0';
+                                    buffer_i += 1;
+                                }
+                            } break;
+                        default:
+                            trap();
+                    }
+
+                    if (*it != brace_close)
+                    {
+                        fail();
+                    }
+
+                    it += 1;
+                }
+            }
+        }
+
+        String final_string = s_get_slice(u8, buffer, 0, buffer_i);
+        syscall_write(1, final_string.pointer, final_string.length);
+#endif
+}
+
+global u64 minimum_granularity = page_size;
+global u64 middle_granularity = MB(2);
+global u64 default_size = GB(4);
+
+struct Arena
+{
+    u64 reserved_size;
+    u64 committed;
+    u64 commit_position;
+    u64 granularity;
+    u8 reserved[4 * 8];
+};
+
+typedef struct Arena Arena;
+static_assert(sizeof(Arena) == 64);
+
+fn Arena* arena_init(u64 reserved_size, u64 granularity, u64 initial_size)
+{
+    Arena* arena = (Arena*)reserve(reserved_size);
+    commit(arena, initial_size);
+    *arena = (Arena){
+        .reserved_size = reserved_size,
+        .committed = initial_size,
+        .commit_position = sizeof(Arena),
+        .granularity = granularity,
+    };
+    return arena;
+}
+
+fn Arena* arena_init_default(u64 initial_size)
+{
+    return arena_init(default_size, minimum_granularity, initial_size);
+}
+
+fn void* arena_allocate_bytes(Arena* arena, u64 size, u64 alignment)
+{
+    u64 aligned_offset = align_forward(arena->commit_position, alignment);
+    u64 aligned_size_after = aligned_offset + size;
+
+    if (aligned_size_after > arena->committed)
+    {
+        u64 committed_size = align_forward(aligned_size_after, arena->granularity);
+        u64 size_to_commit = committed_size - arena->committed;
+        void* commit_pointer = (u8*)arena + arena->committed;
+        commit(commit_pointer, size_to_commit);
+        arena->committed = committed_size;
+    }
+
+    void* result = (u8*)arena + aligned_offset;
+    arena->commit_position = aligned_size_after;
+    assert(arena->commit_position <= arena->committed);
+    return result;
+}
+
+#define arena_allocate(arena, T, count) arena_allocate_bytes(arena, sizeof(T) * count, alignof(T))
+#define arena_allocate_slice(arena, T, count) (Slice(T)){ .pointer = arena_allocate(arena, T, count), .length = count }
+
+fn void arena_reset(Arena* arena)
+{
+    arena->commit_position = sizeof(Arena);
+    memset(arena + 1, 0, arena->committed - sizeof(Arena));
+}
+
+#define transmute(D, source) *(D*)&source
+
+struct StringMapValue
+{
+    String string;
+    u32 value;
+};
+typedef struct StringMapValue StringMapValue;
+
+struct StringMap
+{
+    u32* pointer;
+    u32 length;
+    u32 capacity;
+};
+typedef struct StringMap StringMap;
+
+fn s32 string_map_find_slot(StringMap* map, u32 original_index, String key, u32 value)
+{
+    auto it_index = original_index;
+    auto existing_capacity = map->capacity;
+    s32 result = -1;
+
+    for (u32 i = 0; i < existing_capacity; i += 1)
+    {
+        auto index = it_index & (existing_capacity - 1);
+        u32 key = map->pointer[index];
+
+        // Not set
+        if (key == 0)
+        {
+            result = index;
+            break;
+        }
+        else
+        {
+            trap();
+        }
+
+        it_index += 1;
+    }
+
+    return result;
+}
+
+struct StringMapPut
+{
+    u32 value;
+    u8 existing;
+};
+typedef struct StringMapPut StringMapPut;
+
+fn StringMapValue* string_map_values(StringMap* map)
+{
+    assert(map->pointer);
+    return (StringMapValue*)(map->pointer + map->capacity);
+}
+
+fn void string_map_ensure_capacity(StringMap* map, Arena* arena, u32 additional)
+{
+    auto current_capacity = map->capacity;
+    auto half_capacity = current_capacity >> 1;
+    auto destination_length = map->length + additional;
+
+    if (destination_length > half_capacity)
+    {
+        u32 new_capacity = MAX(round_up_to_next_power_of_2(destination_length), 32);
+        auto new_capacity_bytes = sizeof(u32) * new_capacity + new_capacity * sizeof(StringMapValue);
+
+        void* ptr = arena_allocate_bytes(arena, new_capacity_bytes, MAX(alignof(u32), alignof(StringMapValue)));
+        memset(ptr, 0, new_capacity_bytes);
+
+        auto* keys = (u32*)ptr;
+        auto* values = (StringMapValue*)(keys + new_capacity);
+
+        auto* old_keys = map->pointer;
+        auto old_capacity = map->capacity;
+        auto* old_values = (StringMapValue*)(map->pointer + current_capacity);
+
+        map->length = 0;
+        map->pointer = keys;
+        map->capacity = new_capacity;
+
+        for (u32 i = 0; i < old_capacity; i += 1)
+        {
+            auto key = old_keys[i];
+            if (key)
+            {
+                trap();
+            }
+        }
+
+        for (u32 i = 0; i < old_capacity; i += 1)
+        {
+            trap();
+        }
+    }
+}
+
+fn StringMapPut string_map_put_at_assume_not_existent_assume_capacity(StringMap* map, u32 hash, String key, u32 value, u32 index)
+{
+    u32 existing_hash = map->pointer[index];
+    map->pointer[index] = hash;
+    auto* values = string_map_values(map);
+    auto existing_value = values[index];
+    values[index] = (StringMapValue) {
+        .value = value,
+        .string = key,
+    };
+    map->length += 1;
+    assert(existing_hash ? s_equal(existing_value.string, key) : 1);
+
+    return (StringMapPut)
+    {
+        .value = existing_value.value,
+        .existing = existing_hash != 0,
+    };
+}
+
+fn StringMapPut string_map_put_assume_not_existent_assume_capacity(StringMap* map, u32 hash, String key, u32 value)
+{
+    assert(map->length < map->capacity);
+    auto index = hash & (map->capacity - 1);
+    
+    return string_map_put_at_assume_not_existent_assume_capacity(map, hash, key, value, index);
+}
+
+fn StringMapPut string_map_put_assume_not_existent(StringMap* map, Arena* arena, u32 hash, String key, u32 value)
+{
+    string_map_ensure_capacity(map, arena, 1);
+    return string_map_put_assume_not_existent_assume_capacity(map, hash, key, value);
+}
+
+fn StringMapPut string_map_put(StringMap* map, Arena* arena, String key, u32 value)
+{
+    Hash long_hash = hash_bytes(key);
+    auto hash = (u32)long_hash;
+    assert(hash);
+    auto index = hash & (map->capacity - 1);
+    auto slot = string_map_find_slot(map, index, key, value);
+    if (slot != -1)
+    {
+        trap();
+    }
+    else
+    {
+        if (map->length < map->capacity)
+        {
+            trap();
+        }
+        else if (map->length == map->capacity)
+        {
+            auto result = string_map_put_assume_not_existent(map, arena, hash, key, value);
+            assert(!result.existing);
+            return result;
+        }
+        else
+        {
+            trap();
+        }
+    }
+    trap();
+}
+
+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 = (String){
+        .pointer = (u8*)arena_allocate_bytes(arena, 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(String message)
+{
+#if SILENT == 0
+        ssize_t result = syscall_write(1, message.pointer, message.length);
+        assert(result >= 0);
+        assert((u64)result == message.length);
+#else
+        unused(message);
+#endif
+}
+
+typedef enum ELFSectionType : u32
+{
+    null = 0x00,
+    program = 0x01,
+    symbol_table = 0x02,
+    string_table = 0x03,
+    relocation_with_addends = 0x04,
+    symbol_hash_table = 0x05,
+    dynamic = 0x06,
+    note = 0x07,
+    bss = 0x08,
+    relocation_no_addends = 0x09,
+    lib = 0x0a, // reserved
+    dynamic_symbol_table = 0x0b,
+    init_array = 0x0e,
+    fini_array = 0x0f,
+    preinit_array = 0x10,
+    group = 0x11,
+    symbol_table_section_header_index = 0x12,
+} ELFSectionType;
+
+struct ELFSectionHeaderFlags
+{
+    u64 write:1;
+    u64 alloc:1;
+    u64 executable:1;
+    u64 blank:1;
+    u64 merge:1;
+    u64 strings:1;
+    u64 info_link:1;
+    u64 link_order:1;
+    u64 os_non_conforming:1;
+    u64 group:1;
+    u64 tls:1;
+};
+typedef struct ELFSectionHeaderFlags ELFSectionHeaderFlags;
+
+struct ELFSectionHeader
+{
+    u32 name_offset;
+    ELFSectionType type;
+    ELFSectionHeaderFlags flags;
+    u64 address;
+    u64 offset;
+    u64 size;
+    u32 link;
+    u32 info;
+    u64 alignment;
+    u64 entry_size;
+};
+typedef struct ELFSectionHeader ELFSectionHeader;
+static_assert(sizeof(ELFSectionHeader) == 64);
+
+typedef enum ELFBitCount : u8
+{
+    bits32 = 1,
+    bits64 = 2,
+} ELFBitCount;
+
+typedef enum ELFEndianness : u8
+{
+    little = 1,
+    big = 2,
+} ELFEndianness;
+
+typedef enum ELFAbi : u8
+{
+    system_v_abi = 0,
+    linux_abi = 3,
+} ELFAbi;
+
+typedef enum ELFType : u16
+{
+    none = 0,
+    relocatable = 1,
+    executable = 2,
+    shared = 3,
+    core = 4,
+} ELFType;
+
+typedef enum ELFMachine : u16
+{
+    x86_64 = 0x3e,
+    aarch64 = 0xb7,
+} ELFMachine;
+
+typedef enum ELFSectionIndex : u16
+{
+    UNDEFINED = 0,
+    ABSOLUTE = 0xfff1,
+    COMMON = 0xfff2,
+} ELFSectionIndex;
+
+struct ELFHeader
+{
+    u8 identifier[4];
+    ELFBitCount bit_count;
+    ELFEndianness endianness;
+    u8 format_version;
+    ELFAbi abi;
+    u8 abi_version;
+    u8 padding[7];
+    ELFType type;
+    ELFMachine machine;
+    u32 version;
+    u64 entry_point;
+    u64 program_header_offset;
+    u64 section_header_offset;
+    u32 flags;
+    u16 elf_header_size;
+    u16 program_header_size;
+    u16 program_header_count;
+    u16 section_header_size;
+    u16 section_header_count;
+    u16 section_header_string_table_index;
+
+};
+typedef struct ELFHeader ELFHeader;
+static_assert(sizeof(ELFHeader) == 0x40);
+
+typedef enum ELFSymbolBinding : u8
+{
+    LOCAL = 0,
+    GLOBAL = 1,
+    WEAK = 2,
+} ELFSymbolBinding;
+
+typedef enum ELFSymbolType : u8
+{
+    ELF_SYMBOL_TYPE_NONE = 0,
+    ELF_SYMBOL_TYPE_OBJECT = 1,
+    ELF_SYMBOL_TYPE_FUNCTION = 2,
+    ELF_SYMBOL_TYPE_SECTION = 3,
+    ELF_SYMBOL_TYPE_FILE = 4,
+    ELF_SYMBOL_TYPE_COMMON = 5,
+    ELF_SYMBOL_TYPE_TLS = 6,
+} ELFSymbolType;
+struct ELFSymbol
+{
+    u32 name_offset;
+    ELFSymbolType type:4;
+    ELFSymbolBinding binding:4;
+    u8 other;
+    u16 section_index; // In the section header table
+    u64 value;
+    u64 size;
+};
+typedef struct ELFSymbol ELFSymbol;
+static_assert(sizeof(ELFSymbol) == 24);
+
+struct NameReference
+{
+    u32 offset;
+    u32 length;
+};
+
+typedef struct NameReference NameReference;
+
+#define VirtualBuffer(T) VirtualBuffer_ ## T
+#define VirtualBufferP(T) VirtualBufferPointerTo_ ## T
+
+#define decl_vb_ex(T, StructName) \
+struct StructName \
+{\
+    T* pointer;\
+    u32 length;\
+    u32 capacity;\
+};\
+typedef struct StructName StructName
+
+#define decl_vb(T) decl_vb_ex(T, VirtualBuffer(T))
+#define decl_vbp(T) decl_vb_ex(T*, VirtualBufferP(T))
+
+decl_vb(u8);
+
+fn void vb_generic_ensure_capacity(VirtualBuffer(u8)* vb, u32 item_size, u32 item_count)
+{
+    u32 old_capacity = vb->capacity;
+    u32 wanted_capacity = vb->length + item_count;
+
+    if (old_capacity < wanted_capacity)
+    {
+        if (old_capacity == 0)
+        {
+            vb->pointer = reserve(default_size);
+        }
+
+        u32 old_page_capacity = align_forward(old_capacity * item_size, minimum_granularity);
+        u32 new_page_capacity = align_forward(wanted_capacity * item_size, minimum_granularity);
+
+        u32 commit_size = new_page_capacity - old_page_capacity;
+        void* commit_pointer = vb->pointer + old_page_capacity;
+
+        commit(commit_pointer, commit_size);
+
+        u32 new_capacity = new_page_capacity / item_size;
+        vb->capacity = new_capacity;
+    }
+}
+
+fn u8* vb_generic_add_assume_capacity(VirtualBuffer(u8)* vb, u32 item_size, u32 item_count)
+{
+    u32 index = vb->length;
+    assert(vb->capacity >= index + item_count);
+    vb->length = index + item_count;
+    return vb->pointer + (index * item_size);
+}
+
+fn u8* vb_generic_append_assume_capacity(VirtualBuffer(u8)* vb, void* item_pointer, u32 item_size, u32 item_count)
+{
+    u8* new_memory = vb_generic_add_assume_capacity(vb, item_size, item_count);
+    memcpy(new_memory, item_pointer, item_size * item_count);
+    return new_memory;
+}
+
+fn u8* vb_generic_add(VirtualBuffer(u8)* vb, u32 item_size, u32 item_count)
+{
+    vb_generic_ensure_capacity(vb, item_size, item_count);
+    return vb_generic_add_assume_capacity(vb, item_size, item_count);
+}
+
+
+fn u8* vb_generic_append(VirtualBuffer(u8)* vb, void* item_pointer, u32 item_size, u32 item_count)
+{
+    vb_generic_ensure_capacity(vb, item_size, item_count);
+    return vb_generic_append_assume_capacity(vb, item_pointer, item_size, item_count);
+}
+
+#define vb_add(a, count) (typeof((a)->pointer)) vb_generic_add((VirtualBuffer(u8)*)(a), sizeof(*((a)->pointer)), count)
+#define vb_append_one(a, item) (typeof((a)->pointer)) vb_generic_append((VirtualBuffer(u8)*)(a), &(item), sizeof(*((a)->pointer)), 1)
+
+typedef struct Thread Thread;
+
+typedef enum TypeId : u32
+{
+    // Simple types
+    TYPE_BOT = 0,
+    TYPE_TOP,
+    TYPE_LIVE_CONTROL,
+    TYPE_DEAD_CONTROL,
+    // Not simple types
+    TYPE_INTEGER,
+    TYPE_TUPLE,
+
+    TYPE_COUNT,
+} TypeId;
+
+typedef struct BackendType BackendType;
+struct TypeIndex
+{
+    u32 index;
+};
+typedef struct TypeIndex TypeIndex;
+#define index_equal(a, b) (a.index == b.index)
+static_assert(sizeof(TypeIndex) == sizeof(u32));
+declare_slice(TypeIndex);
+
+#define RawIndex(T, i) (T ## Index) { .index = (i) }
+#define Index(T, i) RawIndex(T, (i) + 1)
+#define geti(i) ((i).index - 1)
+#define validi(i) ((i).index != 0)
+#define invalidi(T) RawIndex(T, 0)
+
+struct TypeInteger
+{
+    u64 constant;
+    u64 bit_count:7;
+    u64 is_constant:1;
+    u64 is_signed:1;
+};
+typedef struct TypeInteger TypeInteger;
+static_assert(sizeof(TypeInteger) == 16);
+
+struct TypeTuple
+{
+    Slice(TypeIndex) types;
+};
+typedef struct TypeTuple TypeTuple;
+
+struct Type
+{
+    union
+    {
+        TypeInteger integer;
+        TypeTuple tuple;
+    };
+    Hash hash;
+    TypeId id;
+};
+typedef struct Type Type;
+decl_vb(Type);
+
+typedef enum NodeId : u8
+{
+    NODE_START,
+    NODE_STOP,
+    NODE_CONTROL_PROJECTION,
+    NODE_DEAD_CONTROL,
+    NODE_SCOPE,
+    NODE_PROJECTION,
+    NODE_RETURN,
+    NODE_REGION,
+    NODE_REGION_LOOP,
+    NODE_IF,
+
+    NODE_INTEGER_ADD,
+    NODE_INTEGER_SUBSTRACT,
+    NODE_INTEGER_MULTIPLY,
+    NODE_INTEGER_UNSIGNED_DIVIDE,
+    NODE_INTEGER_SIGNED_DIVIDE,
+    NODE_INTEGER_UNSIGNED_REMAINDER,
+    NODE_INTEGER_SIGNED_REMAINDER,
+    NODE_INTEGER_UNSIGNED_SHIFT_LEFT,
+    NODE_INTEGER_SIGNED_SHIFT_LEFT,
+    NODE_INTEGER_UNSIGNED_SHIFT_RIGHT,
+    NODE_INTEGER_SIGNED_SHIFT_RIGHT,
+    NODE_INTEGER_AND,
+    NODE_INTEGER_OR,
+    NODE_INTEGER_XOR,
+
+    NODE_CONSTANT,
+
+    NODE_COUNT,
+} NodeId;
+
+struct NodeIndex
+{
+    u32 index;
+};
+typedef struct NodeIndex NodeIndex;
+declare_slice(NodeIndex);
+decl_vb(NodeIndex);
+
+struct NodeConstant
+{
+    TypeIndex type;
+};
+typedef struct NodeConstant NodeConstant;
+
+struct NodeStart
+{
+    TypeIndex arguments;
+};
+typedef struct NodeStart NodeStart;
+
+struct ScopePair
+{
+    StringMap values;
+    StringMap types;
+};
+typedef struct ScopePair ScopePair;
+
+struct StackScope
+{
+    ScopePair* pointer;
+    u32 length;
+    u32 capacity;
+};
+typedef struct StackScope StackScope;
+
+struct NodeScope
+{
+    StackScope stack;
+};
+typedef struct NodeScope NodeScope;
+
+struct NodeCFG
+{
+    s32 immediate_dominator_tree_depth;
+    s32 loop_depth;
+    s32 anti_dependency;
+};
+typedef struct NodeCFG NodeCFG;
+
+struct NodeProjection
+{
+    String label;
+    u32 index;
+};
+typedef struct NodeProjection NodeProjection;
+
+struct NodeControlProjection
+{
+    NodeProjection projection;
+    NodeCFG cfg;
+};
+typedef struct NodeControlProjection NodeControlProjection;
+
+struct Node
+{
+    Hash hash;
+    u32 input_offset;
+    u32 output_offset;
+    u32 dependency_offset;
+    u16 output_count;
+    u16 input_count;
+    u16 dependency_count;
+    u16 input_capacity;
+    u16 output_capacity;
+    u16 dependency_capacity;
+    u16 thread_id;
+    TypeIndex type;
+    NodeId id;
+    union
+    {
+        NodeConstant constant;
+        NodeStart start;
+        NodeScope scope;
+        NodeControlProjection control_projection;
+    };
+};
+typedef struct Node Node;
+declare_slice_p(Node);
+
+decl_vb(Node);
+decl_vbp(Node);
+
+struct ArrayReference
+{
+    u32 offset;
+    u32 length;
+};
+typedef struct ArrayReference ArrayReference;
+decl_vb(ArrayReference);
+
+struct Function
+{
+    NodeIndex start;
+    NodeIndex stop;
+};
+typedef struct Function Function;
+decl_vb(Function);
+
+struct File
+{
+    String path;
+    String source;
+    StringMap values;
+    StringMap types;
+};
+typedef struct File File;
+
+struct FunctionBuilder
+{
+    Function* function;
+    File* file;
+    NodeIndex scope;
+};
+typedef struct FunctionBuilder FunctionBuilder;
+
+struct InternPool
+{
+    u32* pointer;
+    u32 length;
+    u32 capacity;
+};
+typedef struct InternPool InternPool;
+
+struct Thread
+{
+    Arena* arena;
+    struct
+    {
+        VirtualBuffer(Type) types;
+        VirtualBuffer(Node) nodes;
+        VirtualBuffer(NodeIndex) uses;
+        VirtualBuffer(u8) name_buffer;
+        VirtualBuffer(ArrayReference) use_free_list;
+        VirtualBuffer(Function) functions;
+    } buffer;
+    struct
+    {
+        InternPool types;
+        InternPool nodes;
+    } interned;
+    struct
+    {
+        TypeIndex bottom;
+        TypeIndex top;
+        TypeIndex live_control;
+        TypeIndex dead_control;
+        struct
+        {
+            TypeIndex top;
+            TypeIndex bottom;
+            TypeIndex zero;
+        } integer;
+    } types;
+    struct
+    {
+        u64 total;
+        u64 nop;
+    } iteration;
+};
+typedef struct Thread Thread;
+
+fn Type* thread_type_get(Thread* thread, TypeIndex type_index)
+{
+    assert(validi(type_index));
+    auto* type = &thread->buffer.types.pointer[geti(type_index)];
+    return type;
+}
+
+fn Node* thread_node_get(Thread* thread, NodeIndex node_index)
+{
+    assert(validi(node_index));
+    auto* node = &thread->buffer.nodes.pointer[geti(node_index)];
+    return node;
+}
+
+fn void thread_node_set_use(Thread* thread, u32 offset, u16 index, NodeIndex new_use)
+{
+    thread->buffer.uses.pointer[offset + index] = new_use;
+}
+
+fn NodeIndex thread_node_get_use(Thread* thread, u32 offset, u16 index)
+{
+    NodeIndex i = thread->buffer.uses.pointer[offset + index];
+    return i;
+}
+
+fn NodeIndex node_input_get(Thread* thread, Node* node, u16 index)
+{
+    assert(index < node->input_count);
+    NodeIndex result = thread_node_get_use(thread, node->input_offset, index);
+    return result;
+}
+
+fn NodeIndex node_output_get(Thread* thread, Node* node, u16 index)
+{
+    assert(index < node->output_count);
+    NodeIndex result = thread_node_get_use(thread, node->output_offset, index);
+    return result;
+}
+
+fn NodeIndex scope_get_control(Thread* thread, Node* node)
+{
+    assert(node->id == NODE_SCOPE);
+    auto control = node_input_get(thread, node, 0);
+    return control;
+}
+
+fn NodeIndex builder_get_control_node_index(Thread* thread, FunctionBuilder* builder)
+{
+    auto* scope_node = thread_node_get(thread, builder->scope);
+    auto result = scope_get_control(thread, scope_node);
+    return result;
+}
+
+typedef struct NodeDualReference NodeDualReference;
+
+struct UseReference
+{
+    NodeIndex* pointer;
+    u32 index;
+};
+typedef struct UseReference UseReference;
+
+fn UseReference thread_get_node_reference_array(Thread* thread, u16 count)
+{
+    u32 free_list_count = thread->buffer.use_free_list.length;
+    for (u32 i = 0; i < free_list_count; i += 1)
+    {
+        if (thread->buffer.use_free_list.pointer[i].length >= count)
+        {
+            trap();
+        }
+    }
+
+    u32 index = thread->buffer.uses.length;
+    auto* node_indices = vb_add(&thread->buffer.uses, count);
+    return (UseReference)
+    {
+        .pointer = node_indices,
+        .index = index,
+    };
+}
+
+fn void node_ensure_capacity(Thread* thread, u32* offset, u16* capacity, u16 current_length, u16 additional)
+{
+    auto current_offset = *offset;
+    auto current_capacity = *capacity;
+    auto desired_capacity = current_length + additional;
+
+    if (desired_capacity > current_capacity)
+    {
+        auto* ptr = vb_add(&thread->buffer.uses, desired_capacity);
+        u32 new_offset = ptr - thread->buffer.uses.pointer;
+        memcpy(ptr, &thread->buffer.uses.pointer[current_offset], current_length * sizeof(NodeIndex));
+        memset(ptr + current_length, 0, (desired_capacity - current_length) * sizeof(NodeIndex));
+        *offset = new_offset;
+        *capacity = desired_capacity;
+    }
+}
+
+fn void node_add_one_assume_capacity(Thread* thread, NodeIndex node, u32 offset, u16 capacity, u16* length)
+{
+    auto index = *length;
+    assert(index < capacity);
+    thread->buffer.uses.pointer[offset + index] = node;
+    *length = index + 1;
+}
+
+fn void node_add_one(Thread* thread, u32* offset, u16* capacity, u16* count, NodeIndex node_index)
+{
+    node_ensure_capacity(thread, offset, capacity, *count, 1);
+    node_add_one_assume_capacity(thread, node_index, *offset, *capacity, count);
+}
+
+fn NodeIndex node_add_output(Thread* thread, NodeIndex node_index, NodeIndex output_index)
+{
+    auto* this_node = thread_node_get(thread, node_index);
+    node_add_one(thread, &this_node->output_offset, &this_node->output_capacity, &this_node->output_count, output_index);
+
+    return node_index;
+}
+
+fn void node_unlock(Thread* thread, Node* node)
+{
+    if (node->hash)
+    {
+        trap();
+    }
+}
+
+fn s32 node_find(Slice(NodeIndex) nodes, NodeIndex node_index)
+{
+    s32 result = -1;
+    for (u32 i = 0; i < nodes.length; i += 1)
+    {
+        if (index_equal(nodes.pointer[i], node_index))
+        {
+            result = i;
+            break;
+        }
+    }
+    return result;
+}
+
+fn void thread_node_remove_use(Thread* thread, u32 offset, u16* length, u16 index)
+{
+    auto current_length = *length;
+    assert(index < current_length);
+    auto item_to_remove = &thread->buffer.uses.pointer[offset + index];
+    auto substitute = &thread->buffer.uses.pointer[offset + current_length - 1];
+    *item_to_remove = *substitute;
+    *length = current_length - 1;
+}
+
+fn Slice(NodeIndex) node_get_inputs(Thread* thread, Node* node)
+{
+    auto result = (Slice(NodeIndex)) {
+        .pointer = &thread->buffer.uses.pointer[node->input_offset],
+        .length = node->input_count,
+    };
+    return result;
+}
+
+fn Slice(NodeIndex) node_get_outputs(Thread* thread, Node* node)
+{
+    auto result = (Slice(NodeIndex)) {
+        .pointer = &thread->buffer.uses.pointer[node->output_offset],
+        .length = node->output_count,
+    };
+    return result;
+}
+
+fn u8 node_remove_output(Thread* thread, NodeIndex node_index, NodeIndex use_index)
+{
+    auto* node = thread_node_get(thread, node_index);
+    auto outputs = node_get_outputs(thread, node);
+    auto index = node_find(outputs, use_index);
+    assert(index != -1);
+    thread_node_remove_use(thread, node->output_offset, &node->output_count, index);
+    return node->output_count == 0;
+}
+
+fn void move_dependencies_to_worklist(Thread* thread, Node* node)
+{
+    assert(node->dependency_count == 0);
+    for (u32 i = 0; i < node->dependency_count; i += 1)
+    {
+        trap();
+    }
+}
+
+fn NodeIndex node_set_input(Thread* thread, NodeIndex node_index, u16 index, NodeIndex new_input)
+{
+    auto* node = thread_node_get(thread, node_index);
+    assert(index < node->input_count);
+    node_unlock(thread, node);
+    auto old_input = node_input_get(thread, node, index);
+
+    if (!index_equal(old_input, new_input))
+    {
+        if (validi(new_input))
+        {
+            node_add_output(thread, new_input, node_index);
+        }
+
+        thread_node_set_use(thread, node->input_offset, index, new_input);
+
+        if (validi(old_input))
+        {
+            if (node_remove_output(thread, old_input, node_index))
+            {
+                trap();
+            }
+        }
+
+        move_dependencies_to_worklist(thread, node);
+    }
+
+    return new_input;
+}
+
+fn NodeIndex builder_set_control(Thread* thread, FunctionBuilder* builder, NodeIndex node_index)
+{
+    return node_set_input(thread, builder->scope, 0, node_index);
+}
+
+fn NodeIndex node_add_input(Thread* thread, NodeIndex node_index, NodeIndex input_index)
+{
+    Node* this_node = thread_node_get(thread, node_index);
+    node_unlock(thread, this_node);
+    node_add_one(thread, &this_node->input_offset, &this_node->input_capacity, &this_node->input_count, input_index);
+    if (validi(input_index))
+    {
+        node_add_output(thread, input_index, node_index);
+    }
+
+    return input_index;
+}
+
+fn NodeIndex builder_add_return(Thread* thread, FunctionBuilder* builder, NodeIndex node_index)
+{
+    return node_add_input(thread, builder->function->stop, node_index);
+}
+
+struct NodeCreate
+{
+    NodeId id;
+    Slice(NodeIndex) inputs;
+};
+typedef struct NodeCreate NodeCreate;
+
+fn NodeIndex thread_node_add(Thread* thread, NodeCreate data)
+{
+    auto input_result = thread_get_node_reference_array(thread, data.inputs.length);
+    memcpy(input_result.pointer, data.inputs.pointer, sizeof(Node*) * data.inputs.length);
+
+    auto* node = vb_add(&thread->buffer.nodes, 1);
+    auto node_index = Index(Node, node - thread->buffer.nodes.pointer);
+    memset(node, 0, sizeof(Node));
+    node->id = data.id;
+    node->input_offset = input_result.index;
+    node->input_count = data.inputs.length;
+    node->type = invalidi(Type);
+
+    for (u32 i = 0; i < data.inputs.length; i += 1)
+    {
+        NodeIndex input = data.inputs.pointer[i];
+        if (validi(input))
+        {
+            node_add_output(thread, input, node_index);
+        }
+    }
+
+    return node_index;
+}
+
+fn void scope_push(Thread* thread, FunctionBuilder* builder)
+{
+    auto* scope = thread_node_get(thread, builder->scope);
+    auto current_length = scope->scope.stack.length;
+    auto desired_length = current_length + 1;
+    auto current_capacity = scope->scope.stack.capacity;
+
+    if (current_capacity < desired_length)
+    {
+        auto optimal_capacity = MAX(round_up_to_next_power_of_2(desired_length), 8);
+        auto* new_pointer = arena_allocate(thread->arena, ScopePair, optimal_capacity);
+        memcpy(new_pointer, scope->scope.stack.pointer, current_length * sizeof(ScopePair));
+        scope->scope.stack.capacity = optimal_capacity;
+        scope->scope.stack.pointer = new_pointer;
+    }
+
+    memset(&scope->scope.stack.pointer[current_length], 0, sizeof(ScopePair));
+    scope->scope.stack.length = current_length + 1;
+}
+
+fn void node_pop_inputs(Thread* thread, NodeIndex node_index, u16 input_count)
+{
+    auto* node = thread_node_get(thread, node_index);
+    node_unlock(thread, node);
+    auto inputs = node_get_inputs(thread, node);
+    for (u16 i = 0; i < input_count; i += 1)
+    {
+        auto old_input = inputs.pointer[node->input_count - 1];
+        node->input_count -= 1;
+        if (validi(old_input))
+        {
+            if (node_remove_output(thread, old_input, node_index))
+            {
+                trap();
+            }
+        }
+    }
+}
+
+fn void scope_pop(Thread* thread, FunctionBuilder* builder)
+{
+    auto scope_index = builder->scope;
+    auto* scope = thread_node_get(thread, scope_index);
+    auto index = scope->scope.stack.length - 1;
+    auto popped_scope = scope->scope.stack.pointer[index];
+    scope->scope.stack.length = index;
+    auto input_count = popped_scope.values.length;
+    node_pop_inputs(thread, scope_index, input_count);
+}
+
+fn ScopePair* scope_get_last(Node* node)
+{
+    assert(node->id == NODE_SCOPE);
+    return &node->scope.stack.pointer[node->scope.stack.length - 1];
+}
+
+fn NodeIndex scope_define(Thread* thread, FunctionBuilder* builder, String name, TypeIndex type_index, NodeIndex node_index)
+{
+    auto scope_node_index = builder->scope;
+    auto* scope_node = thread_node_get(thread, scope_node_index);
+    auto* last = scope_get_last(scope_node);
+    string_map_put(&last->types, thread->arena, name, geti(type_index));
+
+    auto existing = string_map_put(&last->values, thread->arena, name, scope_node->input_count).existing;
+    NodeIndex result;
+
+    if (existing)
+    {
+        result = invalidi(Node);
+    }
+    else
+    {
+        result = node_add_input(thread, scope_node_index, node_index);
+    }
+
+    return result;
+}
+
+fn NodeIndex node_keep(Thread* thread, NodeIndex node_index)
+{
+    return node_add_output(thread, node_index, invalidi(Node));
+}
+
+fn u8 type_equal(Thread* thread, Type* a, Type* b)
+{
+    u8 result = 0;
+    if (a == b)
+    {
+        result = 1;
+    }
+    else
+    {
+        assert(a->hash);
+        assert(b->hash);
+        if ((a->hash == b->hash) & (a->id == b->id))
+        {
+            switch (a->id)
+            {
+                case TYPE_INTEGER:
+                    {
+                        result = 
+                            (a->integer.constant == b->integer.constant) &
+                            (a->integer.bit_count == b->integer.bit_count) &
+                            (a->integer.is_signed == b->integer.is_signed) &
+                            (a->integer.is_constant == b->integer.is_constant);
+                    } break;
+                case TYPE_TUPLE:
+                    {
+                        result = a->tuple.types.length == b->tuple.types.length;
+
+                        if (result)
+                        {
+                            for (u32 i = 0; i < a->tuple.types.length; i += 1)
+                            {
+                                if (!index_equal(a->tuple.types.pointer[i], b->tuple.types.pointer[i]))
+                                {
+                                }
+                            }
+                        }
+                    } break;
+                default:
+                    trap();
+            }
+        }
+    }
+
+    return result;
+}
+
+typedef NodeIndex NodeIdealize(Thread* thread, NodeIndex node_index);
+typedef TypeIndex NodeComputeType(Thread* thread, NodeIndex node_index);
+typedef Hash TypeGetHash(Thread* thread, Type* type);
+typedef Hash NodeGetHash(Thread* thread, Node* node);
+
+struct NodeVirtualTable
+{
+    NodeComputeType* const compute_type;
+    NodeIdealize* const idealize;
+    NodeGetHash* const get_hash;
+};
+typedef struct NodeVirtualTable NodeVirtualTable;
+
+struct TypeVirtualTable
+{
+    TypeGetHash* const get_hash;
+};
+typedef struct TypeVirtualTable TypeVirtualTable;
+fn Hash hash_type(Thread* thread, Type* type);
+
+fn NodeIndex idealize_null(Thread* thread, NodeIndex node_index)
+{
+    return invalidi(Node);
+}
+
+fn TypeIndex compute_type_constant(Thread* thread, NodeIndex node_index)
+{
+    auto* node = thread_node_get(thread, node_index);
+    assert(node->id == NODE_CONSTANT);
+    return node->constant.type;
+}
+
+fn Hash type_get_hash_default(Thread* thread, Type* type)
+{
+    Hash hash = fnv_offset;
+
+    for (auto* it = (u8*)type; it < (u8*)(type + 1); it += 1)
+    {
+        hash = hash_byte(hash, *it);
+    }
+
+    return hash;
+}
+
+fn Hash type_get_hash_tuple(Thread* thread, Type* type)
+{
+    Hash hash = fnv_offset;
+    for (u64 i = 0; i < type->tuple.types.length; i += 1)
+    {
+        auto* tuple_type = thread_type_get(thread,type->tuple.types.pointer[i]); 
+        auto type_hash = hash_type(thread, tuple_type);
+        for (u8* it = (u8*)&type_hash; it < (u8*)(&type_hash + 1); it += 1)
+        {
+            hash = hash_byte(hash, *it);
+        }
+    }
+
+    return hash;
+}
+
+fn Hash node_get_hash_constant(Thread* thread, Node* node)
+{
+    trap();
+}
+
+fn u8 is_projection(Node* n)
+{
+    return (n->id == NODE_CONTROL_PROJECTION) | (n->id == NODE_PROJECTION);
+}
+
+fn NodeIndex projection_get_control(Thread* thread, Node* node)
+{
+    assert(is_projection(node));
+    auto node_index = node_input_get(thread, node, 0);
+    return node_index;
+}
+
+fn s32 projection_get_index(Thread* thread, Node* node)
+{
+    assert(is_projection(node));
+
+    switch (node->id)
+    {
+        case NODE_CONTROL_PROJECTION:
+            return node->control_projection.projection.index;
+        default:
+            trap();
+    }
+}
+
+fn TypeIndex compute_type_projection(Thread* thread, NodeIndex node_index)
+{
+    auto* node = thread_node_get(thread, node_index);
+    assert(is_projection(node));
+    auto control_node_index = projection_get_control(thread, node);
+    auto* control_node = thread_node_get(thread, control_node_index);
+    auto* control_type = thread_type_get(thread, control_node->type);
+
+    if (control_type->id == TYPE_TUPLE)
+    {
+        auto index = projection_get_index(thread, node);
+        auto type_index = control_type->tuple.types.pointer[index];
+        return type_index;
+    }
+    else
+    {
+        return thread->types.bottom;
+    }
+}
+
+fn NodeIndex idealize_control_projection(Thread* thread, NodeIndex node_index)
+{
+    auto* node = thread_node_get(thread, node_index);
+    assert(node->id == NODE_CONTROL_PROJECTION);
+    auto control_node_index = projection_get_control(thread, node);
+    auto* control_node = thread_node_get(thread, control_node_index);
+    auto* control_type = thread_type_get(thread, control_node->type);
+    auto index = node->control_projection.projection.index;
+
+    if (control_type->id == TYPE_TUPLE)
+    {
+        if (index_equal(control_type->tuple.types.pointer[index], thread->types.dead_control))
+        {
+            trap();
+        }
+        if (control_node->id == NODE_IF)
+        {
+            trap();
+        }
+    }
+
+    if (control_node->id == NODE_IF)
+    {
+        trap();
+    }
+
+    return invalidi(Node);
+}
+
+fn NodeIndex return_get_control(Thread* thread, Node* node)
+{
+    return node_input_get(thread, node, 0);
+}
+
+fn NodeIndex return_get_value(Thread* thread, Node* node)
+{
+    return node_input_get(thread, node, 1);
+}
+
+fn void intern_pool_ensure_capacity(InternPool* pool, Arena* arena, u32 additional)
+{
+    auto current_capacity = pool->capacity;
+    auto half_capacity = current_capacity >> 1;
+    auto destination_length = pool->length + additional;
+
+    if (destination_length > half_capacity)
+    {
+        u32 new_capacity = MAX(round_up_to_next_power_of_2(destination_length), 32);
+        u32* new_array = arena_allocate(arena, u32, new_capacity);
+        memset(new_array, 0, sizeof(u32) * new_capacity);
+
+        auto* old_pointer = pool->pointer;
+        auto old_capacity = pool->capacity;
+
+        pool->length = 0;
+        pool->pointer = new_array;
+        pool->capacity = new_capacity;
+
+        for (u32 i = 0; i < old_capacity; i += 1)
+        {
+           auto key = old_pointer[i];
+           if (key)
+           {
+               trap();
+           }
+        }
+
+        for (u32 i = 0; i < old_capacity; i += 1)
+        {
+            trap();
+        }
+    }
+}
+
+fn TypeIndex intern_pool_put_new_type_at_assume_not_existent_assume_capacity(Thread* thread, Type* type, u32 index)
+{
+    auto* result = vb_add(&thread->buffer.types, 1);
+    auto buffer_index = result - thread->buffer.types.pointer;
+    auto type_index = Index(Type, buffer_index);
+    *result = *type;
+
+    thread->interned.types.pointer[index] = *(u32*)&type_index;
+    thread->interned.types.length += 1;
+
+    return type_index;
+}
+
+fn TypeIndex intern_pool_put_new_type_assume_not_existent_assume_capacity(Thread* thread, Hash hash, Type* type)
+{
+    assert(thread->interned.types.length < thread->interned.types.capacity);
+    auto index = hash & (thread->interned.types.capacity - 1);
+
+    return intern_pool_put_new_type_at_assume_not_existent_assume_capacity(thread, type, index);
+}
+
+fn TypeIndex intern_pool_put_new_type_assume_not_existent(Thread* thread, Hash hash, Type* type)
+{
+    intern_pool_ensure_capacity(&thread->interned.types, thread->arena, 1);
+    return intern_pool_put_new_type_assume_not_existent_assume_capacity(thread, hash, type);
+}
+
+struct TypeGetOrPut
+{
+    TypeIndex index;
+    u8 existing;
+};
+typedef struct TypeGetOrPut TypeGetOrPut;
+
+fn s32 intern_pool_find_type_slot(Thread* thread, u32 original_index, Type* type)
+{
+    auto it_index = original_index;
+    auto existing_capacity = thread->interned.types.capacity;
+    s32 result = -1;
+
+    for (u32 i = 0; i < existing_capacity; i += 1)
+    {
+        auto index = it_index & (existing_capacity - 1);
+        u32 key = thread->interned.types.pointer[index];
+
+        // Not set
+        if (key == 0)
+        {
+            result = index;
+            break;
+        }
+        else
+        {
+            TypeIndex existing_type_index = *(TypeIndex*)&key;
+            Type* existing_type = thread_type_get(thread, existing_type_index);
+            if (type_equal(thread, existing_type, type))
+            {
+                result = index;
+                break;
+            }
+        }
+
+        it_index += 1;
+    }
+
+    return result;
+}
+
+fn TypeGetOrPut intern_pool_get_or_put_new_type(Thread* thread, Type* type)
+{
+    auto existing_capacity = thread->interned.types.capacity;
+    auto hash = hash_type(thread, type);
+    auto original_index = hash & (existing_capacity - 1);
+    
+    auto slot = intern_pool_find_type_slot(thread, original_index, type);
+    if (slot != -1)
+    {
+        u32 index = slot;
+        TypeIndex type_index = *(TypeIndex*)&thread->interned.types.pointer[index];
+        u8 existing = validi(type_index);
+        if (!existing)
+        {
+            type_index = intern_pool_put_new_type_at_assume_not_existent_assume_capacity(thread, type, index);
+        }
+
+        return (TypeGetOrPut) {
+            .index = type_index,
+            .existing = existing,
+        };
+    }
+    else
+    {
+        if (thread->interned.types.length < existing_capacity)
+        {
+            trap();
+        }
+        else if (thread->interned.types.length == existing_capacity)
+        {
+            auto result = intern_pool_put_new_type_assume_not_existent(thread, hash, type);
+            return (TypeGetOrPut) {
+                .index = result,
+                .existing = 0,
+            };
+        }
+        else
+        {
+            trap();
+        }
+    }
+}
+
+
+fn TypeGetOrPut type_make_tuple(Thread* thread, Slice(TypeIndex) types)
+{
+    Type type;
+    memset(&type, 0, sizeof(Type));
+    type.tuple = (TypeTuple){
+        .types = types,
+    };
+    type.id = TYPE_TUPLE;
+    auto result = intern_pool_get_or_put_new_type(thread, &type);
+    return result;
+}
+
+fn TypeIndex type_make_tuple_allocate(Thread* thread, Slice(TypeIndex) types)
+{
+    auto gop = type_make_tuple(thread, types);
+    // Need to reallocate the type array
+    if (!gop.existing)
+    {
+        auto* type = thread_type_get(thread, gop.index);
+        assert(type->tuple.types.pointer == types.pointer);
+        assert(type->tuple.types.length == types.length);
+        type->tuple.types = arena_allocate_slice(thread->arena, TypeIndex, types.length);
+        memcpy(type->tuple.types.pointer, types.pointer, sizeof(TypeIndex) * types.length);
+    }
+
+    return gop.index;
+}
+
+fn TypeIndex compute_type_return(Thread* thread, NodeIndex node_index)
+{
+    Node* node = thread_node_get(thread, node_index);
+    auto control_type = thread_node_get(thread, return_get_control(thread, node))->type;
+    auto return_type = thread_node_get(thread, return_get_value(thread, node))->type;
+    Slice(TypeIndex) types = array_to_slice(((TypeIndex[]) {
+        control_type,
+        return_type,
+    }));
+    auto result = type_make_tuple_allocate(thread, types);
+    return result;
+}
+
+fn NodeIndex idealize_return(Thread* thread, NodeIndex node_index)
+{
+    auto* node = thread_node_get(thread, node_index);
+    auto control_node_index = return_get_control(thread, node);
+    auto* control_node = thread_node_get(thread, control_node_index);
+    if (index_equal(control_node->type, thread->types.dead_control))
+    {
+        return control_node_index;
+    }
+    else
+    {
+        return invalidi(Node);
+    }
+
+}
+
+fn TypeIndex compute_type_dead_control(Thread* thread, NodeIndex node_index)
+{
+    unused(node_index);
+    return thread->types.dead_control;
+}
+
+fn TypeIndex compute_type_bottom(Thread* thread, NodeIndex node_index)
+{
+    unused(node_index);
+    return thread->types.bottom;
+}
+
+fn NodeIndex idealize_stop(Thread* thread, NodeIndex node_index)
+{
+    auto* node = thread_node_get(thread, node_index);
+    auto original_input_count = node->input_count;
+    for (u16 i = 0; i < node->input_count; i += 1)
+    {
+        auto input_node_index = node_input_get(thread, node, i);
+        auto* input_node = thread_node_get(thread, input_node_index);
+        if (index_equal(input_node->type, thread->types.dead_control))
+        {
+            trap();
+        }
+    }
+
+    if (node->input_count != original_input_count)
+    {
+        return node_index;
+    }
+    else
+    {
+        return invalidi(Node);
+    }
+}
+
+global const TypeVirtualTable type_functions[TYPE_COUNT] = {
+    [TYPE_BOT] = { .get_hash = &type_get_hash_default },
+    [TYPE_TOP] = { .get_hash = &type_get_hash_default },
+    [TYPE_LIVE_CONTROL] = { .get_hash = &type_get_hash_default },
+    [TYPE_DEAD_CONTROL] = { .get_hash = &type_get_hash_default },
+    [TYPE_INTEGER] = { .get_hash = &type_get_hash_default },
+    [TYPE_TUPLE] = { .get_hash = &type_get_hash_tuple },
+};
+
+global const NodeVirtualTable node_functions[NODE_COUNT] = {
+    [NODE_STOP] = {
+        .compute_type = &compute_type_bottom,
+        .idealize = &idealize_stop,
+    },
+    [NODE_CONTROL_PROJECTION] = {
+        .compute_type = &compute_type_projection,
+        .idealize = &idealize_control_projection,
+    },
+    [NODE_DEAD_CONTROL] = {
+        .compute_type = &compute_type_dead_control,
+        .idealize = &idealize_null,
+    },
+    [NODE_RETURN] = {
+        .compute_type = &compute_type_return,
+        .idealize = &idealize_return,
+    },
+    [NODE_CONSTANT] = {
+        .compute_type = &compute_type_constant,
+        .idealize = &idealize_null,
+        .get_hash = &node_get_hash_constant,
+    },
+};
+
+fn Hash hash_type(Thread* thread, Type* type)
+{
+    Hash hash = type->hash;
+
+    if (!hash)
+    {
+        hash = type_functions[type->id].get_hash(thread, type);
+    }
+
+    assert(hash);
+    type->hash = hash;
+
+    return hash;
+}
+
+fn NodeIndex intern_pool_put_node_at_assume_not_existent_assume_capacity(Thread* thread, NodeIndex node, u32 index)
+{
+    thread->interned.nodes.pointer[index] = geti(node);
+    thread->interned.nodes.length += 1;
+
+    return node;
+}
+
+fn NodeIndex intern_pool_put_node_assume_not_existent_assume_capacity(Thread* thread, Hash hash, NodeIndex node)
+{
+    auto capacity = thread->interned.nodes.capacity;
+    assert(thread->interned.nodes.length < capacity);
+    auto index = hash & (capacity - 1);
+
+    return intern_pool_put_node_at_assume_not_existent_assume_capacity(thread, node, index);
+}
+
+fn NodeIndex intern_pool_put_node_assume_not_existent(Thread* thread, Hash hash, NodeIndex node)
+{
+    intern_pool_ensure_capacity(&thread->interned.nodes, thread->arena, 1);
+    return intern_pool_put_node_assume_not_existent_assume_capacity(thread, hash, node);
+}
+
+struct NodeGetOrPut
+{
+    NodeIndex index;
+    u8 existing;
+};
+typedef struct NodeGetOrPut NodeGetOrPut;
+
+fn s32 intern_pool_find_node_slot(Thread* thread, u32 original_index, NodeIndex node_index)
+{
+    assert(validi(node_index));
+    auto it_index = original_index;
+    auto existing_capacity = thread->interned.nodes.capacity;
+    s32 result = -1;
+    auto* node = thread_node_get(thread, node_index);
+
+    for (u32 i = 0; i < existing_capacity; i += 1)
+    {
+        auto index = it_index & (existing_capacity - 1);
+        u32 key = thread->interned.nodes.pointer[index];
+
+        if ((key == 0) | (key == geti(node_index)))
+        {
+            result = index;
+            break;
+        }
+        else
+        {
+            NodeIndex existing_node_index = *(NodeIndex*)&key;
+            auto* existing_node = &thread->buffer.nodes.pointer[geti(existing_node_index)];
+            if (existing_node->id == node->id)
+            {
+                trap();
+            }
+        }
+
+        it_index += 1;
+    }
+
+    return result;
+}
+
+fn Hash hash_node(Node* node)
+{
+    auto hash = node->hash;
+    if (!hash)
+    {
+        hash = fnv_offset;
+        for (auto* it = (u8*)node; it < (u8*)(node + 1); it += 1)
+        {
+            hash = hash_byte(hash, *it);
+        }
+
+        node->hash = hash;
+    }
+
+    return hash;
+}
+
+fn NodeGetOrPut intern_pool_get_or_put_node(Thread* thread, NodeIndex node_index)
+{
+    auto existing_capacity = thread->interned.nodes.capacity;
+    auto* node = &thread->buffer.nodes.pointer[geti(node_index)];
+    auto hash = hash_node(node);
+    auto original_index = hash & (existing_capacity - 1);
+    
+    auto slot = intern_pool_find_node_slot(thread, original_index, node_index);
+    if (slot != -1)
+    {
+        u32 index = slot;
+        u8 existing = thread->interned.nodes.pointer[index];
+        auto result = intern_pool_put_node_at_assume_not_existent_assume_capacity(thread, node_index, index);
+        return (NodeGetOrPut) {
+            .index = result,
+            .existing = existing,
+        };
+    }
+    else
+    {
+        if (thread->interned.nodes.length < existing_capacity)
+        {
+            trap();
+        }
+        else if (thread->interned.nodes.length == existing_capacity)
+        {
+            auto result = intern_pool_put_node_assume_not_existent(thread, hash, node_index);
+            return (NodeGetOrPut) {
+                .index = result,
+                .existing = 0,
+            };
+        }
+        else
+        {
+            trap();
+        }
+    }
+}
+global String test_files[] = {
+    strlit("tests/first/main.nat"),
+};
+
+struct Parser
+{
+    u64 i;
+    u32 line;
+    u32 column;
+};
+typedef struct Parser Parser;
+
+[[gnu::hot]] fn void skip_space(Parser* parser, String src)
+{
+    u64 original_i = parser->i;
+
+    if (original_i != src.length)
+    {
+        if (is_space(src.pointer[original_i], get_next_ch_safe(src, original_i)))
+        {
+            while (parser->i < src.length)
+            {
+                u64 index = parser->i;
+                u8 ch = src.pointer[index];
+                u64 new_line = ch == '\n';
+                parser->line += new_line;
+
+                if (new_line)
+                {
+                    parser->column = index + 1;
+                }
+
+                if (!is_space(ch, get_next_ch_safe(src, parser->i)))
+                {
+                    break;
+                }
+
+                u32 is_comment = src.pointer[index] == '/';
+                parser->i += is_comment + is_comment;
+                if (is_comment)
+                {
+                    while (parser->i < src.length)
+                    {
+                        if (src.pointer[parser->i] == '\n')
+                        {
+                            break;
+                        }
+
+                        parser->i += 1;
+                    }
+
+                    continue;
+                }
+
+                parser->i += 1;
+            }
+        }
+    }
+}
+
+[[gnu::hot]] fn void expect_character(Parser* parser, String src, u8 expected_ch)
+{
+    u64 index = parser->i;
+    if (expect(index < src.length, 1))
+    {
+        u8 ch = src.pointer[index];
+        u64 matches = ch == expected_ch;
+        expect(matches, 1);
+        parser->i += matches;
+        if (!matches)
+        {
+            print_string(strlit("expected character '"));
+            print_string(ch_to_str(expected_ch));
+            print_string(strlit("', but found '"));
+            print_string(ch_to_str(ch));
+            print_string(strlit("'\n"));
+            fail();
+        }
+    }
+    else
+    {
+        print_string(strlit("expected character '"));
+        print_string(ch_to_str(expected_ch));
+        print_string(strlit("', but found end of file\n"));
+        fail();
+    }
+}
+
+[[gnu::hot]] fn String parse_identifier(Parser* parser, String src)
+{
+    u64 identifier_start_index = parser->i;
+    u64 is_string_literal = src.pointer[identifier_start_index] == '"';
+    parser->i += is_string_literal;
+    u8 identifier_start_ch = src.pointer[parser->i];
+    u64 is_valid_identifier_start = is_identifier_start(identifier_start_ch);
+    parser->i += is_valid_identifier_start;
+
+    if (expect(is_valid_identifier_start, 1))
+    {
+        while (parser->i < src.length)
+        {
+            u8 ch = src.pointer[parser->i];
+            u64 is_identifier = is_identifier_ch(ch);
+            expect(is_identifier, 1);
+            parser->i += is_identifier;
+
+            if (!is_identifier)
+            {
+                if (expect(is_string_literal, 0))
+                {
+                    expect_character(parser, src, '"');
+                }
+
+                String result = s_get_slice(u8, src, identifier_start_index, parser->i - is_string_literal);
+                return result;
+            }
+        }
+
+        fail();
+    }
+    else
+    {
+        fail();
+    }
+}
+
+typedef struct Parser Parser;
+
+#define array_start '['
+#define array_end ']'
+
+#define argument_start '('
+#define argument_end ')'
+
+#define block_start '{'
+#define block_end '}'
+
+#define pointer_sign '*'
+
+fn void thread_add_job(Thread* thread, NodeIndex node_index)
+{
+    trap();
+}
+
+fn void thread_add_jobs(Thread* thread, Slice(NodeIndex) nodes)
+{
+    for (u32 i = 0; i < nodes.length; i += 1)
+    {
+        NodeIndex node_index = nodes.pointer[i];
+        thread_add_job(thread, node_index);
+    }
+}
+
+fn u8 type_is_simple(Type* type)
+{
+    return type->id <= TYPE_DEAD_CONTROL;
+}
+
+fn TypeIndex type_meet(Thread* thread, TypeIndex a, TypeIndex b)
+{
+    TypeIndex result = invalidi(Type);
+    if (index_equal(a, b))
+    {
+        result = a;
+    }
+    else
+    {
+        Type* a_type = thread_type_get(thread, a);
+        Type* b_type = thread_type_get(thread, b);
+        TypeIndex left = invalidi(Type);
+        TypeIndex right = invalidi(Type);
+
+        assert(a_type != b_type);
+        if (a_type->id == b_type->id)
+        {
+            left = a;
+            right = b;
+        }
+        else if (type_is_simple(a_type))
+        {
+            trap();
+        }
+        else if (type_is_simple(b_type))
+        {
+            trap();
+        }
+        else
+        {
+            result = thread->types.bottom;
+        }
+
+        assert(!!validi(left) == !!validi(right));
+        assert((validi(left) & validi(right)) | (validi(result)));
+
+        if (validi(left))
+        {
+            assert(!validi(result));
+            auto* left_type = thread_type_get(thread, left);
+            auto* right_type = thread_type_get(thread, left);
+            switch (left_type->id)
+            {
+                case TYPE_INTEGER:
+                    {
+                        auto integer_bot = thread->types.integer.bottom;
+                        auto integer_top = thread->types.integer.top;
+                        if (index_equal(left, integer_bot))
+                        {
+                            result = a; 
+                        }
+                        else if (index_equal(right, integer_bot))
+                        {
+                            result = b; 
+                        }
+                        else if (index_equal(right, integer_top))
+                        {
+                            result = a; 
+                        }
+                        else if (index_equal(left, integer_top))
+                        {
+                            result = b; 
+                        }
+                        else
+                        {
+                            result = integer_bot;
+                        }
+                    } break;
+                default:
+                    trap();
+            }
+        }
+    }
+
+    assert(validi(result));
+
+    return result;
+}
+
+fn u8 type_is_a(Thread* thread, TypeIndex a, TypeIndex b)
+{
+    auto m = type_meet(thread, a, b);
+    return index_equal(m, b);
+}
+
+
+fn void set_type(Thread* thread, Node* node, TypeIndex new_type)
+{
+    auto old_type = node->type;
+    assert(!validi(old_type) || type_is_a(thread, new_type, old_type));
+    if (!index_equal(old_type, new_type))
+    {
+        node->type = new_type;
+        auto outputs = node_get_outputs(thread, node);
+        thread_add_jobs(thread, outputs);
+        move_dependencies_to_worklist(thread, node);
+    }
+}
+
+fn u8 node_is_unused(Node* node)
+{
+    return node->output_count == 0;
+}
+
+fn u8 node_is_dead(Node* node)
+{
+    return node_is_unused(node) & ((node->input_count == 0) & (!validi(node->type)));
+}
+
+union NodePair
+{
+    struct
+    {
+        NodeIndex old;
+        NodeIndex new;
+    };
+    NodeIndex nodes[2];
+};
+typedef union NodePair NodePair;
+
+fn NodeIndex dead_code_elimination(Thread* thread, NodePair nodes)
+{
+    NodeIndex old = nodes.old;
+    NodeIndex new = nodes.new;
+
+    if (!index_equal(old, new))
+    {
+        auto* old_node = thread_node_get(thread, old);
+        if (node_is_unused(old_node) & !node_is_dead(old_node))
+        {
+            trap();
+        }
+    }
+
+    return new;
+}
+
+fn u8 type_is_high_or_const(Thread* thread, TypeIndex type_index)
+{
+    u8 result = index_equal(type_index, thread->types.top) | index_equal(type_index, thread->types.dead_control);
+    if (!result)
+    {
+        Type* type = thread_type_get(thread, type_index);
+        switch (type->id)
+        {
+            case TYPE_INTEGER:
+                result = type->integer.is_constant | (type->integer.constant == 0);
+                break;
+            default:
+                break;
+        }
+    }
+
+    return result;
+}
+
+global auto enable_peephole = 1;
+
+fn NodeIndex peephole_optimize(Thread* thread, NodeIndex node_index)
+{
+    assert(enable_peephole);
+    auto result = node_index;
+    auto* node = thread_node_get(thread, node_index);
+    auto old_type = node->type;
+    auto new_type = node_functions[node->id].compute_type(thread, node_index);
+
+    if (enable_peephole)
+    {
+        thread->iteration.total += 1;
+        set_type(thread, node, new_type);
+
+        if (node->id != NODE_CONSTANT && node->id != NODE_DEAD_CONTROL && type_is_high_or_const(thread, node->type))
+        {
+            trap();
+        }
+
+        auto idealize = 1;
+        if (!node->hash)
+        {
+            auto gop = intern_pool_get_or_put_node(thread, node_index);
+            if (gop.existing)
+            {
+                idealize = 0;
+                trap();
+            }
+        }
+
+        if (idealize)
+        {
+            auto idealized_node = node_functions[node->id].idealize(thread, node_index);
+            if (validi(idealized_node))
+            {
+                result = idealized_node;
+            }
+            else
+            {
+                u64 are_types_equal = index_equal(new_type, old_type);
+                thread->iteration.nop += are_types_equal;
+                
+                result = are_types_equal ? invalidi(Node) : node_index;
+            }
+        }
+    }
+    else
+    {
+        node->type = new_type;
+    }
+
+    return result;
+}
+
+fn NodeIndex peephole(Thread* thread, NodeIndex node_index)
+{
+    NodeIndex result;
+    if (enable_peephole)
+    {
+        NodeIndex new_node = peephole_optimize(thread, node_index);
+        if (validi(new_node))
+        {
+            NodeIndex peephole_new_node = peephole(thread, new_node);
+            auto dce_node = dead_code_elimination(thread, (NodePair)
+            {
+                .old = node_index,
+                .new = peephole_new_node,
+            });
+
+            result = dce_node;
+        }
+        else
+        {
+            result = node_index;
+        }
+    }
+    else
+    {
+        auto* node = thread_node_get(thread, node_index);
+        auto new_type = node_functions[node->id].compute_type(thread, node_index);
+        node->type = new_type;
+        result = node_index;
+    }
+
+    return result;
+}
+
+fn TypeIndex thread_get_integer_type(Thread* thread, TypeInteger type_integer)
+{
+    Type type;
+    memset(&type, 0, sizeof(Type));
+    type.integer = type_integer;
+    type.id = TYPE_INTEGER;
+
+    auto result = intern_pool_get_or_put_new_type(thread, &type);
+    return result.index;
+}
+
+fn TypeIndex analyze_type(Thread* thread, Parser* parser, String src)
+{
+    u64 start_index = parser->i;
+    u8 start_ch = src.pointer[start_index];
+    u32 is_array_start = start_ch == array_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 (is_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 type_index = thread_get_integer_type(thread, (TypeInteger) {
+                        .constant = 0,
+                        .bit_count = bit_size,
+                        .is_constant = 0,
+                        .is_signed = signedness,
+                    });
+                    return type_index;
+                }
+                else
+                {
+                    fail();
+                }
+            }
+            else if (float_start)
+            {
+                trap();
+            }
+            else
+            {
+                trap();
+            }
+        }
+        else
+        {
+            fail();
+        }
+    }
+
+    trap();
+}
+
+fn NodeIndex constant_int_create(Thread* thread, Function* function, u64 value)
+{
+    auto node_index = thread_node_add(thread, (NodeCreate){
+        .id = NODE_CONSTANT,
+        .inputs = array_to_slice(((NodeIndex []) {
+            function->start,
+        }))
+    });
+    auto* node = thread_node_get(thread, node_index);
+
+    auto type_index = thread_get_integer_type(thread, (TypeInteger){
+        .constant = value,
+        .bit_count = 0,
+        .is_constant = 1,
+        .is_signed = 0,
+    });
+    node->constant = (NodeConstant) {
+        .type = type_index,
+    };
+
+    auto result = peephole(thread, node_index);
+    return result;
+}
+
+fn NodeIndex analyze_primary_expression(Thread* thread, Parser* parser, Function* function, String src)
+{
+    u8 starting_ch = src.pointer[parser->i];
+    u64 is_digit = is_decimal_digit(starting_ch);
+    if (is_digit)
+    {
+        typedef enum IntegerPrefix {
+            INTEGER_PREFIX_HEXADECIMAL,
+            INTEGER_PREFIX_DECIMAL,
+            INTEGER_PREFIX_OCTAL,
+            INTEGER_PREFIX_BINARY,
+        } IntegerPrefix;
+        IntegerPrefix prefix = INTEGER_PREFIX_DECIMAL;
+        u64 value = 0;
+
+        if (starting_ch == '0')
+        {
+            auto follow_up_character = src.pointer[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 & !follow_up_alpha);
+
+            if (is_prefixed_start) {
+                switch (follow_up_character) {
+                    case 'x': prefix = INTEGER_PREFIX_HEXADECIMAL; break;
+                    case 'o': prefix = INTEGER_PREFIX_OCTAL; break;
+                    case 'd': prefix = INTEGER_PREFIX_DECIMAL; break;
+                    case 'b': prefix = INTEGER_PREFIX_BINARY; break;
+                    default: fail();
+                };
+
+                parser->i += 2;
+
+            } else if (!is_valid_after_zero) {
+                fail();
+            }
+        }
+
+        auto start = parser->i;
+
+        switch (prefix) {
+            case INTEGER_PREFIX_HEXADECIMAL:
+                {
+                    // while (is_hex_digit(src[parser->i])) {
+                    //     parser->i += 1;
+                    // }
+
+                    trap();
+                    // auto slice = src.slice(start, parser->i);
+                    // value = parse_hex(slice);
+                } break;
+            case INTEGER_PREFIX_DECIMAL:
+                {
+                    while (is_decimal_digit(src.pointer[parser->i]))
+                    {
+                        parser->i += 1;
+                    }
+
+                    value = parse_decimal(s_get_slice(u8, src, start, parser->i));
+                } break;
+            case INTEGER_PREFIX_OCTAL:
+                trap();
+            case INTEGER_PREFIX_BINARY:
+                trap();
+        }
+
+        auto node_index = constant_int_create(thread, function, value);
+        return node_index;
+    }
+    else
+    {
+        trap();
+    }
+}
+
+fn NodeIndex analyze_expression(Thread* thread, Parser* parser, Function* function, String src, TypeIndex result_type)
+{
+    NodeIndex result = analyze_primary_expression(thread, parser, function, src);
+    return result;
+}
+
+fn void analyze_file(Thread* thread, File* file)
+{
+    Parser p = {};
+    Parser* parser = &p;
+    String src = file->source;
+
+    while (1)
+    {
+        skip_space(parser, src);
+
+        if (parser->i == src.length)
+        {
+            break;
+        }
+
+        // Parse top level declaration
+        u64 start_ch_index = parser->i;
+        u8 start_ch = s_get(src, start_ch_index);
+
+        u64 is_identifier = is_identifier_start(start_ch);
+
+        if (is_identifier)
+        {
+            u8 next_ch = get_next_ch_safe(src, start_ch_index);
+            u64 is_fn = (start_ch == 'f') & (next_ch == 'n');
+
+            if (is_fn)
+            {
+                parser->i += 2;
+
+                FunctionBuilder function_builder = {};
+                FunctionBuilder* builder = &function_builder;
+                builder->file = file;
+
+                skip_space(parser, src);
+
+                String function_name = parse_identifier(parser, src);
+
+                Function* function = vb_add(&thread->buffer.functions, 1);
+                memset(function, 0, sizeof(Function));
+                builder->function = function;
+
+                skip_space(parser, src);
+                
+                // Parse arguments
+                u32 argument_count = 0;
+                expect_character(parser, src, argument_start);
+                // TODO: arguments
+                skip_space(parser, src);
+                expect_character(parser, src, argument_end);
+                skip_space(parser, src);
+
+                {
+                    // Create the start node early since it is needed as a dependency for control and arguments
+                    function->start = thread_node_add(thread, (NodeCreate) {
+                        .id = NODE_START,
+                        .inputs = {},
+                    });
+                    TypeIndex tuple = invalidi(Type);
+                    if (argument_count)
+                    {
+                        Slice(TypeIndex) start_argument_types = arena_allocate_slice(thread->arena, TypeIndex, argument_count + 1);
+                        u32 argument_i = 0;
+                        start_argument_types.pointer[argument_i] = thread->types.live_control;
+                        argument_i += 1;
+
+                        for (u32 i = 0; i < argument_count; i += 1)
+                        {
+                            trap();
+                        }
+                    }
+                    else
+                    {
+                        auto start_argument_types = (Slice(TypeIndex)) {
+                            .pointer = &thread->types.live_control,
+                                .length = 1,
+                        };
+                        tuple = type_make_tuple(thread, start_argument_types).index;
+                    }
+
+                    {
+                        auto* start_node = thread_node_get(thread, function->start);
+                        assert(validi(tuple));
+                        start_node->type = tuple;
+                        start_node->start.arguments = tuple;
+                    }
+                }
+
+                // Create stop node
+                {
+                    function->stop = thread_node_add(thread, (NodeCreate) {
+                        .id = NODE_STOP,
+                        .inputs = {},
+                    });
+                }
+
+                auto dead_control = thread_node_add(thread, (NodeCreate) {
+                            .id = NODE_DEAD_CONTROL,
+                            .inputs = { .pointer = &function->start, .length = 1 },
+                        });
+                dead_control = peephole(thread, dead_control);
+
+                node_keep(thread, dead_control);
+
+                // Create the function scope node
+                {
+                    auto scope_node_index = thread_node_add(thread, (NodeCreate)
+                            {
+                                .id = NODE_SCOPE,
+                                .inputs = {},
+                            });
+                    auto* scope_node = thread_node_get(thread, scope_node_index);
+                    scope_node->type = thread->types.bottom;
+                    builder->scope = scope_node_index;
+
+                    scope_push(thread, builder);
+                    auto control_node_index = thread_node_add(thread, (NodeCreate){
+                        .id = NODE_CONTROL_PROJECTION,
+                        .inputs = {
+                            .pointer = &function->start,
+                            .length = 1,
+                        },
+                    });
+                    auto* control_node = thread_node_get(thread, control_node_index);
+                    auto control_name = strlit("$control");
+                    control_node->control_projection.projection = (NodeProjection) {
+                        .label = control_name,
+                        .index = 0,
+                    };
+                    control_node_index = peephole(thread, control_node_index);
+                    scope_define(thread, builder, control_name, thread->types.live_control, control_node_index);
+                }
+
+                TypeIndex return_type = analyze_type(thread, parser, src);
+
+                skip_space(parser, src);
+                expect_character(parser, src, block_start);
+
+                while (1)
+                {
+                    skip_space(parser, src);
+
+                    if (s_get(src, parser->i) == block_end)
+                    {
+                        break;
+                    }
+
+                    u64 statement_start_index = parser->i;
+                    u8 statement_start_ch = src.pointer[parser->i];
+                    if (is_identifier_start(statement_start_ch))
+                    {
+                        String statement_start_identifier = parse_identifier(parser, src); 
+                        if (s_equal(statement_start_identifier, (strlit("return"))))
+                        {
+                            skip_space(parser, src);
+                            NodeIndex return_value = analyze_expression(thread, parser, function, src, return_type);
+                            skip_space(parser, src);
+                            expect_character(parser, src, ';');
+
+                            auto return_node_index = thread_node_add(thread, (NodeCreate) {
+                                .id = NODE_RETURN,
+                                .inputs = array_to_slice(((NodeIndex[]) {
+                                    builder_get_control_node_index(thread, builder),
+                                    return_value,
+                                })),
+                            });
+
+                            if (validi(builder->scope))
+                            {
+                                // TODO: make this happen
+                                // trap();
+                            }
+
+                            return_node_index = peephole(thread, return_node_index);
+
+                            builder_add_return(thread, builder, return_node_index);
+
+                            builder_set_control(thread, builder, dead_control);
+                        }
+                        else
+                        {
+                            trap();
+                        }
+
+                    }
+                    else
+                    {
+                        trap();
+                    }
+                }
+
+                parser->i += 1;
+
+                scope_pop(thread, builder);
+                function->stop = peephole(thread, function->stop);
+            }
+            else
+            {
+                trap();
+            }
+        }
+        else
+        {
+            trap();
+        }
+    }
+}
+
+fn void thread_init(Thread* thread)
+{
+    *thread = (Thread) {
+        .arena = arena_init_default(KB(64)),
+    };
+    Type top, bot, live_control, dead_control;
+    memset(&top, 0, sizeof(Type));
+    top.id = TYPE_TOP;
+    memset(&bot, 0, sizeof(Type));
+    bot.id = TYPE_BOT;
+    memset(&live_control, 0, sizeof(Type));
+    live_control.id = TYPE_LIVE_CONTROL;
+    memset(&dead_control, 0, sizeof(Type));
+    dead_control.id = TYPE_DEAD_CONTROL;
+
+    thread->types.top = intern_pool_get_or_put_new_type(thread, &top).index;
+    thread->types.bottom = intern_pool_get_or_put_new_type(thread, &bot).index;
+    thread->types.live_control = intern_pool_get_or_put_new_type(thread, &live_control).index;
+    thread->types.dead_control = intern_pool_get_or_put_new_type(thread, &dead_control).index;
+
+    thread->types.integer.top = thread_get_integer_type(thread, (TypeInteger) {
+        .constant = 0,
+        .is_constant = 0,
+        .is_signed = 0,
+        .bit_count = 0,
+    });
+    thread->types.integer.bottom = thread_get_integer_type(thread, (TypeInteger) {
+        .constant = 1,
+        .is_constant = 0,
+        .is_signed = 0,
+        .bit_count = 0,
+    });
+    thread->types.integer.zero = thread_get_integer_type(thread, (TypeInteger) {
+        .constant = 0,
+        .is_constant = 1,
+        .is_signed = 0,
+        .bit_count = 0,
+    });
+}
+
+fn void thread_clear(Thread* thread)
+{
+    arena_reset(thread->arena);
+}
+
+//     Arena* arena = Arena::init_default(64*1024);
+//
+//     auto main_c_path = strlit("main.c");
+//     {
+//         auto main_c_content = strlit("int main()\n{\n    return 0;\n}");
+//         int fd = syscall_open("main.c", O_WRONLY | O_CREAT | O_TRUNC, 0644);
+//         assert(fd != -1);
+//         auto result = syscall_write(fd, main_c_content.pointer, main_c_content.length);
+//         assert(result >= 0);
+//         assert((u64)result == main_c_content.length);
+//         syscall_close(fd);
+//     }
+//     system("clang -c main.c -o main.o -Oz -fno-exceptions -fno-asynchronous-unwind-tables -fno-addrsig -fno-stack-protector -fno-ident");
+//     system("objcopy --remove-section .note.GNU-stack main.o main2.o");
+//     String main_o;
+//     {
+//
+//         int fd = syscall_open("main2.o", 0, 0);
+//         assert(fd != -1);
+//         auto file_size = file_get_size(fd);
+//
+//         auto file = arena->allocate_slice<u8>(file_size);
+//         auto read_bytes = syscall_read(fd, file.pointer, file.length);
+//         assert(read_bytes == file_size);
+//         main_o = file;
+//         syscall_close(fd);
+//         auto r1 = syscall_unlink("main.o");
+//         assert(!r1);
+//         auto r2 = syscall_unlink("main2.o");
+//         assert(!r2);
+//         auto r3 = syscall_unlink("main.c");
+//         assert(!r3);
+//     }
+//
+//     PinnedArray<u8> file = {};
+//     auto elf_header = ELF::Header
+//     {
+//         .type = ELF::Type::relocatable,
+//         .machine = ELF::Machine::x86_64,
+//         .version = 1,
+//         .entry_point = 0,
+//         .program_header_offset = 0,
+//         .section_header_offset = 192,
+//         .flags = 0,
+//         .program_header_size = 0,
+//         .program_header_count = 0,
+//         .section_header_size = sizeof(ELF::SectionHeader),
+//         .section_header_count = 5,
+//         .section_header_string_table_index = 4,
+//     };
+//     auto* original_elf_header = (ELF::Header*)&main_o[0];
+//     file.append(struct_to_bytes(elf_header));
+//     // .text
+//     
+//     // Code: 
+//     // main:
+//     //      xor eax, eax
+//     //      ret
+//     file.append_one(0x31);
+//     file.append_one(0xc0);
+//     file.append_one(0xc3);
+//     for (int i = 0; i < 5; i += 1)
+//     {
+//         file.append_one(0);
+//     }
+//
+//     // .symtab
+//
+//     ELF::Symbol null_symbol = {};
+//     file.append(struct_to_bytes(null_symbol));
+//
+//     ELF::Symbol symbol1 = {
+//         .name_offset = 1,
+//         .type = ELF::Symbol::Type::FILE,
+//         .binding = ELF::Symbol::Binding::LOCAL,
+//         .section_index = (u16)ELF::SectionIndex::ABSOLUTE,
+//         .value = 0,
+//         .size = 0,
+//     };
+//     file.append(struct_to_bytes(symbol1));
+//
+//     ELF::Symbol symbol2 = {
+//         .name_offset = 8,
+//         .type = ELF::Symbol::Type::FUNCTION,
+//         .binding = ELF::Symbol::Binding::GLOBAL,
+//         .section_index = 1,
+//         .value = 0,
+//         .size = 3,
+//     };
+//     file.append(struct_to_bytes(symbol2));
+//
+//     // .strtab
+//     // Null string
+//     file.append_one(0);
+//
+//     file.append(strlit("main.c"));
+//     file.append_one(0);
+//
+//     file.append(strlit("main"));
+//     file.append_one(0);
+//
+//     // .shstrtab
+//     // Null string
+//     file.append_one(0);
+//
+//     file.append(strlit(".symtab"));
+//     file.append_one(0);
+//
+//     file.append(strlit(".strtab"));
+//     file.append_one(0);
+//
+//     file.append(strlit(".shstrtab"));
+//     file.append_one(0);
+//
+//     file.append(strlit(".text"));
+//     file.append_one(0);
+//
+//     // Align
+//     file.append_one(0);
+//     file.append_one(0);
+//
+//     assert(file.length == 0xc0);
+//
+//     // Section headers
+//     ELF::SectionHeader null_section = {};
+//     file.append(struct_to_bytes(null_section));
+//
+//     ELF::SectionHeader text_section = {
+//         .name_offset = 27,
+//         .type = ELF::SectionHeader::Type::program,
+//         .flags = {
+//             .alloc = 1,
+//             .executable = 1,
+//         },
+//         .address = 0,
+//         .offset = 64,
+//         .size = 3,
+//         .link = 0,
+//         .info = 0,
+//         .alignment = 4,
+//         .entry_size = 0,
+//     };
+//     file.append(struct_to_bytes(text_section));
+//
+//     ELF::SectionHeader symtab_section = {
+//         .name_offset = 1,
+//         .type = ELF::SectionHeader::Type::symbol_table,
+//         .flags = {},
+//         .address = 0,
+//         .offset = 72,
+//         .size = 72,
+//         .link = 3,
+//         .info = 2,
+//         .alignment = alignof(ELF::Symbol),
+//         .entry_size = sizeof(ELF::Symbol),
+//     };
+//     file.append(struct_to_bytes(symtab_section));
+//
+//     ELF::SectionHeader strtab_section = {
+//         .name_offset = 9,
+//         .type = ELF::SectionHeader::Type::string_table,
+//         .flags = {},
+//         .address = 0,
+//         .offset = 144,
+//         .size = 13,
+//         .link = 0,
+//         .info = 0,
+//         .alignment = 1,
+//         .entry_size = 0,
+//     };
+//     file.append(struct_to_bytes(strtab_section));
+//
+//     ELF::SectionHeader shstrtab_section = {
+//         .name_offset = 17,
+//         .type = ELF::SectionHeader::Type::string_table,
+//         .flags = {},
+//         .address = 0,
+//         .offset = 157,
+//         .size = 33,
+//         .link = 0,
+//         .info = 0,
+//         .alignment = 1,
+//         .entry_size = 0,
+//     };
+//     file.append(struct_to_bytes(shstrtab_section));
+//
+//     auto mine = file.slice();
+//     auto original = main_o.slice(0, file.length);
+//     assert(mine.equal(original));
+//     assert(file.length == main_o.length);
+//
+//     {
+//         int fd = syscall_open("main.o", O_WRONLY | O_CREAT | O_TRUNC, 0644);
+//         assert(fd != -1);
+//         syscall_write(fd, file.pointer, file.length);
+//
+//         syscall_close(fd);
+//     }
+//
+//     system("clang main.o -o main.exe");
+
+
+fn void unit_tests()
+{
+    for (u64 power = 1, log2_i = 0; log2_i < 64; power <<= 1, log2_i += 1)
+    {
+        assert(log2_alignment(power) == log2_i);
+    }
+}
+
+#if LINK_LIBC
+int main()
+#else
+extern "C" void entry_point()
+#endif
+{
+    {
+        unit_tests();
+    }
+    Arena* global_arena = arena_init_default(KB(64));
+    Thread* thread = arena_allocate(global_arena, Thread, 1);
+    thread_init(thread);
+
+    for (u32 i = 0; i < array_length(test_files); i += 1)
+    {
+        File file = {
+            .path = test_files[i],
+            .source = file_read(thread->arena, test_files[i]),
+        };
+        analyze_file(thread, &file);
+        thread_clear(thread);
+    }
+}
diff --git a/bootstrap/main.cpp b/bootstrap/main.cpp
deleted file mode 100644
index a847500..0000000
--- a/bootstrap/main.cpp
+++ /dev/null
@@ -1,6429 +0,0 @@
-#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"));
-}
diff --git a/compile.sh b/compile.sh
index 612dc6b..93da080 100755
--- a/compile.sh
+++ b/compile.sh
@@ -9,15 +9,16 @@ function compile()
 
     mkdir -p $build_dir
 
-    compile_command="clang++ -o $build_dir/$exe_name $debug_info $optimizations -std=gnu++20 -Wall -Wextra -Wpedantic -Wno-nested-anon-types -pedantic -fno-exceptions -fno-stack-protector -ferror-limit=1 -MJ $build_dir/compile_commands.json"
+    compile_command="clang -o $build_dir/$exe_name $debug_info $optimizations -std=gnu2x -Wall -Wextra -Wpedantic -Wno-nested-anon-types -Wno-keyword-macro -Wno-gnu-auto-type -Wno-auto-decl-extensions -pedantic -fno-exceptions -fno-stack-protector -ferror-limit=1 -MJ $build_dir/compile_commands.json"
 
     case "$OSTYPE" in
         darwin*)  compile_command="$compile_command -DDEMAND_LIBC=1";;
-        linux*)   compile_command="$compile_command -ffreestanding -nostdlib -static bootstrap/entry.S -DDEMAND_LIBC=0" ;;
+        # linux*)   compile_command="$compile_command -ffreestanding -nostdlib -static bootstrap/entry.S -DDEMAND_LIBC=0" ;;
+        linux*)   compile_command="$compile_command -DDEMAND_LIBC=1" ;;
         *)        echo "Unknown operating system $OSTYPE: no specific flags were added" ;;
     esac
 
-    compile_command="$compile_command bootstrap/main.cpp"
+    compile_command="$compile_command bootstrap/main.c"
     echo -e "\x1b[36m$compile_command\x1b[0m"
     eval "time $compile_command"
 }
diff --git a/tests/first/main.nat b/tests/first/main.nat
index 425cd5a..57b873e 100644
--- a/tests/first/main.nat
+++ b/tests/first/main.nat
@@ -1,4 +1,4 @@
-fn[cc(.c)] main [export] () s32
+fn main () s32
 {
     return 0;
 }