5335 lines
152 KiB
C
5335 lines
152 KiB
C
#include "lib.h"
|
|
#define clang_path "/usr/bin/clang"
|
|
|
|
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 = index;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
auto pair = &values[index];
|
|
if (s_equal(pair->string, key))
|
|
{
|
|
result = 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)
|
|
{
|
|
u32 new_capacity = MAX(round_up_to_next_power_of_2(destination_length), 32);
|
|
auto new_capacity_bytes = sizeof(u32) * new_capacity + new_capacity * sizeof(StringMapValue);
|
|
|
|
void* ptr = arena_allocate_bytes(arena, new_capacity_bytes, MAX(alignof(u32), alignof(StringMapValue)));
|
|
memset(ptr, 0, new_capacity_bytes);
|
|
|
|
auto* keys = (u32*)ptr;
|
|
auto* values = (StringMapValue*)(keys + new_capacity);
|
|
|
|
auto* old_keys = map->pointer;
|
|
auto old_capacity = map->capacity;
|
|
auto* old_values = (StringMapValue*)(map->pointer + current_capacity);
|
|
|
|
map->length = 0;
|
|
map->pointer = keys;
|
|
map->capacity = new_capacity;
|
|
|
|
for (u32 i = 0; i < old_capacity; i += 1)
|
|
{
|
|
auto key = old_keys[i];
|
|
if (key)
|
|
{
|
|
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;
|
|
Hash long_hash = hash_bytes(key);
|
|
auto hash = (u32)long_hash;
|
|
assert(hash);
|
|
auto index = hash & (map->capacity - 1);
|
|
auto slot = string_map_find_slot(map, index, key);
|
|
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)
|
|
{
|
|
Hash long_hash = hash_bytes(key);
|
|
auto hash = (u32)long_hash;
|
|
assert(hash);
|
|
auto index = hash & (map->capacity - 1);
|
|
auto slot = string_map_find_slot(map, index, key);
|
|
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 void string_map_get_or_put(StringMap* map, Arena* arena, String key, u32 value)
|
|
// {
|
|
// assert(value);
|
|
// auto hash = hash_bytes(key);
|
|
// 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];
|
|
// todo();
|
|
// // 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)
|
|
// {
|
|
// todo();
|
|
// }
|
|
// else if (map->length == map->capacity)
|
|
// {
|
|
// todo();
|
|
// // auto result = string_map_put_assume_not_existent(map, arena, hash, key, value);
|
|
// // assert(!result.existing);
|
|
// // return result;
|
|
// }
|
|
// else
|
|
// {
|
|
// todo();
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
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 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);
|
|
|
|
u64 file_size = 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 NameReference
|
|
{
|
|
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;
|
|
|
|
typedef struct BackendType BackendType;
|
|
struct TypeIndex
|
|
{
|
|
u32 index;
|
|
};
|
|
|
|
typedef struct TypeIndex TypeIndex;
|
|
#define index_equal(a, b) (a.index == b.index)
|
|
static_assert(sizeof(TypeIndex) == sizeof(u32));
|
|
declare_slice(TypeIndex);
|
|
|
|
#define RawIndex(T, i) (T ## Index) { .index = (i) }
|
|
#define Index(T, i) RawIndex(T, (i) + 1)
|
|
#define geti(i) ((i).index - 1)
|
|
#define validi(i) ((i).index != 0)
|
|
#define invalidi(T) RawIndex(T, 0)
|
|
|
|
struct TypeInteger
|
|
{
|
|
u64 constant;
|
|
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
|
|
{
|
|
Hash hash;
|
|
union
|
|
{
|
|
TypeInteger integer;
|
|
TypeTuple tuple;
|
|
};
|
|
TypeId id;
|
|
};
|
|
typedef struct Type Type;
|
|
static_assert(offsetof(Type, hash) == 0);
|
|
decl_vb(Type);
|
|
|
|
struct NodeIndex
|
|
{
|
|
u32 index;
|
|
};
|
|
typedef struct NodeIndex NodeIndex;
|
|
declare_slice(NodeIndex);
|
|
decl_vb(NodeIndex);
|
|
|
|
struct Function
|
|
{
|
|
String name;
|
|
NodeIndex start;
|
|
NodeIndex stop;
|
|
TypeIndex return_type;
|
|
};
|
|
typedef struct Function Function;
|
|
decl_vb(Function);
|
|
|
|
typedef enum NodeId : u8
|
|
{
|
|
NODE_START,
|
|
NODE_STOP,
|
|
NODE_CONTROL_PROJECTION,
|
|
NODE_DEAD_CONTROL,
|
|
NODE_SCOPE,
|
|
NODE_PROJECTION,
|
|
NODE_RETURN,
|
|
NODE_REGION,
|
|
NODE_REGION_LOOP,
|
|
NODE_IF,
|
|
NODE_PHI,
|
|
|
|
NODE_INTEGER_ADD,
|
|
NODE_INTEGER_SUBSTRACT,
|
|
NODE_INTEGER_MULTIPLY,
|
|
NODE_INTEGER_UNSIGNED_DIVIDE,
|
|
NODE_INTEGER_SIGNED_DIVIDE,
|
|
NODE_INTEGER_UNSIGNED_REMAINDER,
|
|
NODE_INTEGER_SIGNED_REMAINDER,
|
|
NODE_INTEGER_UNSIGNED_SHIFT_LEFT,
|
|
NODE_INTEGER_SIGNED_SHIFT_LEFT,
|
|
NODE_INTEGER_UNSIGNED_SHIFT_RIGHT,
|
|
NODE_INTEGER_SIGNED_SHIFT_RIGHT,
|
|
NODE_INTEGER_AND,
|
|
NODE_INTEGER_OR,
|
|
NODE_INTEGER_XOR,
|
|
NODE_INTEGER_NEGATION,
|
|
|
|
NODE_INTEGER_COMPARE_EQUAL,
|
|
NODE_INTEGER_COMPARE_NOT_EQUAL,
|
|
|
|
NODE_CONSTANT,
|
|
|
|
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 NodeProjection
|
|
{
|
|
String label;
|
|
u32 index;
|
|
};
|
|
typedef struct NodeProjection NodeProjection;
|
|
|
|
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 Node
|
|
{
|
|
Hash hash;
|
|
u32 input_offset;
|
|
u32 output_offset;
|
|
u32 dependency_offset;
|
|
u16 output_count;
|
|
u16 input_count;
|
|
u16 dependency_count;
|
|
u16 input_capacity;
|
|
u16 output_capacity;
|
|
u16 dependency_capacity;
|
|
u16 thread_id;
|
|
TypeIndex type;
|
|
NodeId id;
|
|
union
|
|
{
|
|
NodeConstant constant;
|
|
NodeStart start;
|
|
NodeStop stop;
|
|
NodeScope scope;
|
|
NodeControlProjection control_projection;
|
|
NodeProjection projection;
|
|
NodeReturn return_node;
|
|
NodeDeadControl dead_control;
|
|
};
|
|
};
|
|
typedef struct Node Node;
|
|
declare_slice_p(Node);
|
|
static_assert(offsetof(Node, hash) == 0);
|
|
|
|
decl_vb(Node);
|
|
decl_vbp(Node);
|
|
|
|
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 scope;
|
|
NodeIndex dead_control;
|
|
};
|
|
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;
|
|
auto 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 = 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;
|
|
bitset->arr.pointer[element_index] |= (!!value) << bit_index;
|
|
}
|
|
|
|
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;
|
|
|
|
struct Thread
|
|
{
|
|
Arena* arena;
|
|
struct
|
|
{
|
|
VirtualBuffer(Type) types;
|
|
VirtualBuffer(Node) nodes;
|
|
VirtualBuffer(NodeIndex) uses;
|
|
VirtualBuffer(u8) name_buffer;
|
|
VirtualBuffer(ArrayReference) use_free_list;
|
|
VirtualBuffer(Function) functions;
|
|
} buffer;
|
|
struct
|
|
{
|
|
InternPool types;
|
|
InternPool nodes;
|
|
} interned;
|
|
struct
|
|
{
|
|
TypeIndex bottom;
|
|
TypeIndex top;
|
|
TypeIndex live_control;
|
|
TypeIndex dead_control;
|
|
struct
|
|
{
|
|
TypeIndex top;
|
|
TypeIndex bottom;
|
|
TypeIndex zero;
|
|
TypeIndex u8;
|
|
TypeIndex u16;
|
|
TypeIndex u32;
|
|
TypeIndex u64;
|
|
TypeIndex s8;
|
|
TypeIndex s16;
|
|
TypeIndex s32;
|
|
TypeIndex s64;
|
|
} integer;
|
|
} types;
|
|
WorkList worklist;
|
|
s64 main_function;
|
|
struct
|
|
{
|
|
u64 total;
|
|
u64 nop;
|
|
} iteration;
|
|
};
|
|
typedef struct Thread Thread;
|
|
|
|
fn NodeIndex thread_worklist_push(Thread* thread, NodeIndex node_index)
|
|
{
|
|
if (validi(node_index))
|
|
{
|
|
if (!bitset_get(&thread->worklist.bitset, geti(node_index)))
|
|
{
|
|
bitset_set_value(&thread->worklist.bitset, geti(node_index), 1);
|
|
*vb_add(&thread->worklist.nodes, 1) = node_index;
|
|
}
|
|
}
|
|
|
|
return node_index;
|
|
}
|
|
|
|
fn NodeIndex thread_worklist_pop(Thread* thread)
|
|
{
|
|
auto result = invalidi(Node);
|
|
|
|
auto len = thread->worklist.nodes.length;
|
|
if (len)
|
|
{
|
|
auto index = len - 1;
|
|
auto node_index = thread->worklist.nodes.pointer[index];
|
|
thread->worklist.nodes.length = index;
|
|
bitset_set_value(&thread->worklist.bitset, index, 0);
|
|
result = node_index;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
fn void thread_worklist_clear(Thread* thread)
|
|
{
|
|
bitset_clear(&thread->worklist.visited);
|
|
bitset_clear(&thread->worklist.bitset);
|
|
thread->worklist.nodes.length = 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 Node* thread_node_get(Thread* thread, NodeIndex node_index)
|
|
{
|
|
assert(validi(node_index));
|
|
auto* node = &thread->buffer.nodes.pointer[geti(node_index)];
|
|
return node;
|
|
}
|
|
|
|
fn void thread_node_set_use(Thread* thread, u32 offset, u16 index, NodeIndex new_use)
|
|
{
|
|
thread->buffer.uses.pointer[offset + index] = new_use;
|
|
}
|
|
|
|
fn NodeIndex thread_node_get_use(Thread* thread, u32 offset, u16 index)
|
|
{
|
|
NodeIndex i = thread->buffer.uses.pointer[offset + index];
|
|
return i;
|
|
}
|
|
|
|
fn NodeIndex node_input_get(Thread* thread, Node* node, u16 index)
|
|
{
|
|
assert(index < node->input_count);
|
|
NodeIndex result = thread_node_get_use(thread, node->input_offset, index);
|
|
return result;
|
|
}
|
|
|
|
fn NodeIndex node_output_get(Thread* thread, Node* node, u16 index)
|
|
{
|
|
assert(index < node->output_count);
|
|
NodeIndex result = thread_node_get_use(thread, node->output_offset, index);
|
|
return result;
|
|
}
|
|
|
|
fn NodeIndex scope_get_control(Thread* thread, Node* node)
|
|
{
|
|
assert(node->id == NODE_SCOPE);
|
|
auto control = node_input_get(thread, node, 0);
|
|
return control;
|
|
}
|
|
|
|
fn NodeIndex builder_get_control_node_index(Thread* thread, FunctionBuilder* builder)
|
|
{
|
|
auto* scope_node = thread_node_get(thread, builder->scope);
|
|
auto result = scope_get_control(thread, scope_node);
|
|
return result;
|
|
}
|
|
|
|
typedef struct NodeDualReference NodeDualReference;
|
|
|
|
struct UseReference
|
|
{
|
|
NodeIndex* pointer;
|
|
u32 index;
|
|
};
|
|
typedef struct UseReference UseReference;
|
|
|
|
fn UseReference thread_get_node_reference_array(Thread* thread, u16 count)
|
|
{
|
|
u32 free_list_count = thread->buffer.use_free_list.length;
|
|
for (u32 i = 0; i < free_list_count; i += 1)
|
|
{
|
|
if (thread->buffer.use_free_list.pointer[i].length >= count)
|
|
{
|
|
trap();
|
|
}
|
|
}
|
|
|
|
u32 index = thread->buffer.uses.length;
|
|
auto* node_indices = vb_add(&thread->buffer.uses, count);
|
|
return (UseReference)
|
|
{
|
|
.pointer = node_indices,
|
|
.index = index,
|
|
};
|
|
}
|
|
|
|
fn void node_ensure_capacity(Thread* thread, u32* offset, u16* capacity, u16 current_length, u16 additional)
|
|
{
|
|
auto current_offset = *offset;
|
|
auto current_capacity = *capacity;
|
|
auto desired_capacity = current_length + additional;
|
|
|
|
if (desired_capacity > current_capacity)
|
|
{
|
|
auto* ptr = vb_add(&thread->buffer.uses, desired_capacity);
|
|
u32 new_offset = ptr - thread->buffer.uses.pointer;
|
|
memcpy(ptr, &thread->buffer.uses.pointer[current_offset], current_length * sizeof(NodeIndex));
|
|
memset(ptr + current_length, 0, (desired_capacity - current_length) * sizeof(NodeIndex));
|
|
*offset = new_offset;
|
|
*capacity = desired_capacity;
|
|
}
|
|
}
|
|
|
|
fn void node_add_one_assume_capacity(Thread* thread, NodeIndex node, u32 offset, u16 capacity, u16* length)
|
|
{
|
|
auto index = *length;
|
|
assert(index < capacity);
|
|
thread->buffer.uses.pointer[offset + index] = node;
|
|
*length = index + 1;
|
|
}
|
|
|
|
fn void node_add_one(Thread* thread, u32* offset, u16* capacity, u16* count, NodeIndex node_index)
|
|
{
|
|
node_ensure_capacity(thread, offset, capacity, *count, 1);
|
|
node_add_one_assume_capacity(thread, node_index, *offset, *capacity, count);
|
|
}
|
|
|
|
fn NodeIndex node_add_output(Thread* thread, NodeIndex node_index, NodeIndex output_index)
|
|
{
|
|
auto* this_node = thread_node_get(thread, node_index);
|
|
node_add_one(thread, &this_node->output_offset, &this_node->output_capacity, &this_node->output_count, output_index);
|
|
|
|
return node_index;
|
|
}
|
|
|
|
fn NodeIndex intern_pool_remove_node(Thread* thread, NodeIndex node_index);
|
|
fn void node_unlock(Thread* thread, NodeIndex node_index)
|
|
{
|
|
auto* node = thread_node_get(thread, node_index);
|
|
if (node->hash)
|
|
{
|
|
auto old_node_index = intern_pool_remove_node(thread, node_index);
|
|
assert(index_equal(old_node_index, node_index));
|
|
node->hash = 0;
|
|
}
|
|
}
|
|
|
|
fn s32 node_find(Slice(NodeIndex) nodes, NodeIndex node_index)
|
|
{
|
|
s32 result = -1;
|
|
for (u32 i = 0; i < nodes.length; i += 1)
|
|
{
|
|
if (index_equal(nodes.pointer[i], node_index))
|
|
{
|
|
result = i;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
fn void thread_node_remove_use(Thread* thread, u32 offset, u16* length, u16 index)
|
|
{
|
|
auto current_length = *length;
|
|
assert(index < current_length);
|
|
auto item_to_remove = &thread->buffer.uses.pointer[offset + index];
|
|
auto substitute = &thread->buffer.uses.pointer[offset + current_length - 1];
|
|
*item_to_remove = *substitute;
|
|
*length = current_length - 1;
|
|
}
|
|
|
|
fn Slice(NodeIndex) node_get_inputs(Thread* thread, Node* node)
|
|
{
|
|
auto result = (Slice(NodeIndex)) {
|
|
.pointer = &thread->buffer.uses.pointer[node->input_offset],
|
|
.length = node->input_count,
|
|
};
|
|
return result;
|
|
}
|
|
|
|
fn Slice(NodeIndex) node_get_outputs(Thread* thread, Node* node)
|
|
{
|
|
auto result = (Slice(NodeIndex)) {
|
|
.pointer = &thread->buffer.uses.pointer[node->output_offset],
|
|
.length = node->output_count,
|
|
};
|
|
return result;
|
|
}
|
|
|
|
fn u8 node_remove_output(Thread* thread, NodeIndex node_index, NodeIndex use_index)
|
|
{
|
|
auto* node = thread_node_get(thread, node_index);
|
|
auto outputs = node_get_outputs(thread, node);
|
|
auto index = node_find(outputs, use_index);
|
|
assert(index != -1);
|
|
thread_node_remove_use(thread, node->output_offset, &node->output_count, index);
|
|
return node->output_count == 0;
|
|
}
|
|
|
|
fn void move_dependencies_to_worklist(Thread* thread, Node* node)
|
|
{
|
|
assert(node->dependency_count == 0);
|
|
for (u32 i = 0; i < node->dependency_count; i += 1)
|
|
{
|
|
unused(thread);
|
|
trap();
|
|
}
|
|
}
|
|
|
|
fn String node_id_to_string(Node* node)
|
|
{
|
|
switch (node->id)
|
|
{
|
|
case_to_name(NODE_, START);
|
|
case_to_name(NODE_, STOP);
|
|
case_to_name(NODE_, CONTROL_PROJECTION);
|
|
case_to_name(NODE_, DEAD_CONTROL);
|
|
case_to_name(NODE_, SCOPE);
|
|
case_to_name(NODE_, PROJECTION);
|
|
case_to_name(NODE_, RETURN);
|
|
case_to_name(NODE_, REGION);
|
|
case_to_name(NODE_, REGION_LOOP);
|
|
case_to_name(NODE_, IF);
|
|
case_to_name(NODE_, PHI);
|
|
case_to_name(NODE_, INTEGER_ADD);
|
|
case_to_name(NODE_, INTEGER_SUBSTRACT);
|
|
case_to_name(NODE_, INTEGER_MULTIPLY);
|
|
case_to_name(NODE_, INTEGER_UNSIGNED_DIVIDE);
|
|
case_to_name(NODE_, INTEGER_SIGNED_DIVIDE);
|
|
case_to_name(NODE_, INTEGER_UNSIGNED_REMAINDER);
|
|
case_to_name(NODE_, INTEGER_SIGNED_REMAINDER);
|
|
case_to_name(NODE_, INTEGER_UNSIGNED_SHIFT_LEFT);
|
|
case_to_name(NODE_, INTEGER_SIGNED_SHIFT_LEFT);
|
|
case_to_name(NODE_, INTEGER_UNSIGNED_SHIFT_RIGHT);
|
|
case_to_name(NODE_, INTEGER_SIGNED_SHIFT_RIGHT);
|
|
case_to_name(NODE_, INTEGER_AND);
|
|
case_to_name(NODE_, INTEGER_OR);
|
|
case_to_name(NODE_, INTEGER_XOR);
|
|
case_to_name(NODE_, INTEGER_NEGATION);
|
|
case_to_name(NODE_, CONSTANT);
|
|
case_to_name(NODE_, COUNT);
|
|
case_to_name(NODE_, INTEGER_COMPARE_EQUAL);
|
|
case_to_name(NODE_, INTEGER_COMPARE_NOT_EQUAL);
|
|
break;
|
|
}
|
|
}
|
|
|
|
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));
|
|
node->type = invalidi(Type);
|
|
|
|
auto inputs = node_get_inputs(thread, node);
|
|
while (node->input_count > 0)
|
|
{
|
|
auto input_index = 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 old_input = node_input_get(thread, node, index);
|
|
|
|
if (!index_equal(old_input, new_input))
|
|
{
|
|
if (validi(new_input))
|
|
{
|
|
node_add_output(thread, new_input, node_index);
|
|
}
|
|
|
|
thread_node_set_use(thread, node->input_offset, index, new_input);
|
|
|
|
if (validi(old_input))
|
|
{
|
|
if (node_remove_output(thread, old_input, node_index))
|
|
{
|
|
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;
|
|
}
|
|
|
|
fn NodeIndex builder_add_return(Thread* thread, FunctionBuilder* builder, NodeIndex node_index)
|
|
{
|
|
return node_add_input(thread, builder->function->stop, node_index);
|
|
}
|
|
|
|
struct NodeCreate
|
|
{
|
|
NodeId id;
|
|
Slice(NodeIndex) inputs;
|
|
};
|
|
typedef struct NodeCreate NodeCreate;
|
|
|
|
|
|
fn NodeIndex thread_node_add(Thread* thread, NodeCreate data)
|
|
{
|
|
auto input_result = thread_get_node_reference_array(thread, data.inputs.length);
|
|
memcpy(input_result.pointer, data.inputs.pointer, sizeof(NodeIndex) * data.inputs.length);
|
|
|
|
auto* node = vb_add(&thread->buffer.nodes, 1);
|
|
auto node_index = Index(Node, node - thread->buffer.nodes.pointer);
|
|
memset(node, 0, sizeof(Node));
|
|
node->id = data.id;
|
|
node->input_offset = input_result.index;
|
|
node->input_count = data.inputs.length;
|
|
node->type = invalidi(Type);
|
|
|
|
// print("[NODE CREATION] #{u32} {s} | INPUTS: { ", node_index.index, node_id_to_string(node));
|
|
|
|
for (u32 i = 0; i < data.inputs.length; 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 Hash hash_type(Thread* thread, Type* type);
|
|
|
|
fn Hash node_get_hash_default(Thread* thread, Node* node, NodeIndex node_index, Hash hash)
|
|
{
|
|
unused(thread);
|
|
unused(node);
|
|
unused(node_index);
|
|
return hash;
|
|
}
|
|
|
|
fn Hash node_get_hash_projection(Thread* thread, Node* node, NodeIndex node_index, Hash 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 Hash node_get_hash_control_projection(Thread* thread, Node* node, NodeIndex node_index, Hash 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 Hash node_get_hash_constant(Thread* thread, Node* node, NodeIndex node_index, Hash hash)
|
|
{
|
|
unused(node_index);
|
|
assert(hash == fnv_offset);
|
|
// 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 Hash node_get_hash_scope(Thread* thread, Node* node, NodeIndex node_index, Hash 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 == NODE_INTEGER_NEGATION)
|
|
{
|
|
trap();
|
|
}
|
|
else if (left->id == NODE_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 == NODE_INTEGER_COMPARE_EQUAL)
|
|
{
|
|
if (right->id != NODE_CONSTANT)
|
|
{
|
|
if (left->id == NODE_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);
|
|
}
|
|
|
|
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 Hash TypeGetHash(Thread* thread, Type* type);
|
|
typedef Hash NodeGetHash(Thread* thread, Node* node, NodeIndex node_index, Hash 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 = NODE_CONSTANT,
|
|
.inputs = array_to_slice(((NodeIndex []) {
|
|
function->start,
|
|
}))
|
|
});
|
|
auto* node = thread_node_get(thread, node_index);
|
|
|
|
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 Hash 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 == NODE_CONSTANT);
|
|
return node->constant.type;
|
|
}
|
|
|
|
fn Hash type_get_hash_default(Thread* thread, Type* type)
|
|
{
|
|
unused(thread);
|
|
assert(!type->hash);
|
|
Hash 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 Hash type_get_hash_tuple(Thread* thread, Type* type)
|
|
{
|
|
Hash hash = fnv_offset;
|
|
for (u64 i = 0; i < type->tuple.types.length; i += 1)
|
|
{
|
|
auto* tuple_type = thread_type_get(thread,type->tuple.types.pointer[i]);
|
|
auto type_hash = hash_type(thread, tuple_type);
|
|
for (u8* it = (u8*)&type_hash; it < (u8*)(&type_hash + 1); it += 1)
|
|
{
|
|
hash = hash_byte(hash, *it);
|
|
}
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
fn u8 node_is_projection(Node* n)
|
|
{
|
|
return (n->id == NODE_CONTROL_PROJECTION) | (n->id == NODE_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 NODE_CONTROL_PROJECTION:
|
|
return node->control_projection.projection.index;
|
|
case NODE_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 == NODE_CONTROL_PROJECTION);
|
|
auto control_node_index = projection_get_control(thread, node);
|
|
auto* control_node = thread_node_get(thread, control_node_index);
|
|
auto* control_type = thread_type_get(thread, control_node->type);
|
|
auto index = node->control_projection.projection.index;
|
|
|
|
if (control_type->id == TYPE_TUPLE)
|
|
{
|
|
if (index_equal(control_type->tuple.types.pointer[index], thread->types.dead_control))
|
|
{
|
|
trap();
|
|
}
|
|
if (control_node->id == NODE_IF)
|
|
{
|
|
trap();
|
|
}
|
|
}
|
|
|
|
if (control_node->id == NODE_IF)
|
|
{
|
|
trap();
|
|
}
|
|
|
|
return invalidi(Node);
|
|
}
|
|
|
|
fn NodeIndex return_get_control(Thread* thread, Node* node)
|
|
{
|
|
return node_input_get(thread, node, 0);
|
|
}
|
|
|
|
fn NodeIndex return_get_value(Thread* thread, Node* node)
|
|
{
|
|
return node_input_get(thread, node, 1);
|
|
}
|
|
|
|
fn TypeIndex intern_pool_put_new_type_at_assume_not_existent_assume_capacity(Thread* thread, Type* type, u32 index)
|
|
{
|
|
auto* result = vb_add(&thread->buffer.types, 1);
|
|
auto buffer_index = result - thread->buffer.types.pointer;
|
|
auto type_index = Index(Type, buffer_index);
|
|
*result = *type;
|
|
|
|
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);
|
|
Hash hash = type->hash;
|
|
assert(hash);
|
|
auto index = 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;
|
|
|
|
// This assumes the indices are not equal
|
|
fn u8 node_equal(Thread* thread, Node* a, Node* b)
|
|
{
|
|
u8 result = 0;
|
|
assert(a != b);
|
|
assert(a->hash);
|
|
assert(b->hash);
|
|
|
|
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)
|
|
{
|
|
switch (a->id)
|
|
{
|
|
case NODE_CONSTANT:
|
|
result = index_equal(a->constant.type, b->constant.type);
|
|
break;
|
|
case NODE_START:
|
|
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;
|
|
if (index_equal(a, b))
|
|
{
|
|
result = 1;
|
|
}
|
|
else
|
|
{
|
|
auto* node_a = thread_node_get(thread, a);
|
|
auto* node_b = thread_node_get(thread, b);
|
|
assert(node_a != node_b);
|
|
result = node_equal(thread, node_a, node_b);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
[[gnu::hot]] fn s32 intern_pool_find_node_slot(Thread* thread, u32 original_index, NodeIndex node_index)
|
|
{
|
|
assert(validi(node_index));
|
|
auto it_index = original_index;
|
|
auto existing_capacity = thread->interned.nodes.capacity;
|
|
s32 result = -1;
|
|
// auto* node = thread_node_get(thread, node_index);
|
|
|
|
for (u32 i = 0; i < existing_capacity; i += 1)
|
|
{
|
|
auto index = it_index & (existing_capacity - 1);
|
|
u32 key = thread->interned.nodes.pointer[index];
|
|
|
|
if (key == 0)
|
|
{
|
|
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, Hash hash)
|
|
{
|
|
auto original_index = hash & (thread->interned.nodes.capacity - 1);
|
|
auto slot = intern_pool_find_node_slot(thread, original_index, key);
|
|
auto* pointer_to_slot = &thread->interned.nodes.pointer[slot];
|
|
return *(NodeIndex*)pointer_to_slot;
|
|
}
|
|
|
|
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, Hash hash, NodeIndex node)
|
|
{
|
|
auto capacity = thread->interned.nodes.capacity;
|
|
assert(thread->interned.nodes.length < capacity);
|
|
auto original_index = 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)
|
|
{
|
|
u32 new_capacity = MAX(round_up_to_next_power_of_2(destination_length), 32);
|
|
u32* 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 = *(Hash*)(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);
|
|
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 = *(Hash*)(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);
|
|
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 s32 intern_pool_find_type_slot(Thread* thread, u32 original_index, Type* type)
|
|
{
|
|
auto it_index = original_index;
|
|
auto existing_capacity = thread->interned.types.capacity;
|
|
s32 result = -1;
|
|
|
|
for (u32 i = 0; i < existing_capacity; i += 1)
|
|
{
|
|
auto index = it_index & (existing_capacity - 1);
|
|
u32 key = thread->interned.types.pointer[index];
|
|
|
|
// Not set
|
|
if (key == 0)
|
|
{
|
|
result = index;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
TypeIndex existing_type_index = *(TypeIndex*)&key;
|
|
Type* existing_type = thread_type_get(thread, existing_type_index);
|
|
if (type_equal(existing_type, type))
|
|
{
|
|
result = index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
it_index += 1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
fn TypeGetOrPut intern_pool_get_or_put_new_type(Thread* thread, Type* type)
|
|
{
|
|
auto existing_capacity = thread->interned.types.capacity;
|
|
auto hash = hash_type(thread, type);
|
|
auto original_index = hash & (existing_capacity - 1);
|
|
|
|
auto slot = intern_pool_find_type_slot(thread, original_index, type);
|
|
if (slot != -1)
|
|
{
|
|
u32 index = slot;
|
|
TypeIndex type_index = *(TypeIndex*)&thread->interned.types.pointer[index];
|
|
u8 existing = validi(type_index);
|
|
if (!existing)
|
|
{
|
|
type_index = intern_pool_put_new_type_at_assume_not_existent_assume_capacity(thread, type, index);
|
|
}
|
|
|
|
return (TypeGetOrPut) {
|
|
.index = type_index,
|
|
.existing = existing,
|
|
};
|
|
}
|
|
else
|
|
{
|
|
if (thread->interned.types.length < existing_capacity)
|
|
{
|
|
trap();
|
|
}
|
|
else if (thread->interned.types.length == existing_capacity)
|
|
{
|
|
auto result = intern_pool_put_new_type_assume_not_existent(thread, type);
|
|
return (TypeGetOrPut) {
|
|
.index = result,
|
|
.existing = 0,
|
|
};
|
|
}
|
|
else
|
|
{
|
|
trap();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
fn TypeGetOrPut type_make_tuple(Thread* thread, Slice(TypeIndex) types)
|
|
{
|
|
Type type;
|
|
memset(&type, 0, sizeof(Type));
|
|
type.tuple = (TypeTuple){
|
|
.types = types,
|
|
};
|
|
type.id = TYPE_TUPLE;
|
|
auto result = intern_pool_get_or_put_new_type(thread, &type);
|
|
return result;
|
|
}
|
|
|
|
fn TypeIndex type_make_tuple_allocate(Thread* thread, Slice(TypeIndex) types)
|
|
{
|
|
auto gop = type_make_tuple(thread, types);
|
|
// Need to reallocate the type array
|
|
if (!gop.existing)
|
|
{
|
|
auto* type = thread_type_get(thread, gop.index);
|
|
assert(type->tuple.types.pointer == types.pointer);
|
|
assert(type->tuple.types.length == types.length);
|
|
type->tuple.types = arena_allocate_slice(thread->arena, TypeIndex, types.length);
|
|
memcpy(type->tuple.types.pointer, types.pointer, sizeof(TypeIndex) * types.length);
|
|
}
|
|
|
|
return gop.index;
|
|
}
|
|
|
|
fn TypeIndex compute_type_return(Thread* thread, NodeIndex node_index)
|
|
{
|
|
Node* node = thread_node_get(thread, node_index);
|
|
auto control_type = thread_node_get(thread, return_get_control(thread, node))->type;
|
|
auto return_type = thread_node_get(thread, return_get_value(thread, node))->type;
|
|
Slice(TypeIndex) types = array_to_slice(((TypeIndex[]) {
|
|
control_type,
|
|
return_type,
|
|
}));
|
|
auto result = type_make_tuple_allocate(thread, types);
|
|
return result;
|
|
}
|
|
|
|
fn NodeIndex idealize_return(Thread* thread, NodeIndex node_index)
|
|
{
|
|
auto* node = thread_node_get(thread, node_index);
|
|
auto control_node_index = return_get_control(thread, node);
|
|
auto* control_node = thread_node_get(thread, control_node_index);
|
|
if (index_equal(control_node->type, thread->types.dead_control))
|
|
{
|
|
return control_node_index;
|
|
}
|
|
else
|
|
{
|
|
return invalidi(Node);
|
|
}
|
|
|
|
}
|
|
|
|
fn TypeIndex compute_type_dead_control(Thread* thread, NodeIndex node_index)
|
|
{
|
|
unused(node_index);
|
|
return thread->types.dead_control;
|
|
}
|
|
|
|
fn TypeIndex compute_type_bottom(Thread* thread, NodeIndex node_index)
|
|
{
|
|
unused(node_index);
|
|
return thread->types.bottom;
|
|
}
|
|
|
|
fn NodeIndex idealize_stop(Thread* thread, NodeIndex node_index)
|
|
{
|
|
auto* node = thread_node_get(thread, node_index);
|
|
auto original_input_count = node->input_count;
|
|
for (u16 i = 0; i < node->input_count; i += 1)
|
|
{
|
|
auto input_node_index = node_input_get(thread, node, i);
|
|
auto* input_node = thread_node_get(thread, input_node_index);
|
|
if (index_equal(input_node->type, thread->types.dead_control))
|
|
{
|
|
trap();
|
|
}
|
|
}
|
|
|
|
if (node->input_count != original_input_count)
|
|
{
|
|
return node_index;
|
|
}
|
|
else
|
|
{
|
|
return invalidi(Node);
|
|
}
|
|
}
|
|
|
|
fn TypeIndex compute_type_start(Thread* thread, NodeIndex node_index)
|
|
{
|
|
auto* node = thread_node_get(thread, node_index);
|
|
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 NODE_INTEGER_ADD:
|
|
result = left_value + right_value;
|
|
break;
|
|
case NODE_INTEGER_SUBSTRACT:
|
|
result = left_value - right_value;
|
|
break;
|
|
case NODE_INTEGER_MULTIPLY:
|
|
result = left_value * right_value;
|
|
break;
|
|
case NODE_INTEGER_SIGNED_DIVIDE:
|
|
result = left_value * right_value;
|
|
break;
|
|
case NODE_INTEGER_AND:
|
|
result = left_value & right_value;
|
|
break;
|
|
case NODE_INTEGER_OR:
|
|
result = left_value | right_value;
|
|
break;
|
|
case NODE_INTEGER_XOR:
|
|
result = left_value ^ right_value;
|
|
break;
|
|
case NODE_INTEGER_SIGNED_SHIFT_LEFT:
|
|
result = left_value << right_value;
|
|
break;
|
|
case NODE_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,
|
|
},
|
|
[NODE_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 Hash hash_type(Thread* thread, Type* type)
|
|
{
|
|
Hash 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, Hash 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);
|
|
}
|
|
|
|
struct NodeGetOrPut
|
|
{
|
|
NodeIndex index;
|
|
u8 existing;
|
|
};
|
|
typedef struct NodeGetOrPut NodeGetOrPut;
|
|
|
|
|
|
|
|
|
|
fn Hash 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, 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)
|
|
{
|
|
parser->column = index + 1;
|
|
}
|
|
|
|
if (!is_space(ch, get_next_ch_safe(src, parser->i)))
|
|
{
|
|
break;
|
|
}
|
|
|
|
u32 is_comment = src.pointer[index] == '/';
|
|
parser->i += is_comment + is_comment;
|
|
if (is_comment)
|
|
{
|
|
while (parser->i < src.length)
|
|
{
|
|
if (src.pointer[parser->i] == '\n')
|
|
{
|
|
break;
|
|
}
|
|
|
|
parser->i += 1;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
parser->i += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[[gnu::hot]] fn void expect_character(Parser* parser, String src, u8 expected_ch)
|
|
{
|
|
u64 index = parser->i;
|
|
if (expect(index < src.length, 1))
|
|
{
|
|
u8 ch = src.pointer[index];
|
|
u64 matches = ch == expected_ch;
|
|
expect(matches, 1);
|
|
parser->i += matches;
|
|
if (!matches)
|
|
{
|
|
print_string(strlit("expected character '"));
|
|
print_string(ch_to_str(expected_ch));
|
|
print_string(strlit("', but found '"));
|
|
print_string(ch_to_str(ch));
|
|
print_string(strlit("'\n"));
|
|
fail();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
print_string(strlit("expected character '"));
|
|
print_string(ch_to_str(expected_ch));
|
|
print_string(strlit("', but found end of file\n"));
|
|
fail();
|
|
}
|
|
}
|
|
|
|
[[gnu::hot]] fn String parse_identifier(Parser* parser, String src)
|
|
{
|
|
u64 identifier_start_index = parser->i;
|
|
u64 is_string_literal = src.pointer[identifier_start_index] == '"';
|
|
parser->i += is_string_literal;
|
|
u8 identifier_start_ch = src.pointer[parser->i];
|
|
u64 is_valid_identifier_start = is_identifier_start(identifier_start_ch);
|
|
parser->i += is_valid_identifier_start;
|
|
|
|
if (expect(is_valid_identifier_start, 1))
|
|
{
|
|
while (parser->i < src.length)
|
|
{
|
|
u8 ch = src.pointer[parser->i];
|
|
u64 is_identifier = is_identifier_ch(ch);
|
|
expect(is_identifier, 1);
|
|
parser->i += is_identifier;
|
|
|
|
if (!is_identifier)
|
|
{
|
|
if (expect(is_string_literal, 0))
|
|
{
|
|
expect_character(parser, src, '"');
|
|
}
|
|
|
|
String result = s_get_slice(u8, src, identifier_start_index, parser->i - is_string_literal);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
fail();
|
|
}
|
|
else
|
|
{
|
|
fail();
|
|
}
|
|
}
|
|
|
|
typedef struct Parser Parser;
|
|
|
|
#define array_start '['
|
|
#define array_end ']'
|
|
|
|
#define argument_start '('
|
|
#define argument_end ')'
|
|
|
|
#define block_start '{'
|
|
#define block_end '}'
|
|
|
|
#define pointer_sign '*'
|
|
|
|
fn void thread_add_job(Thread* thread, NodeIndex node_index)
|
|
{
|
|
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);
|
|
if (node_is_unused(old_node) & !node_is_dead(old_node))
|
|
{
|
|
node_keep(thread, new);
|
|
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)
|
|
{
|
|
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);
|
|
|
|
if (enable_peephole)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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);
|
|
node->type = new_type;
|
|
result = node_index;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
fn TypeIndex analyze_type(Thread* thread, Parser* parser, String src)
|
|
{
|
|
u64 start_index = parser->i;
|
|
u8 start_ch = src.pointer[start_index];
|
|
u32 is_array_start = start_ch == array_start;
|
|
u32 u_start = start_ch == 'u';
|
|
u32 s_start = start_ch == 's';
|
|
u32 float_start = start_ch == 'f';
|
|
u32 void_start = start_ch == 'v';
|
|
u32 pointer_start = start_ch == pointer_sign;
|
|
u32 integer_start = u_start | s_start;
|
|
u32 number_start = integer_start | float_start;
|
|
|
|
if (void_start)
|
|
{
|
|
trap();
|
|
}
|
|
else if (is_array_start)
|
|
{
|
|
trap();
|
|
}
|
|
else if (pointer_start)
|
|
{
|
|
trap();
|
|
}
|
|
else if (number_start)
|
|
{
|
|
u64 expected_digit_start = start_index + 1;
|
|
u64 i = expected_digit_start;
|
|
u32 decimal_digit_count = 0;
|
|
u64 top = i + 5;
|
|
|
|
while (i < top)
|
|
{
|
|
u8 ch = src.pointer[i];
|
|
u32 is_digit = is_decimal_digit(ch);
|
|
decimal_digit_count += is_digit;
|
|
if (!is_digit)
|
|
{
|
|
u32 is_alpha = is_alphabetic(ch);
|
|
if (is_alpha)
|
|
{
|
|
decimal_digit_count = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
i += 1;
|
|
}
|
|
|
|
|
|
if (decimal_digit_count)
|
|
{
|
|
parser->i += 1;
|
|
|
|
if (integer_start)
|
|
{
|
|
u64 signedness = s_start;
|
|
u64 bit_size;
|
|
u64 current_i = parser->i;
|
|
assert(src.pointer[current_i] >= '0' & src.pointer[current_i] <= '9');
|
|
switch (decimal_digit_count) {
|
|
case 0:
|
|
fail();
|
|
case 1:
|
|
bit_size = src.pointer[current_i] - '0';
|
|
break;
|
|
case 2:
|
|
bit_size = (src.pointer[current_i] - '0') * 10 + (src.pointer[current_i + 1] - '0');
|
|
break;
|
|
default:
|
|
fail();
|
|
}
|
|
parser->i += decimal_digit_count;
|
|
|
|
assert(!is_decimal_digit(src.pointer[parser->i]));
|
|
|
|
if (bit_size)
|
|
{
|
|
auto type_index = thread_get_integer_type(thread, (TypeInteger) {
|
|
.constant = 0,
|
|
.bit_count = bit_size,
|
|
.is_constant = 0,
|
|
.is_signed = signedness,
|
|
});
|
|
return type_index;
|
|
}
|
|
else
|
|
{
|
|
fail();
|
|
}
|
|
}
|
|
else if (float_start)
|
|
{
|
|
trap();
|
|
}
|
|
else
|
|
{
|
|
trap();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fail();
|
|
}
|
|
}
|
|
|
|
trap();
|
|
}
|
|
|
|
fn NodeIndex 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);
|
|
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();
|
|
case INTEGER_PREFIX_BINARY:
|
|
trap();
|
|
}
|
|
|
|
auto node_index = constant_int_create(thread, builder->function, value);
|
|
return 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;
|
|
auto skip_count = 1;
|
|
|
|
switch (src.pointer[parser->i])
|
|
{
|
|
case '*':
|
|
node_id = NODE_INTEGER_MULTIPLY;
|
|
break;
|
|
case '/':
|
|
node_id = NODE_INTEGER_SIGNED_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);
|
|
// 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);
|
|
|
|
// 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 = NODE_INTEGER_ADD;
|
|
break;
|
|
case '-':
|
|
node_id = NODE_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);
|
|
// 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);
|
|
|
|
// 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 = NODE_INTEGER_SIGNED_SHIFT_LEFT;
|
|
}
|
|
else if ((src.pointer[parser->i] == '>') & (src.pointer[parser->i + 1] == '>'))
|
|
{
|
|
node_id = NODE_INTEGER_SIGNED_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);
|
|
// 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);
|
|
|
|
// 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;
|
|
auto skip_count = 1;
|
|
|
|
switch (src.pointer[parser->i])
|
|
{
|
|
case '&':
|
|
node_id = NODE_INTEGER_AND;
|
|
break;
|
|
case '|':
|
|
node_id = NODE_INTEGER_OR;
|
|
break;
|
|
case '^':
|
|
node_id = NODE_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);
|
|
// 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);
|
|
|
|
// 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;
|
|
auto skip_count = 1;
|
|
|
|
switch (src.pointer[parser->i])
|
|
{
|
|
case '=':
|
|
todo();
|
|
case '!':
|
|
if (src.pointer[parser->i + 1] == '=')
|
|
{
|
|
skip_count = 2;
|
|
node_id = NODE_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);
|
|
// 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);
|
|
|
|
// 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, TypeIndex 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);
|
|
|
|
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 return_node_index = thread_node_add(thread, (NodeCreate) {
|
|
.id = NODE_RETURN,
|
|
.inputs = array_to_slice(((NodeIndex[]) {
|
|
builder_get_control_node_index(thread, builder),
|
|
return_value,
|
|
})),
|
|
});
|
|
|
|
if (validi(builder->scope))
|
|
{
|
|
// TODO:
|
|
// Look for memory slices
|
|
// todo();
|
|
}
|
|
|
|
return_node_index = peephole(thread, function, return_node_index);
|
|
|
|
builder_add_return(thread, builder, return_node_index);
|
|
|
|
builder_set_control(thread, builder, builder->dead_control);
|
|
continue;
|
|
}
|
|
|
|
String left_name = statement_start_identifier;
|
|
|
|
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();
|
|
}
|
|
|
|
skip_space(parser, src);
|
|
|
|
NodeIndex initial_right = analyze_expression(thread, parser, builder, src, invalidi(Type));
|
|
|
|
expect_character(parser, src, ';');
|
|
|
|
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);
|
|
|
|
skip_space(parser, src);
|
|
|
|
TypeIndex type = invalidi(Type);
|
|
|
|
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);
|
|
|
|
// TODO: typecheck
|
|
|
|
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* function = vb_add(&thread->buffer.functions, 1);
|
|
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);
|
|
|
|
// Create the start node early since it is needed as a dependency for control and arguments
|
|
function->start = thread_node_add(thread, (NodeCreate) {
|
|
.id = NODE_START,
|
|
.inputs = {},
|
|
});
|
|
TypeIndex tuple = invalidi(Type);
|
|
TypeIndex start_argument_type_buffer[256];
|
|
String argument_names[255];
|
|
start_argument_type_buffer[0] = thread->types.live_control;
|
|
u32 argument_i = 1;
|
|
|
|
while (1)
|
|
{
|
|
skip_space(parser, src);
|
|
if (src.pointer[parser->i] == argument_end)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (argument_i == 256)
|
|
{
|
|
// Maximum arguments reached
|
|
fail();
|
|
}
|
|
|
|
auto argument_name = parse_identifier(parser, src);
|
|
argument_names[argument_i - 1] = argument_name;
|
|
|
|
skip_space(parser, src);
|
|
expect_character(parser, src, ':');
|
|
skip_space(parser, src);
|
|
|
|
auto type_index = analyze_type(thread, parser, src);
|
|
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);
|
|
|
|
auto start_argument_types = s_get_slice(TypeIndex, (Slice(TypeIndex)) array_to_slice(start_argument_type_buffer), 0, argument_i);
|
|
tuple = type_make_tuple_allocate(thread, start_argument_types);
|
|
|
|
auto argument_count = argument_i - 1;
|
|
|
|
auto* start_node = thread_node_get(thread, function->start);
|
|
assert(validi(tuple));
|
|
start_node->type = tuple;
|
|
start_node->start.arguments = tuple;
|
|
start_node->start.function = function;
|
|
|
|
|
|
// Create stop node
|
|
{
|
|
function->stop = thread_node_add(thread, (NodeCreate) {
|
|
.id = NODE_STOP,
|
|
.inputs = {},
|
|
});
|
|
}
|
|
|
|
auto dead_control = thread_node_add(thread, (NodeCreate) {
|
|
.id = NODE_DEAD_CONTROL,
|
|
.inputs = { .pointer = &function->start, .length = 1 },
|
|
});
|
|
dead_control = peephole(thread, function, dead_control);
|
|
|
|
builder->dead_control = node_keep(thread, dead_control);
|
|
|
|
// Create the function scope node
|
|
{
|
|
auto scope_node_index = thread_node_add(thread, (NodeCreate)
|
|
{
|
|
.id = NODE_SCOPE,
|
|
.inputs = {},
|
|
});
|
|
auto* scope_node = thread_node_get(thread, scope_node_index);
|
|
scope_node->type = thread->types.bottom;
|
|
builder->scope = scope_node_index;
|
|
|
|
scope_push(thread, builder);
|
|
auto control_node_index = thread_node_add(thread, (NodeCreate){
|
|
.id = NODE_CONTROL_PROJECTION,
|
|
.inputs = {
|
|
.pointer = &function->start,
|
|
.length = 1,
|
|
},
|
|
});
|
|
auto* control_node = thread_node_get(thread, control_node_index);
|
|
auto control_name = strlit("$control");
|
|
control_node->control_projection.projection = (NodeProjection) {
|
|
.label = control_name,
|
|
.index = 0,
|
|
};
|
|
control_node_index = peephole(thread, function, control_node_index);
|
|
scope_define(thread, builder, control_name, thread->types.live_control, control_node_index);
|
|
}
|
|
|
|
for (u32 i = 0; i < argument_count; i += 1)
|
|
{
|
|
TypeIndex argument_type = start_argument_types.pointer[i + 1];
|
|
String argument_name = argument_names[i];
|
|
|
|
auto argument_node_index = thread_node_add(thread, (NodeCreate){
|
|
.id = NODE_PROJECTION,
|
|
.inputs = {
|
|
.pointer = &function->start,
|
|
.length = 1,
|
|
},
|
|
});
|
|
auto* argument_node = thread_node_get(thread, argument_node_index);
|
|
argument_node->projection = (NodeProjection) {
|
|
.index = i + 1,
|
|
.label = argument_name,
|
|
};
|
|
|
|
argument_node_index = peephole(thread, function, argument_node_index);
|
|
|
|
scope_define(thread, builder, argument_name, argument_type, argument_node_index);
|
|
}
|
|
|
|
function->return_type = analyze_type(thread, parser, src);
|
|
|
|
skip_space(parser, src);
|
|
|
|
analyze_block(thread, parser, builder, src);
|
|
|
|
scope_pop(thread, builder);
|
|
function->stop = peephole(thread, function, function->stop);
|
|
}
|
|
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);
|
|
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 NODE_START:
|
|
case NODE_DEAD_CONTROL:
|
|
case NODE_CONTROL_PROJECTION:
|
|
case NODE_RETURN:
|
|
case NODE_STOP:
|
|
return 1;
|
|
case NODE_SCOPE:
|
|
case NODE_CONSTANT:
|
|
case NODE_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 NODE_START:
|
|
{
|
|
loop_depth = node->start.cfg.loop_depth;
|
|
if (!loop_depth)
|
|
{
|
|
loop_depth = node->start.cfg.loop_depth = 1;
|
|
}
|
|
} break;
|
|
case NODE_STOP:
|
|
{
|
|
loop_depth = node->stop.cfg.loop_depth;
|
|
if (!loop_depth)
|
|
{
|
|
loop_depth = node->stop.cfg.loop_depth = 1;
|
|
}
|
|
} break;
|
|
case NODE_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 NODE_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 NODE_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 == NODE_REGION) | (node->id == NODE_REGION_LOOP);
|
|
}
|
|
|
|
fn u8 node_is_pinned(Node* node)
|
|
{
|
|
switch (node->id)
|
|
{
|
|
case NODE_PROJECTION:
|
|
case NODE_START:
|
|
return 1;
|
|
case NODE_CONSTANT:
|
|
case NODE_INTEGER_SUBSTRACT:
|
|
case NODE_INTEGER_COMPARE_EQUAL:
|
|
case NODE_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 NODE_START:
|
|
return 0;
|
|
case NODE_DEAD_CONTROL:
|
|
todo();
|
|
case NODE_CONTROL_PROJECTION:
|
|
todo();
|
|
case NODE_RETURN:
|
|
todo();
|
|
case NODE_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 NODE_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 == NODE_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);
|
|
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)
|
|
{
|
|
auto* node = thread_node_get(thread, node_index);
|
|
auto* type = thread_type_get(thread, node->type);
|
|
auto inputs = node_get_inputs(thread, node);
|
|
|
|
switch (node->id)
|
|
{
|
|
case NODE_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 NODE_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 NODE_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 NODE_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 NODE_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();
|
|
}
|
|
}
|
|
|
|
fn String c_lower(Thread* thread)
|
|
{
|
|
CBackend backend_stack = {};
|
|
CBackend* backend = &backend_stack;
|
|
auto program_epilogue = strlit("#include <stdint.h>\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;
|
|
auto 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 NODE_CONTROL_PROJECTION:
|
|
break;
|
|
case NODE_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 NODE_STOP:
|
|
break;
|
|
default:
|
|
trap();
|
|
}
|
|
|
|
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 };
|
|
}
|
|
|
|
fn void thread_init(Thread* thread)
|
|
{
|
|
memset(thread, 0, sizeof(Thread));
|
|
thread->arena = arena_init_default(KB(64));
|
|
thread->main_function = -1;
|
|
|
|
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,
|
|
});
|
|
}
|
|
|
|
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;
|
|
|
|
fn Interpreter* interpreter_create(Thread* thread)
|
|
{
|
|
auto* interpreter = arena_allocate(thread->arena, Interpreter, 1);
|
|
*interpreter = (Interpreter){};
|
|
return interpreter;
|
|
}
|
|
|
|
fn s32 emit_node(Interpreter* interpreter, Thread* thread, NodeIndex node_index)
|
|
{
|
|
auto* node = thread_node_get(thread, node_index);
|
|
auto inputs = node_get_inputs(thread, node);
|
|
s32 result = -1;
|
|
|
|
switch (node->id)
|
|
{
|
|
case NODE_STOP:
|
|
case NODE_CONTROL_PROJECTION:
|
|
break;
|
|
case NODE_RETURN:
|
|
{
|
|
auto return_value = emit_node(interpreter, thread, inputs.pointer[1]);
|
|
result = return_value;
|
|
} break;
|
|
case NODE_CONSTANT:
|
|
{
|
|
auto constant_type_index = node->constant.type;
|
|
auto* constant_type = thread_type_get(thread, constant_type_index);
|
|
switch (constant_type->id)
|
|
{
|
|
case TYPE_INTEGER:
|
|
{
|
|
assert(constant_type->integer.is_constant);
|
|
result = constant_type->integer.constant;
|
|
} break;
|
|
default:
|
|
trap();
|
|
}
|
|
} break;
|
|
case NODE_INTEGER_SUBSTRACT:
|
|
{
|
|
auto left = emit_node(interpreter, thread, inputs.pointer[1]);
|
|
auto right = emit_node(interpreter, thread, inputs.pointer[2]);
|
|
result = left - right;
|
|
} break;
|
|
case NODE_PROJECTION:
|
|
{
|
|
auto projected_node_index = inputs.pointer[0];
|
|
auto projection_index = node->projection.index;
|
|
|
|
if (index_equal(projected_node_index, interpreter->function->start))
|
|
{
|
|
if (projection_index == 0)
|
|
{
|
|
fail();
|
|
}
|
|
if (projection_index > interpreter->arguments.length + 1)
|
|
{
|
|
fail();
|
|
}
|
|
|
|
switch (projection_index)
|
|
{
|
|
case 1:
|
|
return interpreter->arguments.length;
|
|
case 2:
|
|
trap();
|
|
default:
|
|
trap();
|
|
}
|
|
trap();
|
|
}
|
|
else
|
|
{
|
|
trap();
|
|
}
|
|
|
|
} break;
|
|
case NODE_INTEGER_COMPARE_EQUAL:
|
|
{
|
|
auto left = emit_node(interpreter, thread, inputs.pointer[1]);
|
|
auto right = emit_node(interpreter, thread, inputs.pointer[2]);
|
|
result = left == right;
|
|
} break;
|
|
case NODE_INTEGER_COMPARE_NOT_EQUAL:
|
|
{
|
|
auto left = emit_node(interpreter, thread, inputs.pointer[1]);
|
|
auto right = emit_node(interpreter, thread, inputs.pointer[2]);
|
|
result = left != right;
|
|
} break;
|
|
default:
|
|
trap();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
fn s32 interpreter_run(Interpreter* interpreter, Thread* thread)
|
|
{
|
|
Function* function = interpreter->function;
|
|
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;
|
|
|
|
s32 result = -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 this_result = emit_node(interpreter, thread, it_node_index);
|
|
if (this_result != -1)
|
|
{
|
|
result = this_result;
|
|
}
|
|
|
|
assert(outputs.length == 1);
|
|
it_node_index = outputs.pointer[0];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
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 = 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;
|
|
}
|
|
|
|
fn void write_elf(Thread* thread, char** envp, const ELFOptions* const options)
|
|
{
|
|
// {
|
|
// 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 = builder->section_table.length,
|
|
.section_header_string_table_index = 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);
|
|
}
|
|
|
|
#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];
|
|
#endif
|
|
#if DO_UNIT_TESTS
|
|
unit_tests();
|
|
#endif
|
|
|
|
if (argc < 3)
|
|
{
|
|
fail();
|
|
}
|
|
|
|
Arena* global_arena = arena_init_default(KB(64));
|
|
{
|
|
arguments.pointer = arena_allocate(global_arena, String, argc);
|
|
arguments.length = argc;
|
|
|
|
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];
|
|
|
|
Thread* thread = arena_allocate(global_arena, Thread, 1);
|
|
thread_init(thread);
|
|
|
|
syscall_mkdir("nest", 0755);
|
|
|
|
File file = {
|
|
.path = source_file_path,
|
|
.source = file_read(thread->arena, source_file_path),
|
|
};
|
|
analyze_file(thread, &file);
|
|
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);
|
|
|
|
for (u32 function_i = 0; function_i < thread->buffer.functions.length; function_i += 1)
|
|
{
|
|
Function* function = &thread->buffer.functions.pointer[function_i];
|
|
NodeIndex start_node_index = function->start;
|
|
NodeIndex stop_node_index = function->stop;
|
|
iterate_peepholes(thread, function, stop_node_index);
|
|
// print_string(strlit("Before optimizations\n"));
|
|
// print_function(thread, function);
|
|
gcm_build_cfg(thread, start_node_index, stop_node_index);
|
|
// print_string(strlit("After optimizations\n"));
|
|
// print_function(thread, function);
|
|
}
|
|
|
|
if (thread->main_function == -1)
|
|
{
|
|
fail();
|
|
}
|
|
|
|
auto object_path = arena_join_string(thread->arena, (Slice(String)) array_to_slice(((String[]) {
|
|
strlit("nest/"),
|
|
test_name,
|
|
compiler_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 (compiler_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);
|
|
} 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);
|
|
} break;
|
|
case COMPILER_BACKEND_MACHINE:
|
|
{
|
|
// TODO:
|
|
// Code:
|
|
// main:
|
|
// xor eax, eax
|
|
// ret
|
|
u8 code[] = { 0x31, 0xc0, 0xc3 };
|
|
auto code_slice = (Slice(u8)) { .pointer = code, .length = sizeof(code) };
|
|
write_elf(thread, envp, &(ELFOptions) {
|
|
.object_path = string_to_c(object_path),
|
|
.exe_path = exe_path,
|
|
.code = code_slice,
|
|
});
|
|
} break;
|
|
}
|
|
|
|
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
|