#include "lib.h" #define clang_path "/usr/bin/clang" #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) #define InternPool(T) InternPool_ ## T #define GetOrPut(T) T ## GetOrPut #define declare_ip(T) \ struct InternPool(T) \ { \ T ## Index * pointer; \ u32 length;\ u32 capacity;\ }; \ typedef struct InternPool(T) InternPool(T);\ struct GetOrPut(T) \ {\ T ## Index index; \ u8 existing;\ };\ typedef struct GetOrPut(T) GetOrPut(T) struct StringMapValue { String string; u32 value; }; typedef struct StringMapValue StringMapValue; struct StringMap { u32* pointer; u32 length; u32 capacity; }; typedef struct StringMap StringMap; // fn StringMapValue* string_map_values(StringMap* map) // { // assert(map->pointer); // return (StringMapValue*)(map->pointer + map->capacity); // } // fn s32 string_map_find_slot(StringMap* map, u32 original_index, String key) // { // s32 result = -1; // // if (map->pointer) // { // auto it_index = original_index; // auto existing_capacity = map->capacity; // auto* values = string_map_values(map); // // for (u32 i = 0; i < existing_capacity; i += 1) // { // auto index = it_index & (existing_capacity - 1); // u32 existing_key = map->pointer[index]; // // // Not set // if (existing_key == 0) // { // result = cast(s32, u32, index); // break; // } // else // { // auto pair = &values[index]; // if (s_equal(pair->string, key)) // { // result = cast(s32, u32, index); // break; // } // else // { // trap(); // } // } // // it_index += 1; // } // } // // return result; // } struct StringMapPut { u32 value; u8 existing; }; typedef struct StringMapPut StringMapPut; // 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) // { // auto new_capacity = cast(u32, u64, 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) // { // unused(values); // unused(old_values); // 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_get(StringMap* map, String key) // { // u32 value = 0; // auto long_hash = hash_bytes(key); // static_assert(sizeof(long_hash) == sizeof(u64)); // auto hash = hash64_to_hash32(long_hash); // static_assert(sizeof(hash) == sizeof(u32)); // assert(hash); // auto index = hash & (map->capacity - 1); // auto slot = string_map_find_slot(map, index, key); // u8 existing = slot != -1; // if (existing) // { // existing = map->pointer[slot] != 0; // auto* value_pair = &string_map_values(map)[slot]; // value = value_pair->value; // } // // return (StringMapPut) { // .value = value, // .existing = existing, // }; // } // fn StringMapPut string_map_put(StringMap* map, Arena* arena, String key, u32 value) // { // auto long_hash = hash_bytes(key); // static_assert(sizeof(long_hash) == sizeof(u64)); // auto hash = hash64_to_hash32(long_hash); // static_assert(sizeof(hash) == sizeof(u32)); // assert(hash); // auto index = hash & (map->capacity - 1); // auto slot = string_map_find_slot(map, index, key); // if (slot != -1) // { // auto* values = string_map_values(map); // auto* key_pointer = &map->pointer[slot]; // auto old_key_pointer = *key_pointer; // *key_pointer = hash; // values[slot].string = key; // values[slot].value = value; // return (StringMapPut) { // .value = value, // .existing = old_key_pointer != 0, // }; // } // 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(); // } // } // } // fn int file_write(String file_path, String file_data) // { // int file_descriptor = syscall_open(string_to_c(file_path), O_WRONLY | O_CREAT | O_TRUNC, 0644); // assert(file_descriptor != -1); // // auto bytes = syscall_write(file_descriptor, file_data.pointer, file_data.length); // assert(bytes >= 0); // assert((u64)bytes == file_data.length); // // int close_result = syscall_close(file_descriptor); // assert(close_result == 0); // return 0; // } fn int dir_make(const char* path) { return syscall_mkdir(path, 0755); } fn String file_read(Arena* arena, String path) { String result = {}; int file_descriptor = syscall_open(string_to_c(path), 0, 0); assert(file_descriptor != -1); struct stat stat_buffer; int stat_result = syscall_fstat(file_descriptor, &stat_buffer); assert(stat_result == 0); auto file_size = cast(u64, s64, stat_buffer.st_size); result = (String){ .pointer = 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); auto close_result = syscall_close(file_descriptor); assert(close_result == 0); 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 { ELF_SECTION_NULL = 0X00, ELF_SECTION_PROGRAM = 0X01, ELF_SECTION_SYMBOL_TABLE = 0X02, ELF_SECTION_STRING_TABLE = 0X03, ELF_SECTION_RELOCATION_WITH_ADDENDS = 0X04, ELF_SECTION_SYMBOL_HASH_TABLE = 0X05, ELF_SECTION_DYNAMIC = 0X06, ELF_SECTION_NOTE = 0X07, ELF_SECTION_BSS = 0X08, ELF_SECTION_RELOCATION_NO_ADDENDS = 0X09, ELF_SECTION_LIB = 0X0A, // RESERVED ELF_SECTION_DYNAMIC_SYMBOL_TABLE = 0X0B, ELF_SECTION_INIT_ARRAY = 0X0E, ELF_SECTION_FINI_ARRAY = 0X0F, ELF_SECTION_PREINIT_ARRAY = 0X10, ELF_SECTION_GROUP = 0X11, ELF_SECTION_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; u64 reserved:53; }; typedef struct ELFSectionHeaderFlags ELFSectionHeaderFlags; static_assert(sizeof(ELFSectionHeaderFlags) == sizeof(u64)); 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); decl_vb(ELFSectionHeader); 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; decl_vb(ELFSymbol); static_assert(sizeof(ELFSymbol) == 24); // DWARF struct DwarfCompilationUnit { u32 length; u16 version; u8 type; u8 address_size; u32 debug_abbreviation_offset; }; typedef struct DwarfCompilationUnit DwarfCompilationUnit; struct StringReference { u32 offset; u32 length; }; typedef struct NameReference NameReference; typedef struct Thread Thread; typedef enum TypeId : u32 { // Simple types TYPE_BOTTOM = 0, TYPE_TOP, TYPE_LIVE_CONTROL, TYPE_DEAD_CONTROL, // Not simple types TYPE_INTEGER, TYPE_TUPLE, TYPE_COUNT, } TypeId; 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); struct TypeInteger { u64 constant; u8 bit_count; u8 is_constant; u8 is_signed; u8 padding1; u32 padding; }; typedef struct TypeInteger TypeInteger; static_assert(sizeof(TypeInteger) == 16); struct TypeTuple { Slice(TypeIndex) types; }; typedef struct TypeTuple TypeTuple; struct Type { Hash64 hash; union { TypeInteger integer; TypeTuple tuple; }; TypeId id; }; typedef struct Type Type; static_assert(offsetof(Type, hash) == 0); decl_vb(Type); struct DebugTypeIndex { u32 index; }; typedef struct DebugTypeIndex DebugTypeIndex; struct DebugTypeInteger { u8 bit_count:7; u8 signedness:1; }; typedef struct DebugTypeInteger DebugTypeInteger; typedef enum DebugTypeId : u8 { DEBUG_TYPE_VOID = 0, DEBUG_TYPE_INTEGER, } DebugTypeId; struct DebugType { union { DebugTypeInteger integer; }; DebugTypeId id; }; typedef struct DebugType DebugType; decl_vb(DebugType); declare_ip(DebugType); typedef enum BackendTypeId { BACKEND_TYPE_VOID = 0x00, BACKEND_TYPE_INTEGER_8 = 0x01, BACKEND_TYPE_INTEGER_16 = 0x02, BACKEND_TYPE_INTEGER_32 = 0x03, BACKEND_TYPE_INTEGER_64 = 0x03, BACKEND_TYPE_POINTER = 0x04, BACKEND_TYPE_SCALAR_LAST = BACKEND_TYPE_POINTER, BACKEND_TYPE_TUPLE, BACKEND_TYPE_MEMORY, BACKEND_TYPE_CONTROL, BACKEND_TYPE_REGION, } BackendTypeId; struct TypePair { u32 raw; }; typedef struct TypePair TypePair; decl_vb(TypePair); global const TypePair type_pair_invalid; global const u32 debug_mask = 0xffffff; fn TypePair type_pair_make(DebugTypeIndex debug_type, BackendTypeId backend_type) { u32 value = backend_type; value <<= 24; auto debug_raw = *(u32*)&debug_type; assert(debug_raw <= debug_mask); value |= debug_raw; return (TypePair){ .raw = value }; } // fn DebugTypeIndex type_pair_get_debug(TypePair type_pair) // { // return (DebugTypeIndex) { // .index = type_pair.raw & debug_mask, // }; // } fn BackendTypeId type_pair_get_backend(TypePair type_pair) { return type_pair.raw >> 24; } struct NodeIndex { u32 index; }; typedef struct NodeIndex NodeIndex; declare_slice(NodeIndex); decl_vb(NodeIndex); struct Function { String name; NodeIndex root; TypePair return_type; }; typedef struct Function Function; decl_vb(Function); typedef enum NodeId : u8 { IR_ROOT, IR_PROJECTION, IR_RETURN, IR_REGION, IR_PHI, IR_SYMBOL_TABLE, // Binary integer IR_INTEGER_ADD, IR_INTEGER_SUBSTRACT, IR_INTEGER_MULTIPLY, IR_INTEGER_DIVIDE, IR_INTEGER_REMAINDER, IR_INTEGER_SHIFT_LEFT, IR_INTEGER_SHIFT_RIGHT, IR_INTEGER_AND, IR_INTEGER_OR, IR_INTEGER_XOR, IR_INTEGER_COMPARE_EQUAL, IR_INTEGER_COMPARE_NOT_EQUAL, // Unary integer IR_INTEGER_NEGATION, IR_INTEGER_CONSTANT, MACHINE_COPY, MACHINE_MOVE, MACHINE_JUMP, NODE_COUNT, } NodeId; // struct NodeCFG // { // s32 immediate_dominator_tree_depth; // s32 loop_depth; // s32 anti_dependency; // }; // typedef struct NodeCFG NodeCFG; // struct NodeConstant // { // TypeIndex type; // }; // typedef struct NodeConstant NodeConstant; // // struct NodeStart // { // // NodeCFG cfg; // TypeIndex arguments; // Function* function; // }; // typedef struct NodeStart NodeStart; // // // struct NodeStop // // { // // // NodeCFG cfg; // // }; // // typedef struct NodeStop NodeStop; // // 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 NodeControlProjection // { // NodeProjection projection; // // NodeCFG cfg; // }; // typedef struct NodeControlProjection NodeControlProjection; // // struct NodeReturn // { // // NodeCFG cfg; // }; // typedef struct NodeReturn NodeReturn; // // struct NodeDeadControl // { // // NodeCFG cfg; // }; // typedef struct NodeDeadControl NodeDeadControl; struct NodeProjection { u32 index; }; typedef struct NodeProjection NodeProjection; struct NodeRoot { u32 function_index; }; typedef struct NodeRoot NodeRoot; struct NodeRegion { NodeIndex in_mem; }; typedef struct NodeRegion NodeRegion; union NodeIntegerConstant { s64 signed_value; u64 unsigned_value; }; typedef union NodeIntegerConstant NodeIntegerConstant; struct RegisterMaskIndex { u32 index; }; typedef struct RegisterMaskIndex RegisterMaskIndex; declare_slice(RegisterMaskIndex); struct NodeMachineCopy { RegisterMaskIndex use_mask; RegisterMaskIndex def_mask; }; typedef struct NodeMachineCopy NodeMachineCopy; struct Node { u32 input_offset; u32 output_offset; u16 output_count; u16 input_count; u16 input_capacity; u16 output_capacity; TypePair type; NodeId id:8; u32 interned:1; u32 reserved:23; NodeIndex next_free; union { NodeProjection projection; NodeRoot root; NodeRegion region; NodeIntegerConstant integer_constant; NodeMachineCopy machine_copy; }; // union // { // NodeConstant constant; // NodeStart start; // NodeStop stop; // NodeScope scope; // NodeControlProjection control_projection; // NodeProjection projection; // NodeReturn return_node; // NodeDeadControl dead_control; // }; }; typedef struct Node Node; // See above bitset static_assert(sizeof(NodeId) == 1); declare_slice_p(Node); decl_vb(Node); decl_vbp(Node); declare_ip(Node); fn u8 node_is_control_projection(Node* restrict node) { return node->id == IR_PROJECTION && type_pair_get_backend(node->type) == BACKEND_TYPE_TUPLE; } fn u8 node_is_cfg_fork(Node* restrict node) { switch (node->id) { case IR_ROOT: case IR_PROJECTION: case IR_RETURN: case IR_REGION: case IR_PHI: case IR_SYMBOL_TABLE: case IR_INTEGER_ADD: case IR_INTEGER_SUBSTRACT: case IR_INTEGER_MULTIPLY: case IR_INTEGER_DIVIDE: case IR_INTEGER_REMAINDER: case IR_INTEGER_SHIFT_LEFT: case IR_INTEGER_SHIFT_RIGHT: case IR_INTEGER_AND: case IR_INTEGER_OR: case IR_INTEGER_XOR: case IR_INTEGER_COMPARE_EQUAL: case IR_INTEGER_COMPARE_NOT_EQUAL: case IR_INTEGER_NEGATION: case IR_INTEGER_CONSTANT: case MACHINE_COPY: case MACHINE_MOVE: case MACHINE_JUMP: case NODE_COUNT: return 0; } } struct ArrayReference { u32 offset; u32 length; }; typedef struct ArrayReference ArrayReference; decl_vb(ArrayReference); struct File { String path; String source; StringMap values; StringMap types; }; typedef struct File File; struct FunctionBuilder { Function* function; File* file; NodeIndex current; }; typedef struct FunctionBuilder FunctionBuilder; // struct InternPool // { // u32* pointer; // u32 length; // u32 capacity; // }; // typedef struct InternPool InternPool; typedef u64 BitsetElement; decl_vb(BitsetElement); declare_slice(BitsetElement); struct Bitset { VirtualBuffer(BitsetElement) arr; u32 length; }; typedef struct Bitset Bitset; const u64 element_bitsize = sizeof(u64) * 8; fn u8 bitset_get(Bitset* bitset, u64 index) { auto element_index = index / element_bitsize; if (element_index < bitset->arr.length) { auto bit_index = index % element_bitsize; u8 result = (bitset->arr.pointer[element_index] & (1 << bit_index)) != 0; return result; } return 0; } fn void bitset_ensure_length(Bitset* bitset, u64 max) { auto length = (max / element_bitsize) + (max % element_bitsize != 0); auto old_length = bitset->arr.length; if (old_length < length) { auto new_element_count = cast(u32, u64, length - old_length); unused(vb_add(&bitset->arr, new_element_count)); } } fn void bitset_set_value(Bitset* bitset, u64 index, u8 value) { bitset_ensure_length(bitset, index + 1); auto element_index = index / element_bitsize; auto bit_index = index % element_bitsize; auto mask = ~((u64)1 << bit_index); bitset->arr.pointer[element_index] = (bitset->arr.pointer[element_index] & mask) | ((u64)(!!value) << bit_index); if (value) { bitset->length += 1; } else { bitset->length -= 1; } assert(bitset_get(bitset, index) == value); } fn void bitset_clear(Bitset* bitset) { memset(bitset->arr.pointer, 0, bitset->arr.capacity); bitset->arr.length = 0; bitset->length = 0; } struct WorkList { VirtualBuffer(NodeIndex) nodes; Bitset visited; Bitset bitset; u32 mid_assert:1; }; typedef struct WorkList WorkList; enum { REGISTER_CLASS_STACK = 0, }; typedef enum x86_64_RegisterClass : u8 { REGISTER_CLASS_X86_64_GPR = 1, REGISTER_CLASS_X86_64_XMM, REGISTER_CLASS_X86_64_COUNT } x86_64_RegisterClass; const global u8 register_count_per_class[] = { [0] = 0, [REGISTER_CLASS_X86_64_GPR] = 16, [REGISTER_CLASS_X86_64_XMM] = 16, }; static_assert(array_length(register_count_per_class) == REGISTER_CLASS_X86_64_COUNT); typedef enum GPR : u8 { RAX = 0, RCX = 1, RDX = 2, RBX = 3, RSP = 4, RBP = 5, RSI = 6, RDI = 7, R8 = 8 + 0, R9 = 8 + 1, R10 = 8 + 2, R11 = 8 + 3, R12 = 8 + 4, R13 = 8 + 5, R14 = 8 + 6, R15 = 8 + 7, GPR_NONE = -1 } GPR; typedef enum RegisterMask_x86_64: u8 { REGISTER_MASK_EMPTY = 0, REGISTER_MASK_GPR = 1, } RegisterMask_x86_64; const global auto empty_register_mask = Index(RegisterMask, REGISTER_MASK_EMPTY); struct RegisterMask { u32 mask; u32 class:3; u32 may_spill:1; u32 reserved:28; }; typedef struct RegisterMask RegisterMask; decl_vb(RegisterMask); declare_ip(RegisterMask); struct Thread { Arena* arena; struct { VirtualBuffer(Type) types; VirtualBuffer(Node) nodes; VirtualBuffer(DebugType) debug_types; VirtualBuffer(NodeIndex) uses; VirtualBuffer(ArrayReference) use_free_list; VirtualBuffer(Function) functions; VirtualBuffer(u8) string; VirtualBuffer(RegisterMask) register_masks; } buffer; struct { InternPool(Node) nodes; InternPool(DebugType) debug_types; InternPool(RegisterMask) register_masks; } interned; struct { NodeIndex nodes; } free_list; struct { TypeIndex bottom; TypeIndex top; TypeIndex live_control; TypeIndex dead_control; struct { TypeIndex top; TypeIndex bottom; TypeIndex zero; TypeIndex u8; TypeIndex u16; TypeIndex u32; TypeIndex u64; TypeIndex s8; TypeIndex s16; TypeIndex s32; TypeIndex s64; } integer; struct { union { struct { DebugTypeIndex u8; DebugTypeIndex u16; DebugTypeIndex u32; DebugTypeIndex u64; DebugTypeIndex s8; DebugTypeIndex s16; DebugTypeIndex s32; DebugTypeIndex s64; }; DebugTypeIndex array[8]; } integer; } debug; } types; s64 main_function; WorkList worklists[8]; u64 worklist_bitset:3; u64 reserved:61; }; typedef struct Thread Thread; struct WorkListHandle { u8 index:3; u8 is_valid:1; u8 reserved:4; }; typedef struct WorkListHandle WorkListHandle; fn WorkListHandle thread_worklist_acquire(Thread* thread) { u8 bitset = thread->worklist_bitset; if (bitset) { auto index = cast(u8, s32, __builtin_ctz(~thread->worklist_bitset)); thread->worklist_bitset |= (1 << index); return (WorkListHandle) { .index = index, .is_valid = 1, }; } else { thread->worklist_bitset |= (1 << 0); return (WorkListHandle) { .index = 0, .is_valid = 1, }; } } fn u32 thread_worklist_length(Thread* thread, WorkListHandle handle) { assert(handle.is_valid); assert((thread->worklist_bitset & (1 << handle.index)) != 0); return thread->worklists[handle.index].nodes.length; } fn NodeIndex thread_worklist_get(Thread* thread, WorkListHandle handle, u32 index) { assert(handle.is_valid); assert((thread->worklist_bitset & (1 << handle.index)) != 0); auto* worklist = &thread->worklists[handle.index]; assert(index < worklist->nodes.length); return worklist->nodes.pointer[index]; } fn u8 thread_worklist_test(Thread* thread, WorkListHandle handle, NodeIndex node_index) { assert(handle.is_valid); assert((thread->worklist_bitset & (1 << handle.index)) != 0); u8 result = 0; if (validi(node_index)) { WorkList* restrict worklist = &thread->worklists[handle.index]; result = bitset_get(&worklist->bitset, geti(node_index)); } return result; } fn u8 thread_worklist_test_and_set(Thread* thread, WorkListHandle handle, NodeIndex node_index) { auto result = thread_worklist_test(thread, handle, node_index); if (!result) { WorkList* restrict worklist = &thread->worklists[handle.index]; bitset_set_value(&worklist->bitset, geti(node_index), 1); } return result; } 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; } may_be_unused fn String node_id_to_string(NodeId node_id) { switch (node_id) { case_to_name(IR_, ROOT); case_to_name(IR_, PROJECTION); case_to_name(IR_, RETURN); case_to_name(IR_, REGION); case_to_name(IR_, PHI); case_to_name(IR_, SYMBOL_TABLE); case_to_name(IR_, INTEGER_ADD); case_to_name(IR_, INTEGER_SUBSTRACT); case_to_name(IR_, INTEGER_MULTIPLY); case_to_name(IR_, INTEGER_DIVIDE); case_to_name(IR_, INTEGER_REMAINDER); case_to_name(IR_, INTEGER_SHIFT_LEFT); case_to_name(IR_, INTEGER_SHIFT_RIGHT); case_to_name(IR_, INTEGER_AND); case_to_name(IR_, INTEGER_OR); case_to_name(IR_, INTEGER_XOR); case_to_name(IR_, INTEGER_COMPARE_EQUAL); case_to_name(IR_, INTEGER_COMPARE_NOT_EQUAL); case_to_name(IR_, INTEGER_NEGATION); case_to_name(IR_, INTEGER_CONSTANT); case_to_name(MACHINE_, COPY); case_to_name(MACHINE_, MOVE); case_to_name(MACHINE_, JUMP); case NODE_COUNT: unreachable(); break; } } fn void thread_worklist_push(Thread* thread, WorkListHandle handle, NodeIndex node_index) { // print("Pushing node #{u32} ({s})\n", geti(node_index), node_id_to_string(thread_node_get(thread, node_index)->id)); if (!thread_worklist_test_and_set(thread, handle, node_index)) { WorkList* restrict worklist = &thread->worklists[handle.index]; *vb_add(&worklist->nodes, 1) = node_index; } } fn void thread_worklist_push_array(Thread* thread, WorkListHandle handle, NodeIndex node_index) { assert(handle.is_valid); auto* worklist = &thread->worklists[handle.index]; *vb_add(&worklist->nodes, 1) = node_index; } fn NodeIndex thread_worklist_pop_array(Thread* thread, WorkListHandle handle) { assert(handle.is_valid); assert((thread->worklist_bitset & (1 << handle.index)) != 0); auto result = invalidi(Node); assert(handle.is_valid); auto* worklist = &thread->worklists[handle.index]; auto len = worklist->nodes.length; if (len) { auto index = len - 1; result = worklist->nodes.pointer[index]; worklist->nodes.length = index; } return result; } fn NodeIndex thread_worklist_pop(Thread* thread, WorkListHandle handle) { assert(handle.is_valid); assert((thread->worklist_bitset & (1 << handle.index)) != 0); auto result = invalidi(Node); assert(handle.is_valid); auto* worklist = &thread->worklists[handle.index]; auto len = worklist->nodes.length; if (len) { auto index = len - 1; auto node_index = worklist->nodes.pointer[index]; worklist->nodes.length = index; bitset_set_value(&worklist->bitset, index, 0); result = node_index; } return result; } fn void thread_worklist_clear(Thread* thread, WorkListHandle handle) { assert(handle.is_valid); assert((thread->worklist_bitset & (1 << handle.index)) != 0); auto* restrict worklist = &thread->worklists[handle.index]; bitset_clear(&worklist->visited); bitset_clear(&worklist->bitset); worklist->nodes.length = 0; } // fn void thread_worklist_release(Thread* thread, WorkListHandle* handle) // { // thread_worklist_clear(thread, *handle); // handle->is_valid = 0; // } 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 DebugType* thread_debug_type_get(Thread* thread, DebugTypeIndex debug_type_index) { assert(validi(debug_type_index)); auto* type = &thread->buffer.debug_types.pointer[geti(debug_type_index)]; return type; } fn RegisterMask* thread_register_mask_get(Thread* thread, RegisterMaskIndex register_mask_index) { assert(validi(register_mask_index)); auto* register_mask = &thread->buffer.register_masks.pointer[geti(register_mask_index)]; return register_mask; } 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 = cast(u16, u32, current_length + additional); if (desired_capacity > current_capacity) { auto* ptr = vb_add(&thread->buffer.uses, desired_capacity); u32 new_offset = cast(u32, s64, 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 NodeIndex intern_pool_remove_node(Thread* thread, NodeIndex node_index); fn Slice(NodeIndex) node_get_inputs(Thread* thread, const Node * restrict const 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_is_constant(const Node* const restrict node) { switch (node->id) { case IR_INTEGER_CONSTANT: return 1; default: return 0; } } fn Hash32 node_hash(Thread* thread, const Node* restrict const node) { Hash32 hash = 0; hash += node->id; hash += sizeof(u8); auto inputs = node_get_inputs(thread, node); // Constants are allowed to live across functions if (!node_is_constant(node)) { u32 valid_input_count = 0; for (u16 i = 0; i < inputs.length; i += 1) { auto input = inputs.pointer[i]; if (validi(input)) { valid_input_count += 1; hash += geti(input); hash += sizeof(input); } } hash += valid_input_count; hash += sizeof(u16); } auto* union_start = (u8*)&node->projection; auto* union_end = union_start + size_until_end(Node, projection); for (auto* it = union_start; it < union_end; it += 1) { hash += *it; } hash += union_end - union_start; auto result = hash32_fib_end(hash); return result; } fn Hash32 node_hash_index(Thread* thread, NodeIndex node_index) { auto* node = thread_node_get(thread, node_index); auto hash = node_hash(thread, node); return hash; } fn Hash32 register_mask_hash(Thread* thread, const RegisterMask* const restrict mask) { unused(thread); static_assert(sizeof(RegisterMask) == sizeof(u64)); auto hash = *(Hash64*)mask; auto result = hash64_fib_end(hash); return result; } fn Hash32 register_mask_hash_index(Thread* thread, RegisterMaskIndex register_mask_index) { auto* mask = thread_register_mask_get(thread, register_mask_index); auto hash = register_mask_hash(thread, mask); return hash; } fn void node_gvn_remove(Thread* thread, NodeIndex node_index); fn void node_unlock(Thread* thread, NodeIndex node_index) { unused(thread); unused(node_index); // auto* node = thread_node_get(thread, node_index); // if (node->interned) // { // auto r = node_gvn_remove(thread, node_index); // assert(index_equal(r, node_index)); // } } fn s64 node_find(Slice(NodeIndex) nodes, NodeIndex node_index) { s64 result = -1; for (u64 i = 0; i < nodes.length; i += 1) { if (index_equal(nodes.pointer[i], node_index)) { result = cast(s64, u64, 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 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 maybe_index = node_find(outputs, use_index); assert(maybe_index != -1); auto index = cast(u16, s64, maybe_index); 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) // { // unused(thread); // trap(); // } // } // 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))); // } // fn void node_kill(Thread* thread, NodeIndex node_index) // { // node_unlock(thread, node_index); // auto* node = thread_node_get(thread, node_index); // // print("[NODE KILLING] (#{u32}, {s}) START\n", node_index.index, node_id_to_string(node)); // assert(node_is_unused(node)); // todo(); // // node->type = invalidi(TypePair); // // auto inputs = node_get_inputs(thread, node); // while (node->input_count > 0) // { // auto input_index = cast(u16, u32, node->input_count - 1); // node->input_count = input_index; // auto old_input_index = inputs.pointer[input_index]; // // // print("[NODE KILLING] (#{u32}, {s}) Removing input #{u32} at slot {u32}\n", node_index.index, node_id_to_string(node), old_input_index.index, input_index); // if (validi(old_input_index)) // { // thread_worklist_push(thread, old_input_index); // u8 no_more_outputs = node_remove_output(thread, old_input_index, node_index); // if (no_more_outputs) // { // // print("[NODE KILLING] (#{u32}, {s}) (NO MORE OUTPUTS - KILLING) Input #{u32}\n", node_index.index, node_id_to_string(node), old_input_index.index); // node_kill(thread, old_input_index); // } // } // } // // assert(node_is_dead(node)); // // print("[NODE KILLING] (#{u32}, {s}) END\n", node_index.index, node_id_to_string(node)); // } 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_index); auto inputs = node_get_inputs(thread, node); auto old_input = inputs.pointer[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)) { // todo(); // node_kill(thread, old_input); } } // 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_unlock(thread, node_index); Node* this_node = thread_node_get(thread, node_index); 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; } struct NodeCreate { Slice(NodeIndex) inputs; TypePair type_pair; NodeId id; }; typedef struct NodeCreate NodeCreate; fn NodeIndex thread_node_add(Thread* thread, NodeCreate data) { auto input_count = cast(u16, u64, data.inputs.length); auto input_result = thread_get_node_reference_array(thread, input_count); memcpy(input_result.pointer, data.inputs.pointer, sizeof(NodeIndex) * input_count); auto* node = vb_add(&thread->buffer.nodes, 1); auto node_index = Index(Node, cast(u32, s64, node - thread->buffer.nodes.pointer)); memset(node, 0, sizeof(Node)); node->id = data.id; node->input_offset = input_result.index; node->input_count = input_count; node->input_capacity = input_count; node->type = type_pair_invalid; // node->type = invalidi(TypePair); node->type = data.type_pair; // print("[NODE CREATION] #{u32} {s} | INPUTS: { ", node_index.index, node_id_to_string(node)); for (u16 i = 0; i < input_count; i += 1) { NodeIndex input = data.inputs.pointer[i]; // print("{u32} ", input.index); if (validi(input)) { node_add_output(thread, input, node_index); } } // print("}\n"); return node_index; } // fn void node_pop_inputs(Thread* thread, NodeIndex node_index, u16 input_count) // { // node_unlock(thread, node_index); // auto* node = thread_node_get(thread, node_index); // 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_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 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 scope_update_extended(Thread* thread, FunctionBuilder* builder, String name, NodeIndex node_index, s32 nesting_level) // { // NodeIndex result = invalidi(Node); // // if (nesting_level >= 0) // { // auto* scope_node = thread_node_get(thread, builder->scope); // auto* string_map = &scope_node->scope.stack.pointer[nesting_level].values; // auto lookup_result = string_map_get(string_map, name); // if (lookup_result.existing) // { // auto index = lookup_result.value; // auto old_index = node_input_get(thread, scope_node, index); // auto* old_node = thread_node_get(thread, old_index); // // // if (old_node->id == NODE_SCOPE) // // { // // trap(); // // } // // if (validi(node_index)) // { // auto result = node_set_input(thread, builder->scope, index, node_index); // return result; // } // else // { // return old_index; // } // } // else // { // return scope_update_extended(thread, builder, name, node_index, nesting_level - 1); // } // } // // return result; // } // fn NodeIndex scope_lookup(Thread* thread, FunctionBuilder* builder, String name) // { // auto* scope_node = thread_node_get(thread, builder->scope); // return scope_update_extended(thread, builder, name, invalidi(Node), scope_node->scope.stack.length - 1); // } // fn NodeIndex scope_update(Thread* thread, FunctionBuilder* builder, String name, NodeIndex value_node_index) // { // auto* scope_node = thread_node_get(thread, builder->scope); // auto result = scope_update_extended(thread, builder, name, value_node_index, scope_node->scope.stack.length - 1); // return result; // } // fn u8 type_equal(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])) // { // todo(); // } // } // } // } break; // default: // trap(); // } // } // } // // return result; // } fn Hash64 node_get_hash_default(Thread* thread, Node* node, NodeIndex node_index, Hash64 hash) { unused(thread); unused(node); unused(node_index); return hash; } // fn Hash64 node_get_hash_projection(Thread* thread, Node* node, NodeIndex node_index, Hash64 hash) // { // unused(thread); // unused(node_index); // auto projection_index = node->projection.index; // auto proj_index_bytes = struct_to_bytes(projection_index); // for (u32 i = 0; i < proj_index_bytes.length; i += 1) // { // hash = hash_byte(hash, proj_index_bytes.pointer[i]); // } // // return hash; // } // fn Hash64 node_get_hash_control_projection(Thread* thread, Node* node, NodeIndex node_index, Hash64 hash) // { // unused(thread); // unused(node_index); // auto projection_index = node->control_projection.projection.index; // auto proj_index_bytes = struct_to_bytes(projection_index); // for (u32 i = 0; i < proj_index_bytes.length; i += 1) // { // hash = hash_byte(hash, proj_index_bytes.pointer[i]); // } // // return hash; // } // fn Hash64 node_get_hash_constant(Thread* thread, Node* node, NodeIndex node_index, Hash64 hash) // { // unused(node_index); // unused(thread); // unused(node); // assert(hash == fnv_offset); // todo(); // // auto type_index = node->type; // // auto* type = thread_type_get(thread, node->type); // // auto type_hash = hash_type(thread, type); // // print("Hashing node #{u32} (constant) (type: #{u32}) (hash: {u64:x})\n", node_index.index, type_index.index, type_hash); // // return type_hash; // } // fn Hash64 node_get_hash_scope(Thread* thread, Node* node, NodeIndex node_index, Hash64 hash) // { // unused(thread); // unused(node); // unused(node_index); // return hash; // } // fn NodeIndex node_idealize_substract(Thread* thread, NodeIndex node_index) // { // auto* node = thread_node_get(thread, node_index); // auto inputs = node_get_inputs(thread, node); // auto left_node_index = inputs.pointer[1]; // auto right_node_index = inputs.pointer[2]; // auto* left = thread_node_get(thread, left_node_index); // auto* right = thread_node_get(thread, right_node_index); // if (index_equal(left_node_index, right_node_index)) // { // trap(); // } // else if (right->id == IR_INTEGER_NEGATION) // { // trap(); // } // else if (left->id == IR_INTEGER_NEGATION) // { // trap(); // } // else // { // return invalidi(Node); // } // } // fn NodeIndex node_idealize_compare(Thread* thread, NodeIndex node_index) // { // auto* node = thread_node_get(thread, node_index); // auto inputs = node_get_inputs(thread, node); // auto left_node_index = inputs.pointer[1]; // auto right_node_index = inputs.pointer[2]; // auto* left = thread_node_get(thread, left_node_index); // auto* right = thread_node_get(thread, right_node_index); // if (index_equal(left_node_index, right_node_index)) // { // trap(); // } // // if (node->id == IR_INTEGER_COMPARE_EQUAL) // { // if (right->id != IR_CONSTANT) // { // if (left->id == IR_CONSTANT) // { // todo(); // } // else if (left_node_index.index > right_node_index.index) // { // todo(); // } // } // // // TODO: null pointer // if (index_equal(right->type, thread->types.integer.zero)) // { // todo(); // } // } // // // TODO: phi constant // // return invalidi(Node); // } fn Hash32 debug_type_hash(Thread* thread, const DebugType* const restrict type) { unused(thread); auto* start = (const u8*) type; Hash32 hash = 0; for (auto* it = start; it < start + sizeof(*type); it += 1) { hash += *it; } auto result = hash32_fib_end(hash); return result; } fn Hash32 debug_type_hash_index(Thread* thread, DebugTypeIndex type_index) { auto* type = thread_debug_type_get(thread, type_index); return debug_type_hash(thread, type); } global const u64 intern_pool_min_capacity = 64; struct GenericInternPool { u32* pointer; u32 length; u32 capacity; }; typedef struct GenericInternPool GenericInternPool; struct GenericInternPoolBufferResult { void* pointer; u32 index; }; typedef struct GenericInternPoolBufferResult GenericInternPoolBufferResult; struct GenericGetOrPut { u32 index; u8 existing; }; typedef struct GenericGetOrPut GenericGetOrPut; typedef s64 FindSlotCallback(GenericInternPool* pool, Thread* thread, Hash32 hash, u32 raw_item_index, u32 saved_index, u32 slots_ahead); typedef GenericInternPoolBufferResult AddToBufferCallback(Thread* thread); // typedef s64 Find struct InternPoolInterface { FindSlotCallback * const find_slot; AddToBufferCallback* const add_to_buffer; }; typedef struct InternPoolInterface InternPoolInterface; fn s64 ip_find_slot_debug_type(GenericInternPool* generic_pool, Thread* thread, Hash32 hash, u32 raw_item_index, u32 saved_index, u32 slots_ahead) { auto* pool = (InternPool(DebugType)*)generic_pool; assert(pool == &thread->interned.debug_types); auto* ptr = pool->pointer; s64 result = -1; unused(raw_item_index); for (auto index = saved_index; index < saved_index + slots_ahead; index += 1) { auto typed_index = ptr[index]; auto debug_type = thread_debug_type_get(thread, typed_index); auto existing_hash = debug_type_hash(thread, debug_type); if (existing_hash == hash) { todo(); } } return result; } fn s64 ip_generic_find_slot(GenericInternPool* pool, Thread* thread, u32 item_index, Hash32 hash, const InternPoolInterface* restrict const interface) { auto* pointer = pool->pointer; auto existing_capacity = pool->capacity; auto original_index = hash & (existing_capacity - 1); auto it_index = original_index; s64 result = -1; while (1) { auto index = it_index & (existing_capacity - 1); auto* ptr = &pointer[index]; if (!*ptr) { result = index; break; } #if (__AVX2__) #if (__AVX512F__) auto chunk = _mm512_loadu_epi32(ptr); auto is_zero = _mm512_cmpeq_epi32_mask(chunk, _mm512_setzero_epi32()); #elif (__AVX2__) auto chunk = _mm256_loadu_si256(ptr); auto is_zero = _mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpeq_epi32(chunk, _mm256_setzero_si256()))); #endif auto occupied_slots_ahead = cast(u32, s32, __builtin_ctz(is_zero)); #else u32 occupied_slots_ahead = 0; for (u32 fake_i = it_index; fake_i < it_index + existing_capacity; fake_i += 1) { auto i = fake_i & (existing_capacity - 1); auto item = pointer[i]; if (item == 0) { break; } occupied_slots_ahead += 1; } #endif auto cap_ahead = existing_capacity - index; auto slots_ahead = MIN(occupied_slots_ahead, cap_ahead); auto slot = interface->find_slot(pool, thread, hash, item_index, index, slots_ahead); if (slot != -1) { assert(pointer[slot] != 0); result = slot; break; } if (occupied_slots_ahead < cap_ahead) { result = index + occupied_slots_ahead; break; } it_index += slots_ahead; } return result; } fn GenericInternPoolBufferResult ip_DebugType_add_to_buffer(Thread* thread) { auto* result = vb_add(&thread->buffer.debug_types, 1); auto buffer_index = cast(u32, s64, result - thread->buffer.debug_types.pointer); auto type_index = Index(DebugType, buffer_index); static_assert(sizeof(type_index) == sizeof(u32)); return (GenericInternPoolBufferResult) { .pointer = result, .index = *(u32*)&type_index, }; } fn u32 ip_generic_put_new_at_assume_not_existent_assume_capacity(GenericInternPool* pool, Thread* thread, u32 item_index, const void* restrict const item_pointer, u32 item_size, u32 pool_index, const InternPoolInterface* restrict const interface) { if (!item_index) { auto buffer_result = interface->add_to_buffer(thread); assert(buffer_result.index); memcpy(buffer_result.pointer, item_pointer, item_size); item_index = buffer_result.index; } auto* ptr = &pool->pointer[pool_index]; *ptr = item_index; pool->length += 1; return item_index; } fn u32 intern_pool_put_new_assume_not_existent_assume_capacity(GenericInternPool* pool, Thread* thread, u32 item_index, const void* restrict const item_pointer, u32 item_size, Hash32 hash, const InternPoolInterface* restrict const interface) { auto capacity = pool->capacity; assert(pool->length < capacity); assert(hash); auto pool_index = hash & (capacity - 1); auto result = ip_generic_put_new_at_assume_not_existent_assume_capacity(pool, thread, item_index, item_pointer, item_size, pool_index, interface); return result; } fn void ip_generic_ensure_capacity(GenericInternPool* pool, Thread* thread, u32 additional) { auto current_length = pool->length; auto current_capacity = pool->capacity; auto half_capacity = current_capacity >> 1; auto destination_length = current_length + additional; if (destination_length > half_capacity) { auto new_capacity = cast(u32, u64, MAX(round_up_to_next_power_of_2(destination_length), intern_pool_min_capacity)); auto* new_array = arena_allocate(thread->arena, u32, new_capacity); memset(new_array, 0, sizeof(u32) * new_capacity); auto old_capacity = current_capacity; pool->pointer = new_array; pool->length = 0; pool->capacity = new_capacity; if (old_capacity) { todo(); } } } fn u32 ip_generic_put_new_assume_not_existent(GenericInternPool* pool, Thread* thread, u32 item_index, const void* item_pointer, u32 item_size, Hash32 hash, const InternPoolInterface* const restrict interface) { ip_generic_ensure_capacity(pool, thread, 1); auto result = intern_pool_put_new_assume_not_existent_assume_capacity(pool, thread, item_index, item_pointer, item_size, hash, interface); return result; } fn GenericGetOrPut ip_generic_get_or_put(GenericInternPool* pool, Thread* thread, u32 item_index, const void* const restrict item_pointer, u32 item_size, Hash32 hash, const InternPoolInterface* const restrict interface) { assert(hash); auto length = pool->length; auto capacity = pool->capacity; if (capacity) { auto maybe_slot = ip_generic_find_slot(pool, thread, item_index, hash, interface); if (maybe_slot != -1) { auto index = cast(u32, s64, maybe_slot); auto element = pool->pointer[index]; u8 is_valid_or_existing = element != 0; if (!is_valid_or_existing) { element = ip_generic_put_new_at_assume_not_existent_assume_capacity(pool, thread, item_index, item_pointer, item_size, index, interface); assert(element != 0); } return (GenericGetOrPut) { .index = element, .existing = is_valid_or_existing, }; } } if (length < capacity) { todo(); } else if (length == capacity) { auto index = ip_generic_put_new_assume_not_existent(pool, thread, item_index, item_pointer, item_size, hash, interface); return (GenericGetOrPut) { .index = index, .existing = 0, }; } else { unreachable(); } } // This assumes the indices are not equal fn u8 node_equal(Thread* thread, NodeIndex a_index, NodeIndex b_index) { u8 result = 0; auto a_hash = node_hash_index(thread, a_index); auto b_hash = node_hash_index(thread, b_index); auto* a = thread_node_get(thread, a_index); auto* b = thread_node_get(thread, b_index); assert(!index_equal(a_index, b_index)); assert(a != b); if (((a->id == b->id) & (a_hash == b_hash)) & (a->input_count == b->input_count)) { auto inputs_a = node_get_inputs(thread, a); auto inputs_b = node_get_inputs(thread, b); result = 1; for (u16 i = 0; i < a->input_count; i += 1) { if (!index_equal(inputs_a.pointer[i], inputs_b.pointer[i])) { result = 0; break; } } if (result) { todo(); // switch (a->id) // { // case IR_CONSTANT: // todo(); // // result = index_equal(a->constant.type, b->constant.type); // break; // case IR_START: // todo(); // // result = a->start.function == b->start.function; // break; // default: // trap(); // } } } return result; } fn u8 node_index_equal(Thread* thread, NodeIndex a, NodeIndex b) { u8 result = 0; result = index_equal(a, b) || node_equal(thread, a, b); return result; } fn s64 ip_find_slot_node(GenericInternPool* generic_pool, Thread* thread, Hash32 hash, u32 raw_item_index, u32 saved_index, u32 slots_ahead) { auto* pool = (InternPool(Node)*)generic_pool; assert(pool == &thread->interned.nodes); auto* ptr = pool->pointer; auto item_index = *(NodeIndex*)&raw_item_index; unused(hash); s64 result = -1; for (auto index = saved_index; index < saved_index + slots_ahead; index += 1) { auto typed_index = ptr[index]; if (node_index_equal(thread, item_index, typed_index)) { result = index; break; } } return result; } fn s64 ip_find_slot_register_mask(GenericInternPool* generic_pool, Thread* thread, Hash32 hash, u32 raw_item_index, u32 saved_index, u32 slots_ahead) { auto* pool = (InternPool(RegisterMask)*)generic_pool; assert(pool == &thread->interned.register_masks); auto* ptr = pool->pointer; auto item_index = *(RegisterMaskIndex*)&raw_item_index; unused(hash); s64 result = -1; RegisterMask rm = *thread_register_mask_get(thread, item_index); for (auto index = saved_index; index < saved_index + slots_ahead; index += 1) { auto typed_index = ptr[index]; static_assert(sizeof(RegisterMaskIndex) == sizeof(u32)); if (index_equal(item_index, typed_index)) { result = index; break; } auto register_mask = thread_register_mask_get(thread, typed_index); static_assert(sizeof(RegisterMask) == sizeof(u64)); if (*(u64*)register_mask == *(u64*)&rm) { result = index; break; } } return result; } global const auto ip_interface_debug_type = (InternPoolInterface) { .add_to_buffer = &ip_DebugType_add_to_buffer, .find_slot = &ip_find_slot_debug_type, }; global const auto ip_interface_node = (InternPoolInterface) { .find_slot = &ip_find_slot_node, }; global const auto ip_interface_register_mask = (InternPoolInterface) { .find_slot = &ip_find_slot_register_mask, }; #define declare_ip_functions(T, lower) \ fn Hash32 lower ## _hash_index(Thread* thread, T ## Index item_index); \ fn Hash32 lower ## _hash(Thread* thread, const T * const restrict item); \ \ may_be_unused fn T ## GetOrPut ip_ ## T ## _get_or_put(InternPool(T)* pool, Thread* thread, T ## Index item_index) \ { \ auto hash = lower ## _hash_index(thread, item_index); \ auto* item = thread_ ## lower ## _get(thread, item_index); \ static_assert(sizeof(item_index) == sizeof(u32));\ auto raw_item_index = *(u32*)&item_index;\ auto result = ip_generic_get_or_put((GenericInternPool*)pool, thread, raw_item_index, (void*)item, sizeof(T), hash, &ip_interface_ ## lower); \ return (T ## GetOrPut)\ {\ .index = *(T ## Index*)&result.index,\ .existing = result.existing,\ };\ }\ may_be_unused fn T ## GetOrPut ip_ ## T ## _get_or_put_new(InternPool(T)* pool, Thread* thread, const T* item) \ { \ auto hash = lower ## _hash(thread, item); \ auto result = ip_generic_get_or_put((GenericInternPool*)pool, thread, 0, (void*)item, sizeof(T), hash, &ip_interface_ ## lower); \ return (T ## GetOrPut)\ {\ .index = *(T ## Index*)&result.index,\ .existing = result.existing,\ };\ }\ may_be_unused fn T ## Index ip_ ## T ## _remove(InternPool(T)* pool, Thread* thread, T ## Index item_index)\ {\ auto existing_capacity = pool->capacity;\ auto* item = thread_ ## lower ## _get(thread, item_index);\ auto hash = lower ## _hash(thread, item);\ static_assert(sizeof(item_index) == sizeof(u32));\ auto raw_item_index = *(u32*)&item_index;\ auto maybe_slot = ip_generic_find_slot((GenericInternPool*)pool, thread, raw_item_index, hash, &ip_interface_ ## lower);\ \ if (maybe_slot != -1)\ {\ auto i = cast(u32, s64, maybe_slot);\ auto* slot_pointer = &pool->pointer[i];\ auto old_item_index = *slot_pointer;\ assert(validi(old_item_index));\ pool->length -= 1;\ *slot_pointer = invalidi(T);\ auto j = i;\ \ while (1)\ {\ j = (j + 1) & (existing_capacity - 1);\ \ auto existing = pool->pointer[j];\ if (!validi(existing))\ {\ break;\ }\ \ auto existing_item_index = *(T ## Index*)&existing;\ auto* existing_item = thread_ ## lower ## _get(thread, existing_item_index);\ auto existing_item_hash = lower ## _hash(thread, existing_item);\ auto k = existing_item_hash & (existing_capacity - 1);\ \ if (i <= j)\ {\ if ((i < k) & (k <= j))\ {\ continue;\ }\ }\ else\ {\ if ((k <= j) | (i < k))\ {\ continue;\ }\ }\ \ pool->pointer[i] = pool->pointer[j];\ pool->pointer[j] = invalidi(T);\ \ i = j;\ }\ \ \ return old_item_index;\ }\ else\ {\ todo();\ }\ } struct TypeGetOrPut { TypeIndex index; u8 existing; }; typedef struct TypeGetOrPut TypeGetOrPut; // fn TypeGetOrPut intern_pool_get_or_put_new_type(Thread* thread, Type* type); typedef NodeIndex NodeIdealize(Thread* thread, NodeIndex node_index); typedef TypeIndex NodeComputeType(Thread* thread, NodeIndex node_index); typedef Hash64 TypeGetHash(Thread* thread, Type* type); typedef Hash64 NodeGetHash(Thread* thread, Node* node, NodeIndex node_index, Hash64 hash); // 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 NodeIndex peephole(Thread* thread, Function* function, NodeIndex node_index); // fn NodeIndex constant_int_create_with_type(Thread* thread, Function* function, TypeIndex type_index) // { // auto node_index = thread_node_add(thread, (NodeCreate){ // .id = IR_CONSTANT, // .inputs = array_to_slice(((NodeIndex []) { // // function->start, // })) // }); // auto* node = thread_node_get(thread, node_index); // unused(node); // unused(type_index); // // todo(); // // // node->constant = (NodeConstant) { // // .type = type_index, // // }; // // // // // print("Creating constant integer node #{u32} with value: {u64:x}\n", node_index.index, thread_type_get(thread, type_index)->integer.constant); // // // // auto result = peephole(thread, function, node_index); // // return result; // } // fn NodeIndex constant_int_create(Thread* thread, Function* function, u64 value) // { // auto type_index = thread_get_integer_type(thread, (TypeInteger){ // .constant = value, // .bit_count = 0, // .is_constant = 1, // .is_signed = 0, // }); // // auto constant_int = constant_int_create_with_type(thread, function, type_index); // return constant_int; // } 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 Hash64 hash_type(Thread* thread, Type* type); // fn NodeIndex idealize_null(Thread* thread, NodeIndex node_index) // { // unused(thread); // unused(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 == IR_CONSTANT); // todo(); // // return node->constant.type; // } fn Hash64 type_get_hash_default(Thread* thread, Type* type) { unused(thread); assert(!type->hash); Hash64 hash = fnv_offset; // u32 i = 0; for (auto* it = (u8*)type; it < (u8*)(type + 1); it += 1) { hash = hash_byte(hash, *it); if (type->id == TYPE_INTEGER) { // print("Byte [{u32}] = 0x{u32:x}\n", i, (u32)*it); // i += 1; } } return hash; } fn Hash64 type_get_hash_tuple(Thread* thread, Type* type) { Hash64 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 void intern_pool_ensure_capacity(InternPool(T)* pool, Thread* thread, u32 additional) \ // {\ // auto current_capacity = pool->capacity; \ // auto current_length = pool->length; \ // assert(current_capacity % 2 == 0); \ // auto half_capacity = current_capacity >> 1; \ // auto destination_length = current_length + additional; \ // \ // if (destination_length > half_capacity) \ // {\ // auto new_capacity = cast(u32, u64, MAX(round_up_to_next_power_of_2(destination_length), 32)); \ // auto* new_array = arena_allocate(thread->arena, u32, new_capacity); \ // memset(new_array, 0, sizeof(u32) * new_capacity); \ // \ // auto* old_pointer = pool->pointer;\ // auto old_capacity = current_capacity;\ // auto old_length = current_length;\ // \ // pool->length = 0; // pool->pointer = new_array; // pool->capacity = new_capacity; // // u8* buffer; // u64 stride; // switch (kind) // { // case INTERN_POOL_KIND_TYPE: // buffer = (u8*)thread->buffer.types.pointer; // stride = sizeof(Type); // assert(pool == &thread->interned.types); // break; // case INTERN_POOL_KIND_NODE: // buffer = (u8*)thread->buffer.nodes.pointer; // stride = sizeof(Node); // assert(pool == &thread->interned.nodes); // break; // } // // for (u32 i = 0; i < old_capacity; i += 1) // { // auto key = old_pointer[i]; // if (key) // { // auto hash = *(Hash64*)(buffer + (stride * (key - 1))); // assert(hash); // switch (kind) // { // case INTERN_POOL_KIND_TYPE: // { // auto type_index = *(TypeIndex*)&key; // auto* type = thread_type_get(thread, type_index); // assert(type->hash == hash); // } break; // case INTERN_POOL_KIND_NODE: // { // auto node_index = *(NodeIndex*)&key; // auto* node = thread_node_get(thread, node_index); // todo(); // // assert(node->hash == hash); // // intern_pool_put_node_assume_not_existent_assume_capacity(thread, hash, node_index); // } break; // } // // } // } // // assert(old_length == pool->length); // assert(pool->capacity == new_capacity); // // for (u32 i = 0; i < old_capacity; i += 1) // { // auto key = old_pointer[i]; // if (key) // { // auto hash = *(Hash64*)(buffer + (stride * (key - 1))); // assert(hash); // switch (kind) // { // case INTERN_POOL_KIND_TYPE: // { // auto type_index = *(TypeIndex*)&key; // unused(type_index); // trap(); // } break; // case INTERN_POOL_KIND_NODE: // { // auto node_index = *(NodeIndex*)&key; // auto* node = thread_node_get(thread, node_index); // todo(); // // assert(node->hash == hash); // // auto result = intern_pool_get_node(thread, node_index, hash); // // assert(validi(node_index)); // // assert(index_equal(node_index, result)); // } break; // } // } // } // } // } // fn u8 node_is_projection(Node* n) // { // return (n->id == IR_CONTROL_PROJECTION) | (n->id == IR_PROJECTION); // } // fn NodeIndex projection_get_control(Thread* thread, Node* node) // { // assert(node_is_projection(node)); // auto node_index = node_input_get(thread, node, 0); // return node_index; // } // fn s32 projection_get_index(Node* node) // { // assert(node_is_projection(node)); // // switch (node->id) // { // case IR_CONTROL_PROJECTION: // return node->control_projection.projection.index; // case IR_PROJECTION: // return node->projection.index; // default: // trap(); // } // } // fn TypeIndex compute_type_projection(Thread* thread, NodeIndex node_index) // { // auto* node = thread_node_get(thread, node_index); // assert(node_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(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 == IR_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 == IR_IF) // { // trap(); // } // } // // if (control_node->id == IR_IF) // { // trap(); // } // // return invalidi(Node); // } fn NodeIndex return_get_control(Thread* thread, Node* node) { return node_get_inputs(thread, node).pointer[0]; } fn NodeIndex return_get_value(Thread* thread, Node* node) { return node_get_inputs(thread, node).pointer[1]; } // 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 = cast(u32, s64, result - thread->buffer.types.pointer); // auto type_index = Index(Type, buffer_index); // *result = *type; // // u32 raw_type = *(u32*)&type_index; // thread->interned.types.pointer[index] = raw_type; // assert(raw_type); // thread->interned.types.length += 1; // // return type_index; // } // fn TypeIndex intern_pool_put_new_type_assume_not_existent_assume_capacity(Thread* thread, Type* type) // { // assert(thread->interned.types.length < thread->interned.types.capacity); // Hash64 hash = type->hash; // assert(hash); // auto index = cast(u32, u64, hash & (thread->interned.types.capacity - 1)); // // return intern_pool_put_new_type_at_assume_not_existent_assume_capacity(thread, type, index); // } // // typedef enum InternPoolKind // { // INTERN_POOL_KIND_TYPE, // INTERN_POOL_KIND_NODE, // } InternPoolKind; // [[gnu::hot]] fn s64 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; // s64 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) // { // assert(thread->interned.nodes.length < thread->interned.nodes.capacity); // result = index; // break; // } // else // { // NodeIndex existing_node_index = *(NodeIndex*)&key; // // Exhaustive comparation, shortcircuit when possible // if (node_index_equal(thread, existing_node_index, node_index)) // { // result = index; // break; // } // } // // it_index += 1; // } // // return result; // } // fn NodeIndex intern_pool_get_node(Thread* thread, NodeIndex key, Hash64 hash) // { // auto original_index = cast(u32, u64, hash & (thread->interned.nodes.capacity - 1)); // auto maybe_slot = intern_pool_find_node_slot(thread, original_index, key); // auto node_index = invalidi(Node); // // if (maybe_slot != -1) // { // auto slot = cast(u32, s64, maybe_slot); // auto* pointer_to_slot = &thread->interned.nodes.pointer[slot]; // node_index = *(NodeIndex*)pointer_to_slot; // } // // return node_index; // } // fn NodeIndex intern_pool_put_node_at_assume_not_existent_assume_capacity(Thread* thread, NodeIndex node, u32 index) // { // u32 raw_node = *(u32*)&node; // assert(raw_node); // thread->interned.nodes.pointer[index] = raw_node; // thread->interned.nodes.length += 1; // // return node; // } // fn NodeIndex intern_pool_put_node_assume_not_existent_assume_capacity(Thread* thread, Hash64 hash, NodeIndex node) // { // auto capacity = thread->interned.nodes.capacity; // assert(thread->interned.nodes.length < capacity); // auto original_index = cast(u32, u64, hash & (capacity - 1)); // // auto slot = intern_pool_find_node_slot(thread, original_index, node); // if (slot == -1) // { // fail(); // } // auto index = (u32)slot; // // return intern_pool_put_node_at_assume_not_existent_assume_capacity(thread, node, index); // } // fn void intern_pool_ensure_capacity(InternPool* pool, Thread* thread, u32 additional, InternPoolKind kind) // { // auto current_capacity = pool->capacity; // auto current_length = pool->length; // auto half_capacity = current_capacity >> 1; // auto destination_length = current_length + additional; // // if (destination_length > half_capacity) // { // auto new_capacity = cast(u32, u64, MAX(round_up_to_next_power_of_2(destination_length), 32)); // auto* new_array = arena_allocate(thread->arena, u32, new_capacity); // memset(new_array, 0, sizeof(u32) * new_capacity); // // auto* old_pointer = pool->pointer; // auto old_capacity = current_capacity; // auto old_length = current_length; // // pool->length = 0; // pool->pointer = new_array; // pool->capacity = new_capacity; // // u8* buffer; // u64 stride; // switch (kind) // { // case INTERN_POOL_KIND_TYPE: // buffer = (u8*)thread->buffer.types.pointer; // stride = sizeof(Type); // assert(pool == &thread->interned.types); // break; // case INTERN_POOL_KIND_NODE: // buffer = (u8*)thread->buffer.nodes.pointer; // stride = sizeof(Node); // assert(pool == &thread->interned.nodes); // break; // } // // for (u32 i = 0; i < old_capacity; i += 1) // { // auto key = old_pointer[i]; // if (key) // { // auto hash = *(Hash64*)(buffer + (stride * (key - 1))); // assert(hash); // switch (kind) // { // case INTERN_POOL_KIND_TYPE: // { // auto type_index = *(TypeIndex*)&key; // auto* type = thread_type_get(thread, type_index); // assert(type->hash == hash); // } break; // case INTERN_POOL_KIND_NODE: // { // auto node_index = *(NodeIndex*)&key; // auto* node = thread_node_get(thread, node_index); // todo(); // // assert(node->hash == hash); // // intern_pool_put_node_assume_not_existent_assume_capacity(thread, hash, node_index); // } break; // } // // } // } // // assert(old_length == pool->length); // assert(pool->capacity == new_capacity); // // for (u32 i = 0; i < old_capacity; i += 1) // { // auto key = old_pointer[i]; // if (key) // { // auto hash = *(Hash64*)(buffer + (stride * (key - 1))); // assert(hash); // switch (kind) // { // case INTERN_POOL_KIND_TYPE: // { // auto type_index = *(TypeIndex*)&key; // unused(type_index); // trap(); // } break; // case INTERN_POOL_KIND_NODE: // { // auto node_index = *(NodeIndex*)&key; // auto* node = thread_node_get(thread, node_index); // todo(); // // assert(node->hash == hash); // // auto result = intern_pool_get_node(thread, node_index, hash); // // assert(validi(node_index)); // // assert(index_equal(node_index, result)); // } break; // } // } // } // } // } // // fn TypeIndex intern_pool_put_new_type_assume_not_existent(Thread* thread, Type* type) // { // intern_pool_ensure_capacity(&thread->interned.types, thread, 1, INTERN_POOL_KIND_TYPE); // return intern_pool_put_new_type_assume_not_existent_assume_capacity(thread, type); // } // // fn s64 intern_pool_find_type_slot(Thread* thread, u32 original_index, Type* type) // { // auto it_index = original_index; // auto existing_capacity = thread->interned.types.capacity; // s64 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(existing_type, type)) // { // result = index; // break; // } // } // // it_index += 1; // } // // return result; // } // fn s64 intern_pool_find_debug_type_slot(Thread* thread, const DebugType* type, Hash32 hash) // { // auto it_index = original_index; // auto existing_capacity = thread->interned.types.capacity; // s64 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 // { // auto existing_type_index = *(DebugTypeIndex*)&key; // DebugType* existing_type = thread_debug_type_get(thread, existing_type_index); // auto existing_hash = hash_debug_type(existing_type); // trap(); // // if (type_equal(existing_type, type)) // // { // // result = index; // // break; // // } // } // // it_index += 1; // } // // return result; // } // fn DebugTypeIndex intern_pool_put_new_debug_type_at_assume_not_existent_assume_capacity(Thread* thread, const DebugType* type, u32 index) // { // auto* result = vb_add(&thread->buffer.debug_types, 1); // auto buffer_index = cast(u32, s64, result - thread->buffer.debug_types.pointer); // auto type_index = Index(DebugType, buffer_index); // *result = *type; // // u32 raw_type = *(u32*)&type_index; // thread->interned.types.pointer[index] = raw_type; // assert(raw_type); // thread->interned.types.length += 1; // // return type_index; // } // fn DebugTypeIndex intern_pool_put_new_debug_type_assume_not_existent_assume_capacity(Thread* thread, const DebugType* type, Hash32 hash) // { // assert(thread->interned.types.length < thread->interned.types.capacity); // assert(hash); // auto index = hash & (thread->interned.types.capacity - 1); // // return intern_pool_put_new_debug_type_at_assume_not_existent_assume_capacity(thread, type, index); // } // fn DebugTypeIndex intern_pool_put_new_debug_type_assume_not_existent(Thread* thread, const DebugType* type, Hash32 hash) // { // intern_pool_ensure_capacity(&thread->interned.types, thread, 1, INTERN_POOL_KIND_TYPE); // return intern_pool_put_new_debug_type_assume_not_existent_assume_capacity(thread, type, hash); // } // fn DebugTypeGetOrPut intern_pool_get_or_put_new_debug_type(Thread* thread, const DebugType* type) // { // auto existing_capacity = thread->interned.types.capacity; // auto hash = hash_debug_type(type); // auto original_index = cast(u32, u64, hash & (existing_capacity - 1)); // // auto maybe_slot = intern_pool_find_debug_type_slot(thread, original_index, type); // if (maybe_slot != -1) // { // auto index = cast(u32, s64, maybe_slot); // auto type_index = *(DebugTypeIndex*)&thread->interned.types.pointer[index]; // u8 existing = validi(type_index); // if (!existing) // { // type_index = intern_pool_put_new_debug_type_at_assume_not_existent_assume_capacity(thread, type, index); // } // // return (DebugTypeGetOrPut) { // .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_debug_type_assume_not_existent(thread, type, hash); // return (DebugTypeGetOrPut) { // .index = result, // .existing = 0, // }; // } // else // { // trap(); // } // } // } // 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 = cast(u32, u64, hash & (existing_capacity - 1)); // // auto maybe_slot = intern_pool_find_type_slot(thread, original_index, type); // if (maybe_slot != -1) // { // auto index = cast(u32, s64, maybe_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, 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; unused(control_type); auto return_type = thread_node_get(thread, return_get_value(thread, node))->type; unused(return_type); todo(); // 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); unused(control_node); // if (index_equal(control_node->type, thread->types.dead_control)) // { // return control_node_index; // } // else // { // return invalidi(Node); // } todo(); } // 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); // } // } // fn TypeIndex compute_type_start(Thread* thread, NodeIndex node_index) // { // auto* node = thread_node_get(thread, node_index); // unused(node); // todo(); // // return node->start.arguments; // } // fn u8 type_is_constant(Type* type) // { // switch (type->id) // { // case TYPE_INTEGER: // return type->integer.is_constant; // default: // return 0; // } // } // 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)) // { // left = a; // right = b; // } // 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, right); // // 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 = left; // // } // // else if (index_equal(right, integer_bot)) // // { // // result = right; // // } // // else if (index_equal(right, integer_top)) // // { // // result = left; // // } // // else if (index_equal(left, integer_top)) // // { // // result = right; // // } // // else // // { // // result = integer_bot; // // } // if (left_type->integer.bit_count == right_type->integer.bit_count) // { // todo(); // } // else // { // if ((!left_type->integer.is_constant & !!left_type->integer.bit_count) & (right_type->integer.is_constant & !right_type->integer.bit_count)) // { // result = left; // } // else if ((left_type->integer.is_constant & !left_type->integer.bit_count) & (!right_type->integer.is_constant & !!right_type->integer.bit_count)) // { // trap(); // } // } // } break; // case TYPE_BOTTOM: // { // assert(type_is_simple(left_type)); // if ((left_type->id == TYPE_BOTTOM) | (right_type->id == TYPE_TOP)) // { // result = left; // } // else if ((left_type->id == TYPE_TOP) | (right_type->id == TYPE_BOTTOM)) // { // result = right; // } // else if (!type_is_simple(right_type)) // { // result = thread->types.bottom; // } // else if (left_type->id == TYPE_LIVE_CONTROL) // { // result = thread->types.live_control; // } // else // { // result = thread->types.dead_control; // } // } 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 TypeIndex compute_type_integer_binary(Thread* thread, NodeIndex node_index) // { // auto* node = thread_node_get(thread, node_index); // auto inputs = node_get_inputs(thread, node); // auto* left = thread_node_get(thread, inputs.pointer[1]); // auto* right = thread_node_get(thread, inputs.pointer[2]); // assert(!node_is_dead(left)); // assert(!node_is_dead(right)); // auto* left_type = thread_type_get(thread, left->type); // auto* right_type = thread_type_get(thread, right->type); // // if (((left_type->id == TYPE_INTEGER) & (right_type->id == TYPE_INTEGER)) & (type_is_constant(left_type) & type_is_constant(right_type))) // { // auto left_value = left_type->integer.constant; // auto right_value = right_type->integer.constant; // assert(left_type->integer.bit_count == 0); // assert(right_type->integer.bit_count == 0); // assert(!left_type->integer.is_signed); // assert(!right_type->integer.is_signed); // // u64 result; // TypeInteger type_integer = left_type->integer; // // switch (node->id) // { // case IR_INTEGER_ADD: // result = left_value + right_value; // break; // case IR_INTEGER_SUBSTRACT: // result = left_value - right_value; // break; // case IR_INTEGER_MULTIPLY: // result = left_value * right_value; // break; // case IR_INTEGER_SIGNED_DIVIDE: // result = left_value * right_value; // break; // case IR_INTEGER_AND: // result = left_value & right_value; // break; // case IR_INTEGER_OR: // result = left_value | right_value; // break; // case IR_INTEGER_XOR: // result = left_value ^ right_value; // break; // case IR_INTEGER_SIGNED_SHIFT_LEFT: // result = left_value << right_value; // break; // case IR_INTEGER_SIGNED_SHIFT_RIGHT: // result = left_value >> right_value; // break; // default: // trap(); // } // // type_integer.constant = result; // // auto new_type = thread_get_integer_type(thread, type_integer); // return new_type; // } // else // { // auto result = type_meet(thread, left->type, right->type); // return result; // } // } global const TypeVirtualTable type_functions[TYPE_COUNT] = { [TYPE_BOTTOM] = { .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_START] = { // .compute_type = &compute_type_start, // .idealize = &idealize_null, // .get_hash = &node_get_hash_default, // }, // [NODE_STOP] = { // .compute_type = &compute_type_bottom, // .idealize = &idealize_stop, // .get_hash = &node_get_hash_default, // }, // [NODE_CONTROL_PROJECTION] = { // .compute_type = &compute_type_projection, // .idealize = &idealize_control_projection, // .get_hash = &node_get_hash_control_projection, // }, // [NODE_DEAD_CONTROL] = { // .compute_type = &compute_type_dead_control, // .idealize = &idealize_null, // .get_hash = &node_get_hash_default, // }, [IR_RETURN] = { .compute_type = &compute_type_return, .idealize = &idealize_return, .get_hash = &node_get_hash_default, }, // [NODE_PROJECTION] = { // .compute_type = &compute_type_projection, // .idealize = &idealize_null, // .get_hash = &node_get_hash_projection, // }, // [NODE_SCOPE] = { // .compute_type = &compute_type_bottom, // .idealize = &idealize_null, // .get_hash = &node_get_hash_scope, // }, // Integer operations // [NODE_INTEGER_ADD] = { // .compute_type = &compute_type_integer_binary, // }, // [NODE_INTEGER_SUBSTRACT] = { // .compute_type = &compute_type_integer_binary, // .idealize = &node_idealize_substract, // .get_hash = &node_get_hash_default, // }, // [NODE_INTEGER_SIGNED_DIVIDE] = { // .compute_type = &compute_type_integer_binary, // }, // [NODE_INTEGER_MULTIPLY] = { // .compute_type = &compute_type_integer_binary, // }, // [NODE_INTEGER_AND] = { // .compute_type = &compute_type_integer_binary, // }, // [NODE_INTEGER_OR] = { // .compute_type = &compute_type_integer_binary, // }, // [NODE_INTEGER_XOR] = { // .compute_type = &compute_type_integer_binary, // }, // [NODE_INTEGER_SIGNED_SHIFT_LEFT] = { // .compute_type = &compute_type_integer_binary, // }, // [NODE_INTEGER_SIGNED_SHIFT_RIGHT] = { // .compute_type = &compute_type_integer_binary, // }, // // [NODE_INTEGER_COMPARE_EQUAL] = { // .compute_type = &compute_type_integer_binary, // .idealize = &node_idealize_compare, // .get_hash = &node_get_hash_default, // }, // [NODE_INTEGER_COMPARE_NOT_EQUAL] = { // .compute_type = &compute_type_integer_binary, // .idealize = &node_idealize_compare, // .get_hash = &node_get_hash_default, // }, // // // Constant // [NODE_CONSTANT] = { // .compute_type = &compute_type_constant, // .idealize = &idealize_null, // .get_hash = &node_get_hash_constant, // }, }; may_be_unused fn String type_id_to_string(Type* type) { switch (type->id) { case_to_name(TYPE_, BOTTOM); case_to_name(TYPE_, TOP); case_to_name(TYPE_, LIVE_CONTROL); case_to_name(TYPE_, DEAD_CONTROL); case_to_name(TYPE_, INTEGER); case_to_name(TYPE_, TUPLE); case_to_name(TYPE_, COUNT); } } fn Hash64 hash_type(Thread* thread, Type* type) { Hash64 hash = type->hash; if (!hash) { hash = type_functions[type->id].get_hash(thread, type); // print("Hashing type id {s}: {u64:x}\n", type_id_to_string(type), hash); } assert(hash != 0); type->hash = hash; return hash; } // fn NodeIndex intern_pool_put_node_assume_not_existent(Thread* thread, Hash64 hash, NodeIndex node) // { // intern_pool_ensure_capacity(&thread->interned.nodes, thread, 1, INTERN_POOL_KIND_NODE); // return intern_pool_put_node_assume_not_existent_assume_capacity(thread, hash, node); // } // fn Hash64 hash_node(Thread* thread, Node* node, NodeIndex node_index) // { // auto hash = node->hash; // if (!hash) // { // hash = fnv_offset; // hash = node_functions[node->id].get_hash(thread, node, node_index, hash); // // print("[HASH #{u32}] Received hash from callback: {u64:x}\n", node_index.index, hash); // // hash = hash_byte(hash, node->id); // // auto inputs = node_get_inputs(thread, node); // for (u32 i = 0; i < inputs.length; i += 1) // { // auto input_index = inputs.pointer[i]; // if (validi(input_index)) // { // for (u8* it = (u8*)&input_index; it < (u8*)(&input_index + 1); it += 1) // { // hash = hash_byte(hash, *it); // } // } // } // // // print("[HASH] Node #{u32}, {s}: {u64:x}\n", node_index.index, node_id_to_string(node), hash); // // node->hash = hash; // } // // assert(hash); // // return hash; // } // fn NodeGetOrPut intern_pool_get_or_put_node(Thread* thread, NodeIndex node_index) // { // assert(thread->interned.nodes.length <= thread->interned.nodes.capacity); // auto existing_capacity = thread->interned.nodes.capacity; // auto* node = &thread->buffer.nodes.pointer[geti(node_index)]; // auto hash = hash_node(thread, node, node_index); // 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; // auto* existing_ptr = &thread->interned.nodes.pointer[index]; // NodeIndex existing_value = *(NodeIndex*)existing_ptr; // u8 existing = validi(existing_value); // NodeIndex new_value = existing_value; // if (!existing) // { // assert(thread->interned.nodes.length < thread->interned.nodes.capacity); // new_value = intern_pool_put_node_at_assume_not_existent_assume_capacity(thread, node_index, index); // assert(!index_equal(new_value, existing_value)); // assert(index_equal(new_value, node_index)); // } // return (NodeGetOrPut) { // .index = new_value, // .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(); // } // } // } // fn NodeIndex intern_pool_remove_node(Thread* thread, NodeIndex node_index) // { // auto existing_capacity = thread->interned.nodes.capacity; // auto* node = thread_node_get(thread, node_index); // auto hash = hash_node(thread, node, node_index); // // auto original_index = hash & (existing_capacity - 1); // auto slot = intern_pool_find_node_slot(thread, cast(u32, u64, original_index), node_index); // // if (slot != -1) // { // auto i = (u32)slot; // auto* slot_pointer = &thread->interned.nodes.pointer[i]; // auto old_node_index = *(NodeIndex*)slot_pointer; // assert(validi(old_node_index)); // thread->interned.nodes.length -= 1; // *slot_pointer = 0; // // auto j = i; // // while (1) // { // j = (j + 1) & (existing_capacity - 1); // // auto existing = thread->interned.nodes.pointer[j]; // if (existing == 0) // { // break; // } // // auto existing_node_index = *(NodeIndex*)&existing; // auto* existing_node = thread_node_get(thread, existing_node_index); // auto existing_node_hash = hash_node(thread, existing_node, existing_node_index); // auto k = existing_node_hash & (existing_capacity - 1); // // if (i <= j) // { // if ((i < k) & (k <= j)) // { // continue; // } // } // else // { // if ((k <= j) | (i < k)) // { // continue; // } // } // // thread->interned.nodes.pointer[i] = thread->interned.nodes.pointer[j]; // thread->interned.nodes.pointer[j] = 0; // // i = j; // } // // return old_node_index; // } // else // { // trap(); // } // } 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) { // TODO: is this a bug? parser->column = cast(u32, u64, 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 (likely(index < src.length)) { u8 ch = src.pointer[index]; auto matches = cast(u64, s64, likely(ch == expected_ch)); 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 (likely(is_valid_identifier_start)) { while (parser->i < src.length) { u8 ch = src.pointer[parser->i]; auto is_identifier = cast(u64, s64, likely(is_identifier_ch(ch))); parser->i += is_identifier; if (!is_identifier) { if (unlikely(is_string_literal)) { 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) // { // unused(thread); // unused(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); // } // } union NodePair { struct { NodeIndex old; NodeIndex new; }; NodeIndex nodes[2]; }; typedef union NodePair NodePair; // fn NodeIndex node_keep(Thread* thread, NodeIndex node_index) // { // return node_add_output(thread, node_index, invalidi(Node)); // } // fn NodeIndex node_unkeep(Thread* thread, NodeIndex node_index) // { // node_remove_output(thread, node_index, invalidi(Node)); // return node_index; // } fn NodeIndex dead_code_elimination(Thread* thread, NodePair nodes) { NodeIndex old = nodes.old; NodeIndex new = nodes.new; if (!index_equal(old, new)) { // print("[DCE] old: #{u32} != new: #{u32}. Proceeding to eliminate\n", old.index, new.index); auto* old_node = thread_node_get(thread, old); unused(old_node); todo(); // if (node_is_unused(old_node) & !node_is_dead(old_node)) // { // node_keep(thread, new); // todo(); // // node_kill(thread, old); // // node_unkeep(thread, new); // } } 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) & (type->integer.bit_count == 0)); // break; // default: // break; // } // } // // return result; // } // fn TypeIndex type_join(Thread* thread, TypeIndex a, TypeIndex b) // { // TypeIndex result; // if (index_equal(a, b)) // { // result = a; // } // else // { // unused(thread); // trap(); // } // // return result; // } // fn void node_set_type(Thread* thread, Node* node, TypeIndex new_type) // { // todo(); // // 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); // // } // } global auto enable_peephole = 1; fn NodeIndex peephole_optimize(Thread* thread, Function* function, NodeIndex node_index) { assert(enable_peephole); auto result = node_index; auto* node = thread_node_get(thread, node_index); // print("Peepholing node #{u32} ({s})\n", node_index.index, node_id_to_string(node)); auto old_type = node->type; auto new_type = node_functions[node->id].compute_type(thread, node_index); unused(new_type); unused(old_type); if (enable_peephole) { unused(function); // thread->iteration.total += 1; // node_set_type(thread, node, new_type); // // if (node->id != NODE_CONSTANT && node->id != NODE_DEAD_CONTROL && type_is_high_or_const(thread, node->type)) // { // if (index_equal(node->type, thread->types.dead_control)) // { // trap(); // } // else // { // auto constant_node = constant_int_create_with_type(thread, function, node->type); // return constant_node; // } // } // // auto idealize = 1; // if (!node->hash) // { // auto gop = intern_pool_get_or_put_node(thread, node_index); // idealize = !gop.existing; // // if (gop.existing) // { // auto interned_node_index = gop.index; // auto* interned_node = thread_node_get(thread, interned_node_index); // auto new_type = type_join(thread, interned_node->type, node->type); // node_set_type(thread, interned_node, new_type); // node->hash = 0; // // print("[peephole_optimize] Eliminating #{u32} because an existing node was found: #{u32}\n", node_index.index, interned_node_index.index); // auto dce_node = dead_code_elimination(thread, (NodePair) { // .old = node_index, // .new = interned_node_index, // }); // // result = dce_node; // } // } // // 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; // } // } todo(); } else { todo(); // node->type = new_type; } return result; } fn NodeIndex peephole(Thread* thread, Function* function, NodeIndex node_index) { NodeIndex result; if (enable_peephole) { NodeIndex new_node = peephole_optimize(thread, function, node_index); if (validi(new_node)) { NodeIndex peephole_new_node = peephole(thread, function, new_node); // print("[peephole] Eliminating #{u32} because a better node was found: #{u32}\n", node_index.index, new_node.index); 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); unused(new_type); todo(); // node->type = new_type; // result = node_index; } return result; } fn NodeIndex node_project(Thread* thread, NodeIndex node_index, TypePair type, u32 index) { auto* node = thread_node_get(thread, node_index); assert(type_pair_get_backend(node->type) == BACKEND_TYPE_TUPLE); auto projection_node_index = thread_node_add(thread, (NodeCreate) { .id = IR_PROJECTION, .inputs = array_to_slice(((NodeIndex[]) { node_index })), .type_pair = type, }); auto* projection = thread_node_get(thread, projection_node_index); projection->projection = (NodeProjection) { .index = index, }; return projection_node_index; } fn TypePair 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]; auto is_digit = is_decimal_digit(ch); decimal_digit_count += is_digit; if (!is_digit) { auto 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) { auto signedness = cast(u8, u64, 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 bit_count = cast(u8, u64, bit_size); auto valid = MIN(MAX(8, round_up_to_next_power_of_2(MAX(bit_count, 1))), 64); if (bit_count != valid) { fail(); } auto bit_index = cast(u32, s32, __builtin_ctz(bit_count >> 3)); static_assert(array_length(thread->types.debug.integer.array) == 8); auto index = signedness * 4 + bit_index; auto debug_type_index = thread->types.debug.integer.array[index]; BackendTypeId backend_type = bit_index + 1; auto type_pair = type_pair_make(debug_type_index, backend_type); return type_pair; } else { fail(); } } else if (float_start) { trap(); } else { trap(); } } else { fail(); } } trap(); } declare_ip_functions(Node, node) // TODO: fn NodeIndex node_gvn_intern(Thread* thread, NodeIndex node_index) { auto result = ip_Node_get_or_put(&thread->interned.nodes, thread, node_index); if (result.existing) { assert(thread_node_get(thread, result.index)->interned); } else { thread_node_get(thread, node_index)->interned = 1; } return result.index; } fn void node_gvn_remove(Thread* thread, NodeIndex node_index) { auto result = ip_Node_remove(&thread->interned.nodes, thread, node_index); assert(index_equal(result, node_index)); thread_node_get(thread, node_index)->interned = 0; } fn NodeIndex analyze_primary_expression(Thread* thread, Parser* parser, FunctionBuilder* builder, String src) { u8 starting_ch = src.pointer[parser->i]; u64 is_digit = is_decimal_digit(starting_ch); u64 is_identifier = is_identifier_start(starting_ch); if (is_identifier) { String identifier = parse_identifier(parser, src); unused(identifier); todo(); // auto node_index = scope_lookup(thread, builder, identifier); // if (validi(node_index)) // { // return node_index; // } // else // { // fail(); // } } else 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(); } break; case INTEGER_PREFIX_BINARY: { trap(); } break; } auto node_index = thread_node_add(thread, (NodeCreate){ .inputs = array_to_slice(((NodeIndex []) { builder->function->root, })), .type_pair = type_pair_make(thread->types.debug.integer.u64, BACKEND_TYPE_INTEGER_64), .id = IR_INTEGER_CONSTANT, }); auto* node = thread_node_get(thread, node_index); node->integer_constant = (NodeIntegerConstant) { .unsigned_value = value, }; auto new_node_index = node_gvn_intern(thread, node_index); return new_node_index; } else { trap(); } } fn NodeIndex analyze_unary(Thread* thread, Parser* parser, FunctionBuilder* builder, String src) { typedef enum PrefixOperator { PREFIX_OPERATOR_NONE = 0, PREFIX_OPERATOR_NEGATION, PREFIX_OPERATOR_LOGICAL_NOT, PREFIX_OPERATOR_BITWISE_NOT, PREFIX_OPERATOR_ADDRESS_OF, } PrefixOperator; PrefixOperator prefix_operator; NodeIndex node_index; switch (src.pointer[parser->i]) { case '-': todo(); case '!': todo(); case '~': todo(); case '&': todo(); default: { node_index = analyze_primary_expression(thread, parser, builder, src); prefix_operator = PREFIX_OPERATOR_NONE; } break; } // typedef enum SuffixOperator // { // SUFFIX_OPERATOR_NONE = 0, // SUFFIX_OPERATOR_CALL, // SUFFIX_OPERATOR_ARRAY, // SUFFIX_OPERATOR_FIELD, // SUFFIX_OPERATOR_POINTER_DEREFERENCE, // } SuffixOperator; // // SuffixOperator suffix_operator; skip_space(parser, src); switch (src.pointer[parser->i]) { case argument_start: todo(); case array_start: todo(); case '.': todo(); default: break; } if (prefix_operator != PREFIX_OPERATOR_NONE) { todo(); } return node_index; } fn NodeIndex analyze_multiplication(Thread* thread, Parser* parser, FunctionBuilder* builder, String src) { auto left = analyze_unary(thread, parser, builder, src); while (1) { skip_space(parser, src); NodeId node_id; u64 skip_count = 1; switch (src.pointer[parser->i]) { case '*': node_id = IR_INTEGER_MULTIPLY; break; case '/': node_id = IR_INTEGER_DIVIDE; break; case '%': todo(); default: node_id = NODE_COUNT; break; } if (node_id == NODE_COUNT) { break; } parser->i += skip_count; skip_space(parser, src); auto new_node_index = thread_node_add(thread, (NodeCreate) { .id = node_id, .inputs = array_to_slice(((NodeIndex[]) { invalidi(Node), left, invalidi(Node), })), }); // print("Before right: LEFT is #{u32}\n", left.index); // print("Left code:\n```\n{s}\n```\n", s_get_slice(u8, src, parser->i, src.length)); auto right = analyze_multiplication(thread, parser, builder, src); unused(right); // print("Addition: left: #{u32}, right: #{u32}\n", left.index, right.index); // print("Left code:\n```\n{s}\n```\n", s_get_slice(u8, src, parser->i, src.length)); todo(); // node_set_input(thread, new_node_index, 2, right); // print("Addition new node #{u32}\n", new_node_index.index); // print("Left code:\n```\n{s}\n```\n", s_get_slice(u8, src, parser->i, src.length)); left = peephole(thread, builder->function, new_node_index); } // print("Analyze addition returned node #{u32}\n", left.index); return left; } fn NodeIndex analyze_addition(Thread* thread, Parser* parser, FunctionBuilder* builder, String src) { auto left = analyze_multiplication(thread, parser, builder, src); while (1) { skip_space(parser, src); NodeId node_id; switch (src.pointer[parser->i]) { case '+': node_id = IR_INTEGER_ADD; break; case '-': node_id = IR_INTEGER_SUBSTRACT; break; default: node_id = NODE_COUNT; break; } if (node_id == NODE_COUNT) { break; } parser->i += 1; skip_space(parser, src); auto new_node_index = thread_node_add(thread, (NodeCreate) { .id = node_id, .inputs = array_to_slice(((NodeIndex[]) { invalidi(Node), left, invalidi(Node), })), }); // print("Before right: LEFT is #{u32}\n", left.index); // print("Left code:\n```\n{s}\n```\n", s_get_slice(u8, src, parser->i, src.length)); auto right = analyze_multiplication(thread, parser, builder, src); unused(right); // print("Addition: left: #{u32}, right: #{u32}\n", left.index, right.index); // print("Left code:\n```\n{s}\n```\n", s_get_slice(u8, src, parser->i, src.length)); // node_set_input(thread, new_node_index, 2, right); todo(); // print("Addition new node #{u32}\n", new_node_index.index); // print("Left code:\n```\n{s}\n```\n", s_get_slice(u8, src, parser->i, src.length)); left = peephole(thread, builder->function, new_node_index); } // print("Analyze addition returned node #{u32}\n", left.index); return left; } fn NodeIndex analyze_shift(Thread* thread, Parser* parser, FunctionBuilder* builder, String src) { auto left = analyze_addition(thread, parser, builder, src); while (1) { skip_space(parser, src); NodeId node_id; if ((src.pointer[parser->i] == '<') & (src.pointer[parser->i + 1] == '<')) { node_id = IR_INTEGER_SHIFT_LEFT; } else if ((src.pointer[parser->i] == '>') & (src.pointer[parser->i + 1] == '>')) { node_id = IR_INTEGER_SHIFT_RIGHT; } else { break; } parser->i += 2; skip_space(parser, src); auto new_node_index = thread_node_add(thread, (NodeCreate) { .id = node_id, .inputs = array_to_slice(((NodeIndex[]) { invalidi(Node), left, invalidi(Node), })), }); // print("Before right: LEFT is #{u32}\n", left.index); // print("Left code:\n```\n{s}\n```\n", s_get_slice(u8, src, parser->i, src.length)); auto right = analyze_addition(thread, parser, builder, src); unused(right); // print("Addition: left: #{u32}, right: #{u32}\n", left.index, right.index); // print("Left code:\n```\n{s}\n```\n", s_get_slice(u8, src, parser->i, src.length)); // node_set_input(thread, new_node_index, 2, right); todo(); // print("Addition new node #{u32}\n", new_node_index.index); // print("Left code:\n```\n{s}\n```\n", s_get_slice(u8, src, parser->i, src.length)); left = peephole(thread, builder->function, new_node_index); } return left; } fn NodeIndex analyze_bitwise_binary(Thread* thread, Parser* parser, FunctionBuilder* builder, String src) { auto left = analyze_shift(thread, parser, builder, src); while (1) { skip_space(parser, src); NodeId node_id; u64 skip_count = 1; switch (src.pointer[parser->i]) { case '&': node_id = IR_INTEGER_AND; break; case '|': node_id = IR_INTEGER_OR; break; case '^': node_id = IR_INTEGER_XOR; break; default: node_id = NODE_COUNT; break; } if (node_id == NODE_COUNT) { break; } parser->i += skip_count; skip_space(parser, src); auto new_node_index = thread_node_add(thread, (NodeCreate) { .id = node_id, .inputs = array_to_slice(((NodeIndex[]) { invalidi(Node), left, invalidi(Node), })), }); // print("Before right: LEFT is #{u32}\n", left.index); // print("Left code:\n```\n{s}\n```\n", s_get_slice(u8, src, parser->i, src.length)); auto right = analyze_shift(thread, parser, builder, src); unused(right); // print("Addition: left: #{u32}, right: #{u32}\n", left.index, right.index); // print("Left code:\n```\n{s}\n```\n", s_get_slice(u8, src, parser->i, src.length)); // node_set_input(thread, new_node_index, 2, right); todo(); // print("Addition new node #{u32}\n", new_node_index.index); // print("Left code:\n```\n{s}\n```\n", s_get_slice(u8, src, parser->i, src.length)); left = peephole(thread, builder->function, new_node_index); } return left; } fn NodeIndex analyze_comparison(Thread* thread, Parser* parser, FunctionBuilder* builder, String src) { auto left = analyze_bitwise_binary(thread, parser, builder, src); while (1) { skip_space(parser, src); NodeId node_id; u64 skip_count = 1; switch (src.pointer[parser->i]) { case '=': todo(); case '!': if (src.pointer[parser->i + 1] == '=') { skip_count = 2; node_id = IR_INTEGER_COMPARE_NOT_EQUAL; } else { fail(); } break; case '<': todo(); case '>': todo(); default: node_id = NODE_COUNT; break; } if (node_id == NODE_COUNT) { break; } parser->i += skip_count; skip_space(parser, src); auto new_node_index = thread_node_add(thread, (NodeCreate) { .id = node_id, .inputs = array_to_slice(((NodeIndex[]) { invalidi(Node), left, invalidi(Node), })), }); // print("Before right: LEFT is #{u32}\n", left.index); // print("Left code:\n```\n{s}\n```\n", s_get_slice(u8, src, parser->i, src.length)); auto right = analyze_bitwise_binary(thread, parser, builder, src); unused(right); // print("Addition: left: #{u32}, right: #{u32}\n", left.index, right.index); // print("Left code:\n```\n{s}\n```\n", s_get_slice(u8, src, parser->i, src.length)); // node_set_input(thread, new_node_index, 2, right); todo(); // print("Addition new node #{u32}\n", new_node_index.index); // print("Left code:\n```\n{s}\n```\n", s_get_slice(u8, src, parser->i, src.length)); left = peephole(thread, builder->function, new_node_index); } return left; } fn NodeIndex analyze_expression(Thread* thread, Parser* parser, FunctionBuilder* builder, String src, TypePair result_type) { NodeIndex result = analyze_comparison(thread, parser, builder, src); // TODO: typecheck unused(result_type); return result; } fn void analyze_block(Thread* thread, Parser* parser, FunctionBuilder* builder, String src) { expect_character(parser, src, block_start); // TODO // scope_push(thread, builder); Function* function = builder->function; while (1) { skip_space(parser, src); if (s_get(src, parser->i) == block_end) { break; } 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, builder, src, function->return_type); skip_space(parser, src); expect_character(parser, src, ';'); auto* current_node = thread_node_get(thread, builder->current); auto current_inputs = node_get_inputs(thread, current_node); auto mem_state = current_inputs.pointer[2]; auto return_node_index = node_get_inputs(thread, thread_node_get(thread, builder->function->root)).pointer[1]; auto* return_node = thread_node_get(thread, return_node_index); assert(return_node->input_count >= 4); auto return_inputs = node_get_inputs(thread, return_node); node_add_input(thread, return_inputs.pointer[1], mem_state); node_add_input(thread, return_inputs.pointer[3], return_value); auto control = return_inputs.pointer[0]; assert(thread_node_get(thread, control)->id == IR_REGION); assert(validi(current_inputs.pointer[0])); node_add_input(thread, control, current_inputs.pointer[0]); builder->current = invalidi(Node); continue; } String left_name = statement_start_identifier; unused(left_name); skip_space(parser, src); typedef enum AssignmentOperator { ASSIGNMENT_OPERATOR_NONE, } AssignmentOperator; AssignmentOperator assignment_operator; switch (src.pointer[parser->i]) { case '=': assignment_operator = ASSIGNMENT_OPERATOR_NONE; parser->i += 1; break; default: trap(); } unused(assignment_operator); skip_space(parser, src); NodeIndex initial_right = analyze_expression(thread, parser, builder, src, type_pair_invalid); unused(initial_right); expect_character(parser, src, ';'); todo(); // auto left = scope_lookup(thread, builder, left_name); // if (!validi(left)) // { // fail(); // } // // NodeIndex right; // switch (assignment_operator) // { // case ASSIGNMENT_OPERATOR_NONE: // right = initial_right; // break; // } // // scope_update(thread, builder, left_name, right); } else { switch (statement_start_ch) { case '>': { parser->i += 1; skip_space(parser, src); String local_name = parse_identifier(parser, src); unused(local_name); skip_space(parser, src); auto type = type_pair_invalid; u8 has_type_declaration = src.pointer[parser->i] == ':'; if (has_type_declaration) { parser->i += 1; skip_space(parser, src); type = analyze_type(thread, parser, src); skip_space(parser, src); } expect_character(parser, src, '='); skip_space(parser, src); auto initial_value_node_index = analyze_expression(thread, parser, builder, src, type); skip_space(parser, src); expect_character(parser, src, ';'); auto* initial_value_node = thread_node_get(thread, initial_value_node_index); unused(initial_value_node); // TODO: typecheck todo(); // auto result = scope_define(thread, builder, local_name, initial_value_node->type, initial_value_node_index); // if (!validi(result)) // { // fail(); // } } break; case block_start: analyze_block(thread, parser, builder, src); break; default: todo(); break; } } } expect_character(parser, src, block_end); // scope_pop(thread, builder); } 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); Function* restrict function = vb_add(&thread->buffer.functions, 1); auto function_index = cast(u32, s64, function - thread->buffer.functions.pointer); memset(function, 0, sizeof(Function)); builder->function = function; function->name = parse_identifier(parser, src); if (s_equal(function->name, strlit("main"))) { thread->main_function = thread->buffer.functions.length - 1; } skip_space(parser, src); // Parse arguments expect_character(parser, src, argument_start); u32 argument_i = 0; String argument_names[255]; while (1) { skip_space(parser, src); if (src.pointer[parser->i] == argument_end) { break; } if (argument_i == 255) { // Maximum arguments reached fail(); } auto argument_name = parse_identifier(parser, src); argument_names[argument_i] = argument_name; skip_space(parser, src); expect_character(parser, src, ':'); skip_space(parser, src); auto type_index = analyze_type(thread, parser, src); unused(type_index); // start_argument_type_buffer[argument_i] = type_index; argument_i += 1; skip_space(parser, src); switch (src.pointer[parser->i]) { case argument_end: break; default: trap(); } } expect_character(parser, src, argument_end); skip_space(parser, src); function->return_type = analyze_type(thread, parser, src); function->root = thread_node_add(thread, (NodeCreate) { .type_pair = type_pair_make(invalidi(DebugType), BACKEND_TYPE_TUPLE), .id = IR_ROOT, .inputs = array_to_slice(((NodeIndex[]){ invalidi(Node), // TODO: add callgraph node invalidi(Node), // return node })), }); auto* root_node = thread_node_get(thread, function->root); root_node->root = (NodeRoot) { .function_index = function_index, }; auto control = node_project(thread, function->root, type_pair_make(invalidi(DebugType), BACKEND_TYPE_CONTROL), 0); auto memory = node_project(thread, function->root, type_pair_make(invalidi(DebugType), BACKEND_TYPE_MEMORY), 1); auto pointer = node_project(thread, function->root, type_pair_make(invalidi(DebugType), BACKEND_TYPE_POINTER), 2); if (argument_i > 0) { // TODO: project arguments todo(); } NodeIndex fake[256] = {}; auto slice = (Slice(NodeIndex)) array_to_slice(fake); slice.length = 4; auto return_node_index = thread_node_add(thread, (NodeCreate) { .id = IR_RETURN, .inputs = slice, .type_pair = type_pair_make(invalidi(DebugType), BACKEND_TYPE_CONTROL), }); node_set_input(thread, function->root, 1, return_node_index); auto region = thread_node_add(thread, (NodeCreate) { .id = IR_REGION, .inputs = {}, .type_pair = type_pair_make(invalidi(DebugType), BACKEND_TYPE_CONTROL), }); auto memory_phi = thread_node_add(thread, (NodeCreate) { .id = IR_PHI, .inputs = array_to_slice(((NodeIndex[]) { region, })), .type_pair = type_pair_make(invalidi(DebugType), BACKEND_TYPE_MEMORY), }); node_set_input(thread, return_node_index, 0, region); node_set_input(thread, return_node_index, 1, memory_phi); node_set_input(thread, return_node_index, 2, pointer); auto ret_phi = thread_node_add(thread, (NodeCreate) { .id = IR_PHI, .inputs = array_to_slice(((NodeIndex[]) { region, })), .type_pair = function->return_type, }); node_set_input(thread, ret_phi, 0, region); node_set_input(thread, return_node_index, 3, ret_phi); thread_node_get(thread, region)->region = (NodeRegion) { .in_mem = memory_phi, }; node_gvn_intern(thread, function->root); node_gvn_intern(thread, control); node_gvn_intern(thread, memory); node_gvn_intern(thread, pointer); skip_space(parser, src); auto symbol_table = thread_node_add(thread, (NodeCreate) { .id = IR_SYMBOL_TABLE, .inputs = array_to_slice(((NodeIndex[]) { control, control, memory, pointer, })), }); builder->current = symbol_table; analyze_block(thread, parser, builder, src); node_gvn_intern(thread, return_node_index); node_gvn_intern(thread, region); node_gvn_intern(thread, memory_phi); node_gvn_intern(thread, ret_phi); node_gvn_intern(thread, symbol_table); } else { trap(); } } else { trap(); } } } // typedef NodeIndex NodeCallback(Thread* thread, Function* function, NodeIndex node_index); // // fn NodeIndex node_walk_internal(Thread* thread, Function* function, NodeIndex node_index, NodeCallback* callback) // { // if (bitset_get(&thread->worklist.visited, geti(node_index))) // { // return invalidi(Node); // } // else // { // bitset_set_value(&thread->worklist.visited, geti(node_index), 1); // auto callback_result = callback(thread, function, node_index); // if (validi(callback_result)) // { // return callback_result; // } // // auto* node = thread_node_get(thread, node_index); // auto inputs = node_get_inputs(thread, node); // auto outputs = node_get_outputs(thread, node); // // for (u64 i = 0; i < inputs.length; i += 1) // { // auto n = inputs.pointer[i]; // if (validi(n)) // { // auto n_result = node_walk_internal(thread, function, n, callback); // if (validi(n_result)) // { // return n_result; // } // } // } // // for (u64 i = 0; i < outputs.length; i += 1) // { // auto n = outputs.pointer[i]; // if (validi(n)) // { // auto n_result = node_walk_internal(thread, function, n, callback); // if (validi(n_result)) // { // return n_result; // } // } // } // // return invalidi(Node); // } // } // // fn NodeIndex node_walk(Thread* thread, Function* function, NodeIndex node_index, NodeCallback* callback) // { // assert(thread->worklist.visited.length == 0); // NodeIndex result = node_walk_internal(thread, function, node_index, callback); // bitset_clear(&thread->worklist.visited); // return result; // } // // fn NodeIndex progress_on_list_callback(Thread* thread, Function* function, NodeIndex node_index) // { // if (bitset_get(&thread->worklist.bitset, geti(node_index))) // { // return invalidi(Node); // } // else // { // NodeIndex new_node = peephole_optimize(thread, function, node_index); // return new_node; // } // } // // fn u8 progress_on_list(Thread* thread, Function* function, NodeIndex stop_node_index) // { // thread->worklist.mid_assert = 1; // // NodeIndex changed = node_walk(thread, function, stop_node_index, &progress_on_list_callback); // // thread->worklist.mid_assert = 0; // // return !validi(changed); // } // // fn void iterate_peepholes(Thread* thread, Function* function, NodeIndex stop_node_index) // { // assert(progress_on_list(thread, function, stop_node_index)); // if (thread->worklist.nodes.length > 0) // { // while (1) // { // auto node_index = thread_worklist_pop(thread); // if (!validi(node_index)) // { // break; // } // // auto* node = thread_node_get(thread, node_index); // todo(); // // if (!node_is_dead(node)) // // { // // auto new_node_index = peephole_optimize(thread, function, node_index); // // if (validi(new_node_index)) // // { // // trap(); // // } // // } // } // } // // thread_worklist_clear(thread); // } // fn u8 node_is_cfg(Node* node) // { // switch (node->id) // { // case IR_START: // case IR_DEAD_CONTROL: // case IR_CONTROL_PROJECTION: // case IR_RETURN: // case IR_STOP: // return 1; // case IR_SCOPE: // case IR_CONSTANT: // case IR_PROJECTION: // return 0; // default: // trap(); // } // } // fn void rpo_cfg(Thread* thread, NodeIndex node_index) // { // auto* node = thread_node_get(thread, node_index); // if (node_is_cfg(node) && !bitset_get(&thread->worklist.visited, geti(node_index))) // { // bitset_set_value(&thread->worklist.visited, geti(node_index), 1); // auto outputs = node_get_outputs(thread, node); // for (u64 i = 0; i < outputs.length; i += 1) // { // auto output = outputs.pointer[i]; // if (validi(output)) // { // rpo_cfg(thread, output); // } // } // // *vb_add(&thread->worklist.nodes, 1) = node_index; // } // } // fn s32 node_loop_depth(Thread* thread, Node* node) // { // assert(node_is_cfg(node)); // s32 loop_depth; // // switch (node->id) // { // case IR_START: // { // loop_depth = node->start.cfg.loop_depth; // if (!loop_depth) // { // loop_depth = node->start.cfg.loop_depth = 1; // } // } break; // case IR_STOP: // { // loop_depth = node->stop.cfg.loop_depth; // if (!loop_depth) // { // loop_depth = node->stop.cfg.loop_depth = 1; // } // } break; // case IR_RETURN: // { // loop_depth = node->return_node.cfg.loop_depth; // if (!loop_depth) // { // auto input_index = node_input_get(thread, node, 0); // auto input = thread_node_get(thread, input_index); // node->return_node.cfg.loop_depth = loop_depth = node_loop_depth(thread, input); // } // } break; // case IR_CONTROL_PROJECTION: // { // loop_depth = node->control_projection.cfg.loop_depth; // if (!loop_depth) // { // auto input_index = node_input_get(thread, node, 0); // auto input = thread_node_get(thread, input_index); // node->control_projection.cfg.loop_depth = loop_depth = node_loop_depth(thread, input); // } // } break; // case IR_DEAD_CONTROL: // { // loop_depth = node->dead_control.cfg.loop_depth; // if (!loop_depth) // { // auto input_index = node_input_get(thread, node, 0); // auto input = thread_node_get(thread, input_index); // node->dead_control.cfg.loop_depth = loop_depth = node_loop_depth(thread, input); // } // } break; // default: // trap(); // } // // return loop_depth; // } // fn u8 node_is_region(Node* node) // { // return (node->id == IR_REGION) | (node->id == IR_REGION_LOOP); // } // // fn u8 node_is_pinned(Node* node) // { // switch (node->id) // { // case IR_PROJECTION: // case IR_START: // return 1; // case IR_CONSTANT: // case IR_INTEGER_SUBSTRACT: // case IR_INTEGER_COMPARE_EQUAL: // case IR_INTEGER_COMPARE_NOT_EQUAL: // return 0; // default: // trap(); // } // } // fn s32 node_cfg_get_immediate_dominator_tree_depth(Node* node) // { // assert(node_is_cfg(node)); // switch (node->id) // { // case IR_START: // return 0; // case IR_DEAD_CONTROL: // todo(); // case IR_CONTROL_PROJECTION: // todo(); // case IR_RETURN: // todo(); // case IR_STOP: // todo(); // default: // trap(); // } // } // fn void schedule_early(Thread* thread, NodeIndex node_index, NodeIndex start_node) // { // if (validi(node_index) && !bitset_get(&thread->worklist.visited, geti(node_index))) // { // bitset_set_value(&thread->worklist.visited, geti(node_index), 1); // // auto* node = thread_node_get(thread, node_index); // auto inputs = node_get_inputs(thread, node); // // for (u64 i = 0; i < inputs.length; i += 1) // { // auto input = inputs.pointer[i]; // // if (validi(input)) // { // auto* input_node = thread_node_get(thread, input); // if (!node_is_pinned(input_node)) // { // schedule_early(thread, node_index, start_node); // } // } // } // // if (!node_is_pinned(node)) // { // auto early = start_node; // // for (u64 i = 1; i < inputs.length; i += 1) // { // auto input_index = inputs.pointer[i]; // auto input_node = thread_node_get(thread, input_index); // auto control_input_index = node_input_get(thread, input_node, 0); // auto* control_input_node = thread_node_get(thread, control_input_index); // auto* early_node = thread_node_get(thread, early); // auto input_depth = node_cfg_get_immediate_dominator_tree_depth(control_input_node); // auto early_depth = node_cfg_get_immediate_dominator_tree_depth(early_node); // if (input_depth > early_depth) // { // early = control_input_index; // trap(); // } // } // // node_set_input(thread, node_index, 0, early); // } // } // } // // fn u8 node_cfg_block_head(Node* node) // { // assert(node_is_cfg(node)); // switch (node->id) // { // case IR_START: // return 1; // default: // trap(); // } // } // // fn u8 is_forwards_edge(Thread* thread, NodeIndex output_index, NodeIndex input_index) // { // u8 result = validi(output_index) & validi(input_index); // if (result) // { // auto* output = thread_node_get(thread, output_index); // result = output->input_count > 2; // if (result) // { // auto input_index2 = node_input_get(thread, output, 2); // // result = index_equal(input_index2, input_index); // // if (result) // { // trap(); // } // } // } // // return result; // } // // fn void schedule_late(Thread* thread, NodeIndex node_index, Slice(NodeIndex) nodes, Slice(NodeIndex) late) // { // if (!validi(late.pointer[geti(node_index)])) // { // auto* node = thread_node_get(thread, node_index); // // if (node_is_cfg(node)) // { // late.pointer[geti(node_index)] = node_cfg_block_head(node) ? node_index : node_input_get(thread, node, 0); // } // // if (node->id == IR_PHI) // { // trap(); // } // // auto outputs = node_get_outputs(thread, node); // // for (u32 i = 0; i < outputs.length; i += 1) // { // NodeIndex output = outputs.pointer[i]; // if (is_forwards_edge(thread, output, node_index)) // { // trap(); // } // } // // for (u32 i = 0; i < outputs.length; i += 1) // { // NodeIndex output = outputs.pointer[i]; // if (is_forwards_edge(thread, output, node_index)) // { // trap(); // } // } // // if (!node_is_pinned(node)) // { // unused(nodes); // trap(); // } // } // } // fn void gcm_build_cfg(Thread* thread, NodeIndex start_node_index, NodeIndex stop_node_index) // { // unused(stop_node_index); // // Fix loops // { // // TODO: // } // // // Schedule early // rpo_cfg(thread, start_node_index); // // u32 i = thread->worklist.nodes.length; // while (i > 0) // { // i -= 1; // auto node_index = thread->worklist.nodes.pointer[i]; // auto* node = thread_node_get(thread, node_index); // node_loop_depth(thread, node); // auto inputs = node_get_inputs(thread, node); // for (u64 i = 0; i < inputs.length; i += 1) // { // auto input = inputs.pointer[i]; // schedule_early(thread, input, start_node_index); // } // // if (node_is_region(node)) // { // trap(); // } // } // // // Schedule late // // auto max_node_count = thread->buffer.nodes.length; // auto* alloc = arena_allocate(thread->arena, NodeIndex, max_node_count * 2); // auto late = (Slice(NodeIndex)) { // .pointer = alloc, // .length = max_node_count, // }; // auto nodes = (Slice(NodeIndex)) { // .pointer = alloc + max_node_count, // .length = max_node_count, // }; // // schedule_late(thread, start_node_index, nodes, late); // // for (u32 i = 0; i < late.length; i += 1) // { // auto node_index = nodes.pointer[i]; // if (validi(node_index)) // { // trap(); // auto late_node_index = late.pointer[i]; // node_set_input(thread, node_index, 0, late_node_index); // } // } // } // may_be_unused fn void print_function(Thread* thread, Function* function) // { // print("fn {s}\n====\n", function->name); // VirtualBuffer(NodeIndex) nodes = {}; // *vb_add(&nodes, 1) = function->stop; // // while (1) // { // auto node_index = nodes.pointer[nodes.length - 1]; // auto* node = thread_node_get(thread, node_index); // // if (node->input_count) // { // for (u32 i = 1; i < node->input_count; i += 1) // { // *vb_add(&nodes, 1) = node_input_get(thread, node, 1); // } // *vb_add(&nodes, 1) = node_input_get(thread, node, 0); // } // else // { // break; // } // } // // u32 i = nodes.length; // while (i > 0) // { // i -= 1; // // auto node_index = nodes.pointer[i]; // auto* node = thread_node_get(thread, node_index); // unused(node); // todo(); // // auto* type = thread_type_get(thread, node->type); // // print("%{u32} - {s} - {s} ", geti(node_index), type_id_to_string(type), node_id_to_string(node)); // // auto inputs = node_get_inputs(thread, node); // // auto outputs = node_get_outputs(thread, node); // // // // print("(INPUTS: { "); // // for (u32 i = 0; i < inputs.length; i += 1) // // { // // auto input_index = inputs.pointer[i]; // // print("%{u32} ", geti(input_index)); // // } // // print("} OUTPUTS: { "); // // for (u32 i = 0; i < outputs.length; i += 1) // // { // // auto output_index = outputs.pointer[i]; // // print("%{u32} ", geti(output_index)); // // } // // print_string(strlit("})\n")); // } // // // print("====\n", function->name); // } // struct CBackend // { // VirtualBuffer(u8) buffer; // Function* function; // }; // // typedef struct CBackend CBackend; // // fn void c_lower_append_string(CBackend* backend, String string) // { // vb_append_bytes(&backend->buffer, string); // } // // fn void c_lower_append_ch(CBackend* backend, u8 ch) // { // *vb_add(&backend->buffer, 1) = ch; // } // // fn void c_lower_append_ch_repeated(CBackend* backend, u8 ch, u32 times) // { // u8* pointer = vb_add(&backend->buffer, times); // memset(pointer, ch, times); // } // // fn void c_lower_append_space(CBackend* backend) // { // c_lower_append_ch(backend, ' '); // } // // fn void c_lower_append_space_margin(CBackend* backend, u32 times) // { // c_lower_append_ch_repeated(backend, ' ', times * 4); // } // fn void c_lower_type(CBackend* backend, Thread* thread, TypeIndex type_index) // { // Type* type = thread_type_get(thread, type_index); // switch (type->id) // { // case TYPE_INTEGER: // { // u8 ch[] = { 'u', 's' }; // auto integer = &type->integer; // u8 signedness_ch = ch[type->integer.is_signed]; // c_lower_append_ch(backend, signedness_ch); // u8 upper_digit = integer->bit_count / 10; // u8 lower_digit = integer->bit_count % 10; // if (upper_digit) // { // c_lower_append_ch(backend, upper_digit + '0'); // } // c_lower_append_ch(backend, lower_digit + '0'); // } break; // default: // trap(); // } // } // fn void c_lower_node(CBackend* backend, Thread* thread, NodeIndex node_index) // { // unused(backend); // auto* node = thread_node_get(thread, node_index); // unused(node); // // auto* type = thread_type_get(thread, node->type); // // auto inputs = node_get_inputs(thread, node); // // // // switch (node->id) // // { // // case IR_CONSTANT: // // { // // switch (type->id) // // { // // case TYPE_INTEGER: // // { // // assert(type->integer.bit_count == 0); // // assert(type->integer.is_constant); // // assert(!type->integer.is_signed); // // vb_generic_ensure_capacity(&backend->buffer, 1, 64); // // auto current_length = backend->buffer.length; // // auto buffer_slice = (String){ .pointer = backend->buffer.pointer + current_length, .length = backend->buffer.capacity - current_length, }; // // auto written_characters = format_hexadecimal(buffer_slice, type->integer.constant); // // backend->buffer.length = current_length + written_characters; // // } break; // // trap(); // // default: // // trap(); // // } // // } break; // // case IR_INTEGER_SUBSTRACT: // // { // // auto left = inputs.pointer[1]; // // auto right = inputs.pointer[2]; // // c_lower_node(backend, thread, left); // // c_lower_append_string(backend, strlit(" - ")); // // c_lower_node(backend, thread, right); // // } break; // // case IR_INTEGER_COMPARE_EQUAL: // // { // // auto left = inputs.pointer[1]; // // auto right = inputs.pointer[2]; // // c_lower_node(backend, thread, left); // // c_lower_append_string(backend, strlit(" == ")); // // c_lower_node(backend, thread, right); // // } break; // // case IR_INTEGER_COMPARE_NOT_EQUAL: // // { // // auto left = inputs.pointer[1]; // // auto right = inputs.pointer[2]; // // c_lower_node(backend, thread, left); // // c_lower_append_string(backend, strlit(" != ")); // // c_lower_node(backend, thread, right); // // } break; // // // case IR_PROJECTION: // // // { // // // auto projected_node_index = inputs.pointer[0]; // // // auto projection_index = node->projection.index; // // // // // // if (index_equal(projected_node_index, backend->function->start)) // // // { // // // if (projection_index == 0) // // // { // // // fail(); // // // } // // // // if (projection_index > interpreter->arguments.length + 1) // // // // { // // // // fail(); // // // // } // // // // // // switch (projection_index) // // // { // // // case 1: // // // c_lower_append_string(backend, strlit("argc")); // // // break; // // // // return interpreter->arguments.length; // // // case 2: // // // trap(); // // // default: // // // trap(); // // // } // // // } // // // else // // // { // // // trap(); // // // } // // // } break; // // default: // // trap(); // // } // todo(); // } // fn String c_lower(Thread* thread) // { // CBackend backend_stack = {}; // CBackend* backend = &backend_stack; // auto program_epilogue = strlit("#include \n" // "typedef uint8_t u8;\n" // "typedef uint16_t u16;\n" // "typedef uint32_t u32;\n" // "typedef uint64_t u64;\n" // "typedef int8_t s8;\n" // "typedef int16_t s16;\n" // "typedef int32_t s32;\n" // "typedef int64_t s64;\n" // ); // c_lower_append_string(backend, program_epilogue); // // for (u32 function_i = 0; function_i < thread->buffer.functions.length; function_i += 1) // { // auto* function = &thread->buffer.functions.pointer[function_i]; // backend->function = function; // c_lower_type(backend, thread, function->return_type); // c_lower_append_space(backend); // // c_lower_append_string(backend, function->name); // c_lower_append_ch(backend, argument_start); // if (s_equal(function->name, strlit("main"))) // { // c_lower_append_string(backend, strlit("int argc, char* argv[]")); // } // // c_lower_append_ch(backend, argument_end); // c_lower_append_ch(backend, '\n'); // c_lower_append_ch(backend, block_start); // c_lower_append_ch(backend, '\n'); // // auto start_node_index = function->start; // auto* start_node = thread_node_get(thread, start_node_index); // assert(start_node->output_count > 0); // auto stop_node_index = function->stop; // // auto proj_node_index = node_output_get(thread, start_node, 1); // auto it_node_index = proj_node_index; // u32 current_statement_margin = 1; // // while (!index_equal(it_node_index, stop_node_index)) // { // auto* it_node = thread_node_get(thread, it_node_index); // auto outputs = node_get_outputs(thread, it_node); // auto inputs = node_get_inputs(thread, it_node); // // switch (it_node->id) // { // // case IR_CONTROL_PROJECTION: // // break; // case IR_RETURN: // { // c_lower_append_space_margin(backend, current_statement_margin); // c_lower_append_string(backend, strlit("return ")); // assert(inputs.length > 1); // assert(inputs.length == 2); // auto input = inputs.pointer[1]; // c_lower_node(backend, thread, input); // c_lower_append_ch(backend, ';'); // c_lower_append_ch(backend, '\n'); // } break; // // case IR_STOP: // // break; // default: // todo(); // } // // assert(outputs.length == 1); // it_node_index = outputs.pointer[0]; // } // // c_lower_append_ch(backend, block_end); // } // // return (String) { .pointer = backend->buffer.pointer, .length = backend->buffer.length }; // } declare_ip_functions(DebugType, debug_type) fn void thread_init(Thread* thread) { memset(thread, 0, sizeof(Thread)); thread->arena = arena_init_default(KB(64)); thread->main_function = -1; // This assertion is here to make the pertinent changes in the reserve syscall // UINT32_MAX so they can be indexed via an unsigned integer of 32 bits const u64 offsets[] = { align_forward(sizeof(Type) * UINT32_MAX, page_size), align_forward(sizeof(Node) * UINT32_MAX, page_size), align_forward(sizeof(DebugType) * UINT32_MAX, page_size), align_forward(sizeof(NodeIndex) * UINT32_MAX, page_size), align_forward(sizeof(ArrayReference) * UINT32_MAX, page_size), align_forward(sizeof(Function) * UINT32_MAX, page_size), align_forward(sizeof(u8) * UINT32_MAX, page_size), align_forward(sizeof(RegisterMask) * UINT32_MAX, page_size), }; static_assert(sizeof(thread->buffer) / sizeof(VirtualBuffer(u8)) == array_length(offsets)); // Compute the total size (this is optimized out into a constant u64 total_size = 0; for (u32 i = 0; i < array_length(offsets); i += 1) { total_size += offsets[i]; } // Actually make the syscall auto* ptr = reserve(total_size); assert(ptr); auto* buffer_it = (VirtualBuffer(u8)*)&thread->buffer; for (u32 i = 0; i < array_length(offsets); i += 1) { buffer_it->pointer = ptr; ptr += offsets[i]; } DebugType integer_type; memset(&integer_type, 0, sizeof(u8)); auto* it = &thread->types.debug.integer.array[0]; for (u8 signedness = 0; signedness <= 1; signedness += 1) { integer_type.integer.signedness = signedness; for (u8 bit_count = 8; bit_count <= 64; bit_count *= 2, it += 1) { integer_type.integer.bit_count = bit_count; auto put_result = ip_DebugType_get_or_put_new(&thread->interned.debug_types, thread, &integer_type); assert(!put_result.existing); assert(validi(put_result.index)); *it = put_result.index; } } // Type top, bot, live_control, dead_control; // memset(&top, 0, sizeof(Type)); // top.id = TYPE_TOP; // memset(&bot, 0, sizeof(Type)); // bot.id = TYPE_BOTTOM; // 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, // }); // thread->types.integer.u8 = thread_get_integer_type(thread, (TypeInteger) { // .constant = 0, // .is_constant = 0, // .is_signed = 0, // .bit_count = 8, // }); // thread->types.integer.u16 = thread_get_integer_type(thread, (TypeInteger) { // .constant = 0, // .is_constant = 0, // .is_signed = 0, // .bit_count = 16, // }); // thread->types.integer.u32 = thread_get_integer_type(thread, (TypeInteger) { // .constant = 0, // .is_constant = 0, // .is_signed = 0, // .bit_count = 32, // }); // thread->types.integer.u64 = thread_get_integer_type(thread, (TypeInteger) { // .constant = 0, // .is_constant = 0, // .is_signed = 0, // .bit_count = 64, // }); // thread->types.integer.s8 = thread_get_integer_type(thread, (TypeInteger) { // .constant = 0, // .is_constant = 0, // .is_signed = 1, // .bit_count = 8, // }); // thread->types.integer.s16 = thread_get_integer_type(thread, (TypeInteger) { // .constant = 0, // .is_constant = 0, // .is_signed = 1, // .bit_count = 16, // }); // thread->types.integer.s32 = thread_get_integer_type(thread, (TypeInteger) { // .constant = 0, // .is_constant = 0, // .is_signed = 1, // .bit_count = 32, // }); // thread->types.integer.s64 = thread_get_integer_type(thread, (TypeInteger) { // .constant = 0, // .is_constant = 0, // .is_signed = 1, // .bit_count = 64, // }); *vb_add(&thread->buffer.register_masks, 1) = (RegisterMask) { .class = 1, .may_spill = 0, .mask = 0, }; *vb_add(&thread->buffer.register_masks, 1) = (RegisterMask) { .class = REGISTER_CLASS_X86_64_GPR, .may_spill = 0, .mask = ((u16)0xffff & ~((u16)1 << RSP)), // & ~((u16)1 << RBP), }; // global RegisterMask register_masks[] = { // { // }, // { // }, // }; } fn void thread_clear(Thread* thread) { arena_reset(thread->arena); } #define DO_UNIT_TESTS 1 #if DO_UNIT_TESTS 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); } } #endif Slice(String) arguments; typedef enum CompilerBackend : u8 { COMPILER_BACKEND_C = 'c', COMPILER_BACKEND_INTERPRETER = 'i', COMPILER_BACKEND_MACHINE = 'm', } CompilerBackend; struct Interpreter { Function* function; Slice(String) arguments; }; typedef struct Interpreter Interpreter; struct ELFOptions { char* object_path; char* exe_path; Slice(u8) code; }; typedef struct ELFOptions ELFOptions; struct ELFBuilder { VirtualBuffer(u8) file; VirtualBuffer(u8) string_table; VirtualBuffer(ELFSymbol) symbol_table; VirtualBuffer(ELFSectionHeader) section_table; }; typedef struct ELFBuilder ELFBuilder; fn u32 elf_builder_add_string(ELFBuilder* builder, String string) { u32 name_offset = 0; if (string.length) { name_offset = builder->string_table.length; vb_append_bytes(&builder->string_table, string); *vb_add(&builder->string_table, 1) = 0; } return name_offset; } fn void elf_builder_add_symbol(ELFBuilder* builder, ELFSymbol symbol, String string) { symbol.name_offset = elf_builder_add_string(builder, string); *vb_add(&builder->symbol_table, 1) = symbol; } fn void vb_align(VirtualBuffer(u8)* buffer, u64 alignment) { auto current_length = buffer->length; auto target_len = align_forward(current_length, alignment); auto count = cast(u32, u64, target_len - current_length); auto* pointer = vb_add(buffer, count); memset(pointer, 0, count); } fn ELFSectionHeader* elf_builder_add_section(ELFBuilder* builder, ELFSectionHeader section, String section_name, Slice(u8) content) { section.name_offset = elf_builder_add_string(builder, section_name); section.offset = builder->file.length; section.size = content.length; if (content.length) { vb_align(&builder->file, section.alignment); section.offset = builder->file.length; vb_append_bytes(&builder->file, content); } auto* section_header = vb_add(&builder->section_table, 1); *section_header = section; return section_header; } may_be_unused fn void write_elf(Thread* thread, char** envp, const ELFOptions* const options) { unused(thread); // { // 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); // } // { // char* command[] = { // clang_path, // "-c", // "main.c", // "-o", // "main.o", // "-Oz", // "-fno-exceptions", // "-fno-asynchronous-unwind-tables", // "-fno-addrsig", // "-fno-stack-protector", // "-fno-ident", // 0, // }; // run_command((CStringSlice) array_to_slice(command), envp); // } // // { // char* command[] = { // "/usr/bin/objcopy", // "--remove-section", // ".note.GNU-stack", // "main.o", // "main2.o", // 0, // }; // run_command((CStringSlice) array_to_slice(command), envp); // } // // { // // main_o = file_read(thread->arena, strlit("main2.o")); // auto r1 = syscall_unlink("main.o"); // assert(!r1); // auto r2 = syscall_unlink("main2.o"); // assert(!r2); // auto r3 = syscall_unlink("main.c"); // assert(!r3); // } ELFBuilder builder_stack = {}; ELFBuilder* builder = &builder_stack; auto* elf_header = (ELFHeader*)(vb_add(&builder->file, sizeof(ELFHeader))); // vb_append_bytes(&file, struct_to_bytes(elf_header)); // .symtab // Null symbol *vb_add(&builder->string_table, 1) = 0; elf_builder_add_symbol(builder, (ELFSymbol){}, (String){}); elf_builder_add_section(builder, (ELFSectionHeader) {}, (String){}, (Slice(u8)){}); assert(builder->string_table.length == 1); elf_builder_add_symbol(builder, (ELFSymbol){ .type = ELF_SYMBOL_TYPE_FILE, .binding = LOCAL, .section_index = (u16)ABSOLUTE, .value = 0, .size = 0, }, strlit("main.c")); assert(builder->string_table.length == 8); elf_builder_add_symbol(builder, (ELFSymbol) { .type = ELF_SYMBOL_TYPE_FUNCTION, .binding = GLOBAL, .section_index = 1, .value = 0, .size = 3, }, strlit("main")); elf_builder_add_section(builder, (ELFSectionHeader) { .type = ELF_SECTION_PROGRAM, .flags = { .alloc = 1, .executable = 1, }, .address = 0, .size = options->code.length, .link = 0, .info = 0, .alignment = 4, .entry_size = 0, }, strlit(".text"), options->code); elf_builder_add_section(builder, (ELFSectionHeader) { .type = ELF_SECTION_SYMBOL_TABLE, .link = builder->section_table.length + 1, // TODO: One greater than the symbol table index of the last local symbol (binding STB_LOCAL). .info = builder->symbol_table.length - 1, .alignment = alignof(ELFSymbol), .entry_size = sizeof(ELFSymbol), }, strlit(".symtab"), vb_to_bytes(builder->symbol_table)); auto strtab_name_offset = elf_builder_add_string(builder, strlit(".strtab")); auto strtab_bytes = vb_to_bytes(builder->string_table); auto strtab_offset = builder->file.length; vb_append_bytes(&builder->file, strtab_bytes); auto* strtab_section_header = vb_add(&builder->section_table, 1); *strtab_section_header = (ELFSectionHeader) { .name_offset = strtab_name_offset, .type = ELF_SECTION_STRING_TABLE, .offset = strtab_offset, .size = strtab_bytes.length, .alignment = 1, }; vb_align(&builder->file, alignof(ELFSectionHeader)); auto section_header_offset = builder->file.length; vb_append_bytes(&builder->file, vb_to_bytes(builder->section_table)); *elf_header = (ELFHeader) { .identifier = { 0x7f, 'E', 'L', 'F' }, .bit_count = bits64, .endianness = little, .format_version = 1, .abi = system_v_abi, .abi_version = 0, .padding = {}, .type = relocatable, .machine = x86_64, .version = 1, .entry_point = 0, .program_header_offset = 0, .section_header_offset = section_header_offset, .flags = 0, .elf_header_size = sizeof(ELFHeader), .program_header_size = 0, .program_header_count = 0, .section_header_size = sizeof(ELFSectionHeader), .section_header_count = cast(u16, u64, builder->section_table.length), .section_header_string_table_index = cast(u16, u64, builder->section_table.length - 1), }; auto object_path_z = options->object_path; { int fd = syscall_open(object_path_z, O_WRONLY | O_CREAT | O_TRUNC, 0644); assert(fd != -1); syscall_write(fd, builder->file.pointer, builder->file.length); syscall_close(fd); } char* command[] = { clang_path, object_path_z, "-o", options->exe_path, 0, }; run_command((CStringSlice) array_to_slice(command), envp); } void subsume_node_without_killing(Thread* thread, NodeIndex old_node_index, NodeIndex new_node_index) { assert(!index_equal(old_node_index, new_node_index)); auto* old = thread_node_get(thread, old_node_index); auto* new = thread_node_get(thread, new_node_index); auto old_node_outputs = node_get_outputs(thread, old); u8 allow_cycle = old->id == IR_PHI || old->id == IR_REGION || new->id == IR_REGION; for (auto i = old->output_count; i > 0; i -= 1) { auto output = old_node_outputs.pointer[i - 1]; old->output_count -= 1; if (!allow_cycle && index_equal(output, new_node_index)) { continue; } auto* output_node = thread_node_get(thread, output); auto output_inputs = node_get_inputs(thread, output_node); u16 output_input_index; for (output_input_index = 0; output_input_index < output_inputs.length; output_input_index += 1) { auto output_input = output_inputs.pointer[output_input_index]; if (index_equal(output_input, old_node_index)) { output_inputs.pointer[output_input_index] = new_node_index; node_add_output(thread, new_node_index, output); break; } } assert(output_input_index < output_inputs.length); } } fn NodeIndex function_get_control_start(Thread* thread, Function* function) { auto* root = thread_node_get(thread, function->root); auto outputs = node_get_outputs(thread, root); auto result = outputs.pointer[0]; return result; } fn u8 cfg_is_control(Thread* thread, NodeIndex node_index) { auto* node = thread_node_get(thread, node_index); auto backend_type = type_pair_get_backend(node->type); if (backend_type == BACKEND_TYPE_CONTROL) { return 1; } else if (backend_type == BACKEND_TYPE_TUPLE) { if (node->id == IR_ROOT) { return 1; } else { todo(); } } return 0; } fn u8 cfg_node_terminator(Node* node) { u8 is_terminator; switch (node->id) { case IR_PROJECTION: case IR_REGION: is_terminator = 0; break; case IR_RETURN: case IR_ROOT: is_terminator = 1; break; default: todo(); } return is_terminator; } fn NodeIndex basic_block_end(Thread* thread, NodeIndex start_index) { auto node_index = start_index; while (1) { auto* node = thread_node_get(thread, node_index); u8 is_terminator = cfg_node_terminator(node); if (is_terminator) { break; } auto outputs = node_get_outputs(thread, node); auto new_node_index = node_index; for (u16 i = 0; i < outputs.length; i += 1) { auto output_index = outputs.pointer[i]; auto* output = thread_node_get(thread, output_index); auto output_inputs = node_get_inputs(thread, output); if (index_equal(output_inputs.pointer[0], node_index) && cfg_is_control(thread, output_index)) { if (output->id == IR_REGION) { return node_index; } new_node_index = output_index; break; } } if (index_equal(node_index, new_node_index)) { break; } node_index = new_node_index; } return node_index; } struct Block { NodeIndex start; NodeIndex end; NodeIndex successors[2]; u32 successor_count; struct Block* parent; }; typedef struct Block Block; fn NodeIndex cfg_next_control(Thread* thread, NodeIndex node_index) { auto* node = thread_node_get(thread, node_index); auto outputs = node_get_outputs(thread, node); for (u16 i = 0; i < outputs.length; i += 1) { auto output_index = outputs.pointer[i]; if (cfg_is_control(thread, output_index)) { return output_index; } } return invalidi(Node); } fn NodeIndex cfg_next_user(Thread* thread, NodeIndex node_index) { auto* node = thread_node_get(thread, node_index); auto outputs = node_get_outputs(thread, node); for (u16 i = 0; i < outputs.length; i += 1) { auto output_index = outputs.pointer[i]; if (cfg_is_control(thread, output_index)) { return output_index; } } return invalidi(Node); } fn u8 cfg_is_endpoint(Thread* thread, Node* node) { unused(thread); switch (node->id) { case IR_ROOT: case IR_RETURN: return 1; default: return 0; } } fn Block* create_block(Thread* thread, NodeIndex node_index) { auto end_of_basic_block_index = basic_block_end(thread, node_index); auto* end_node = thread_node_get(thread, end_of_basic_block_index); u32 successor_count = 0; // Branch auto is_endpoint = cfg_is_endpoint(thread, end_node); auto is_branch = 0; if (is_branch) { todo(); } else if (type_pair_get_backend(end_node->type) == BACKEND_TYPE_TUPLE) { todo(); } else if (!is_endpoint) { successor_count = 1; } auto* block = arena_allocate(thread->arena, Block, 1); *block = (Block) { .start = node_index, .end = end_of_basic_block_index, .successor_count = successor_count, }; if (node_is_cfg_fork(end_node)) { todo(); } else if (!is_endpoint) { block->successors[0] = cfg_next_user(thread, end_of_basic_block_index); } return block; } fn NodeIndex node_select_instruction(Thread* thread, NodeIndex node_index) { auto* node = thread_node_get(thread, node_index); switch (node->id) { case IR_PROJECTION: return node_index; case IR_ROOT: return node_index; case IR_PHI: { auto backend_type = type_pair_get_backend(node->type); if (backend_type <= BACKEND_TYPE_SCALAR_LAST) { { auto copy_index = thread_node_add(thread, (NodeCreate) { .id = MACHINE_COPY, .type_pair = node->type, .inputs = array_to_slice(((NodeIndex[]) { invalidi(Node), invalidi(Node), })), }); thread_node_get(thread, copy_index)->machine_copy = (NodeMachineCopy) { .use_mask = Index(RegisterMask, REGISTER_MASK_GPR), .def_mask = Index(RegisterMask, REGISTER_MASK_GPR), }; subsume_node_without_killing(thread, node_index, copy_index); node_set_input(thread, copy_index, 1, node_index); node_gvn_intern(thread, copy_index); } { auto inputs = node_get_inputs(thread, node); for (u16 i = 1; i < inputs.length; i += 1) { auto input_index = inputs.pointer[i]; auto input = thread_node_get(thread, input_index); assert(input->id != MACHINE_MOVE); auto move_index = thread_node_add(thread, (NodeCreate) { .id = MACHINE_MOVE, .type_pair = input->type, .inputs = array_to_slice(((NodeIndex[]) { invalidi(Node), invalidi(Node), })), }); node_set_input(thread, move_index, 1, input_index); node_set_input(thread, node_index, i, move_index); node_gvn_intern(thread, move_index); } } } return node_index; } break; case IR_RETURN: case IR_REGION: case IR_INTEGER_CONSTANT: case MACHINE_MOVE: case MACHINE_COPY: return invalidi(Node); default: todo(); } } struct BasicBlockIndex { u32 index; }; typedef struct BasicBlockIndex BasicBlockIndex; decl_vb(BasicBlockIndex); struct BasicBlock { VirtualBuffer(NodeIndex) items; Bitset gen; Bitset kill; Bitset live_in; Bitset live_out; NodeIndex start; NodeIndex end; s32 dominator_depth; BasicBlockIndex dominator; s32 forward; }; typedef struct BasicBlock BasicBlock; decl_vb(BasicBlock); decl_vbp(BasicBlock); fn u8 node_is_pinned(Node* node) { switch (node->id) { case IR_ROOT: case IR_PROJECTION: case IR_RETURN: case IR_REGION: case IR_PHI: return 1; case IR_SYMBOL_TABLE: case IR_INTEGER_ADD: case IR_INTEGER_SUBSTRACT: case IR_INTEGER_MULTIPLY: case IR_INTEGER_DIVIDE: case IR_INTEGER_REMAINDER: case IR_INTEGER_SHIFT_LEFT: case IR_INTEGER_SHIFT_RIGHT: case IR_INTEGER_AND: case IR_INTEGER_OR: case IR_INTEGER_XOR: case IR_INTEGER_COMPARE_EQUAL: case IR_INTEGER_COMPARE_NOT_EQUAL: case IR_INTEGER_NEGATION: case IR_INTEGER_CONSTANT: case MACHINE_COPY: case MACHINE_MOVE: case NODE_COUNT: return 0; case MACHINE_JUMP: todo(); } } fn u8 node_has_memory_out(NodeId id) { switch (id) { case IR_ROOT: case IR_PROJECTION: case IR_RETURN: case IR_REGION: case IR_PHI: case IR_SYMBOL_TABLE: case IR_INTEGER_ADD: case IR_INTEGER_SUBSTRACT: case IR_INTEGER_MULTIPLY: case IR_INTEGER_DIVIDE: case IR_INTEGER_REMAINDER: case IR_INTEGER_SHIFT_LEFT: case IR_INTEGER_SHIFT_RIGHT: case IR_INTEGER_AND: case IR_INTEGER_OR: case IR_INTEGER_XOR: case IR_INTEGER_COMPARE_EQUAL: case IR_INTEGER_COMPARE_NOT_EQUAL: case IR_INTEGER_NEGATION: case IR_INTEGER_CONSTANT: case MACHINE_COPY: case MACHINE_MOVE: case NODE_COUNT: return 0; case MACHINE_JUMP: todo(); } } fn u8 node_has_memory_in(NodeId id) { switch (id) { case IR_ROOT: case IR_RETURN: return 1; case IR_PROJECTION: case IR_REGION: case IR_PHI: case IR_SYMBOL_TABLE: case IR_INTEGER_ADD: case IR_INTEGER_SUBSTRACT: case IR_INTEGER_MULTIPLY: case IR_INTEGER_DIVIDE: case IR_INTEGER_REMAINDER: case IR_INTEGER_SHIFT_LEFT: case IR_INTEGER_SHIFT_RIGHT: case IR_INTEGER_AND: case IR_INTEGER_OR: case IR_INTEGER_XOR: case IR_INTEGER_COMPARE_EQUAL: case IR_INTEGER_COMPARE_NOT_EQUAL: case IR_INTEGER_NEGATION: case IR_INTEGER_CONSTANT: case MACHINE_COPY: case MACHINE_MOVE: case NODE_COUNT: return 0; case MACHINE_JUMP: todo(); } } fn NodeIndex node_memory_in(Thread* thread, Node* node) { auto result = invalidi(Node); if (node_has_memory_in(node->id)) { result = node_get_inputs(thread, node).pointer[1]; } return result; } fn s32 node_last_use_in_block(Thread* thread, VirtualBuffer(BasicBlockIndex) scheduled, Slice(s32) order, Node* node, BasicBlockIndex basic_block_index) { auto outputs = node_get_outputs(thread, node); s32 result = 0; for (u16 i = 0; i < node->output_count; i += 1) { auto output_index = outputs.pointer[i]; if (index_equal(basic_block_index, scheduled.pointer[geti(output_index)]) && result < order.pointer[geti(output_index)]) { result = order.pointer[geti(output_index)]; } } return result; } fn BasicBlockIndex find_use_block(Thread* thread, VirtualBuffer(BasicBlockIndex) scheduled, NodeIndex node_index, NodeIndex actual_node_index, NodeIndex use_index) { auto use_block_index = scheduled.pointer[geti(use_index)]; if (!validi(use_block_index)) { return use_block_index; } Node* use = thread_node_get(thread, use_index); auto use_inputs = node_get_inputs(thread, use); if (use->id == IR_PHI) { auto use_first_input_index = use_inputs.pointer[0]; auto use_first_input = thread_node_get(thread, use_first_input_index); assert(use_first_input->id == IR_REGION); assert(use->input_count == use_first_input->input_count + 1); auto use_first_input_inputs = node_get_inputs(thread, use_first_input); u16 i; for (i = 0; i < use_inputs.length; i += 1) { auto use_input_index = use_inputs.pointer[i]; if (index_equal(use_input_index, actual_node_index)) { // TODO: this assertion is mine for debugging when this function is only called from a single code path, // it's not absolutely valid in other contexts assert(index_equal(actual_node_index, node_index)); auto input_index = use_first_input_inputs.pointer[i - 1]; auto bb_index = scheduled.pointer[geti(input_index)]; if (validi(bb_index)) { use_block_index = bb_index; } break; } } assert(i < use_inputs.length); } return use_block_index; } fn BasicBlockIndex find_lca(BasicBlockIndex a, BasicBlockIndex b) { unused(a); unused(b); // TODO: dominators return invalidi(BasicBlock); } fn u8 node_is_ready(Thread* thread, VirtualBuffer(BasicBlockIndex) scheduled, WorkListHandle handle, Node* node, BasicBlockIndex basic_block_index) { // TODO: this is my assert and might not be true after all assert(node->input_capacity == node->input_count); auto inputs = node_get_inputs(thread, node); for (u16 i = 0; i < node->input_capacity; i += 1) { auto input = inputs.pointer[i]; if (validi(input) && index_equal(scheduled.pointer[geti(input)], basic_block_index) && !thread_worklist_test(thread, handle, input)) { return 0; } } return 1; } fn u64 node_get_latency(Thread* thread, Node* node, Node* end) { unused(end); unused(thread); switch (node->id) { case IR_INTEGER_CONSTANT: case IR_RETURN: case MACHINE_COPY: return 1; case MACHINE_MOVE: return 0; default: todo(); } } fn u64 node_get_unit_mask(Thread* thread, Node* node) { unused(thread); unused(node); return 1; } struct ReadyNode { u64 unit_mask; NodeIndex node_index; s32 priority; }; typedef struct ReadyNode ReadyNode; decl_vb(ReadyNode); struct InFlightNode { NodeIndex node_index; u32 end; s32 unit_i; }; typedef struct InFlightNode InFlightNode; decl_vb(InFlightNode); struct Scheduler { Bitset ready_set; VirtualBuffer(ReadyNode) ready; NodeIndex cmp; }; typedef struct Scheduler Scheduler; fn s32 node_best_ready(Scheduler* restrict scheduler, u64 in_use_mask) { auto length = scheduler->ready.length; if (length == 1) { u64 available = scheduler->ready.pointer[0].unit_mask & ~in_use_mask; return available ? 0 : -1; } while (length--) { auto node_index = scheduler->ready.pointer[length].node_index; if (index_equal(node_index, scheduler->cmp)) { continue; } auto available = scheduler->ready.pointer[length].unit_mask & ~in_use_mask; if (available == 0) { continue; } return cast(s32, u32, length); } return -1; } declare_ip_functions(RegisterMask, register_mask) fn RegisterMaskIndex register_mask_intern(Thread* thread, RegisterMask register_mask) { auto* new_rm = vb_add(&thread->buffer.register_masks, 1); *new_rm = register_mask; auto candidate_index = Index(RegisterMask, cast(u32, s64, new_rm - thread->buffer.register_masks.pointer)); auto result = ip_RegisterMask_get_or_put(&thread->interned.register_masks, thread, candidate_index); auto final_index = result.index; assert((!index_equal(candidate_index, final_index)) == result.existing); thread->buffer.register_masks.length -= result.existing; return final_index; } fn RegisterMaskIndex node_constraint(Thread* thread, Node* node, Slice(RegisterMaskIndex) ins) { switch (node->id) { case IR_PROJECTION: { auto backend_type = type_pair_get_backend(node->type); if (backend_type == BACKEND_TYPE_MEMORY || backend_type == BACKEND_TYPE_CONTROL) { return empty_register_mask; } auto index = node->projection.index; auto inputs = node_get_inputs(thread, node); auto* first_input = thread_node_get(thread, inputs.pointer[0]); if (first_input->id == IR_ROOT) { assert(index >= 2); if (index == 2) { return empty_register_mask; } else { todo(); } todo(); } else { todo(); } } break; case IR_INTEGER_CONSTANT: return Index(RegisterMask, REGISTER_MASK_GPR); case MACHINE_MOVE: { // TODO: float auto mask = Index(RegisterMask, REGISTER_MASK_GPR); if (ins.length) { ins.pointer[1] = mask; } return mask; } break; case MACHINE_COPY: { if (ins.length) { ins.pointer[1] = node->machine_copy.use_mask; } return node->machine_copy.def_mask; } break; case IR_REGION: { if (ins.length) { for (u16 i = 1; i < node->input_count; i += 1) { ins.pointer[i] = empty_register_mask; } } return empty_register_mask; } break; case IR_PHI: { if (ins.length) { for (u16 i = 1; i < node->input_count; i += 1) { ins.pointer[i] = empty_register_mask; } } auto backend_type = type_pair_get_backend(node->type); RegisterMaskIndex mask; if (backend_type == BACKEND_TYPE_MEMORY) { mask = empty_register_mask; } // TODO: float else { mask = Index(RegisterMask, REGISTER_MASK_GPR); } return mask; } break; case IR_RETURN: { if (ins.length) { const global s32 ret_gprs[] = { RAX, RDX }; ins.pointer[1] = empty_register_mask; ins.pointer[2] = empty_register_mask; // TODO: returns auto index = 3; ins.pointer[index] = register_mask_intern(thread, (RegisterMask) { .class = REGISTER_CLASS_X86_64_GPR, .may_spill = 0, .mask = ((u32)1 << ret_gprs[index - 3]), }); auto gpr_caller_saved = ((1u << RAX) | (1u << RDI) | (1u << RSI) | (1u << RCX) | (1u << RDX) | (1u << R8) | (1u << R9) | (1u << R10) | (1u << R11)); auto gpr_callee_saved = ~gpr_caller_saved; gpr_callee_saved &= ~(1u << RSP); gpr_callee_saved &= ~(1u << RBP); auto j = 3 + 1; for (u32 i = 0; i < register_count_per_class[REGISTER_CLASS_X86_64_GPR]; i += 1) { if ((gpr_callee_saved >> i) & 1) { ins.pointer[j++] = register_mask_intern(thread, (RegisterMask) { .class = REGISTER_CLASS_X86_64_GPR, .mask = (u32)1 << i, .may_spill = 0, }); } } // TODO: float } return empty_register_mask; } break; default: todo(); } } fn u32 node_tmp_count(Node* node) { switch (node->id) { case IR_ROOT: case IR_PROJECTION: case IR_RETURN: case IR_REGION: case IR_PHI: case IR_SYMBOL_TABLE: case IR_INTEGER_ADD: case IR_INTEGER_SUBSTRACT: case IR_INTEGER_MULTIPLY: case IR_INTEGER_DIVIDE: case IR_INTEGER_REMAINDER: case IR_INTEGER_SHIFT_LEFT: case IR_INTEGER_SHIFT_RIGHT: case IR_INTEGER_AND: case IR_INTEGER_OR: case IR_INTEGER_XOR: case IR_INTEGER_COMPARE_EQUAL: case IR_INTEGER_COMPARE_NOT_EQUAL: case IR_INTEGER_NEGATION: case IR_INTEGER_CONSTANT: case MACHINE_COPY: case MACHINE_MOVE: case NODE_COUNT: return 0; case MACHINE_JUMP: todo(); } } struct VirtualRegister { RegisterMaskIndex mask; NodeIndex node_index; f32 spill_cost; f32 spill_bias; s16 class; s16 assigned; s32 hint_vreg; }; typedef struct VirtualRegister VirtualRegister; decl_vb(VirtualRegister); fn s32 fixed_register_mask(RegisterMask mask) { if (mask.class == REGISTER_CLASS_STACK) { todo(); } else { s32 set = -1; // TODO: count? for (s32 i = 0; i < 1; i += 1) { u32 m = mask.mask; s32 found = 32 - __builtin_clz(m); if (m == ((u32)1 << found)) { if (set >= 0) { return -1; } set = i * 64 + found; } } return set; } } fn RegisterMaskIndex register_mask_meet(Thread* thread, RegisterMaskIndex a_index, RegisterMaskIndex b_index) { if (index_equal(a_index, b_index)) { return a_index; } if (!validi(a_index)) { return b_index; } if (!validi(b_index)) { return a_index; } auto* a = thread_register_mask_get(thread, a_index); auto* b = thread_register_mask_get(thread, b_index); u64 may_spill = a->may_spill && b->may_spill; if (!may_spill && a->class != b->class) { return empty_register_mask; } auto a_mask = a->mask; auto b_mask = b->mask; auto mask = a_mask & b_mask; auto result = register_mask_intern(thread, (RegisterMask) { .class = mask == 0 ? 1 : a->class, .may_spill = may_spill, .mask = mask, }); return result; } fn s32 node_to_address(Thread* thread, NodeIndex node_index) { auto* node = thread_node_get(thread, node_index); switch (node->id) { case IR_PHI: case IR_INTEGER_CONSTANT: return -1; case MACHINE_COPY: return 1; default: todo(); } } fn u8 interfere_in_block(Thread* thread, VirtualBuffer(BasicBlockIndex) scheduled, VirtualBuffer(BasicBlock) bb, Slice(s32) order, NodeIndex left, NodeIndex right, BasicBlockIndex block_index) { assert(!index_equal(left, right)); auto* block = &bb.pointer[geti(block_index)]; auto left_live_out = bitset_get(&block->live_out, geti(left)); auto right_live_out = bitset_get(&block->live_out, geti(right)); auto* left_node = thread_node_get(thread, left); auto* right_node = thread_node_get(thread, right); if (left_node->id == IR_PHI || right_node->id == IR_PHI) { auto phi = right; auto other = left; if (left_node->id == IR_PHI && right_node->id != IR_PHI) { phi = left; other = right; } block_index = scheduled.pointer[geti(phi)]; block = &bb.pointer[geti(block_index)]; if (bitset_get(&block->live_out, geti(phi))) { todo(); } } if (left_live_out && right_live_out) { todo(); } else if (!left_live_out && !right_live_out) { auto first = left; auto last = right; if (order.pointer[geti(left)] > order.pointer[geti(right)]) { first = right; last = left; } block_index = scheduled.pointer[geti(last)]; block = &bb.pointer[geti(block_index)]; auto* first_node = thread_node_get(thread, first); auto outputs = node_get_outputs(thread, first_node); for (u16 i = 0; i < first_node->output_count; i += 1) { auto output_index = outputs.pointer[i]; assert(validi(output_index)); auto* output_node = thread_node_get(thread, output_index); auto output_inputs = node_get_inputs(thread, output_node); u16 i; for (i = 0; i < output_node->input_count; i += 1) { auto input_index = output_inputs.pointer[i]; if (index_equal(input_index, first)) { if (index_equal(block_index, scheduled.pointer[geti( output_index)])) { if (order.pointer[geti(output_index)] > order.pointer[geti(last)]) { return 1; } } break; } } assert(i < output_node->input_count); } } else { todo(); } return 0; } fn u8 interfere(Thread* thread, VirtualBuffer(BasicBlockIndex) scheduled, VirtualBuffer(BasicBlock) bb, Slice(s32) order, NodeIndex left, NodeIndex right) { auto left_block = scheduled.pointer[geti(left)]; auto right_block = scheduled.pointer[geti(right)]; // These asserts are mine, they might not be valid assert(validi(left_block)); assert(validi(right_block)); auto result = interfere_in_block(thread, scheduled, bb, order, left, right, left_block); if (!index_equal(left_block, right_block)) { result = result || interfere_in_block(thread, scheduled, bb, order, right, left, right_block); } return result; } fn Slice(s32) compute_ordinals(Thread* thread, VirtualBuffer(BasicBlock) bb, u32 node_count) { auto order_cap = round_up_to_next_power_of_2(node_count); auto order = arena_allocate(thread->arena, s32, order_cap); for (u32 i = 0; i < bb.length; i += 1) { auto* basic_block = & bb.pointer[i]; s32 timeline = 1; for (u32 i = 0; i < basic_block->items.length; i += 1) { auto node_index = basic_block->items.pointer[i]; order[geti(node_index)] = timeline; timeline += 1; } } return (Slice(s32)) { .pointer = order, .length = order_cap, }; } fn u8 can_remat(Thread* thread, NodeIndex node_index) { auto* node = thread_node_get(thread, node_index); switch (node->id) { case MACHINE_COPY: return 1; default: todo(); } } fn f32 get_spill_cost(Thread* thread, VirtualRegister* virtual_register) { auto spill_cost = virtual_register->spill_cost; if (__builtin_isnan(spill_cost)) { if (can_remat(thread, virtual_register->node_index)) { spill_cost = virtual_register->spill_bias - 1.0f; } else { todo(); } virtual_register->spill_cost = spill_cost; } return spill_cost; } fn u8 register_mask_not_empty(RegisterMask mask) { return mask.mask != 0; } fn u8 register_mask_spill(RegisterMask mask) { return mask.class != REGISTER_CLASS_STACK && (!register_mask_not_empty(mask) && mask.may_spill); } fn void dataflow(Thread* thread, WorkListHandle worker, VirtualBuffer(BasicBlock) bb, VirtualBuffer(BasicBlockIndex) scheduled, u32 node_count) { // Dataflow analysis thread_worklist_clear(thread, worker); // TODO: separate per function for (u32 i = 0; i < bb.length; i += 1) { BasicBlock* basic_block = &bb.pointer[i]; bitset_clear(&basic_block->gen); bitset_clear(&basic_block->kill); bitset_ensure_length(&basic_block->gen, node_count); bitset_ensure_length(&basic_block->kill, node_count); } for (u32 i = 0; i < bb.length; i += 1) { BasicBlock* basic_block = &bb.pointer[i]; for (u32 i = 0; i < basic_block->items.length; i += 1) { NodeIndex node_index = basic_block->items.pointer[i]; Node* node = thread_node_get(thread, node_index); if (node->id == IR_PHI) { auto phi_inputs = node_get_inputs(thread, node); for (u16 i = 1; i < phi_inputs.length; i += 1) { auto input = phi_inputs.pointer[i]; if (validi(input)) { auto input_bb_index = scheduled.pointer[geti(input)]; bitset_set_value(&bb.pointer[geti(input_bb_index)].kill, geti(node_index), 1); } } } else { bitset_set_value(&basic_block->kill, geti(node_index), 1); } } } for (u32 i = 0; i < bb.length; i += 1) { BasicBlock* basic_block = &bb.pointer[i]; for (u32 i = 0; i < basic_block->items.length; i += 1) { NodeIndex node_index = basic_block->items.pointer[i]; Node* node = thread_node_get(thread, node_index); if (node->id != IR_PHI) { auto inputs = node_get_inputs(thread, node); for (u16 i = 1; i < inputs.length; i += 1) { auto input_index = inputs.pointer[i]; if (validi(input_index)) { auto* input = thread_node_get(thread, input_index); if (input->id == IR_PHI || !bitset_get(&basic_block->kill, geti(input_index))) { bitset_set_value(&basic_block->gen, geti(input_index), 1); } } } } } } thread_worklist_clear(thread, worker); for (u32 i = 0; i < bb.length; i += 1) { BasicBlock* basic_block = &bb.pointer[i]; assert(basic_block->gen.arr.length == basic_block->live_in.arr.length); assert(basic_block->gen.arr.capacity == basic_block->live_in.arr.capacity); memcpy(basic_block->live_in.arr.pointer, basic_block->gen.arr.pointer, sizeof(basic_block->gen.arr.pointer[0]) * basic_block->gen.arr.length); basic_block->live_in.length = basic_block->gen.length; thread_worklist_push(thread, worker, basic_block->start); } while (thread_worklist_length(thread, worker) > 0) { auto bb_node_index = thread_worklist_pop(thread, worker); auto basic_block_index = scheduled.pointer[geti(bb_node_index)]; BasicBlock* basic_block = &bb.pointer[geti(basic_block_index)]; auto* live_out = &basic_block->live_out; auto* live_in = &basic_block->live_in; bitset_clear(live_out); auto end_index = basic_block->end; auto* end = thread_node_get(thread, end_index); auto cfg_is_fork = 0; if (cfg_is_fork) { todo(); } else if (!cfg_is_endpoint(thread, end)) { auto succ_index = cfg_next_control(thread, end_index); auto succ_bb_index = scheduled.pointer[geti(succ_index)]; auto succ_bb = &bb.pointer[geti(succ_bb_index)]; assert(live_out->arr.capacity == live_in->arr.capacity); u64 changes = 0; for (u32 i = 0; i < succ_bb->live_in.arr.capacity; i += 1) { auto old = live_out->arr.pointer[i]; auto new = old | succ_bb->live_in.arr.pointer[i]; live_out->arr.pointer[i] = new; changes |= (old ^ new); } unused(changes); } auto* gen = &basic_block->gen; auto* kill = &basic_block->kill; auto changes = 0; for (u32 i = 0; i < kill->arr.length; i += 1) { u64 new_in = (live_out->arr.pointer[i] & ~kill->arr.pointer[i]) | gen->arr.pointer[i]; changes |= live_in->arr.pointer[i] != new_in; live_in->arr.pointer[i] = new_in; } if (changes) { todo(); } } } fn void redo_dataflow(Thread* thread, WorkListHandle worker, VirtualBuffer(BasicBlock) bb, VirtualBuffer(BasicBlockIndex) scheduled, u32 node_count) { for (u32 i = 0; i < bb.length; i += 1) { BasicBlock* basic_block = &bb.pointer[i]; bitset_clear(&basic_block->gen); bitset_clear(&basic_block->kill); bitset_ensure_length(&basic_block->gen, node_count); bitset_ensure_length(&basic_block->kill, node_count); } dataflow(thread, worker, bb, scheduled, node_count); } fn String gpr_to_string(GPR gpr) { switch (gpr) { case_to_name(, RAX); case_to_name(, RCX); case_to_name(, RDX); case_to_name(, RBX); case_to_name(, RSP); case_to_name(, RBP); case_to_name(, RSI); case_to_name(, RDI); case_to_name(, R8); case_to_name(, R9); case_to_name(, R10); case_to_name(, R11); case_to_name(, R12); case_to_name(, R13); case_to_name(, R14); case_to_name(, R15); case_to_name(, GPR_NONE); } } fn u8 register_allocate(Thread* thread, VirtualBuffer(VirtualRegister) virtual_registers, VirtualBuffer(s32)* spills, Bitset* active, Bitset* future_active, VirtualBuffer(BasicBlockIndex) scheduled, VirtualBuffer(BasicBlock) bb, Slice(s32) order, u32 virtual_register_id, u32 in_use) { if (bitset_get(future_active, virtual_register_id)) { todo(); } auto* virtual_register = &virtual_registers.pointer[virtual_register_id]; auto mask = thread_register_mask_get(thread, virtual_register->mask); if (virtual_register->assigned >= 0) { bitset_set_value(active, virtual_register_id, 1); return 1; } else if (register_mask_spill(*mask)) { todo(); } else if (mask->class == REGISTER_CLASS_STACK) { todo(); } auto mask_value = mask->mask; auto old_in_use = in_use; in_use |= ~mask_value; print("Vreg mask: {u32:x}. Complement: {u32:x}. In use before: {u32:x}. In use after: {u32:x}\n", mask_value, ~mask_value, old_in_use, in_use); spills->length = 0; *vb_add(spills, 1) = virtual_register_id; FOREACH_SET(i, active) { print("Active[{u64}] set\n", i); VirtualRegister* other = &virtual_registers.pointer[i]; if (other->class == mask->class) { print("Interfere with active: {u32}\n", (s32)other->assigned); in_use |= ((u32)1 << other->assigned); *vb_add(spills, 1) = i; } } FOREACH_SET(i, future_active) { print("Future active[{u64}] set\n", i); VirtualRegister* other = &virtual_registers.pointer[i]; if (other->class == mask->class && (in_use & ((u32)1 << other->assigned)) == 0) { if (interfere(thread, scheduled, bb, order, virtual_register->node_index, other->node_index)) { todo(); } } } NodeIndex node_index = virtual_register->node_index; auto hint_vreg = virtual_register->hint_vreg; auto shared_edge = node_to_address(thread, node_index); if (shared_edge >= 0) { todo(); } if (in_use == UINT32_MAX) { return 0; } virtual_register->class = mask->class; auto hint_virtual_register = virtual_registers.pointer[hint_vreg]; s32 hint_reg = hint_vreg > 0 && hint_virtual_register.class == mask->class ? hint_virtual_register.assigned : -1; print("IN USE: {u32:x}: ~ -> {u32:x}\n", in_use, ~in_use); if (hint_reg >= 0 && (in_use & ((u64)1 << hint_reg)) == 0) { todo(); } else { virtual_register->assigned = __builtin_ffsll(~in_use) - 1; print("Register assigned: {s}\n", gpr_to_string(virtual_register->assigned)); } bitset_set_value(active, virtual_register_id, 1); return 1; } fn s32 machine_operand_at(u32* virtual_register_map, VirtualBuffer(VirtualRegister) virtual_registers, NodeIndex node_index, s32 class) { assert(validi(node_index)); auto virtual_register_id = virtual_register_map[geti(node_index)]; assert(virtual_register_id > 0); assert(virtual_register_id < virtual_registers.length); auto* virtual_register = &virtual_registers.pointer[virtual_register_id]; assert(virtual_register->assigned >= 0); assert(virtual_register->class == class); return virtual_register->assigned; } typedef enum MachineOperandId : u8 { MACHINE_OPERAND_MEMORY, MACHINE_OPERAND_GPR, MACHINE_OPERAND_XMM, } MachineOperandId; struct MachineOperand { MachineOperandId id; s16 register_value; }; typedef struct MachineOperand MachineOperand; fn MachineOperand operand_from_node(Thread* thread, VirtualBuffer(VirtualRegister) virtual_registers, u32* virtual_register_map, NodeIndex node_index) { assert(validi(node_index)); auto virtual_register_id = virtual_register_map[geti(node_index)]; assert(virtual_register_id > 0); auto* virtual_register = &virtual_registers.pointer[virtual_register_id]; if (virtual_register->class == REGISTER_CLASS_STACK) { todo(); } else { assert(virtual_register->assigned >= 0); MachineOperandId id; switch (virtual_register->class) { case REGISTER_CLASS_X86_64_GPR: id = MACHINE_OPERAND_GPR; break; default: todo(); } return (MachineOperand) { .id = id, .register_value = virtual_register->assigned, }; } todo(); } fn void node_ready_up(Thread* thread, Scheduler* scheduler, NodeIndex node_index, Node* end) { auto* node = thread_node_get(thread, node_index); auto priority = node_get_latency(thread, node, end); auto unit_mask = node_get_unit_mask(thread, node); bitset_set_value(&scheduler->ready_set, geti(node_index), 1); if (type_pair_get_backend(node->type) == BACKEND_TYPE_TUPLE) { todo(); } u32 i; auto count = scheduler->ready.length; for (i = 0; i < count; i += 1) { if (cast(s32, u64, priority) < scheduler->ready.pointer[i].priority) { break; } } *vb_add(&scheduler->ready, 1) = (ReadyNode){}; memmove(&scheduler->ready.pointer[i + 1], &scheduler->ready.pointer[i], (count - i) * sizeof(ReadyNode)); // print("Readying up node #{u32} ({s}) at index {u32}\n", geti(node_index), node_id_to_string(node->id), i); scheduler->ready.pointer[i] = (ReadyNode) { .node_index = node_index, .priority = cast(s32, u64, priority), .unit_mask = unit_mask, }; } struct FixedBlockMap { NodeIndex* keys; u32 count; }; typedef struct FixedBlockMap FixedBlockMap; fn FixedBlockMap fixed_block_map_create(Thread* restrict thread, u32 count) { auto* pointer = arena_allocate_bytes(thread->arena, sizeof(NodeIndex) * count + sizeof(BasicBlockIndex) * count, MAX(alignof(BasicBlockIndex), alignof(NodeIndex))); return (FixedBlockMap) { .keys = (NodeIndex*)pointer, .count = count, }; } fn BasicBlockIndex* fixed_block_map_values(FixedBlockMap* restrict map) { return (BasicBlockIndex*)(map->keys + map->count); } fn void fixed_block_map_put(FixedBlockMap* restrict map, NodeIndex key, BasicBlockIndex value) { auto count = map->count; for (u32 i = 0; i < count; i += 1) { if (index_equal(key, map->keys[i])) { fixed_block_map_values(map)[i] = value; break; } else if (!validi(map->keys[i])) { map->keys[i] = key; fixed_block_map_values(map)[i] = value; break; } } } fn BasicBlockIndex fixed_block_map_get(FixedBlockMap* restrict map, NodeIndex key) { auto count = map->count; for (u32 i = 0; i < count; i += 1) { if (index_equal(key, map->keys[i])) { return fixed_block_map_values(map)[i]; } } return invalidi(BasicBlock); } struct CFGBuilder { VirtualBuffer(NodeIndex) pinned; VirtualBuffer(BasicBlock) basic_blocks; VirtualBuffer(BasicBlockIndex) scheduled; FixedBlockMap block_map; WorkListHandle walker; WorkListHandle worker; }; typedef struct CFGBuilder CFGBuilder; fn CFGBuilder cfg_builder_init(Thread* restrict thread) { CFGBuilder cfg_builder = {}; cfg_builder.walker = thread_worklist_acquire(thread); cfg_builder.worker = thread_worklist_acquire(thread); return cfg_builder; } fn void cfg_builder_clear(CFGBuilder* restrict builder, Thread* restrict thread) { thread_worklist_clear(thread, builder->walker); thread_worklist_clear(thread, builder->worker); builder->pinned.length = 0; builder->basic_blocks.length = 0; builder->scheduled.length = 0; } struct CodegenOptions { String test_name; CompilerBackend backend; }; typedef struct CodegenOptions CodegenOptions; fn BasicBlockIndex cfg_get_predicate_basic_block(Thread* restrict thread, CFGBuilder* restrict builder, FixedBlockMap* map, NodeIndex arg_node_index, u16 i) { auto* arg_node = thread_node_get(thread, arg_node_index); auto arg_inputs = node_get_inputs(thread, arg_node); auto node_index = arg_inputs.pointer[i]; while (1) { auto* node = thread_node_get(thread, node_index); auto search = fixed_block_map_get(map, node_index); if (validi(search)) { return search; } else { // TODO: or dead if (node->id == IR_REGION) { return invalidi(BasicBlock); } } auto inputs = node_get_inputs(thread, node); node_index = inputs.pointer[0]; } unreachable(); } fn void cfg_build(CFGBuilder* restrict builder, Thread* restrict thread, Function* restrict function) { thread_worklist_push(thread, builder->worker, function->root); for (u64 i = 0; i < thread_worklist_length(thread, builder->worker); i += 1) { NodeIndex node_index = thread_worklist_get(thread, builder->worker, i); Node* node = thread_node_get(thread, node_index); auto pin = 0; switch (node->id) { case IR_ROOT: case IR_PHI: case IR_RETURN: case IR_REGION: pin = 1; break; case IR_PROJECTION: case IR_INTEGER_CONSTANT: case IR_SYMBOL_TABLE: break; default: todo(); } if (pin) { *vb_add(&builder->pinned, 1) = node_index; } auto outputs = node_get_outputs(thread, node); for (u16 i = 0; i < outputs.length; i += 1) { auto output = outputs.pointer[i]; assert(validi(output)); thread_worklist_push(thread, builder->worker, output); } } thread_worklist_clear(thread, builder->worker); for (u64 pin_index = 0; pin_index < builder->pinned.length; pin_index += 1) { auto pinned_node_index = builder->pinned.pointer[pin_index]; thread_worklist_push(thread, builder->walker, pinned_node_index); // auto* pinned_node = thread_node_get(thread, pinned_node_index); while (thread_worklist_length(thread, builder->walker) > 0) { auto node_index = thread_worklist_pop_array(thread, builder->walker); auto* node = thread_node_get(thread, node_index); assert(node->interned); auto inputs = node_get_inputs(thread, node); auto outputs = node_get_outputs(thread, node); if (node->id != IR_PROJECTION && node->output_count == 0) { todo(); } if (type_pair_get_backend(node->type) == BACKEND_TYPE_MEMORY) { for (u16 i = 0; i < outputs.length; i += 1) { auto output_index = outputs.pointer[i]; auto* output = thread_node_get(thread, output_index); if (output->output_count == 0) { thread_worklist_push(thread, builder->worker, output_index); } } } node_gvn_remove(thread, node_index); auto new_node_index = node_select_instruction(thread, node_index); if (validi(new_node_index) && !index_equal(node_index, new_node_index)) { todo(); } for (auto i = inputs.length; i > 0; i -= 1) { auto input = inputs.pointer[i - 1]; if (validi(input)) { thread_worklist_push(thread, builder->walker, input); } } if (node->id == IR_REGION) { for (u16 i = 0; i < outputs.length; i += 1) { auto output_index = outputs.pointer[i]; assert(validi(output_index)); auto output = thread_node_get(thread, output_index); if (output->id) { thread_worklist_push(thread, builder->walker, output_index); } } } } } auto control_start = function_get_control_start(thread, function); auto* top = create_block(thread, control_start); thread_worklist_clear(thread, builder->worker); thread_worklist_test_and_set(thread, builder->worker, control_start); while (top) { auto successor_count = top->successor_count; if (successor_count > 0) { auto index = successor_count - 1; auto node_index = top->successors[index]; assert(validi(node_index)); top->successor_count = index; // Returns valid when the node hasnt been pushed to the worklist yet if (!thread_worklist_test_and_set(thread, builder->worker, node_index)) { auto* new_top = create_block(thread, node_index); new_top->parent = top; top = new_top; } } else { Block* parent = top->parent; *vb_add(&builder->basic_blocks, 1) = (BasicBlock) { .start = top->start, .end = top->end, .dominator_depth = -1, }; top = parent; } } for (u32 i = 0; i < builder->basic_blocks.length / 2; i += 1) { SWAP(builder->basic_blocks.pointer[i], builder->basic_blocks.pointer[(builder->basic_blocks.length - 1) - i]); } auto* blocks = builder->basic_blocks.pointer; blocks[0].dominator_depth = 0; blocks[0].dominator = Index(BasicBlock, 0); auto block_count = builder->basic_blocks.length; builder->block_map = fixed_block_map_create(thread, block_count); for (u32 i = 0; i < block_count; i += 1) { auto* block = &blocks[i]; auto block_index = Index(BasicBlock, i); fixed_block_map_put(&builder->block_map, block->start, block_index); } // Compute dominators u8 changed = 1; while (changed) { changed = 0; for (u32 i = 1; i < block_count; i += 1) { auto basic_block_index = Index(BasicBlock, i); auto* basic_block = &blocks[i]; auto new_immediate_dominator_index = invalidi(BasicBlock); auto start_index = basic_block->start; auto* start_node = thread_node_get(thread, start_index); auto start_inputs = node_get_inputs(thread, start_node); for (u16 j = 0; j < start_node->input_count; j += 1) { auto predecessor_basic_block_index = cfg_get_predicate_basic_block(thread, builder, &builder->block_map, start_index, j); if (validi(predecessor_basic_block_index)) { auto* predecessor_basic_block = &blocks[geti(predecessor_basic_block_index)]; auto immediate_dominator_predecessor_index = predecessor_basic_block->dominator; if (validi(immediate_dominator_predecessor_index)) { if (validi(new_immediate_dominator_index)) { todo(); } else { new_immediate_dominator_index = predecessor_basic_block_index; } } } } assert(validi(new_immediate_dominator_index)); if (!index_equal(basic_block->dominator, new_immediate_dominator_index)) { basic_block->dominator = new_immediate_dominator_index; changed = 1; } } } // Compute the depths for (u32 i = 0; i < block_count; i += 1) { auto basic_block_index = Index(BasicBlock, i); auto* basic_block = &blocks[geti(basic_block_index)]; auto current_index = basic_block_index; s32 depth = 0; while (1) { auto* current = &blocks[geti(current_index)]; if (current->dominator_depth >= 0) { break; } current_index = current->dominator; depth += 1; } auto* current = &blocks[geti(current_index)]; basic_block->dominator_depth = depth + current->dominator_depth; } } struct GlobalScheduleOptions { u8 dataflow:1; }; typedef struct GlobalScheduleOptions GlobalScheduleOptions; fn void basic_block_add_node(Thread* restrict thread, BasicBlock* restrict basic_block, NodeIndex node_index, u32 place) { // if (geti(node_index) == 1) // { // breakpoint(); // } print("[PLACE #{u32}] Adding node #{u32} ({s}) to basic block 0x{u64:x} with index {u32}\n", place, geti(node_index), node_id_to_string(thread_node_get(thread, node_index)->id), basic_block, basic_block->items.length); *vb_add(&basic_block->items, 1) = node_index; } fn void cfg_global_schedule(CFGBuilder* restrict builder, Thread* restrict thread, Function* restrict function, GlobalScheduleOptions options) { // Global code motion auto node_count = thread->buffer.nodes.length; vb_add(&builder->scheduled, thread->buffer.nodes.length); for (u32 i = 0; i < builder->basic_blocks.length; i += 1) { auto* basic_block = &builder->basic_blocks.pointer[i]; bitset_ensure_length(&basic_block->live_in, node_count); bitset_ensure_length(&basic_block->live_out, node_count); } auto bb0 = Index(BasicBlock, cast(u32, s64, &builder->basic_blocks.pointer[0] - builder->basic_blocks.pointer)); for (u32 i = 0; i < builder->basic_blocks.length; i += 1) { auto* basic_block = &builder->basic_blocks.pointer[i]; auto bb_index = Index(BasicBlock, cast(u32, s64, basic_block - builder->basic_blocks.pointer)); builder->scheduled.pointer[geti(basic_block->start)] = bb_index; if (i == 0) { auto* root_node = thread_node_get(thread, function->root); auto outputs = node_get_outputs(thread, root_node); for (u16 i = 0; i < outputs.length; i += 1) { auto output = outputs.pointer[i]; builder->scheduled.pointer[geti(output)] = bb0; basic_block_add_node(thread, &builder->basic_blocks.pointer[0], output, 0); } } auto* start = thread_node_get(thread, basic_block->start); if (start->id == IR_REGION) { basic_block_add_node(thread, basic_block, basic_block->start, 1); auto outputs = node_get_outputs(thread, start); for (u16 i = 0; i < outputs.length; i += 1) { auto output = outputs.pointer[i]; auto* output_node = thread_node_get(thread, output); if (output_node->id == IR_PHI) { builder->scheduled.pointer[geti(output)] = bb_index; basic_block_add_node(thread, basic_block, output, 2); } } } } thread_worklist_clear(thread, builder->worker); thread_worklist_push(thread, builder->worker, function->root); VirtualBuffer(NodeIndex) pins = {}; for (u32 i = 0; i < thread_worklist_length(thread, builder->worker); i += 1) { auto node_index = thread_worklist_get(thread, builder->worker, i); auto* node = thread_node_get(thread, node_index); if (node->id != IR_ROOT && node_is_pinned(node)) { auto bb_index = builder->scheduled.pointer[geti(node_index)]; if (node->id == IR_PROJECTION && !node_is_pinned(thread_node_get(thread, node_get_inputs(thread, node).pointer[0]))) { } else { auto current = node_index; while (!validi(bb_index)) { bb_index = builder->scheduled.pointer[geti(current)]; auto* current_node = thread_node_get(thread, current); auto current_inputs = node_get_inputs(thread, current_node); current = current_inputs.pointer[0]; } auto* basic_block = &builder->basic_blocks.pointer[geti(bb_index)]; builder->scheduled.pointer[geti(node_index)] = bb_index; *vb_add(&pins, 1) = node_index; basic_block_add_node(thread, basic_block, node_index, 3); } } auto outputs = node_get_outputs(thread, node); for (u16 i = 0; i < outputs.length; i += 1) { auto output = outputs.pointer[i]; thread_worklist_push(thread, builder->worker, output); } } // Early schedule thread_worklist_clear(thread, builder->worker); for (u32 i = 0; i < pins.length; i += 1) { auto pin_node_index = pins.pointer[i]; auto* pin = thread_node_get(thread, pin_node_index); struct Elem { struct Elem* parent; NodeIndex node; u32 i; }; typedef struct Elem Elem; auto* top = arena_allocate(thread->arena, Elem, 1); *top = (Elem) { .node = pin_node_index, .parent = 0, .i = pin->input_count, }; while (top) { NodeIndex node_index = top->node; Node* node = thread_node_get(thread, node_index); auto node_inputs = node_get_inputs(thread, node); if (top->i > 0) { auto new_top_i = top->i - 1; top->i = new_top_i; NodeIndex input_index = node_inputs.pointer[new_top_i]; if (validi(input_index)) { Node* input = thread_node_get(thread, input_index); if (input->id == IR_PROJECTION) { auto input_inputs = node_get_inputs(thread, input); input_index = input_inputs.pointer[0]; input = thread_node_get(thread, input_index); } if (!node_is_pinned(input) && !thread_worklist_test_and_set(thread, builder->worker, input_index)) { auto* new_top = arena_allocate(thread->arena, Elem, 1); *new_top = (Elem) { .parent = top, .node = input_index, .i = input->input_count, }; top = new_top; } } continue; } if (!index_equal(node_index, pin_node_index)) { auto best = Index(BasicBlock, 0); s32 best_depth = 0; auto inputs = node_get_inputs(thread, node); for (u16 i = 0; i < node->input_count; i += 1) { auto input_index = inputs.pointer[i]; if (validi(input_index)) { auto basic_block_index = builder->scheduled.pointer[geti(input_index)]; if (validi(basic_block_index)) { auto* basic_block = &builder->basic_blocks.pointer[geti(basic_block_index)]; if (best_depth < basic_block->dominator_depth) { best_depth = basic_block->dominator_depth; best = basic_block_index; } } } } builder->scheduled.pointer[geti(node_index)] = best; if (type_pair_get_backend(node->type) == BACKEND_TYPE_TUPLE) { todo(); } thread_worklist_push_array(thread, builder->worker, node_index); } top = top->parent; } } // Late schedule for (u64 i = thread_worklist_length(thread, builder->worker); i > 0; i -= 1) { auto node_index = thread_worklist_get(thread, builder->worker, i - 1); auto* node = thread_node_get(thread, node_index); assert(!node_is_pinned(node)); auto current_basic_block_index = builder->scheduled.pointer[geti(node_index)]; auto current_basic_block = &builder->basic_blocks.pointer[geti(current_basic_block_index)]; auto lca = invalidi(BasicBlock); if (!node_has_memory_out(node->id)) { auto memory_in = node_memory_in(thread, node); if (validi(memory_in)) { todo(); } } if (type_pair_get_backend(node->type) == BACKEND_TYPE_TUPLE) { todo(); } else { auto outputs = node_get_outputs(thread, node); for (u16 i = 0; i < outputs.length; i += 1) { auto output = outputs.pointer[i]; auto use_block_index = find_use_block(thread, builder->scheduled, node_index, node_index, output); if (validi(use_block_index)) { lca = find_lca(lca, use_block_index); } } } if (validi(lca)) { todo(); } if (type_pair_get_backend(node->type) == BACKEND_TYPE_TUPLE) { todo(); } basic_block_add_node(thread, current_basic_block, node_index, 4); } if (options.dataflow) { dataflow(thread, builder->worker, builder->basic_blocks, builder->scheduled, node_count); } } fn void cfg_build_and_global_schedule(CFGBuilder* restrict builder, Thread* restrict thread, Function* restrict function, GlobalScheduleOptions options) { cfg_build(builder, thread, function); cfg_global_schedule(builder, thread, function, options); } fn void cfg_list_schedule(Thread* restrict thread, CFGBuilder* restrict builder, Function* restrict function, BasicBlockIndex basic_block_index) { // print("=================================\nLIST SCHEDULER START\n=================================\n"); thread_worklist_clear(thread, builder->worker); auto* restrict basic_block = &builder->basic_blocks.pointer[geti(basic_block_index)]; thread_worklist_push(thread, builder->worker, basic_block->start); if (geti(basic_block_index) == 0) { auto* root_node = thread_node_get(thread, function->root); auto root_outputs = node_get_outputs(thread, root_node); for (u32 i = 0; i < root_outputs.length; i += 1) { auto output = root_outputs.pointer[i]; auto* output_node = thread_node_get(thread, output); if (output_node->id == IR_PROJECTION) { thread_worklist_push(thread, builder->worker, output); } } } else { auto* bb_start = thread_node_get(thread, basic_block->start); auto outputs = node_get_outputs(thread, bb_start); for (u32 i = 0; i < outputs.length; i += 1) { auto output_index = outputs.pointer[i]; auto* output = thread_node_get(thread, output_index); if (output->id == IR_PHI) { thread_worklist_push(thread, builder->worker, output_index); } } } auto end_index = basic_block->end; auto* end = thread_node_get(thread, end_index); Scheduler scheduler = {}; bitset_ensure_length(&scheduler.ready_set, thread->buffer.nodes.length); for (u32 i = 0; i < basic_block->items.length; i += 1) { auto node_index = basic_block->items.pointer[i]; auto* node = thread_node_get(thread, node_index); if (!thread_worklist_test(thread, builder->worker, node_index) && index_equal(builder->scheduled.pointer[geti(node_index)], basic_block_index) && node_is_ready(thread, builder->scheduled, builder->worker, node, basic_block_index)) { node_ready_up(thread, &scheduler, node_index, end); } } // TODO: IS BRANCH VirtualBuffer(InFlightNode) active = {}; u64 in_use_mask = 0; u64 blocked_mask = UINT64_MAX >> (64 - 1); u32 cycle = 0; while (active.length > 0 || scheduler.ready.length > 0) { while (in_use_mask != blocked_mask && scheduler.ready.length > 0) { auto signed_index = node_best_ready(&scheduler, in_use_mask); if (signed_index < 0) { break; } auto index = cast(u32, s32, signed_index); auto available = scheduler.ready.pointer[index].unit_mask & ~in_use_mask; auto unit_i = __builtin_ffsll(cast(s64, u64, available)) - 1; auto node_index = scheduler.ready.pointer[index].node_index; auto* node = thread_node_get(thread, node_index); in_use_mask |= (u64)1 << unit_i; if (index + 1 < scheduler.ready.length) { todo(); } scheduler.ready.length -= 1; assert(node->id != IR_PROJECTION); auto end_cycle = cycle + node_get_latency(thread, node, end); *vb_add(&active, 1) = (InFlightNode) { .node_index = node_index, .end = cast(u32, u64, end_cycle), .unit_i = unit_i, }; if (node != end) { thread_worklist_push(thread, builder->worker, node_index); if (type_pair_get_backend(node->type) == BACKEND_TYPE_TUPLE) { todo(); } } } cycle += 1; for (u32 i = 0; i < active.length; i += 1) { auto active_i = active.pointer[i]; auto node_index = active_i.node_index; auto* node = thread_node_get(thread, node_index); if (active_i.end > cycle) { i += 1; continue; } in_use_mask &= ~((u64)1 << active_i.unit_i); auto last = active.pointer[active.length - 1]; active.pointer[i] = last; active.length -= 1; auto outputs = node_get_outputs(thread, node); for (u16 i = 0; i < outputs.length; i += 1) { auto output_index = outputs.pointer[i]; auto* output = thread_node_get(thread, output_index); if (output->id == IR_PROJECTION) { print("TODO: proj\n"); todo(); } else if (!bitset_get(&scheduler.ready_set, geti(output_index)) && index_equal(builder->scheduled.pointer[geti(output_index)], basic_block_index) && !thread_worklist_test(thread, builder->worker, output_index) && node_is_ready(thread, builder->scheduled, builder->worker, output, basic_block_index)) { node_ready_up(thread, &scheduler, output_index, end); } } } } if (!index_equal(end_index, basic_block->start)) { thread_worklist_push(thread, builder->worker, end_index); } // print("=================================\nLIST SCHEDULER END\n=================================\n"); } fn u8 operand_equal(MachineOperand a, MachineOperand b) { if (a.id != b.id) { return 0; } if (a.id == MACHINE_OPERAND_MEMORY) { todo(); } return (a.id == MACHINE_OPERAND_GPR || a.id == MACHINE_OPERAND_XMM) ? a.register_value == b.register_value : 0; } fn void code_generation(Thread* restrict thread, CodegenOptions options, char** envp) { auto cfg_builder = cfg_builder_init(thread); auto* restrict builder = &cfg_builder; VirtualBuffer(u8) code = {}; for (u32 function_i = 0; function_i < thread->buffer.functions.length; function_i += 1) { Function* restrict function = &thread->buffer.functions.pointer[function_i]; cfg_builder_clear(builder, thread); cfg_build_and_global_schedule(builder, thread, function, (GlobalScheduleOptions) { .dataflow = 1, }); auto node_count = thread->buffer.nodes.length; u32 max_ins = 0; u32 virtual_register_count = 1; auto* virtual_register_map = arena_allocate(thread->arena, u32, round_up_to_next_power_of_2( node_count + 16)); VirtualBuffer(s32) spills = {}; for (u32 i = 0; i < builder->basic_blocks.length; i += 1) { auto basic_block_index = Index(BasicBlock, i); BasicBlock* basic_block = &builder->basic_blocks.pointer[i]; cfg_list_schedule(thread, builder, function, basic_block_index); auto max_item_count = thread_worklist_length(thread, builder->worker); print("Item count: {u32}\n", max_item_count); basic_block->items.length = 0; for (u32 i = 0; i < max_item_count; i += 1) { auto node_index = thread_worklist_get(thread, builder->worker, i); basic_block_add_node(thread, basic_block, node_index, 5); auto* node = thread_node_get(thread, node_index); auto def_mask = node_constraint(thread, node, (Slice(RegisterMaskIndex)){}); auto inputs = node->input_count + node_tmp_count(node); if (inputs > max_ins) { max_ins = inputs; } u32 virtual_register_id = 0; if (!index_equal(def_mask, empty_register_mask)) { if (node->id == MACHINE_MOVE) { assert(node->output_count == 1); auto outputs = node_get_outputs(thread, node); auto phi_index = outputs.pointer[0]; auto* phi = thread_node_get(thread, phi_index); assert(phi->id == IR_PHI); if (virtual_register_map[geti(phi_index)] == 0) { virtual_register_id = virtual_register_count; virtual_register_count += 1; virtual_register_map[geti(phi_index)] = virtual_register_id; } else { todo(); } } else if (node->id == IR_PHI && virtual_register_map[geti(node_index)] > 0) { virtual_register_id = virtual_register_map[geti(node_index)]; } else { virtual_register_id = virtual_register_count; virtual_register_count += 1; } } virtual_register_map[geti(node_index)] = virtual_register_id; print("Assigning VR{u32} to node #{u32} ({s})\n", virtual_register_id, geti(node_index), node_id_to_string(node->id)); } } auto ins = (Slice(RegisterMaskIndex)) { .pointer = arena_allocate(thread->arena, RegisterMaskIndex, max_ins), .length = max_ins, }; // TODO: remove? memset(ins.pointer, 0, sizeof(RegisterMaskIndex) * max_ins); VirtualBuffer(VirtualRegister) virtual_registers = {}; vb_ensure_capacity(&virtual_registers, cast(u32, u64, round_up_to_next_power_of_2(virtual_register_count + 16))); virtual_registers.length = virtual_register_count; for (u32 i = 0; i < builder->basic_blocks.length; i += 1) { auto* basic_block = &builder->basic_blocks.pointer[i]; print("BB items: {u32}\n", basic_block->items.length); for (u32 i = 0; i < basic_block->items.length; i += 1) { auto node_index = basic_block->items.pointer[i]; auto* node = thread_node_get(thread, node_index); auto virtual_register_id = virtual_register_map[geti(node_index)]; assert(virtual_register_id >= 0 && virtual_register_id < virtual_register_count); if (virtual_register_id > 0 && node->id != MACHINE_MOVE) { auto mask_index = node_constraint(thread, node, (Slice(RegisterMaskIndex)){}); print("Node #{u32} ({s}), VR{u32}, mask: ", geti(node_index), node_id_to_string(node->id), virtual_register_id); if (validi(mask_index)) { print("0x{u32:x}", thread_register_mask_get(thread, mask_index)->mask); } else { print("invalid"); } print("\n"); virtual_registers.pointer[virtual_register_id] = (VirtualRegister) { .mask = mask_index, .node_index = node_index, .assigned = -1, .spill_cost = NAN, }; } } } thread_worklist_clear(thread, builder->worker); u32 max_registers_in_class = 0; auto* fixed = arena_allocate(thread->arena, s32, REGISTER_CLASS_X86_64_COUNT); auto* in_use = arena_allocate(thread->arena, u32, REGISTER_CLASS_X86_64_COUNT); for (u32 class = 0; class < REGISTER_CLASS_X86_64_COUNT; class += 1) { auto count = register_count_per_class[class]; max_registers_in_class = MAX(max_registers_in_class, count); auto base = virtual_registers.length; for (u32 i = 0; i < count; i += 1) { auto mask = register_mask_intern(thread, (RegisterMask) { .class = class, .may_spill = 0, .mask = class == 0 ? i : ((u64)1 << i), }); *vb_add(&virtual_registers, 1) = (VirtualRegister) { .mask = mask, .class = cast(s16, u32, class), .assigned = cast(s16, u32, i), .spill_cost = INFINITY, }; } fixed[class] = cast(s32, u32, base); } // Insert legalizing moves for (u32 i = 0; i < builder->basic_blocks.length; i += 1) { auto* basic_block = &builder->basic_blocks.pointer[i]; // auto basic_block_index = Index(BasicBlock, i); for (u32 i = 0; i < basic_block->items.length; i += 1) { auto node_index = basic_block->items.pointer[i]; auto* node = thread_node_get(thread, node_index); auto tmp_count = node_tmp_count(node); node_constraint(thread, node, ins); auto inputs = node_get_inputs(thread, node); for (u16 i = 1; i < inputs.length; i += 1) { auto input_index = inputs.pointer[i]; if (validi(input_index)) { // auto* input = thread_node_get(thread, input_index); auto in_mask_index = ins.pointer[i]; if (!index_equal(in_mask_index, empty_register_mask)) { auto in_mask = thread_register_mask_get(thread, in_mask_index); VirtualRegister* vreg = 0; auto vreg_index = virtual_register_map[geti(input_index)]; if (vreg_index > 0) { vreg = &virtual_registers.pointer[vreg_index]; } assert(vreg); auto vreg_mask = thread_register_mask_get(thread, vreg->mask); auto hint = fixed_register_mask(*in_mask); if (hint >= 0 && vreg_mask->class == in_mask->class) { vreg->hint_vreg = fixed[in_mask->class] + hint; } auto new_mask_index = register_mask_meet(thread, in_mask_index, vreg->mask); print("Input #{u32} ({s})\n", geti(input_index), node_id_to_string(thread_node_get(thread, input_index)->id)); print("IN mask index: {u32}. TODO: not equal: {u32}, {u32}, {u32}\n", i, in_mask_index, empty_register_mask, new_mask_index); if (!index_equal(in_mask_index, empty_register_mask) && index_equal(new_mask_index, empty_register_mask)) { // if (node->id == MACHINE_COPY) { print("{s} input count: {u32}\n", node_id_to_string(node->id), (u32)node->input_count); } todo(); } auto* new_mask = thread_register_mask_get(thread, new_mask_index); auto fixed = fixed_register_mask(*new_mask); if (fixed >= 0) { auto fixed_mask = ((u32)1 << fixed); auto shared_edge = node_to_address(thread, input_index); if (shared_edge >= 0) { auto* input_node = thread_node_get(thread, input_index); auto p_shared_edge = cast(u16, s32, shared_edge); assert(p_shared_edge < input_node->input_count); auto inputs = node_get_inputs(thread, input_node); for (u16 i = 1; i < input_node->input_count; i += 1) { if (i != shared_edge) { auto input_index = inputs.pointer[i]; if (validi(input_index)) { todo(); } } } } } vreg->mask = new_mask_index; } } } auto virtual_register_index = virtual_register_map[geti(node_index)]; if (tmp_count > 0) { todo(); } if (virtual_register_index > 0) { auto* virtual_register = &virtual_registers.pointer[virtual_register_index]; virtual_register->spill_cost = NAN; if (node->id == MACHINE_COPY) { auto* in = thread_node_get(thread, inputs.pointer[1]); if (in->id == IR_PHI) { thread_worklist_push(thread, builder->worker, node_index); } } } } } u8 changes = 0; if (thread_worklist_length(thread, builder->worker) > 0) { // Compute ordinals auto order = compute_ordinals(thread, builder->basic_blocks, node_count); while (thread_worklist_length(thread, builder->worker) > 0) { auto node_index = thread_worklist_pop(thread, builder->worker); auto* node = thread_node_get(thread, node_index); assert(node->id == MACHINE_COPY); auto id = virtual_register_map[geti(node_index)]; assert(id > 0); auto mask_index = virtual_registers.pointer[id].mask; auto inputs = node_get_inputs(thread, node); if (!interfere(thread, builder->scheduled, builder->basic_blocks, order, node_index, inputs.pointer[1])) { auto basic_block_index = builder->scheduled.pointer[geti(node_index)]; auto* basic_block = &builder->basic_blocks.pointer[geti(basic_block_index)]; u64 i = 0; auto count = basic_block->items.length; while (i < count && !index_equal(basic_block->items.pointer[i], node_index)) { i += 1; } assert(index_equal(basic_block->items.pointer[i], node_index)); memmove(&basic_block->items.pointer[i], &basic_block->items.pointer[i + 1], (count - (i + 1)) * sizeof(NodeIndex)); basic_block->items.length -= 1; builder->scheduled.pointer[geti(node_index)] = invalidi(BasicBlock); subsume_node_without_killing(thread, node_index, inputs.pointer[1]); changes = 1; } } } // TODO: spills if (spills.length) { todo(); changes = 1; } if (changes) { redo_dataflow(thread, builder->worker, builder->basic_blocks, builder->scheduled, node_count); } auto al_index = 0; // Allocate loop while (1) { print("==============================\n#{u32} Allocate loop\n==============================\n", al_index++); auto order = compute_ordinals(thread, builder->basic_blocks, node_count); Bitset active = {}; bitset_ensure_length(&active, virtual_registers.length); Bitset future_active = {}; bitset_ensure_length(&future_active, virtual_registers.length); Bitset live_out = {}; bitset_ensure_length(&live_out, node_count); for (u32 block_i = 0; block_i < builder->basic_blocks.length; block_i += 1) { auto* basic_block = &builder->basic_blocks.pointer[block_i]; for (u32 node_i = 0; node_i < basic_block->items.length; node_i += 1) { auto node_index = basic_block->items.pointer[node_i]; auto virtual_register_id = virtual_register_map[geti(node_index)]; if (virtual_register_id > 0) { auto* node = thread_node_get(thread, node_index); auto mask_index = virtual_registers.pointer[virtual_register_id].mask; auto mask_pointer = thread_register_mask_get(thread, mask_index); auto mask_value = *mask_pointer; auto reg = fixed_register_mask(mask_value); // print("Block #{u32}, Node index #{u32}, Node GVN #{u32}, Node id: {s}, VR{u32}. Mask: {u32:x}. Reg: {u32:x}\n", block_i, node_i, geti(node_index), node_id_to_string(node->id), virtual_register_id, mask_value.mask, reg); if (reg >= 0) { todo(); } } } } if (spills.length) { todo(); } for (u32 i = 0; i < builder->basic_blocks.length; i += 1) { auto* basic_block = &builder->basic_blocks.pointer[i]; print("============\nBlock #{u32}\n============\n", i); auto basic_block_index = Index(BasicBlock, i); auto bb_live_in = &basic_block->live_in; auto bb_live_out = &basic_block->live_out; FOREACH_SET(j, &live_out) if (!bitset_get(bb_live_in, j)) { auto virtual_register_id = virtual_register_map[j]; print("General live out not present in basic block live in: N{u64}, VR{u32}\n", j, virtual_register_id); if (virtual_register_id != 0) { u8 pause = 0; for (u32 k = i; k < builder->basic_blocks.length; k += 1) { auto* other_basic_block = &builder->basic_blocks.pointer[k]; if (bitset_get(&other_basic_block->live_in, j)) { todo(); } } bitset_set_value(&active, virtual_register_id, 0); bitset_set_value(&live_out, j, 0); } } FOREACH_SET(j, bb_live_in) if (!bitset_get(&live_out, j)) { auto virtual_register_id = virtual_register_map[j]; print("Basic block live in not present in general live out: N{u64}, VR{u32}\n", j, virtual_register_id); if (virtual_register_id > 0) { { auto* virtual_register = &virtual_registers.pointer[virtual_register_id]; auto node_index = virtual_register->node_index; auto* node = thread_node_get(thread, node_index); print("[BB LIVE IN ] Allocating register for node #{u32} ({s})\n", geti(node_index), node_id_to_string(node->id)); } if (!register_allocate(thread, virtual_registers, &spills, &active, &future_active, builder->scheduled, builder->basic_blocks, order, virtual_register_id, 0)) { todo(); } } } for (u32 i = 0; i < basic_block->items.length; i += 1) { NodeIndex node_index = basic_block->items.pointer[i]; auto* node = thread_node_get(thread, node_index); auto def = order.pointer[geti(node_index)]; auto inputs = node_get_inputs(thread, node); print("Node #{u32} ({s}). Def: {u32}\n", geti(node_index), node_id_to_string(node->id), def); if (node->id == IR_PROJECTION && !index_equal(inputs.pointer[0], function->root)) { print("Skipping...\n"); continue; } if (node->id != IR_PHI) { print("Node is not PHI. Examining inputs ({u32})...\n", (u32)node->input_count); for (u16 i = 1; i < node->input_count; i += 1) { auto input_index = inputs.pointer[i]; if (validi(input_index)) { auto virtual_register_id = virtual_register_map[geti(input_index)]; print("Input {u32}: node #{u32} ({s}). VR{u32}\n", i, geti(input_index), node_id_to_string(thread_node_get(thread, input_index)->id), virtual_register_id); if (virtual_register_id == 0) { print("Invalid vreg id. Removing from general live out and skipping...\n"); bitset_set_value(&live_out, geti(input_index), 0); continue; } if (!bitset_get(&live_out, geti(input_index))) { print("Duplicate input. Skipping...\n"); continue; } auto* input = thread_node_get(thread, input_index); auto last_use = node_last_use_in_block(thread, builder->scheduled, order, input, basic_block_index); print("Last use: {u32}\n", last_use); if (bitset_get(bb_live_out, geti(input_index))) { todo(); } print("Removing node #{u32} from general liveout\n", geti(input_index)); bitset_set_value(&live_out, geti(input_index), 0); auto pause = last_use > def; if (!pause) { for (u32 i = geti(basic_block_index); i < builder->basic_blocks.length; i += 1) { auto* other = &builder->basic_blocks.pointer[i]; if (bitset_get(&other->live_in, geti(input_index))) { pause = 1; break; } } } if (pause) { bitset_set_value(&future_active, virtual_register_id, 1); } print("Removing VR{u32} from general active\n", virtual_register_id); bitset_set_value(&active, virtual_register_id, 0); } } } for (u32 i = 0; i < REGISTER_CLASS_X86_64_COUNT; i += 1) { in_use[i] = 0; } // TODO: tmps auto virtual_register_id = virtual_register_map[geti(node_index)]; if (virtual_register_id > 0) { auto* virtual_register = &virtual_registers.pointer[virtual_register_id]; auto class = virtual_register->class; auto in_use_local = in_use[class]; print("[ALLOCATE LOOP] Allocating register for node #{u32} ({s}), VR{u32}\n", geti(node_index), node_id_to_string(node->id), virtual_register_id); if (!register_allocate(thread, virtual_registers, &spills, &active, &future_active, builder->scheduled, builder->basic_blocks, order, virtual_register_id, in_use_local)) { todo(); } print("[END ALLOCATE LOOP]\n"); assert(virtual_register_map[geti(node_index)] == virtual_register_id); auto def = virtual_register->node_index; bitset_set_value(&live_out, geti(def), 1); print("Setting as general live out node #{u32} ({s})\n", geti(def), node_id_to_string(thread_node_get(thread, def)->id)); } else if (type_pair_get_backend(node->type) == BACKEND_TYPE_TUPLE) { todo(); } } } break; } // Basic block scheduling for (u32 i = 0; i < builder->basic_blocks.length; i += 1) { auto* basic_block = &builder->basic_blocks.pointer[i]; auto basic_block_index = Index(BasicBlock, basic_block - builder->basic_blocks.pointer); auto first_node = thread_node_get(thread, basic_block->items.pointer[0]); auto item_count = basic_block->items.length; u8 empty = 1; if (first_node->id == IR_REGION) { for (u32 i = 1; i < item_count; i += 1) { auto node_index = basic_block->items.pointer[i]; auto* node = thread_node_get(thread, node_index); if (node->id != IR_PHI) { empty = 0; break; } } } else if (item_count > 1 || node_is_control_projection(first_node)) { empty = 0; } if (empty) { todo(); } else { basic_block->forward = cast(s32, u32, i); auto* bb_end = thread_node_get(thread, basic_block->end); if (!cfg_node_terminator(bb_end)) { auto jump_node_index = thread_node_add(thread, (NodeCreate) { .id = MACHINE_JUMP, .inputs = array_to_slice(((NodeIndex[]) { invalidi(Node), })), .type_pair = type_pair_make(invalidi(DebugType), BACKEND_TYPE_CONTROL), }); auto successor_node_index = cfg_next_user(thread, basic_block->end); auto* successor_node = thread_node_get(thread, successor_node_index); auto successor_inputs = node_get_inputs(thread, successor_node); u16 i; for (i = 0; i < successor_node->input_count; i += 1) { auto input_index = successor_inputs.pointer[i]; if (index_equal(input_index, basic_block->end)) { break; } } assert(i < successor_node->input_count); node_set_input(thread, successor_node_index, i, jump_node_index); node_set_input(thread, jump_node_index, 0, basic_block->end); basic_block->end = jump_node_index; basic_block_add_node(thread, basic_block, jump_node_index, 6); assert(builder->scheduled.length == geti(jump_node_index)); *vb_add(&builder->scheduled, 1) = basic_block_index; } } } for (u32 i = 0; i < builder->basic_blocks.length; i += 1) { auto* basic_block = &builder->basic_blocks.pointer[i]; auto forward = basic_block->forward; while (forward != builder->basic_blocks.pointer[forward].forward) { forward = builder->basic_blocks.pointer[forward].forward; } basic_block->forward = forward; } auto* order = arena_allocate(thread->arena, s32, builder->basic_blocks.length); u32 order_index = 0; for (u32 i = 0; i < builder->basic_blocks.length; i += 1) { auto* basic_block = &builder->basic_blocks.pointer[i]; if (basic_block->forward == i) { auto* end_node = thread_node_get(thread, basic_block->end); if (end_node->id != IR_RETURN) { order[order_index] = i; order_index += 1; } } } for (u32 i = 0; i < builder->basic_blocks.length; i += 1) { auto* basic_block = &builder->basic_blocks.pointer[i]; if (basic_block->forward == i) { auto* end_node = thread_node_get(thread, basic_block->end); if (end_node->id == IR_RETURN) { order[order_index] = i; order_index += 1; } } } // Emit auto final_order_count = order_index; for (u32 order_index = 0; order_index < final_order_count; order_index += 1) { auto i = order[order_index]; auto* basic_block = &builder->basic_blocks.pointer[i]; for (u32 i = 0; i < basic_block->items.length; i += 1) { auto node_index = basic_block->items.pointer[i]; auto* node = thread_node_get(thread, node_index); auto virtual_register_id = virtual_register_map[geti(node_index)]; auto* virtual_register = &virtual_registers.pointer[virtual_register_id]; auto inputs = node_get_inputs(thread, node); auto fallthrough = INT32_MAX; if (order_index + 1 < final_order_count) { fallthrough = order[order_index + 1]; } switch (node->id) { case IR_PROJECTION: case IR_REGION: case IR_PHI: break; case IR_INTEGER_CONSTANT: { auto value = node->integer_constant.unsigned_value; GPR gpr = machine_operand_at(virtual_register_map, virtual_registers, node_index, REGISTER_CLASS_X86_64_GPR); auto backend_type = type_pair_get_backend(node->type); if (backend_type == BACKEND_TYPE_INTEGER_32) { if (value == 0) { if (gpr == RAX) { *vb_add(&code, 1) = 0x31; *vb_add(&code, 1) = 0xc0; } else { todo(); } } else { todo(); } } } break; case MACHINE_MOVE: { auto destination = operand_from_node(thread, virtual_registers, virtual_register_map, node_index); auto source = operand_from_node(thread, virtual_registers, virtual_register_map, inputs.pointer[1]); if (!operand_equal(destination, source)) { todo(); } } break; case MACHINE_JUMP: { auto successor_node_index = cfg_next_control(thread, node_index); assert(validi(successor_node_index)); auto successor_basic_block_index = fixed_block_map_get(&builder->block_map, successor_node_index); assert(validi(successor_basic_block_index)); auto* successor_basic_block = &builder->basic_blocks.pointer[geti(successor_basic_block_index)]; if (fallthrough != successor_basic_block->forward) { todo(); } } break; case IR_RETURN: { *vb_add(&code, 1) = 0xc3; } break; default: todo(); } } } } auto object_path = arena_join_string(thread->arena, (Slice(String)) array_to_slice(((String[]) { strlit("nest/"), options.test_name, options.backend == COMPILER_BACKEND_C ? strlit(".c") : strlit(".o"), }))); auto exe_path_view = s_get_slice(u8, object_path, 0, object_path.length - 2); auto exe_path = (char*)arena_allocate_bytes(thread->arena, exe_path_view.length + 1, 1); memcpy(exe_path, exe_path_view.pointer, exe_path_view.length); exe_path[exe_path_view.length] = 0; switch (options.backend) { case COMPILER_BACKEND_C: { // auto lowered_source = c_lower(thread); // // print("Transpiled to C:\n```\n{s}\n```\n", lowered_source); // // file_write(object_path, lowered_source); // // char* command[] = { // clang_path, "-g", // "-o", exe_path, // string_to_c(object_path), // 0, // }; // // run_command((CStringSlice) array_to_slice(command), envp); todo(); } break; case COMPILER_BACKEND_INTERPRETER: { // auto* main_function = &thread->buffer.functions.pointer[thread->main_function]; // auto* interpreter = interpreter_create(thread); // interpreter->function = main_function; // interpreter->arguments = (Slice(String)) array_to_slice(((String[]) { // test_name, // })); // auto exit_code = interpreter_run(interpreter, thread); // print("Interpreter exited with exit code: {u32}\n", exit_code); // syscall_exit(exit_code); todo(); } break; case COMPILER_BACKEND_MACHINE: { auto code_slice = (Slice(u8)) { .pointer = code.pointer, .length = code.length, }; write_elf(thread, envp, &(ELFOptions) { .object_path = string_to_c(object_path), .exe_path = exe_path, .code = code_slice, }); } break; } } fn u8 node_is_empty_control_projection(Thread* restrict thread, CFGBuilder* restrict builder, NodeIndex node_index) { auto* restrict node = thread_node_get(thread, node_index); u8 result = 0; if (node_is_control_projection(node)) { auto basic_block_index = builder->scheduled.pointer[geti(node_index)]; auto* basic_block = &builder->basic_blocks.pointer[geti(basic_block_index)]; result = basic_block->items.length == 1; } return result; } struct SchedPhi { NodeIndex phi; NodeIndex node; }; typedef struct SchedPhi SchedPhi; decl_vb(SchedPhi); fn void fill_phis(Thread* restrict thread, CFGBuilder* restrict builder, Function* restrict function, VirtualBuffer(SchedPhi)* sched_phis, Node* restrict successor_node, NodeIndex original_index) { auto succesor_inputs = node_get_inputs(thread, successor_node); u16 i; for (i = 0; i < successor_node->input_count; i += 1) { auto input_index = succesor_inputs.pointer[i]; if (index_equal(input_index, original_index)) { break; } } assert(i < successor_node->input_count); auto phi_index = i; auto successor_outputs = node_get_outputs(thread, successor_node); for (u16 i = 0; i < successor_node->output_count; i += 1) { auto output_index = successor_outputs.pointer[i]; auto* output_node = thread_node_get(thread, output_index); if (output_node->id == IR_PHI) { auto output_inputs = node_get_inputs(thread, output_node); assert(phi_index + 1 < output_node->input_count); *vb_add(sched_phis, 1) = (SchedPhi) { .phi = output_index, .node = output_inputs.pointer[phi_index + 1], }; } } } typedef struct SchedNode SchedNode; struct SchedNode { SchedNode* parent; NodeIndex node_index; s32 index; }; fn u8 sched_in_basic_block(Thread* restrict thread, CFGBuilder* restrict builder, BasicBlockIndex basic_block_index, NodeIndex node_index) { return index_equal(builder->scheduled.pointer[geti(node_index)], basic_block_index) && !thread_worklist_test_and_set(thread, builder->worker, node_index); } fn void greedy_scheduler(Thread* restrict thread, CFGBuilder* restrict builder, Function* restrict function, BasicBlockIndex basic_block_index) { thread_worklist_clear(thread, builder->worker); auto* restrict basic_block = &builder->basic_blocks.pointer[geti(basic_block_index)]; auto end_index = basic_block->end; auto* end_node = thread_node_get(thread, end_index); VirtualBuffer(SchedPhi) phis = {}; if (node_is_cfg_fork(end_node)) { todo(); } else if (!cfg_is_endpoint(thread, end_node)) { auto successor_index = cfg_next_user(thread, end_index); auto* successor_node = thread_node_get(thread, successor_index); if (successor_node->id == IR_REGION) { fill_phis(thread, builder, function, &phis, successor_node, end_index); } } auto* top = arena_allocate(thread->arena, SchedNode, 1); *top = (SchedNode) { .node_index = end_index, }; thread_worklist_test_and_set(thread, builder->worker, end_index); if (geti(basic_block_index) == 0) { auto* root_node = thread_node_get(thread, function->root); auto outputs = node_get_outputs(thread, root_node); for (u16 i = 0; i < root_node->output_count; i += 1) { auto output_index = outputs.pointer[i]; auto* output_node = thread_node_get(thread, output_index); if (output_node->id == IR_PROJECTION && !thread_worklist_test_and_set(thread, builder->worker, output_index)) { thread_worklist_push_array(thread, builder->worker, output_index); } } } u64 phi_current = 0; u64 leftovers = 0; auto leftover_count = basic_block->items.length; while (top) { auto node_index = top->node_index; auto* node = thread_node_get(thread, node_index); if (node->id != IR_PHI && top->index < node->input_capacity) { auto inputs = node_get_inputs(thread, node); auto input_index = inputs.pointer[top->index]; top->index += 1; if (validi(input_index)) { auto* input_node = thread_node_get(thread, input_index); if (input_node->id == IR_PROJECTION) { auto projection_inputs = node_get_inputs(thread, input_node); input_index = projection_inputs.pointer[0]; input_node = thread_node_get(thread, input_index); } if (sched_in_basic_block(thread, builder, basic_block_index, input_index)) { auto* new_top = arena_allocate(thread->arena, SchedNode, 1); *new_top = (SchedNode) { .node_index = input_index, .parent = top, }; top = new_top; } } continue; } if (index_equal(end_index, node_index)) { if (phi_current < phis.length) { auto* restrict phi = &phis.pointer[phi_current]; phi_current += 1; auto value = phi->node; if (sched_in_basic_block(thread, builder, basic_block_index, value)) { auto* new_top = arena_allocate(thread->arena, SchedNode, 1); *new_top = (SchedNode) { .node_index = value, .parent = top, }; top = new_top; } continue; } auto try_again = 0; while (leftovers < leftover_count) { auto index = leftovers; leftovers += 1; auto bb_node_index = basic_block->items.pointer[index]; if (!thread_worklist_test_and_set(thread, builder->worker, bb_node_index)) { auto* new_top = arena_allocate(thread->arena, SchedNode, 1); *new_top = (SchedNode) { .node_index = bb_node_index, .parent = top, }; top = new_top; try_again = 1; break; } } if (try_again) { continue; } } thread_worklist_push_array(thread, builder->worker, node_index); auto* parent = top->parent; top = parent; if (type_pair_get_backend(node->type) == BACKEND_TYPE_TUPLE) { todo(); } } } fn void print_reference_to_node(Thread* restrict thread, CFGBuilder* restrict builder, Function* restrict function, NodeIndex node_index, u8 def) { auto* restrict node = thread_node_get(thread, node_index); print("[#{u32} ({s})", geti(node_index), node_id_to_string(node->id)); switch (node->id) { case IR_PROJECTION: { if (node_is_control_projection(node)) { todo(); } else { } } break; case IR_INTEGER_CONSTANT: { print(": 0x{u64:x}", node->integer_constant.unsigned_value); } break; // TODO: case IR_REGION: break; case IR_PHI: break; case MACHINE_COPY: case MACHINE_MOVE: break; default: todo(); } print("]"); } fn void print_basic_block(Thread* restrict thread, CFGBuilder* restrict builder, Function* restrict function, BasicBlockIndex basic_block_index) { auto* restrict basic_block = &builder->basic_blocks.pointer[geti(basic_block_index)]; print_reference_to_node(thread, builder, function, basic_block->start, 1); print("\n"); greedy_scheduler(thread, builder, function, basic_block_index); for (u32 i = 0; i < thread_worklist_length(thread, builder->worker); i += 1) { auto node_index = thread_worklist_get(thread, builder->worker, i); auto* node = thread_node_get(thread, node_index); switch (node->id) { case IR_PROJECTION: case IR_INTEGER_CONSTANT: case IR_REGION: case IR_PHI: continue; case MACHINE_MOVE: case MACHINE_COPY: case IR_RETURN: { auto is_branch = 0; if (is_branch) { todo(); } else if (type_pair_get_backend(node->type) == BACKEND_TYPE_TUPLE) { todo(); } else { print(" "); print("#{u32}", geti(node_index)); print(" = {s}.", node_id_to_string(node->id)); // TODO: print type } print(" I({u32})", (u32)node->input_count); u64 first = node->id != IR_PROJECTION; auto inputs = node_get_inputs(thread, node); if (node->input_count - first) { print(": "); for (auto i = first; i < node->input_count; i += 1) { if (i != first) { print(", "); } print_reference_to_node(thread, builder, function, inputs.pointer[i], 0); } } else { print(" "); } switch (node->id) { case MACHINE_MOVE: case MACHINE_COPY: case IR_REGION: case IR_PHI: case IR_RETURN: break; default: todo(); } } break; default: todo(); } print("\n"); } thread_worklist_clear(thread, builder->worker); auto* end_node = thread_node_get(thread, basic_block->end); if (cfg_node_terminator(end_node)) { // todo(); } } fn void print_ir(Thread* restrict thread) { auto cfg_builder = cfg_builder_init(thread); auto* restrict builder = &cfg_builder; for (u32 i = 0; i < thread->buffer.functions.length; i += 1) { Function* restrict function = &thread->buffer.functions.pointer[i]; cfg_builder_clear(builder, thread); cfg_build_and_global_schedule(builder, thread, function, (GlobalScheduleOptions) { .dataflow = 0, }); auto end_basic_block_index = invalidi(BasicBlock); for (u32 i = 0; i < builder->basic_blocks.length; i += 1) { auto* restrict basic_block = &builder->basic_blocks.pointer[i]; auto end_node_index = basic_block->end; auto* end_node = thread_node_get(thread, end_node_index); if (end_node->id == IR_RETURN) { end_basic_block_index = Index(BasicBlock, i); continue; } else if (node_is_empty_control_projection(thread, builder, end_node_index)) { continue; } print_basic_block(thread, builder, function, Index(BasicBlock, i)); } if (validi(end_basic_block_index)) { print_basic_block(thread, builder, function, end_basic_block_index); } } } #if LINK_LIBC int main(int argc, const char* argv[], char* envp[]) { #else void entry_point(int argc, const char* argv[]) { char** envp = (char**)&argv[argc + 1]; unused(envp); #endif #if DO_UNIT_TESTS unit_tests(); #endif // calibrate_cpu_timer(); if (argc < 3) { fail(); } Arena* global_arena = arena_init(MB(2), KB(64), KB(64)); { arguments.length = cast(u64, s32, argc); arguments.pointer = arena_allocate(global_arena, String, arguments.length); for (int i = 0; i < argc; i += 1) { u64 len = strlen(argv[i]); arguments.pointer[i] = (String) { .pointer = (u8*)argv[i], .length = len, }; } } String source_file_path = arguments.pointer[1]; CompilerBackend compiler_backend = arguments.pointer[2].pointer[0]; u8 emit_ir = arguments.length >= 4 && arguments.pointer[3].pointer[0] == 'y'; Thread* thread = arena_allocate(global_arena, Thread, 1); thread_init(thread); dir_make("nest"); File file = { .path = source_file_path, .source = file_read(thread->arena, source_file_path), }; analyze_file(thread, &file); if (thread->main_function == -1) { fail(); } print("File path: {s}\n", source_file_path); auto test_dir = string_no_extension(file.path); print("Test dir path: {s}\n", test_dir); auto test_name = string_base(test_dir); print("Test name: {s}\n", test_name); if (emit_ir) { print_ir(thread); } else { code_generation(thread, (CodegenOptions) { .test_name = test_name, .backend = compiler_backend, }, envp); } thread_clear(thread); #if LINK_LIBC == 0 syscall_exit(0); #endif } #if LINK_LIBC == 0 [[gnu::naked]] [[noreturn]] void _start() { __asm__ __volatile__( "\nxor %ebp, %ebp" "\npopq %rdi" "\nmov %rsp, %rsi" "\nand $~0xf, %rsp" "\npushq %rsp" "\npushq $0" "\ncallq entry_point" "\nud2\n" ); } #endif