From 1bbd39b8c15f16dc883e44090a03c10a8be3005e Mon Sep 17 00:00:00 2001 From: David Gonzalez Martin Date: Sun, 21 Jul 2024 09:01:30 +0200 Subject: [PATCH] Ditch C++ in favor of C --- bootstrap/main.c | 3898 +++++++++++++++++++++++++ bootstrap/main.cpp | 6429 ------------------------------------------ compile.sh | 7 +- tests/first/main.nat | 2 +- 4 files changed, 3903 insertions(+), 6433 deletions(-) create mode 100644 bootstrap/main.c delete mode 100644 bootstrap/main.cpp 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 +#include + +#include +#include +#include +#include + +#ifdef STATIC +#define LINK_LIBC 0 +#else +#define LINK_LIBC 1 +#endif + +#if LINK_LIBC +#include +#include +#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{ .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(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(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(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(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(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(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(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(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(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(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 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 -#include - -#include -#include -#include -#include -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 -forceinline fn u8 mem_equal_range(T* a, T* b, u64 count) -{ - return memcmp(a, b, count * sizeof(T)) == 0; -} - -template -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 -forceinline fn T min(T a, T b) -{ - return a < b ? a : b; -} - -template -forceinline fn T max(T a, T b) -{ - return a > b ? a : b; -} - - -using String = Slice; -#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 -struct DynamicList -{ - T* pointer; - u64 count; - DynamicList* next; -}; - -template -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(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(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(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(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(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(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 - method T* allocate_many(u64 count) - { - return (T*)allocate_bytes(sizeof(T) * count, alignof(T)); - } - - template - method T* allocate_one() - { - return allocate_many(1); - } - - template - method Slice allocate_slice(u64 count) - { - return { - .pointer = allocate_many(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 -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 struct PinnedArray; -fn void generic_pinned_array_ensure_capacity(PinnedArray* array, u32 additional_T, u32 size_of_T); -fn u8* generic_pinned_array_add_with_capacity(PinnedArray* array, u32 additional_T, u32 size_of_T); - -global constexpr auto granularity = page_size; -global constexpr auto reserved_size = ((u64)GB(4) - granularity); - -template -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*)(this); - generic_pinned_array_ensure_capacity(generic_array, additional, sizeof(T)); - } - - method forceinline void clear() - { - length = 0; - } - - method forceinline Slice add_with_capacity(u32 additional) - { - auto generic_array = (PinnedArray*)(this); - auto pointer = generic_pinned_array_add_with_capacity(generic_array, additional, sizeof(T)); - return { - .pointer = (T*)pointer, - .length = additional, - }; - } - - method forceinline Slice add(u32 additional) - { - ensure_capacity(additional); - auto slice = add_with_capacity(additional); - return slice; - } - - method forceinline Slice append(Slice 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 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* 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* 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(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* 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 -struct Put -{ - K* key; - V* value; -}; - -template -struct GetOrPut -{ - Put result; - u8 existing; -}; -template -struct PutResult -{ - K* key; - V* value; -}; - -global constexpr auto map_initial_capacity = 16; - -template -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(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(capacity + additional, map_initial_capacity); - auto* new_pairs = arena->allocate_many(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 -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(round_up_to_next_power_of_2(length + additional), 32); - - if (capacity < wanted_capacity) - { - T** new_array = arena->allocate_many(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 -using Array = PinnedArray; - -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(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 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 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 nodes = {}; - Bitset bitset = {}; - - method void add_many(Slice 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 functions = {}; - PinnedArray types = {}; - PinnedArray nodes = {}; - InternPool interned_types = {}; - InternPool 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 files; - // PinnedArray 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 inputs = {}; - Array outputs = {}; - Array dependencies = {}; - Type* type = 0; - u32 uid; - Hash hash = 0; - Id id; - s32 immediate_depth = 0; - - union - { - struct - { - String name; - s32 index; - } projection; - struct - { - Array> 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 get_inputs() - { - return { - .pointer = inputs.pointer, - .length = inputs.length, - }; - } - - method forceinline Slice get_outputs() - { - return { - .pointer = outputs.pointer, - .length = outputs.length, - }; - } - - struct NodeData - { - Slice 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(input_count); - auto rights = thread->arena->allocate_slice(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 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 scope_reverse_names(Arena* arena) - { - assert(id == Node::Id::SCOPE); - Slice names = arena->allocate_slice(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 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 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(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(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(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(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(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 = { - }; - return unit; -} - -// TODO: make it into an array -fn Thread* instance_add_thread(Instance* instance) -{ - auto* thread = instance->arena->allocate_one(); - *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 = { - .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 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(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(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(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 original_argument_types = {}; - Array 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 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(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 abi_argument_types = {}; - Array 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(2); - if_both_types[0] = live_control; - if_both_types[1] = live_control; - - auto if_neither_types = arena->allocate_slice(2); - if_neither_types[0] = dead_control; - if_neither_types[1] = dead_control; - - auto if_true_types = arena->allocate_slice(2); - if_true_types[0] = live_control; - if_true_types[1] = dead_control; - - auto if_false_types = arena->allocate_slice(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; }