From 81c5f8e750fcda6a2451fb54604130431434f88f Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 17 Aug 2019 02:57:08 +0200 Subject: Implement more instructions, implement function parameters and arguments --- README.md | 13 +- build.sh | 1 + doc/Documentation.md | 9 + executor/executor.h | 10 +- executor/interpreter/executor.c | 197 ++++++++++ executor/x86_64/asm.c | 86 ++++- executor/x86_64/asm.h | 18 +- executor/x86_64/executor.c | 129 ++++++- include/ast.h | 108 ++++-- include/bytecode/bytecode.h | 2 +- include/compiler.h | 2 + include/nullable.h | 2 - include/program.h | 7 +- include/ssa/ssa.h | 4 +- include/std/buffer_view.h | 1 + include/std/misc.h | 9 - include/std/types.h | 9 + include/tokenizer.h | 3 +- src/ast.c | 494 ++++++++++++++++++------- src/bytecode/bytecode.c | 36 +- src/compiler.c | 34 +- src/parser.c | 232 +++++++++--- src/program.c | 44 ++- src/ssa/ssa.c | 239 ++++++++---- src/std/arena_allocator.c | 9 +- src/std/buffer.c | 1 + src/std/buffer_view.c | 5 + src/tokenizer.c | 8 + tests/bytecode.amal | 6 + tests/errors/closure_duplicate_param_name.amal | 1 + tests/main.c | 1 + 31 files changed, 1360 insertions(+), 360 deletions(-) create mode 100644 executor/interpreter/executor.c create mode 100644 tests/errors/closure_duplicate_param_name.amal diff --git a/README.md b/README.md index a96972f..ed64fd6 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ Amalgam is not meant to be a replacement for any other language but rather a new with gpu without writing an external gpu program (glsl/hlsl). Files have to be in utf-8 format and can optionally have utf-8 BOM. +## Important +Amalgam is not ready to be used yet. # Fast compilation Every stage of the compiler is multithreaded and data copy is kept to a minimal, for example tokenization is done without storing tokens in a list. Almost all allocation is done using an arena allocator that @@ -21,7 +23,16 @@ This could be done checking if an AST expression is referenced before evaluating that compiles everything even if not referenced, since another user of the program/library may use the functions that are not used in your program and they might have compile-issues.\ Align machine code to word boundary for the start of functions. No need to pad with NOP, as functions return before the padding.\ -Use const to cleanup ANSI C style variable declarations, since const allows you to declare and assign variables on the same line. +Use const to cleanup ANSI C style variable declarations, since const allows you to declare and assign variables on the same line.\ +Make the bytecode work with big endian. On a big endian machine, the bytecode should be converted to little endian +to make work on little endian as little as possible, meaning it would be a small penality to use big endian.\ +Verify all members of an extern struct are extern as well. Verify all parameters are of extern types for extern functions.\ +Verify all code execution paths in a function return a value, if the function excepts return values.\ +Show compile error if the result of a function call is ignored.\ +Show compile error if function result type and assigned to variable have different types. +## Urgent +Simplify src/compiler.c, it's pretty complex with the thread work done right now. The thread work should be put in a +thread dispatch file that only handles thread job dispatching. # Documents Documents are located under doc. The file doc/Documentation.md is generated from source files by running doc/doc_extract.py but there is no need to run this script unless you are modifying documentation in the source. diff --git a/build.sh b/build.sh index 07a4ee2..568c6c4 100755 --- a/build.sh +++ b/build.sh @@ -17,6 +17,7 @@ if [ "$cpu_arch" = "x86_64" ]; then source_files="$source_files $(find "executor/x86_64" -name "*.c")" else echo "WARNING: There is no machine code implementation for your cpu architecture: $cpu_arch. An interpreter will be used instead" + source_files="$source_files $(find "executor/interpreter" -name "*.c")" fi if [ -z "$CC" ]; then diff --git a/doc/Documentation.md b/doc/Documentation.md index 602d0e1..3f9bd1a 100644 --- a/doc/Documentation.md +++ b/doc/Documentation.md @@ -25,6 +25,9 @@ in the next step. The last step is not done in parallel because the last step is and writing it to a file, which is an IO bottlenecked operation and it won't benefit from multithreading and may even lose performance because of it. +# Bytecode +The layout of the full bytecode is: Header (Intermediates Strings Functions Instructions)* + # Bytecode header ## Header layout |Type|Field |Description | @@ -63,6 +66,12 @@ The versions in the header only changes for every release, not every change. |u16 |Size|The size of the string, in bytes. | |u8* |Data|The data of the string, where the size is defined by @Size. Strings are null-terminated.| +# Bytecode functions +## Internal functions layout +|Type|Field |Description | +|----|-------------------|---------------------------------| +|u16 |Number of functions|The number of internal functions.| + # Bytecode instructions ## Instructions layout |Type |Field |Description | diff --git a/executor/executor.h b/executor/executor.h index fdf6e67..5be9abc 100644 --- a/executor/executor.h +++ b/executor/executor.h @@ -20,6 +20,10 @@ CHECK_RESULT int amal_executor_init(amal_executor **self); void amal_executor_deinit(amal_executor *self); CHECK_RESULT int amal_executor_run(amal_executor *self); +/* These functions are called for every file in the program. Every file has its own list of strings, intermediates, functions and external functions */ +CHECK_RESULT int amal_executor_instructions_start(amal_executor *self, u16 num_functions); +CHECK_RESULT int amal_executor_instructions_end(amal_executor *self); + CHECK_RESULT int amal_exec_nop(amal_executor *self); CHECK_RESULT int amal_exec_setz(amal_executor *self, u8 dst_reg); CHECK_RESULT int amal_exec_mov(amal_executor *self, u8 dst_reg, u8 src_reg); @@ -34,12 +38,12 @@ CHECK_RESULT int amal_exec_div(amal_executor *self, u8 dst_reg, u8 src_reg1, u8 CHECK_RESULT int amal_exec_push(amal_executor *self, u8 reg); CHECK_RESULT int amal_exec_pushi(amal_executor *self, i64 imm); CHECK_RESULT int amal_exec_pushd(amal_executor *self, BufferView data); -/*CHECK_RESULT int amal_exec_call(u8 dst_reg, BufferView data); -CHECK_RESULT int amal_exec_callr(u8 dst_reg, BufferView data);*/ +CHECK_RESULT int amal_exec_call(amal_executor *self, u16 func_index, u8 num_args); +/*CHECK_RESULT int amal_exec_callr(u8 dst_reg, BufferView data);*/ CHECK_RESULT int amal_exec_cmp(amal_executor *self, u8 dst_reg, u8 src_reg1, u8 src_reg2); CHECK_RESULT int amal_exec_jz(amal_executor *self, u8 dst_reg, i16 offset); CHECK_RESULT int amal_exec_jmp(amal_executor *self, i16 offset); -CHECK_RESULT int amal_exec_ret(amal_executor *self); +CHECK_RESULT int amal_exec_ret(amal_executor *self, u8 reg); CHECK_RESULT int amal_exec_func_start(amal_executor *self, u16 num_regs); CHECK_RESULT int amal_exec_func_end(amal_executor *self); diff --git a/executor/interpreter/executor.c b/executor/interpreter/executor.c new file mode 100644 index 0000000..0180b08 --- /dev/null +++ b/executor/interpreter/executor.c @@ -0,0 +1,197 @@ +#include "../executor.h" +#include "../../include/program.h" +#include "../../include/std/alloc.h" +#include + +typedef struct { + usize *stack; + usize stack_size; + usize stack_index; + isize reg[AMAL_PROGRAM_NUM_REGISTERS]; +} amal_executor_impl; + +#define IMPL \ + amal_executor_impl *impl; \ + impl = (amal_executor_impl*)self; + +#define FOUR_MEGABYTES 1024*1024*4 + +static int executor_ensure_stack_capacity(amal_executor_impl *self, usize bytes_to_add) { + const isize overflow = self->stack_size - self->stack_index * sizeof(isize) + bytes_to_add; + if(overflow > 0) { + void *new_data; + const usize new_stack_size = self->stack_size * 1.5; + if(new_stack_size > FOUR_MEGABYTES) + return AMAL_PROGRAM_INSTRUCTION_STACK_OVERFLOW; + return_if_error(am_realloc(self->stack, new_stack_size, &new_data)); + self->stack = new_data; + self->stack_size = new_stack_size; + } + return 0; +} + +int amal_executor_init(amal_executor **self) { + amal_executor_impl **impl; + impl = (amal_executor_impl**)self; + *impl = NULL; + return_if_error(am_malloc(sizeof(amal_executor_impl), (void**)impl)); + (*impl)->stack_size = 4096; + return_if_error(am_malloc((*impl)->stack_size, (void**)&(*impl)->stack)); + (*impl)->stack_index = 0; + return 0; +} + +void amal_executor_deinit(amal_executor *self) { + IMPL + am_free(impl->stack); + am_free(impl); +} + +int amal_executor_run(amal_executor *self) { + (void)self; + assert(bool_false && "TODO: Implement!"); + return 0; +} + +int amal_exec_nop(amal_executor *self) { + (void)self; + return 0; +} + +int amal_exec_setz(amal_executor *self, u8 dst_reg) { + IMPL + impl->reg[dst_reg] = 0; + return 0; +} + +int amal_exec_mov(amal_executor *self, u8 dst_reg, u8 src_reg) { + IMPL + impl->reg[dst_reg] = impl->reg[src_reg]; + return 0; +} + +int amal_exec_movi(amal_executor *self, u8 dst_reg, i64 imm) { + IMPL + impl->reg[dst_reg] = imm; + return 0; +} + +int amal_exec_movd(amal_executor *self, u8 dst_reg, BufferView data) { + IMPL + impl->reg[dst_reg] = (uintptr_t)data.data; + return 0; +} + +int amal_exec_add(amal_executor *self, u8 dst_reg, u8 src_reg1, u8 src_reg2) { + IMPL + impl->reg[dst_reg] = impl->reg[src_reg1] + impl->reg[src_reg2]; + return 0; +} + +int amal_exec_sub(amal_executor *self, u8 dst_reg, u8 src_reg1, u8 src_reg2) { + IMPL + impl->reg[dst_reg] = impl->reg[src_reg1] - impl->reg[src_reg2]; + return 0; +} + +int amal_exec_imul(amal_executor *self, u8 dst_reg, u8 src_reg1, u8 src_reg2) { + IMPL + impl->reg[dst_reg] = impl->reg[src_reg1] * impl->reg[src_reg2]; + return 0; +} + +int amal_exec_mul(amal_executor *self, u8 dst_reg, u8 src_reg1, u8 src_reg2) { + IMPL + impl->reg[dst_reg] = impl->reg[src_reg1] * impl->reg[src_reg2]; + return 0; +} + +int amal_exec_idiv(amal_executor *self, u8 dst_reg, u8 src_reg1, u8 src_reg2) { + IMPL + impl->reg[dst_reg] = impl->reg[src_reg1] / impl->reg[src_reg2]; + return 0; +} + +int amal_exec_div(amal_executor *self, u8 dst_reg, u8 src_reg1, u8 src_reg2) { + IMPL + impl->reg[dst_reg] = impl->reg[src_reg1] / impl->reg[src_reg2]; + return 0; +} + +int amal_exec_push(amal_executor *self, u8 reg) { + IMPL + return_if_error(executor_ensure_stack_capacity(impl, sizeof(isize))); + impl->stack[impl->stack_index++] = impl->reg[reg]; + return 0; +} + +int amal_exec_pushi(amal_executor *self, i64 imm) { + IMPL + return_if_error(executor_ensure_stack_capacity(impl, sizeof(isize))); + impl->stack[impl->stack_index++] = imm; + return 0; +} + +int amal_exec_pushd(amal_executor *self, BufferView data) { + IMPL + return_if_error(executor_ensure_stack_capacity(impl, sizeof(isize))); + impl->stack[impl->stack_index++] = (uintptr_t)data.data; + return 0; +} + +/*int amal_exec_call(u8 dst_reg, BufferView data); +int amal_exec_callr(u8 dst_reg, BufferView data);*/ +int amal_exec_cmp(amal_executor *self, u8 dst_reg, u8 src_reg1, u8 src_reg2) { + IMPL + impl->reg[dst_reg] = (impl->reg[src_reg1] == impl->reg[src_reg2]); + return 0; +} + +int amal_exec_jz(amal_executor *self, u8 dst_reg, i16 offset) { + (void)self; + (void)dst_reg; + (void)offset; + /* TODO: Implement! */ + assert(bool_false && "TODO: Implement!"); + return 0; +} + +int amal_exec_jmp(amal_executor *self, i16 offset) { + (void)self; + (void)offset; + /* TODO: Implement! */ + assert(bool_false && "TODO: Implement!"); + return 0; +} + +int amal_exec_ret(amal_executor *self) { + (void)self; + /* TODO: Implement! */ + assert(bool_false && "TODO: Implement RET. RET needs to restore the stack before returning"); + return 0; +} + +int amal_exec_func_start(amal_executor *self, u16 num_regs) { + /* + TODO: Validate stack size, or maybe remove all validation? do we really need validation? + If we need security, we could fork the process instead. + */ + + /* + Some registers need to be preserved before entering a function scope and these registers are different on different platforms. + 32-bit: EBX, ESI, EDI, EBP + 64-bit Windows: RBX, RSI, RDI, RBP, R12-R15, XMM6-XMM15 + 64-bit Linux,BSD,Mac: RBX, RBP, R12-R15 + */ + /* TODO: Preserve registers and stack frame */ + /*return executor_ensure_stack_capacity(impl, num_regs * sizeof(isize));*/ + (void)self; + (void)num_regs; + return 0; +} + +int amal_exec_func_end(amal_executor *self) { + (void)self; + /* TODO: Restore registers and stack frame and ret */ + return 0; +} diff --git a/executor/x86_64/asm.c b/executor/x86_64/asm.c index 73ae568..f032538 100644 --- a/executor/x86_64/asm.c +++ b/executor/x86_64/asm.c @@ -35,6 +35,10 @@ static void ins_start(Asm *self) { asm_debug_str_buffer_index = 0; } +static int max(int a, int b) { + return a >= b ? a : b; +} + static void ins_end(Asm *self, const char *fmt, ...) { usize ins_end_offset; usize i; @@ -46,7 +50,7 @@ static void ins_end(Asm *self, const char *fmt, ...) { fprintf(stderr, "%02x ", ((u8*)self->code)[i]); } /* Same padding for all instructions, no matter how long they are */ - for(i = 0; i < 35 - (ins_end_offset - ins_start_offset)*3; ++i) { + for(i = 0; i < (usize)max(0, 35 - (ins_end_offset - ins_start_offset)*3); ++i) { putc(' ', stderr); } vfprintf(stderr, fmt, args); @@ -160,9 +164,9 @@ void asm_ptr_init_index_disp(AsmPtr *self, Reg64 base, Reg64 index, i32 disp) { } int asm_init(Asm *self) { - self->size = am_pagesize(); - amal_log_debug("asm: page size: %u", self->size); - self->code = mmap(NULL, self->size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + self->allocated_size = am_pagesize(); + amal_log_debug("asm: page size: %u", self->allocated_size); + self->code = mmap(NULL, self->allocated_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if(self->code == MAP_FAILED) return -errno; self->code_it = self->code; @@ -171,12 +175,17 @@ int asm_init(Asm *self) { void asm_deinit(Asm *self) { if(self->code) - munmap(self->code, self->size); + munmap(self->code, self->allocated_size); self->code = NULL; self->code_it = NULL; - self->size = 0; + self->allocated_size = 0; +} + +usize asm_get_size(Asm *self) { + return self->code_it - (u8*)self->code; } +#if 0 static void asm_print_code_hex(Asm *self) { u8 *ptr; int off; @@ -196,13 +205,14 @@ static void asm_print_code_hex(Asm *self) { if(off != 0) putc('\n', stdout); } +#endif int asm_execute(Asm *self) { void (*func)(); - if(mprotect(self->code, self->size, PROT_READ | PROT_EXEC) != 0) + if(mprotect(self->code, self->allocated_size, PROT_READ | PROT_EXEC) != 0) return -errno; - asm_print_code_hex(self); + /*asm_print_code_hex(self);*/ /* TODO: Verify if this is valid on all platforms. According to ISO C standard it isn't? */ *(void**)(&func) = self->code; @@ -214,17 +224,17 @@ int asm_execute(Asm *self) { static CHECK_RESULT int asm_ensure_capacity(Asm *self, usize size) { usize current_offset; current_offset = (u8*)self->code_it - (u8*)self->code; - if(current_offset + size > self->size) { + if(current_offset + size > self->allocated_size) { void *new_mem; usize new_size; - new_size = self->size + am_pagesize(); + new_size = self->allocated_size + am_pagesize(); new_mem = mmap(NULL, new_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if(self->code == MAP_FAILED) return -errno; - am_memcpy(new_mem, self->code, self->size); + am_memcpy(new_mem, self->code, self->allocated_size); self->code = new_mem; - self->size = new_size; + self->allocated_size = new_size; self->code_it = (u8*)self->code + current_offset; } return 0; @@ -232,7 +242,7 @@ static CHECK_RESULT int asm_ensure_capacity(Asm *self, usize size) { #ifdef DEBUG static isize asm_get_capacity_left(Asm *self) { - return (isize)self->size - (isize)((u8*)self->code_it - (u8*)self->code); + return (isize)self->allocated_size - (isize)((u8*)self->code_it - (u8*)self->code); } #endif @@ -346,21 +356,20 @@ int asm_mov_rm(Asm *self, Reg64 dst, AsmPtr *src) { return 0; } +/* Note: This shows as instruction movabs in intel assembly format */ int asm_mov_ri(Asm *self, Reg64 dst, i64 immediate) { ins_start(self); - /* 10 bytes is the maximum size of the instruction. We don't how how large it will be so we prepare for the largest size */ return_if_error(asm_ensure_capacity(self, 10)); *self->code_it++ = REX_W; *self->code_it++ = 0xB8 + dst; am_memcpy(self->code_it, &immediate, sizeof(immediate)); self->code_it += sizeof(immediate); - ins_end(self, "mov %s, %ld", reg64_to_str(dst), immediate); + ins_end(self, "mov %s, 0x%x", reg64_to_str(dst), immediate); return 0; } int asm_mov_rr(Asm *self, Reg64 dst, Reg64 src) { ins_start(self); - /* 3 bytes is the maximum size of the instruction. We don't how how large it will be so we prepare for the largest size */ return_if_error(asm_ensure_capacity(self, 3)); *self->code_it++ = REX_W; *self->code_it++ = 0x89; @@ -371,7 +380,6 @@ int asm_mov_rr(Asm *self, Reg64 dst, Reg64 src) { int asm_add_rr(Asm *self, Reg64 dst, Reg64 src) { ins_start(self); - /* 3 bytes is the maximum size of the instruction. We don't how how large it will be so we prepare for the largest size */ return_if_error(asm_ensure_capacity(self, 3)); *self->code_it++ = REX_W; *self->code_it++ = 0x01; @@ -382,7 +390,6 @@ int asm_add_rr(Asm *self, Reg64 dst, Reg64 src) { int asm_sub_rr(Asm *self, Reg64 dst, Reg64 src) { ins_start(self); - /* 3 bytes is the maximum size of the instruction. We don't how how large it will be so we prepare for the largest size */ return_if_error(asm_ensure_capacity(self, 3)); *self->code_it++ = REX_W; *self->code_it++ = 0x29; @@ -393,7 +400,6 @@ int asm_sub_rr(Asm *self, Reg64 dst, Reg64 src) { int asm_imul_rr(Asm *self, Reg64 dst, Reg64 src) { ins_start(self); - /* 3 bytes is the maximum size of the instruction. We don't how how large it will be so we prepare for the largest size */ return_if_error(asm_ensure_capacity(self, 4)); *self->code_it++ = REX_W; *self->code_it++ = 0x0F; @@ -403,6 +409,25 @@ int asm_imul_rr(Asm *self, Reg64 dst, Reg64 src) { return 0; } +int asm_cqo(Asm *self) { + ins_start(self); + return_if_error(asm_ensure_capacity(self, 2)); + *self->code_it++ = REX_W; + *self->code_it++ = 0x99; + ins_end(self, "cqo"); + return 0; +} + +int asm_idiv_rr(Asm *self, Reg64 src) { + ins_start(self); + return_if_error(asm_ensure_capacity(self, 4)); + *self->code_it++ = REX_W; + *self->code_it++ = 0xF7; + asm_rr(self, src, 0x7); + ins_end(self, "idiv %s", reg64_to_str(src)); + return 0; +} + int asm_pushr(Asm *self, Reg64 reg) { ins_start(self); return_if_error(asm_ensure_capacity(self, 1)); @@ -419,6 +444,26 @@ int asm_popr(Asm *self, Reg64 reg) { return 0; } +/* + Note: This is sometimes called with @relative 0 (will print call -5), in which case it's most likely a dummy call until the relative position + is later changed with @asm_override_call_rel32. TODO: Update the ins_end debug print to take that into account somehow +*/ +int asm_call_rel32(Asm *self, i32 relative) { + ins_start(self); + relative -= 5; /* In x86, the relative position starts from the next instruction */ + return_if_error(asm_ensure_capacity(self, 5)); + *self->code_it++ = 0xE8; + am_memcpy(self->code_it, &relative, sizeof(relative)); + self->code_it += sizeof(relative); + ins_end(self, "call 0x%x", relative); + return 0; +} + +void asm_override_call_rel32(Asm *self, u32 asm_index, i32 new_relative) { + new_relative -= 5; /* In x86, the relative position starts from the next instruction */ + am_memcpy((u8*)self->code + asm_index + 1, &new_relative, sizeof(new_relative)); +} + /* TODO: Remove these !*/ /* /r */ @@ -559,11 +604,12 @@ int asm_ret(Asm *self, u16 bytes) { if(bytes == 0) { return_if_error(asm_ensure_capacity(self, 1)); *self->code_it++ = 0xC3; + ins_end(self, "ret"); } else { return_if_error(asm_ensure_capacity(self, 3)); *self->code_it++ = 0xC2; am_memcpy(self->code_it, &bytes, sizeof(bytes)); + ins_end(self, "ret 0x%x", bytes); } - ins_end(self, "ret 0x%x", bytes); return 0; } diff --git a/executor/x86_64/asm.h b/executor/x86_64/asm.h index b374e44..7e5ac67 100644 --- a/executor/x86_64/asm.h +++ b/executor/x86_64/asm.h @@ -7,7 +7,7 @@ typedef struct { void *code; u8 *code_it; - usize size; + usize allocated_size; } Asm; typedef enum { @@ -47,6 +47,8 @@ void asm_ptr_init_index_disp(AsmPtr *self, Reg64 base, Reg64 index, i32 disp); CHECK_RESULT int asm_init(Asm *self); void asm_deinit(Asm *self); +usize asm_get_size(Asm *self); + CHECK_RESULT int asm_execute(Asm *self); CHECK_RESULT int asm_nop(Asm *self); @@ -68,9 +70,23 @@ CHECK_RESULT int asm_mov_rr(Asm *self, Reg64 dst, Reg64 src); CHECK_RESULT int asm_add_rr(Asm *self, Reg64 dst, Reg64 src); CHECK_RESULT int asm_sub_rr(Asm *self, Reg64 dst, Reg64 src); CHECK_RESULT int asm_imul_rr(Asm *self, Reg64 dst, Reg64 src); +/* Sign extend RAX into RDX, this is needed for some operations, such as idiv */ +CHECK_RESULT int asm_cqo(Asm *self); +/* + Divide RDX:RAX by @src. Store the quotient in RAX and the remainder in RDX. + @asm_cqo should be called before this, since RAX needs to be sign extended into RDX +*/ +CHECK_RESULT int asm_idiv_rr(Asm *self, Reg64 src); CHECK_RESULT int asm_pushr(Asm *self, Reg64 reg); CHECK_RESULT int asm_popr(Asm *self, Reg64 reg); +/* + In x86 assembly, the @relative position starts from the next instruction. + This offset shouldn't be calculated by the caller and is instead managed + by this asm library itself. +*/ +CHECK_RESULT int asm_call_rel32(Asm *self, i32 relative); +void asm_override_call_rel32(Asm *self, u32 asm_index, i32 new_relative); diff --git a/executor/x86_64/executor.c b/executor/x86_64/executor.c index b53ccea..b7aa91f 100644 --- a/executor/x86_64/executor.c +++ b/executor/x86_64/executor.c @@ -1,5 +1,6 @@ #include "../executor.h" #include "../../include/std/alloc.h" +#include "../../include/std/buffer.h" #include "asm.h" #include @@ -10,8 +11,17 @@ TODO: Operations with memory registers could access outside the stack. Should this be checked? */ +typedef struct { + u32 asm_index; + u16 func_index; +} CallDefer; + typedef struct { Asm asm; + usize *function_indices; + u16 num_functions; + u16 func_counter; + Buffer/*CallDefer*/ call_defer; } amal_executor_impl; #define IMPL \ @@ -26,13 +36,20 @@ static i64 abs_i64(i64 value) { int amal_executor_init(amal_executor **self) { amal_executor_impl **impl; - return_if_error(am_malloc(sizeof(amal_executor_impl), (void**)self)); impl = (amal_executor_impl**)self; + *impl = NULL; + return_if_error(am_malloc(sizeof(amal_executor_impl), (void**)impl)); + (*impl)->function_indices = NULL; + (*impl)->num_functions = 0; + (*impl)->func_counter = 0; + ignore_result_int(buffer_init(&(*impl)->call_defer, NULL)); return asm_init(&(*impl)->asm); } void amal_executor_deinit(amal_executor *self) { IMPL + buffer_deinit(&impl->call_defer); + am_free(impl->function_indices); asm_deinit(&impl->asm); am_free(impl); } @@ -42,6 +59,30 @@ int amal_executor_run(amal_executor *self) { return asm_execute(&impl->asm); } +int amal_executor_instructions_start(amal_executor *self, u16 num_functions) { + void *new_data; + IMPL + return_if_error(am_realloc(impl->function_indices, num_functions * sizeof(*impl->function_indices), &new_data)); + impl->function_indices = new_data; + impl->num_functions = num_functions; + impl->func_counter = 0; + buffer_clear(&impl->call_defer); + return 0; +} + +int amal_executor_instructions_end(amal_executor *self) { + CallDefer *call_defer, *call_defer_end; + IMPL + + call_defer = buffer_begin(&impl->call_defer); + call_defer_end = buffer_end(&impl->call_defer); + for(; call_defer != call_defer_end; ++call_defer) { + const isize func_offset = (isize)impl->function_indices[call_defer->func_index] - (isize)call_defer->asm_index; + asm_override_call_rel32(&impl->asm, call_defer->asm_index, func_offset); + } + return 0; +} + int amal_exec_nop(amal_executor *self) { IMPL return asm_nop(&impl->asm); @@ -158,16 +199,25 @@ int amal_exec_mul(amal_executor *self, u8 dst_reg, u8 src_reg1, u8 src_reg2) { return_if_error(asm_mul_rr(&self->asm, RAX, RCX)); return_if_error(asm_mov_mr(&self->asm, &dst, RAX)); #endif + assert(bool_false && "TODO: Implement!"); return 0; } int amal_exec_idiv(amal_executor *self, u8 dst_reg, u8 src_reg1, u8 src_reg2) { - (void)self; - (void)dst_reg; - (void)src_reg1; - (void)src_reg2; - /* TODO: Implement! */ - return 0; + AsmPtr dst; + AsmPtr reg1; + AsmPtr reg2; + IMPL + + asm_ptr_init_disp(&dst, RBP, get_register_stack_offset(dst_reg)); + asm_ptr_init_disp(®1, RBP, get_register_stack_offset(src_reg1)); + asm_ptr_init_disp(®2, RBP, get_register_stack_offset(src_reg2)); + + return_if_error(asm_mov_rm(&impl->asm, RAX, ®1)); + return_if_error(asm_mov_rm(&impl->asm, RCX, ®2)); + return_if_error(asm_cqo(&impl->asm)); + return_if_error(asm_idiv_rr(&impl->asm, RCX)); + return asm_mov_mr(&impl->asm, &dst, RAX); } int amal_exec_div(amal_executor *self, u8 dst_reg, u8 src_reg1, u8 src_reg2) { @@ -176,20 +226,24 @@ int amal_exec_div(amal_executor *self, u8 dst_reg, u8 src_reg1, u8 src_reg2) { (void)src_reg1; (void)src_reg2; /* TODO: Implement! */ + assert(bool_false && "TODO: Implement!"); return 0; } int amal_exec_push(amal_executor *self, u8 reg) { - (void)self; - (void)reg; - /* TODO: Implement! */ - return 0; + AsmPtr reg_ptr; + IMPL + + asm_ptr_init_disp(®_ptr, RBP, get_register_stack_offset(reg)); + return_if_error(asm_mov_rm(&impl->asm, RAX, ®_ptr)); + return asm_pushr(&impl->asm, RAX); } int amal_exec_pushi(amal_executor *self, i64 imm) { (void)self; (void)imm; /* TODO: Implement! */ + assert(bool_false && "TODO: Implement!"); return 0; } @@ -197,17 +251,48 @@ int amal_exec_pushd(amal_executor *self, BufferView data) { (void)self; (void)data; /* TODO: Implement! */ + assert(bool_false && "TODO: Implement!"); + return 0; +} + +int amal_exec_call(amal_executor *self, u16 func_index, u8 num_args) { + isize asm_size; + IMPL + /* TODO: Preserve necessary registers before call? */ + /* TODO: This assumes all arguments are isize */ + asm_size = asm_get_size(&impl->asm); + if(func_index < impl->func_counter) { + return_if_error(asm_call_rel32(&impl->asm, (isize)impl->function_indices[func_index] - asm_size)); + } else { + /* + The location of the function has not been defined yet. Use call instruction with dummy data and change + the location once the location to the function is known + */ + CallDefer call_defer; + call_defer.asm_index = asm_size; + call_defer.func_index = func_index; + return_if_error(buffer_append(&impl->call_defer, &call_defer, sizeof(call_defer))); + return_if_error(asm_call_rel32(&impl->asm, 0)); + } + + if(num_args > 0) + return asm_add_rm64_imm(&impl->asm, RSP, num_args * sizeof(isize)); return 0; } -/*int amal_exec_call(u8 dst_reg, BufferView data); -int amal_exec_callr(u8 dst_reg, BufferView data);*/ +/* +int amal_exec_callr(u8 dst_reg, BufferView data) { + +} +*/ + int amal_exec_cmp(amal_executor *self, u8 dst_reg, u8 src_reg1, u8 src_reg2) { (void)self; (void)dst_reg; (void)src_reg1; (void)src_reg2; /* TODO: Implement! */ + assert(bool_false && "TODO: Implement!"); return 0; } @@ -216,6 +301,7 @@ int amal_exec_jz(amal_executor *self, u8 dst_reg, i16 offset) { (void)dst_reg; (void)offset; /* TODO: Implement! */ + assert(bool_false && "TODO: Implement!"); return 0; } @@ -223,14 +309,18 @@ int amal_exec_jmp(amal_executor *self, i16 offset) { (void)self; (void)offset; /* TODO: Implement! */ + assert(bool_false && "TODO: Implement!"); return 0; } -int amal_exec_ret(amal_executor *self) { - (void)self; - /* TODO: Implement! */ - assert(bool_false && "TODO: Implement RET. RET needs to restore the stack before returning"); - return 0; +int amal_exec_ret(amal_executor *self, u8 reg) { + AsmPtr ret_reg; + IMPL + + asm_ptr_init_disp(&ret_reg, RBP, get_register_stack_offset(reg)); + /* Result is returned in RAX register. TODO: Make this work for larger data */ + return_if_error(asm_mov_rm(&impl->asm, RAX, &ret_reg)); + return amal_exec_func_end(self); } int amal_exec_func_start(amal_executor *self, u16 num_regs) { @@ -246,10 +336,11 @@ int amal_exec_func_start(amal_executor *self, u16 num_regs) { 64-bit Linux,BSD,Mac: RBX, RBP, R12-R15 */ IMPL + impl->function_indices[impl->func_counter++] = asm_get_size(&impl->asm); return_if_error(asm_pushr(&impl->asm, RBX)); return_if_error(asm_pushr(&impl->asm, RBP)); return_if_error(asm_mov_rr(&impl->asm, RBP, RSP)); - return asm_sub_rm64_imm(&impl->asm, RSP, num_regs * sizeof(usize)); + return asm_sub_rm64_imm(&impl->asm, RSP, num_regs * sizeof(isize)); } int amal_exec_func_end(amal_executor *self) { diff --git a/include/ast.h b/include/ast.h index d89d099..1198a98 100644 --- a/include/ast.h +++ b/include/ast.h @@ -19,6 +19,7 @@ #define AST_ERR_DEF_DUP -20 typedef struct Ast Ast; +typedef struct FunctionParameter FunctionParameter; typedef struct FunctionSignature FunctionSignature; typedef struct FunctionCall FunctionCall; typedef struct StructDecl StructDecl; @@ -33,6 +34,7 @@ typedef struct Binop Binop; typedef struct IfStatement IfStatement; typedef struct ElseIfStatement ElseIfStatement; typedef struct WhileStatement WhileStatement; +typedef struct ReturnExpr ReturnExpr; /* TODO: Instead of using pointers, use the data directly */ typedef union { @@ -50,6 +52,7 @@ typedef union { Binop *binop; IfStatement *if_stmt; WhileStatement *while_stmt; + ReturnExpr *return_expr; } AstValue; typedef enum { @@ -65,7 +68,8 @@ typedef enum { AST_VARIABLE, AST_BINOP, AST_IF_STATEMENT, - AST_WHILE_STATEMENT + AST_WHILE_STATEMENT, + AST_RETURN } AstType; typedef enum { @@ -75,17 +79,50 @@ typedef enum { AST_SSA_RESOLVED } AstResolveStatus; +typedef enum { + RESOLVED_TYPE_NONE, + RESOLVED_TYPE_LHS_EXPR, + RESOLVED_TYPE_FUNC_SIG +} AstResolvedTypeEnum; + typedef struct { - LhsExpr *type; + union { + LhsExpr *lhs_expr; + FunctionSignature *func_sig; + } value; + AstResolvedTypeEnum type; +} AstResolvedType; + +typedef struct { + AstResolvedType type; AstResolveStatus status; - Parser *parser; /* Borrowed. This is the parser that is currently parsing the expression */ + SsaRegister ssa_reg; } AstResolveData; +typedef enum { + NAMED_OBJECT_NONE, + NAMED_OBJECT_LHS_EXPR, + NAMED_OBJECT_FUNC_PARAM +} ScopeNamedObjectType; + +typedef struct { + union { + LhsExpr *lhs_expr; + FunctionParameter *func_param; + } value; + ScopeNamedObjectType type; + AstResolveData *resolve_data; /* Borrowed from @value.func_param or from the Ast @value.lhs_expr belongs to */ +} ScopeNamedObject; + struct Ast { AstValue value; AstType type; AstResolveData resolve_data; - SsaRegister ssa_reg; + /* + Borrowed. This is the parser (thread) that is currently parsing the expression. + TODO: Move this to LhsExpr + */ + Parser *parser; }; struct Scope { @@ -94,8 +131,22 @@ struct Scope { Scope *parent; /* Is null unless the scope is a file scope, in which case this is the parser that owns the scope */ Parser *parser; + FunctionSignature *function_signature; /* Borrowed from FunctionDecl. Only used if the scope belongs to FunctionDecl */ }; +typedef struct { + enum { + VARIABLE_TYPE_NONE, + VARIABLE_TYPE_VARIABLE, + VARIABLE_TYPE_SIGNATURE + } type; + + union { + Variable *variable; + FunctionSignature *signature; + } value; +} VariableType; + struct FileScopeReference { Parser *parser; Buffer canonical_path; @@ -103,15 +154,30 @@ struct FileScopeReference { struct Variable { BufferView name; - Ast *resolved_var; /* resolved_var will always be a LhsExpr after resolved */ + ScopeNamedObject resolved_var; +}; + +struct FunctionParameter { + BufferView name; + VariableType type; + AstResolveData resolve_data; }; +typedef struct { + VariableType type; + AstResolvedType resolved_type; +} FunctionReturnType; + struct FunctionSignature { - int params; /* TODO: Implement signatures */ + Buffer/*FunctionParameter*/ parameters; + Buffer/*FunctionReturnType*/ return_types; + /* Borrowed from the FunctionDecl the function signature belongs to. This is used to access the scope the function signature belongs to */ + FunctionDecl *func_decl; bool resolved; }; struct FunctionDecl { + LhsExpr *lhs_expr; /* Borrowed from the LhsExpr that owns this FunctionDecl if it exists, otherwise NULL */ FunctionSignature *signature; Scope body; SsaFuncIndex ssa_func_index; @@ -128,22 +194,9 @@ struct StructDecl { struct StructField { BufferView name; - Variable type; + VariableType type; }; -typedef struct { - enum { - VARIABLE_TYPE_NONE, - VARIABLE_TYPE_VARIABLE, - VARIABLE_TYPE_SIGNATURE - } type; - - union { - Variable *variable; - FunctionSignature *signature; - } value; -} VariableType; - typedef enum { DECL_FLAG_NONE = 0, DECL_FLAG_EXTERN = 1 << 0, @@ -221,6 +274,10 @@ struct WhileStatement { Scope body; }; +struct ReturnExpr { + Ast *rhs_expr; +}; + typedef struct { jmp_buf env; amal_compiler *compiler; /* Borrowed */ @@ -235,12 +292,18 @@ typedef struct { CHECK_RESULT int ast_create(ArenaAllocator *allocator, void *value, AstType type, Ast **result); BufferView ast_get_name(Ast *self); -void function_signature_init(FunctionSignature *self); +CHECK_RESULT int function_signature_init(FunctionSignature *self, ArenaAllocator *allocator); +/* Adds a copy of @param to the function signature parameter list */ +CHECK_RESULT int function_signature_add_parameter(FunctionSignature *self, const FunctionParameter *param); +/* Adds a copy of @return_type to the function signature return type list */ +CHECK_RESULT int function_signature_add_return_type(FunctionSignature *self, const VariableType *return_type); +void function_parameter_init(FunctionParameter *self); CHECK_RESULT int funcdecl_init(FunctionDecl *self, FunctionSignature *signature, Scope *parent, ArenaAllocator *allocator); CHECK_RESULT int funccall_init(FunctionCall *self, BufferView name, ArenaAllocator *allocator); CHECK_RESULT int structdecl_init(StructDecl *self, Scope *parent, ArenaAllocator *allocator); LhsExpr* structdecl_get_field_by_name(StructDecl *self, BufferView field_name); -void structfield_init(StructField *self, BufferView name, BufferView type_name); +/* Copies @type */ +void structfield_init(StructField *self, BufferView name, VariableType *type); void lhsexpr_init(LhsExpr *self, DeclFlag decl_flag, BufferView var_name); void assignmentexpr_init(AssignmentExpr *self, Ast *lhs_expr, Ast *rhs_expr); void import_init(Import *self, BufferView path); @@ -251,6 +314,7 @@ void binop_init(Binop *self); CHECK_RESULT int if_statement_init(IfStatement *self, Scope *parent, ArenaAllocator *allocator); CHECK_RESULT int else_if_statement_init(ElseIfStatement *self, Scope *parent, ArenaAllocator *allocator); CHECK_RESULT int while_statement_init(WhileStatement *self, Scope *parent, ArenaAllocator *allocator); +void return_expr_init(ReturnExpr *self, Ast *rhs_expr); CHECK_RESULT int scope_init(Scope *self, Scope *parent, ArenaAllocator *allocator); CHECK_RESULT int file_scope_reference_init(FileScopeReference *self, BufferView canonical_path, ArenaAllocator *allocator); diff --git a/include/bytecode/bytecode.h b/include/bytecode/bytecode.h index 4c54fbd..adad291 100644 --- a/include/bytecode/bytecode.h +++ b/include/bytecode/bytecode.h @@ -48,7 +48,7 @@ typedef enum { AMAL_OP_CMP, /* cmp dst, reg1, reg2 - Set dst to 1 if reg1 equals reg2, otherwise set it to 0 */ AMAL_OP_JZ, /* jz reg, offset - Jump to offset if reg is zero. offset is i16 */ AMAL_OP_JMP, /* jmp offset - Unconditional jump to offset. offset is i16 */ - AMAL_OP_RET, /* ret - Return from the function */ + AMAL_OP_RET, /* ret reg - Return from the function with reg result */ AMAL_OP_FUNC_START, /* func_start num_reg - Start of a function which has num_reg registers allocated. num_reg is a u16 */ AMAL_OP_FUNC_END /* func_end - End of a function. Implementation should do a ret here */ } AmalOpcode; diff --git a/include/compiler.h b/include/compiler.h index 83dde63..08b74b8 100644 --- a/include/compiler.h +++ b/include/compiler.h @@ -13,6 +13,7 @@ #define AMAL_COMPILER_OK 0 /* General error */ #define AMAL_COMPILER_ERR -1 +#define AMAL_COMPILER_WORK_FAIL_ABORT -2 #define NUM_ARITHMETIC_TYPES 10 @@ -53,6 +54,7 @@ struct amal_compiler { ParserThreadData *threads; int usable_thread_count; bool started; + bool work_failed; amal_mutex mutex; int generic_work_object_index; }; diff --git a/include/nullable.h b/include/nullable.h index f38fb37..961b210 100644 --- a/include/nullable.h +++ b/include/nullable.h @@ -1,8 +1,6 @@ #ifndef AMAL_NULLABLE_H #define AMAL_NULLABLE_H -struct __nullable_type_dummy{ int _; }; - /* Used by static analysis tool to find null-pointer dereference errors */ #define nullable __attribute__((annotate("nullable"))) diff --git a/include/program.h b/include/program.h index b98c0a6..6603c63 100644 --- a/include/program.h +++ b/include/program.h @@ -6,7 +6,8 @@ #include "../executor/executor.h" #define AMAL_PROGRAM_OK 0 -#define AMAL_PROGRAM_INVALID_HEADER -1 +#define AMAL_PROGRAM_ERR -1 +#define AMAL_PROGRAM_INVALID_HEADER -16 #define AMAL_PROGRAM_INVALID_MAGIC_NUMBER -2 #define AMAL_PROGRAM_INCOMPATIBLE -3 #define AMAL_PROGRAM_INVALID_INTERMEDIATES -4 @@ -21,6 +22,7 @@ #define AMAL_PROGRAM_INSTRUCTION_STACK_OVERFLOW -13 #define AMAL_PROGRAM_INSTRUCTION_STACK_OOM -14 #define AMAL_PROGRAM_INSTRUCTION_ILLEGAL_JUMP_TARGET -15 +#define AMAL_PROGRAM_INVALID_FUNCTIONS -16 #define AMAL_PROGRAM_MAGIC_NUMBER (u32)0xdec05eba #define AMAL_PROGRAM_MAJOR_VERSION 1 @@ -36,8 +38,9 @@ typedef struct { char *strings_start; /* Reference inside @data */ usize read_index; - u16 num_strings; u16 num_intermediates; + u16 num_strings; + u16 num_functions; } amal_program; CHECK_RESULT int amal_program_init(amal_program *self); diff --git a/include/ssa/ssa.h b/include/ssa/ssa.h index 016acc8..21d7f40 100644 --- a/include/ssa/ssa.h +++ b/include/ssa/ssa.h @@ -24,7 +24,8 @@ typedef enum { SSA_PUSH, SSA_CALL, SSA_JUMP_ZERO, - SSA_JUMP + SSA_JUMP, + SSA_RET } SsaInstruction; typedef enum { @@ -75,6 +76,7 @@ typedef struct { } SsaInsFuncStart; typedef struct { + u8 num_args; SsaRegister result; FunctionDecl *func_decl; } SsaInsFuncCall; diff --git a/include/std/buffer_view.h b/include/std/buffer_view.h index 4993dc2..59a97d3 100644 --- a/include/std/buffer_view.h +++ b/include/std/buffer_view.h @@ -10,5 +10,6 @@ typedef struct { BufferView create_buffer_view_null(); BufferView create_buffer_view(const char *data, usize size); +bool buffer_view_equals(const BufferView *self, const BufferView *other); #endif diff --git a/include/std/misc.h b/include/std/misc.h index 2fbe92c..e89f1eb 100644 --- a/include/std/misc.h +++ b/include/std/misc.h @@ -41,13 +41,4 @@ #define ignore_result_int(expr) (void)((expr)+1) -typedef enum { - bool_false, - bool_true -} bool; - -#ifndef NULL -#define NULL ((void*)0) -#endif - #endif diff --git a/include/std/types.h b/include/std/types.h index a8a44fd..bbf31c5 100644 --- a/include/std/types.h +++ b/include/std/types.h @@ -20,4 +20,13 @@ typedef size_t usize; typedef float f32; typedef double f64; +typedef enum { + bool_false, + bool_true +} bool; + +#ifndef NULL +#define NULL ((void*)0) +#endif + #endif diff --git a/include/tokenizer.h b/include/tokenizer.h index f624816..16123cc 100644 --- a/include/tokenizer.h +++ b/include/tokenizer.h @@ -37,7 +37,8 @@ typedef enum { TOK_IF, TOK_ELSE, TOK_WHILE, - TOK_EXTERN + TOK_EXTERN, + TOK_RETURN } Token; typedef struct { diff --git a/src/ast.c b/src/ast.c index 0aa19d4..413f77e 100644 --- a/src/ast.c +++ b/src/ast.c @@ -15,12 +15,118 @@ throw(return_if_result); \ } while(0) +static void variable_type_resolve(VariableType *self, AstCompilerContext *context, AstResolvedType *resolved_type); static void ast_resolve(Ast *self, AstCompilerContext *context); +static BufferView variable_type_get_name(VariableType *self); + +static void scope_named_object_init(ScopeNamedObject *self) { + self->type = NAMED_OBJECT_NONE; + self->value.lhs_expr = NULL; + self->resolve_data = NULL; +} + +/* TODO: Remove these? */ +#if 0 +static bool scope_named_object_equals(ScopeNamedObject *self, ScopeNamedObject *other) { + /* This should be fine, without checking ScopeNamedObject type, since they are only equal if the types are equal as well */ + return self->value.lhs_expr == other->value.lhs_expr; +} + +static BufferView scope_named_object_get_type_name(ScopeNamedObject *self) { + BufferView result; + switch(self->type) { + case NAMED_OBJECT_NONE: + result = create_buffer_view("", 0); + break; + case NAMED_OBJECT_LHS_EXPR: + result = variable_type_get_name(&self->value.lhs_expr->type); + break; + case NAMED_OBJECT_FUNC_PARAM: + result = variable_type_get_name(&self->value.func_param->type); + break; + } + return result; +} +#endif + +static BufferView ast_resolved_type_get_name(AstResolvedType *self) { + BufferView result; + switch(self->type) { + case RESOLVED_TYPE_NONE: + result = create_buffer_view("", 0); + break; + case RESOLVED_TYPE_LHS_EXPR: + result = variable_type_get_name(&self->value.lhs_expr->type); + break; + case RESOLVED_TYPE_FUNC_SIG: { + if(!self->value.func_sig->func_decl || !self->value.func_sig->func_decl->lhs_expr) { + /* + TODO: Use function signature string from the source file, which will also help with error reference. + Currently this would point to an invalid location in the source file + */ + result = create_buffer_view("fn()", 0); + break; + } + result = self->value.func_sig->func_decl->lhs_expr->var_name; + break; + } + } + return result; +} + +static void ast_resolved_type_init(AstResolvedType *self) { + self->value.lhs_expr = NULL; + self->type = RESOLVED_TYPE_NONE; +} + +static bool ast_resolved_type_equals(AstResolvedType *self, AstResolvedType *other) { + /* This should be fine, without checking AstResolvedType type, since they are only equal if the types are equal as well */ + return self->value.lhs_expr == other->value.lhs_expr; +} + +static AstResolvedType lhs_expr_get_resolved_type(LhsExpr *self, AstCompilerContext *context) { + AstResolvedType result; + variable_type_resolve(&self->type, context, &result); + if(result.type != RESOLVED_TYPE_NONE) + return result; + + assert(self->rhs_expr); + ast_resolve(self->rhs_expr, context); + return self->rhs_expr->resolve_data.type; +} + +/* TODO: Detect recursive dependency? is it even possible for function parameters? (it would normally be a recursive dependency on the function declaration) */ +static void function_parameter_resolve(FunctionParameter *self, AstCompilerContext *context) { + if(self->resolve_data.status == AST_RESOLVED) + return; + self->resolve_data.status = AST_RESOLVING; + variable_type_resolve(&self->type, context, &self->resolve_data.type); + self->resolve_data.status = AST_RESOLVED; +} + +static AstResolvedType scope_named_object_get_resolved_type(ScopeNamedObject *self, AstCompilerContext *context) { + AstResolvedType result; + switch(self->type) { + case NAMED_OBJECT_NONE: + /* Type not resolved */ + assert(bool_false); + ast_resolved_type_init(&result); + return result; + case NAMED_OBJECT_LHS_EXPR: + result = lhs_expr_get_resolved_type(self->value.lhs_expr, context); + break; + case NAMED_OBJECT_FUNC_PARAM: + function_parameter_resolve(self->value.func_param, context); + result = self->value.func_param->resolve_data.type; + break; + } + return result; +} static void resolve_data_init(AstResolveData *self) { self->status = AST_NOT_RESOLVED; - self->parser = NULL; - self->type = NULL; + ast_resolved_type_init(&self->type); + self->ssa_reg = 0; } int ast_create(ArenaAllocator *allocator, void *value, AstType type, Ast **result) { @@ -28,7 +134,7 @@ int ast_create(ArenaAllocator *allocator, void *value, AstType type, Ast **resul (*result)->value.data = value; (*result)->type = type; resolve_data_init(&(*result)->resolve_data); - (*result)->ssa_reg = 0; + (*result)->parser = NULL; return 0; } @@ -47,6 +153,7 @@ BufferView ast_get_name(Ast *self) { case AST_BINOP: case AST_IF_STATEMENT: case AST_WHILE_STATEMENT: + case AST_RETURN: name = create_buffer_view_null(); break; case AST_NUMBER: @@ -75,12 +182,47 @@ static BufferView ast_get_code_reference(Ast *self) { return ast_get_name(self); } -void function_signature_init(FunctionSignature *self) { - self->params = 0; +int function_signature_init(FunctionSignature *self, ArenaAllocator *allocator) { self->resolved = bool_false; + self->func_decl = NULL; + return_if_error(buffer_init(&self->parameters, allocator)); + return buffer_init(&self->return_types, allocator); +} + +static FunctionParameter* function_signature_get_parameter_by_name(FunctionSignature *self, BufferView name) { + FunctionParameter *param, *param_end; + param = buffer_begin(&self->parameters); + param_end = buffer_end(&self->parameters); + for(; param != param_end; ++param) { + if(buffer_view_equals(&name, ¶m->name)) + return param; + } + return NULL; +} + +int function_signature_add_parameter(FunctionSignature *self, const FunctionParameter *new_param) { + const FunctionParameter *existing_param = function_signature_get_parameter_by_name(self, new_param->name); + if(existing_param) + return AST_ERR_DEF_DUP; + return buffer_append(&self->parameters, new_param, sizeof(FunctionParameter)); +} + +int function_signature_add_return_type(FunctionSignature *self, const VariableType *var_type) { + FunctionReturnType return_type; + return_type.type = *var_type; + ast_resolved_type_init(&return_type.resolved_type); + return buffer_append(&self->return_types, &return_type, sizeof(return_type)); +} + +void function_parameter_init(FunctionParameter *self) { + self->name = create_buffer_view_null(); + self->type.type = VARIABLE_TYPE_NONE; + self->type.value.variable = NULL; + resolve_data_init(&self->resolve_data); } int funcdecl_init(FunctionDecl *self, FunctionSignature *signature, Scope *parent, ArenaAllocator *allocator) { + self->lhs_expr = NULL; self->signature = signature; self->ssa_func_index = 0; return scope_init(&self->body, parent, allocator); @@ -102,9 +244,9 @@ LhsExpr* structdecl_get_field_by_name(StructDecl *self, BufferView field_name) { return result->value.lhs_expr; } -void structfield_init(StructField *self, BufferView name, BufferView type_name) { +void structfield_init(StructField *self, BufferView name, VariableType *type) { self->name = name; - variable_init(&self->type, type_name); + self->type = *type; } void lhsexpr_init(LhsExpr *self, DeclFlag decl_flag, BufferView var_name) { @@ -140,7 +282,7 @@ void number_init(Number *self, i64 value, bool is_integer, BufferView code_ref) void variable_init(Variable *self, BufferView name) { self->name = name; - self->resolved_var = NULL; + scope_named_object_init(&self->resolved_var); } void binop_init(Binop *self) { @@ -167,11 +309,16 @@ int while_statement_init(WhileStatement *self, Scope *parent, ArenaAllocator *al return scope_init(&self->body, parent, allocator); } +void return_expr_init(ReturnExpr *self, Ast *rhs_expr) { + self->rhs_expr = rhs_expr; +} + int scope_init(Scope *self, Scope *parent, ArenaAllocator *allocator) { return_if_error(buffer_init(&self->ast_objects, allocator)); return_if_error(hash_map_init(&self->named_objects, allocator, sizeof(Ast*), hash_map_compare_string, amal_hash_string)); self->parent = parent; self->parser = NULL; + self->function_signature = NULL; return 0; } @@ -192,7 +339,6 @@ int scope_add_child(Scope *self, Ast *child) { Ast *existing_child; bool child_already_exists; - /* TODO: Implement for parameter */ if(child->type == AST_LHS) { BufferView var_name; var_name = child->value.lhs_expr->var_name; @@ -213,14 +359,16 @@ int scope_add_child(Scope *self, Ast *child) { void scope_resolve(Scope *self, AstCompilerContext *context) { Ast **ast; Ast **ast_end; + Scope *prev_scope; ast = buffer_begin(&self->ast_objects); ast_end = buffer_end(&self->ast_objects); + prev_scope = context->scope; context->scope = self; for(; ast != ast_end; ++ast) { ast_resolve(*ast, context); } - context->scope = self->parent; + context->scope = prev_scope; } static Parser* scope_get_parser(Scope *scope) { @@ -247,78 +395,114 @@ static void parser_print_error(Parser *parser, const char *ref, const char *fmt, va_end(args); } -static Ast* __scope_get_resolved_variable(Scope *self, Scope *start, AstCompilerContext *context, BufferView name) { - Ast *result; +static void __scope_get_resolved_variable(Scope *self, Scope *start, AstCompilerContext *context, BufferView name, ScopeNamedObject *result) { + Ast *ast_result; bool exists; Scope *prev_scope; assert(self); - exists = hash_map_get(&self->named_objects, name, &result); + exists = hash_map_get(&self->named_objects, name, &ast_result); if(!exists) { - Parser *parser; - if(self->parent) - return __scope_get_resolved_variable(self->parent, start, context, name); + if(self->function_signature) { + FunctionParameter *func_param; + func_param = function_signature_get_parameter_by_name(self->function_signature, name); + if(func_param) { + prev_scope = context->scope; + context->scope = self; + function_parameter_resolve(func_param, context); + context->scope = prev_scope; + + result->type = NAMED_OBJECT_FUNC_PARAM; + result->value.func_param = func_param; + result->resolve_data = &func_param->resolve_data; + return; + } + } - parser = scope_get_parser(start); - parser_print_error(parser, name.data, "Undefined reference to variable \"%.*s\"", name.size, name.data); - throw(AST_ERR); + if(self->parent) { + __scope_get_resolved_variable(self->parent, start, context, name, result); + return; + } + + { + Parser *parser; + parser = scope_get_parser(start); + parser_print_error(parser, name.data, "Undefined reference to variable \"%.*s\"", name.size, name.data); + throw(AST_ERR); + } } /* - Need to change scope here because we are changing the visible scope - and the ast object may be in another scope than the current - resolving ast. + Need to change scope here because we are changing the visible scope + and the ast object may be in another scope than the current + resolving ast. */ prev_scope = context->scope; context->scope = self; - ast_resolve(result, context); + ast_resolve(ast_result, context); context->scope = prev_scope; - assert(result->type == AST_LHS); - return result; + assert(ast_result->type == AST_LHS); + result->type = NAMED_OBJECT_LHS_EXPR; + result->value.lhs_expr = ast_result->value.lhs_expr; + result->resolve_data = &ast_result->resolve_data; } -static Ast* scope_get_resolved_variable(Scope *self, AstCompilerContext *context, BufferView name) { - return __scope_get_resolved_variable(self, self, context, name); +static void scope_get_resolved_variable(Scope *self, AstCompilerContext *context, BufferView name, ScopeNamedObject *result) { + __scope_get_resolved_variable(self, self, context, name, result); +} + +static void function_return_type_resolve(FunctionReturnType *self, AstCompilerContext *context) { + variable_type_resolve(&self->type, context, &self->resolved_type); } static void function_signature_resolve(FunctionSignature *self, AstCompilerContext *context) { - /* TODO: Implement */ if(self->resolved) return; - self->resolved = bool_true; - (void)self; - (void)context; -} -static void variable_resolve(Variable *self, AstCompilerContext *context, AstResolveData *resolve_data) { - if(!self->resolved_var) - self->resolved_var = scope_get_resolved_variable(context->scope, context, self->name); - resolve_data->type = self->resolved_var->resolve_data.type; + { + FunctionParameter *param, *param_end; + param = buffer_begin(&self->parameters); + param_end = buffer_end(&self->parameters); + for(; param != param_end; ++param) { + function_parameter_resolve(param, context); + } + } + { + FunctionReturnType *return_type, *return_type_end; + return_type = buffer_begin(&self->return_types); + return_type_end = buffer_end(&self->return_types); + for(; return_type != return_type_end; ++return_type) { + function_return_type_resolve(return_type, context); + } + } + + self->resolved = bool_true; } -static LhsExpr* variable_get_resolved_type(Variable *self) { - assert(self->resolved_var); - return self->resolved_var->value.lhs_expr; +static void variable_resolve(Variable *self, AstCompilerContext *context, AstResolvedType *resolved_type) { + if(self->resolved_var.type == NAMED_OBJECT_NONE) + scope_get_resolved_variable(context->scope, context, self->name, &self->resolved_var); + *resolved_type = scope_named_object_get_resolved_type(&self->resolved_var, context); } -static void variable_type_resolve(LhsExpr *self, AstCompilerContext *context, AstResolveData *resolve_data) { - VariableType *lhs_expr_type; - lhs_expr_type = &self->type; - switch(lhs_expr_type->type) { +void variable_type_resolve(VariableType *self, AstCompilerContext *context, AstResolvedType *resolved_type) { + switch(self->type) { case VARIABLE_TYPE_NONE: + ast_resolved_type_init(resolved_type); return; case VARIABLE_TYPE_VARIABLE: - variable_resolve(lhs_expr_type->value.variable, context, resolve_data); + variable_resolve(self->value.variable, context, resolved_type); break; case VARIABLE_TYPE_SIGNATURE: - function_signature_resolve(lhs_expr_type->value.signature, context); - resolve_data->type = self; + function_signature_resolve(self->value.signature, context); + resolved_type->type = RESOLVED_TYPE_FUNC_SIG; + resolved_type->value.func_sig = self->value.signature; break; } } -static BufferView variable_type_get_name(VariableType *self) { +BufferView variable_type_get_name(VariableType *self) { BufferView result; switch(self->type) { case VARIABLE_TYPE_NONE: @@ -337,14 +521,14 @@ static BufferView variable_type_get_name(VariableType *self) { return result; } -static LhsExpr* lhsexpr_resolve_rhs(LhsExpr *self, AstCompilerContext *context) { - LhsExpr *rhs_resolved_type; +static void lhsexpr_resolve_rhs(LhsExpr *self, AstCompilerContext *context, AstResolvedType *result) { ast_resolve(self->rhs_expr, context); - if(ast_is_decl(self->rhs_expr)) - rhs_resolved_type = self; - else - rhs_resolved_type = self->rhs_expr->resolve_data.type; - return rhs_resolved_type; + if(ast_is_decl(self->rhs_expr)) { + result->type = RESOLVED_TYPE_LHS_EXPR; + result->value.lhs_expr = self; + } else { + *result = self->rhs_expr->resolve_data.type; + } } static void lhsexpr_resolve(Ast *ast, AstCompilerContext *context) { @@ -353,7 +537,7 @@ static void lhsexpr_resolve(Ast *ast, AstCompilerContext *context) { assert(ast->type == AST_LHS); self = ast->value.lhs_expr; - variable_type_resolve(self, context, &ast->resolve_data); + variable_type_resolve(&self->type, context, &ast->resolve_data.type); /* TODO: When parameters and return types are implemented, AST_RESOLVE_END should be set after @@ -361,7 +545,7 @@ static void lhsexpr_resolve(Ast *ast, AstCompilerContext *context) { be allowed but recursive function calls still require parameters and return types to be known. */ if(self->rhs_expr) { - LhsExpr *rhs_resolve_type; + AstResolvedType rhs_resolve_type; if(self->rhs_expr->type == AST_FUNCTION_DECL) { /* The function declaration itself always resolves the signature, but we also do it here because we @@ -371,17 +555,19 @@ static void lhsexpr_resolve(Ast *ast, AstCompilerContext *context) { function_signature_resolve(self->rhs_expr->value.func_decl->signature, context); ast->resolve_data.status = AST_RESOLVED; /* - If rhs is a function declaration then there is no need to wait until it has been resolved before setting the type as the type - is @self (the lhs). We still need to continue after this, so rhs can be resolved. + If rhs is a function declaration then there is no need to wait until it has been resolved before setting the type. + We still need to continue after this, so rhs can be resolved. */ - if(!ast->resolve_data.type) - ast->resolve_data.type = self; + if(ast->resolve_data.type.type == RESOLVED_TYPE_NONE) { + ast->resolve_data.type.type = RESOLVED_TYPE_FUNC_SIG; + ast->resolve_data.type.value.func_sig = self->rhs_expr->value.func_decl->signature; + } } - rhs_resolve_type = lhsexpr_resolve_rhs(self, context); + lhsexpr_resolve_rhs(self, context, &rhs_resolve_type); /* TODO: Add casting */ - if(ast->resolve_data.type && ast->resolve_data.type != rhs_resolve_type) { + if(ast->resolve_data.type.type == RESOLVED_TYPE_LHS_EXPR && !ast_resolved_type_equals(&ast->resolve_data.type, &rhs_resolve_type)) { Parser *parser; parser = scope_get_parser(context->scope); /* @@ -413,23 +599,29 @@ static LhsExpr* binop_get_lhs_expr(Binop *self) { static void assignmentexpr_resolve(Ast *ast, AstCompilerContext *context) { AssignmentExpr *self; - LhsExpr *lhs_source; + bool is_lhs_const; + is_lhs_const = bool_false; assert(ast->type == AST_ASSIGN); self = ast->value.assign_expr; - lhs_source = NULL; ast_resolve(self->lhs_expr, context); ast_resolve(self->rhs_expr, context); - if(self->lhs_expr->type == AST_VARIABLE) - lhs_source = variable_get_resolved_type(self->lhs_expr->value.variable); - else if(self->lhs_expr->type == AST_BINOP) - lhs_source = binop_get_lhs_expr(self->lhs_expr->value.binop); + if(self->lhs_expr->type == AST_VARIABLE) { + /* TODO: Allow non-const function param */ + const ScopeNamedObject *resolved_var = &self->lhs_expr->value.variable->resolved_var; + if(resolved_var->type == NAMED_OBJECT_FUNC_PARAM || LHS_EXPR_IS_CONST(resolved_var->value.lhs_expr)) + is_lhs_const = bool_true; + } else if(self->lhs_expr->type == AST_BINOP) { + LhsExpr *lhs_expr; + lhs_expr = binop_get_lhs_expr(self->lhs_expr->value.binop); + is_lhs_const = LHS_EXPR_IS_CONST(lhs_expr); + } /* This also covers extern variables, since extern variables are always const */ /* TODO: var.field type expressions should also be checked */ - if(lhs_source && LHS_EXPR_IS_CONST(lhs_source)) { + if(is_lhs_const) { Parser *parser; parser = scope_get_parser(context->scope); parser_print_error(parser, ast_get_code_reference(self->lhs_expr).data, "Can't assign to a const value"); @@ -437,14 +629,14 @@ static void assignmentexpr_resolve(Ast *ast, AstCompilerContext *context) { } /* TODO: Add casting */ - if(self->lhs_expr->resolve_data.type != self->rhs_expr->resolve_data.type) { + if(!ast_resolved_type_equals(&self->lhs_expr->resolve_data.type, &self->rhs_expr->resolve_data.type)) { Parser *parser; BufferView rhs_type_name; BufferView lhs_type_name; parser = scope_get_parser(context->scope); - rhs_type_name = variable_type_get_name(&self->rhs_expr->resolve_data.type->type); - lhs_type_name = variable_type_get_name(&self->lhs_expr->resolve_data.type->type); + rhs_type_name = ast_resolved_type_get_name(&self->rhs_expr->resolve_data.type); + lhs_type_name = ast_resolved_type_get_name(&self->lhs_expr->resolve_data.type); /* TODO: Instead of using self->var_name, using type name. This cant be done right now because type can be function signature. @@ -462,39 +654,54 @@ static void import_resolve(Ast *ast, AstCompilerContext *context) { assert(ast->type == AST_IMPORT); (void)context; self = ast->value.import; - ast->resolve_data.type = &self->file_scope->parser->file_decl; - assert(ast->resolve_data.type); + ast->resolve_data.type.type = RESOLVED_TYPE_LHS_EXPR; + ast->resolve_data.type.value.lhs_expr = &self->file_scope->parser->file_decl; } static Scope* lhsexpr_get_scope(LhsExpr *self) { AstValue value; - if(self->rhs_expr) { - value = self->rhs_expr->value; - switch(self->rhs_expr->type) { - case AST_FUNCTION_DECL: - return &value.func_decl->body; - case AST_STRUCT_DECL: - return &value.struct_decl->body; - case AST_IMPORT: - return &value.import->file_scope->parser->struct_decl.body; - default: - break; - } + value = self->rhs_expr->value; + switch(self->rhs_expr->type) { + case AST_FUNCTION_DECL: + return &value.func_decl->body; + case AST_STRUCT_DECL: + return &value.struct_decl->body; + case AST_IMPORT: + return &value.import->file_scope->parser->struct_decl.body; + default: + break; } assert(bool_false && "Expected lhsexpr_get_scope to only be called for non-extern function declaration, struct declaration and import"); return NULL; } +static Scope* ast_resolved_type_get_scope(AstResolvedType *self) { + switch(self->type) { + case RESOLVED_TYPE_NONE: + assert(bool_false && "Expected ast_resolved_type_get_scope to only be called for a resolved object"); + return NULL; + case RESOLVED_TYPE_LHS_EXPR: + return lhsexpr_get_scope(self->value.lhs_expr); + case RESOLVED_TYPE_FUNC_SIG: + assert(self->value.func_sig->func_decl); + return &self->value.func_sig->func_decl->body; + } + return NULL; +} + /* @self has to have a resolved type before calling this */ static Parser* get_resolved_type_parser(Ast *self) { - assert(self->resolve_data.type); - return scope_get_parser(lhsexpr_get_scope(self->resolve_data.type)); + assert(self->resolve_data.type.type == RESOLVED_TYPE_LHS_EXPR && "Expected get_resolved_type_parser to only be called for non-extern function declaration, struct declaration and import"); + return scope_get_parser(lhsexpr_get_scope(self->resolve_data.type.value.lhs_expr)); } -static void funcdecl_resolve(FunctionDecl *self, AstCompilerContext *context) { - /* TODO: Implement parameters and return types */ - function_signature_resolve(self->signature, context); - scope_resolve(&self->body, context); +static void funcdecl_resolve(Ast *self, AstCompilerContext *context) { + FunctionDecl *func_decl; + func_decl = self->value.func_decl; + function_signature_resolve(func_decl->signature, context); + scope_resolve(&func_decl->body, context); + self->resolve_data.type.type = RESOLVED_TYPE_FUNC_SIG; + self->resolve_data.type.value.func_sig = self->value.func_decl->signature; } /* @@ -502,7 +709,10 @@ static void funcdecl_resolve(FunctionDecl *self, AstCompilerContext *context) { Meaning the resolve status wont be set to solved but the resolve type will be set. */ bool resolved_type_is_func_decl(Ast *self) { - const LhsExpr *resolved_type = self->resolve_data.type; + LhsExpr *resolved_type; + if(self->resolve_data.type.type != RESOLVED_TYPE_LHS_EXPR) + return bool_false; + resolved_type = self->resolve_data.type.value.lhs_expr; return (resolved_type->rhs_expr && resolved_type->rhs_expr->type == AST_FUNCTION_DECL) || resolved_type->type.type == VARIABLE_TYPE_SIGNATURE; } @@ -513,16 +723,16 @@ static void funccall_resolve(Ast *self, AstCompilerContext *context) { Ast **ast_end; func_call = self->value.func_call; - variable_resolve(&func_call->func, context, &self->resolve_data); + variable_resolve(&func_call->func, context, &self->resolve_data.type); /* Attemping to use call syntax (variable_name ( ) ) with a variable that is not a function */ - if(!resolved_type_is_func_decl(self)) { + if(self->resolve_data.type.type != RESOLVED_TYPE_FUNC_SIG) { Parser *caller_parser; Parser *callee_parser; BufferView callee_code_ref; caller_parser = scope_get_parser(context->scope); callee_parser = get_resolved_type_parser(self); - callee_code_ref = self->resolve_data.type->var_name; + callee_code_ref = ast_resolved_type_get_name(&self->resolve_data.type); parser_print_error(caller_parser, func_call->func.name.data, "\"%.*s\" is not a function. Only functions can be called", func_call->func.name.size, func_call->func.name.data); @@ -549,11 +759,14 @@ static void structfield_resolve(Ast *self, AstCompilerContext *context) { /* TODO: Implement */ StructField *struct_field; struct_field = self->value.struct_field; - variable_resolve(&struct_field->type, context, &self->resolve_data); + variable_type_resolve(&struct_field->type, context, &self->resolve_data.type); } static bool is_struct_decl(Ast *self) { - const LhsExpr *resolved_type = self->resolve_data.type; + LhsExpr *resolved_type; + if(self->resolve_data.type.type != RESOLVED_TYPE_LHS_EXPR) + return bool_false; + resolved_type = self->resolve_data.type.value.lhs_expr; assert(self->resolve_data.status == AST_RESOLVED); return resolved_type->rhs_expr && resolved_type->rhs_expr->type == AST_STRUCT_DECL; } @@ -565,6 +778,7 @@ static void binop_resolve_dot_access(Ast *ast, AstCompilerContext *context) { Parser *callee_parser; BufferView caller_code_ref; BufferView callee_code_ref; + ScopeNamedObject rhs_resolved_var; assert(ast->type == AST_BINOP); self = ast->value.binop; @@ -578,12 +792,13 @@ static void binop_resolve_dot_access(Ast *ast, AstCompilerContext *context) { throw(AST_ERR); } - lhs_scope = lhsexpr_get_scope(self->lhs->resolve_data.type); - self->rhs->resolve_data.type = scope_get_resolved_variable(lhs_scope, context, self->rhs->value.variable->name)->resolve_data.type; + lhs_scope = ast_resolved_type_get_scope(&self->lhs->resolve_data.type); + scope_get_resolved_variable(lhs_scope, context, self->rhs->value.variable->name, &rhs_resolved_var); + self->rhs->resolve_data.type = scope_named_object_get_resolved_type(&rhs_resolved_var, context); callee_parser = get_resolved_type_parser(self->rhs); caller_code_ref = ast_get_code_reference(self->rhs); - callee_code_ref = self->rhs->resolve_data.type->var_name; + callee_code_ref = ast_resolved_type_get_name(&self->rhs->resolve_data.type); if(!is_struct_decl(self->lhs)) { parser_print_error(caller_parser, caller_code_ref.data, "Can only access field of structs"); @@ -593,7 +808,7 @@ static void binop_resolve_dot_access(Ast *ast, AstCompilerContext *context) { throw(AST_ERR); } - if(!LHS_EXPR_IS_PUB(self->rhs->resolve_data.type)) { + if(self->rhs->resolve_data.type.type != RESOLVED_TYPE_LHS_EXPR || !LHS_EXPR_IS_PUB(self->rhs->resolve_data.type.value.lhs_expr)) { parser_print_error(caller_parser, caller_code_ref.data, "Can't access non-public field \"%.*s\"", caller_code_ref.size, caller_code_ref.data); /* TODO: use tokenizer_print_note, once it has been added */ /* TODO: Print type */ @@ -612,7 +827,7 @@ static void binop_resolve(Ast *ast, AstCompilerContext *context) { /* Only function call has extra data that needs to be resolved (args) */ if(self->rhs->type == AST_FUNCTION_CALL) { Scope *prev_scope = context->scope; - context->scope = lhsexpr_get_scope(self->lhs->resolve_data.type); + context->scope = ast_resolved_type_get_scope(&self->lhs->resolve_data.type); ast_resolve(self->rhs, context); context->scope = prev_scope; } @@ -621,35 +836,43 @@ static void binop_resolve(Ast *ast, AstCompilerContext *context) { } else { ast_resolve(self->rhs, context); /* TODO: Convert types that can be safely converted */ - assert(self->lhs->resolve_data.type); - assert(self->rhs->resolve_data.type); - if(self->rhs->resolve_data.type != self->lhs->resolve_data.type) { + assert(self->lhs->resolve_data.type.type != RESOLVED_TYPE_NONE); + assert(self->rhs->resolve_data.type.type != RESOLVED_TYPE_NONE); + if(!ast_resolved_type_equals(&self->rhs->resolve_data.type, &self->lhs->resolve_data.type)) { /* TODO: For this first error, only print the line without a reference to code. This requires change in tokenizer_print_error to be able to take a line as reference. TODO: Use note for the additional information instead of error. */ + BufferView lhs_type_name, rhs_type_name; Parser *parser; + + lhs_type_name = ast_resolved_type_get_name(&self->lhs->resolve_data.type); + rhs_type_name = ast_resolved_type_get_name(&self->rhs->resolve_data.type); parser = scope_get_parser(context->scope); parser_print_error(parser, ast_get_code_reference(self->rhs).data, "Can't cast type \"%.*s\" to type \"%.*s\"", - self->rhs->resolve_data.type->var_name.size, self->rhs->resolve_data.type->var_name.data, - self->lhs->resolve_data.type->var_name.size, self->lhs->resolve_data.type->var_name.data); + rhs_type_name.size, rhs_type_name.data, + lhs_type_name.size, lhs_type_name.data); parser_print_error(parser, ast_get_code_reference(self->lhs).data, "Left-hand side is of type %.*s", - self->lhs->resolve_data.type->var_name.size, self->lhs->resolve_data.type->var_name.data); + lhs_type_name.size, lhs_type_name.data); parser_print_error(parser, ast_get_code_reference(self->rhs).data, "Right-hand side is of type %.*s", - self->rhs->resolve_data.type->var_name.size, self->rhs->resolve_data.type->var_name.data); + rhs_type_name.size, rhs_type_name.data); throw(AST_ERR); - } else if(!is_arithmetic_type(self->lhs->resolve_data.type, context->compiler)) { /* TODO: Optimize this? store arithmetic type in the LhsExpr itself? */ + /* TODO: Optimize this? store arithmetic type in the LhsExpr itself? */ + } else if(self->lhs->resolve_data.type.type != RESOLVED_TYPE_LHS_EXPR || !is_arithmetic_type(self->lhs->resolve_data.type.value.lhs_expr, context->compiler)) { /* TODO: Point the error at the binop instead of LHS */ + BufferView lhs_type_name; Parser *parser; + + lhs_type_name = ast_resolved_type_get_name(&self->lhs->resolve_data.type); parser = scope_get_parser(context->scope); parser_print_error(parser, ast_get_code_reference(self->lhs).data, "Arithmetic operation can only be performed with the types i8, u8, i16, u16, i32, u32, i64, u64, isize and usize", - self->lhs->resolve_data.type->var_name.size, self->lhs->resolve_data.type->var_name.data); + lhs_type_name.size, lhs_type_name.data); throw(AST_ERR); } ast->resolve_data.type = self->lhs->resolve_data.type; @@ -678,23 +901,36 @@ static void while_statement_resolve(WhileStatement *while_stmt, AstCompilerConte scope_resolve(&while_stmt->body, context); } +static void return_expr_resolve(ReturnExpr *self, AstCompilerContext *context) { + ast_resolve(self->rhs_expr, context); +} + void ast_resolve(Ast *self, AstCompilerContext *context) { assert(self); assert(context->parser); /* - TODO: Move these to the types that need checks for recursive dependency (function declaration, struct declaration) - For function declaration, it should be marked as resolved when the signature has been resolved - instead of the whole function declaration including the body - because the body can have function call that calls functions that are resolving - or even recursive function call, which should be allowed. + TODO: Move these to the types that need checks for recursive dependency (function declaration, struct declaration) + For function declaration, it should be marked as resolved when the signature has been resolved + instead of the whole function declaration including the body + because the body can have function call that calls functions that are resolving + or even recursive function call, which should be allowed. */ /* - This check is outside lhs_expr mutex for optimization purpose as most times there wont be - a race in multiple threads to resolve an AST expression. + This check is outside lhs_expr mutex for optimization purpose as most times there wont be + a race in multiple threads to resolve an AST expression. */ if(self->resolve_data.status == AST_RESOLVED) { return; - } else if(self->resolve_data.status == AST_RESOLVING && self->resolve_data.parser == context->parser) { + /* + NOTE!!!: There is a data race here and it's fine! + A recursive dependency can be missed here in one thread, but it's fine because that + can only happen if another thread is also resolving the same ast, in which case that thread will finally + report the recursive dependency. Note that this can mean that different files can report the + recursive dependency everytime you compile code with recursive dependency. However this can happen + even without a data race since files are compile in parallel so one file might resolve the ast with the issue + before another file has done it, and it can be different everytime the files are compiled. + */ + } else if(self->resolve_data.status == AST_RESOLVING && self->parser == context->parser) { Parser *parser; parser = scope_get_parser(context->scope); parser_print_error(parser, ast_get_code_reference(self).data, "Found recursive dependency"); @@ -702,20 +938,21 @@ void ast_resolve(Ast *self, AstCompilerContext *context) { } self->resolve_data.status = AST_RESOLVING; - self->resolve_data.parser = context->parser; + self->parser = context->parser; switch(self->type) { case AST_NUMBER: { Number *number; number = self->value.number; /* TODO: Support other number types */ + self->resolve_data.type.type = RESOLVED_TYPE_LHS_EXPR; if(number->is_integer) - self->resolve_data.type = (LhsExpr*)context->compiler->default_types.i64; + self->resolve_data.type.value.lhs_expr = (LhsExpr*)context->compiler->default_types.i64; else - self->resolve_data.type = (LhsExpr*)context->compiler->default_types.f64; + self->resolve_data.type.value.lhs_expr = (LhsExpr*)context->compiler->default_types.f64; break; } case AST_FUNCTION_DECL: - funcdecl_resolve(self->value.func_decl, context); + funcdecl_resolve(self, context); break; case AST_FUNCTION_CALL: funccall_resolve(self, context); @@ -738,10 +975,11 @@ void ast_resolve(Ast *self, AstCompilerContext *context) { break; case AST_STRING: /* TODO: Convert special combinations. For example \n to newline */ - self->resolve_data.type = (LhsExpr*)context->compiler->default_types.str; + self->resolve_data.type.type = RESOLVED_TYPE_LHS_EXPR; + self->resolve_data.type.value.lhs_expr = (LhsExpr*)context->compiler->default_types.str; break; case AST_VARIABLE: - variable_resolve(self->value.variable, context, &self->resolve_data); + variable_resolve(self->value.variable, context, &self->resolve_data.type); break; case AST_BINOP: binop_resolve(self, context); @@ -752,7 +990,11 @@ void ast_resolve(Ast *self, AstCompilerContext *context) { case AST_WHILE_STATEMENT: while_statement_resolve(self->value.while_stmt, context); break; + case AST_RETURN: + return_expr_resolve(self->value.return_expr, context); + break; } /* TODO: See comment at the top of this function */ + /*assert(self->resolve_data.type.type != RESOLVED_TYPE_NONE);*/ self->resolve_data.status = AST_RESOLVED; } diff --git a/src/bytecode/bytecode.c b/src/bytecode/bytecode.c index 70a5cfa..8a41900 100644 --- a/src/bytecode/bytecode.c +++ b/src/bytecode/bytecode.c @@ -42,9 +42,10 @@ static CHECK_RESULT usize ssa_extract_func_start(u8 *instruction_data, SsaInsFun } static CHECK_RESULT usize ssa_extract_func_call(u8 *instruction_data, SsaInsFuncCall *result) { - am_memcpy(&result->result, instruction_data, sizeof(result->result)); - am_memcpy(&result->func_decl, instruction_data + sizeof(result->result), sizeof(result->func_decl)); - return sizeof(result->result) + sizeof(result->func_decl); + result->num_args = instruction_data[0]; + am_memcpy(&result->result, instruction_data + 1, sizeof(result->result)); + am_memcpy(&result->func_decl, instruction_data + 1 + sizeof(result->result), sizeof(result->func_decl)); + return sizeof(u8) + sizeof(result->result) + sizeof(result->func_decl); } static CHECK_RESULT usize ssa_extract_jump_zero(u8 *instruction_data, SsaInsJumpZero *result) { @@ -94,7 +95,7 @@ static void add_intermediates(BytecodeCompilerContext *self) { } } -void add_strings(BytecodeCompilerContext *self) { +static void add_strings(BytecodeCompilerContext *self) { /*doc(Bytecode strings) # Strings layout |Type |Field |Description | @@ -144,6 +145,18 @@ void add_strings(BytecodeCompilerContext *self) { } } +static void add_functions(BytecodeCompilerContext *self) { + /*doc(Bytecode functions) + # Internal functions layout + |Type|Field |Description | + |----|-------------------|---------------------------------| + |u16 |Number of functions|The number of internal functions.| + */ + + assert(sizeof(SsaFuncIndex) == sizeof(u16) && "Program decoder needs to be updated since size of func index has changed"); + throw_if_error(buffer_append(&self->bytecode.data, &self->parser->ssa->func_counter, sizeof(u16))); +} + static void add_ins1(BytecodeCompilerContext *self, AmalOpcode opcode, const char *fmt) { throw_if_error(buffer_append(&self->bytecode.data, &opcode, sizeof(AmalOpcodeType))); fprintf(stderr, fmt); @@ -437,7 +450,7 @@ static void add_instructions(BytecodeCompilerContext *self) { } case SSA_ASSIGN_REG: { instruction += ssa_extract_form1(instruction, &ssa_ins_form1); - add_ins3(self, AMAL_OP_MOV, ssa_ins_form1.lhs, ssa_ins_form1.rhs, "mov r%d, d%d"); + add_ins3(self, AMAL_OP_MOV, ssa_ins_form1.lhs, ssa_ins_form1.rhs, "mov r%d, r%d"); break; } case SSA_ADD: { @@ -505,8 +518,8 @@ static void add_instructions(BytecodeCompilerContext *self) { is defined as the size of all previous files' number of functions. */ instruction += ssa_extract_func_call(instruction, &ssa_ins_func_call); - /* TODO: Replace 0 with the number of arguments */ - add_ins7(self, AMAL_OP_CALL, ssa_ins_func_call.func_decl->ssa_func_index, 0, "call %d, %d"); + add_ins7(self, AMAL_OP_CALL, ssa_ins_func_call.func_decl->ssa_func_index, ssa_ins_func_call.num_args, "call %d, %d"); + assert(bool_false && "TODO: Assign function result (RAX for x86_64) to ssa_ins_func_call.result reg"); break; } case SSA_JUMP_ZERO: { @@ -519,6 +532,13 @@ static void add_instructions(BytecodeCompilerContext *self) { add_ins4(self, AMAL_OP_JMP, ssa_ins_jump.jump_offset, "jmp %d"); break; } + case SSA_RET: { + SsaRegister reg; + am_memcpy(®, instruction, sizeof(SsaRegister)); + instruction += sizeof(SsaRegister); + add_ins2(self, AMAL_OP_RET, reg, "ret r%d"); + break; + } } } @@ -537,6 +557,6 @@ static void add_instructions(BytecodeCompilerContext *self) { void generate_bytecode_from_ssa(BytecodeCompilerContext *self) { add_intermediates(self); add_strings(self); - /* TODO: Also add strings in ssa, so we can index them */ + add_functions(self); add_instructions(self); } diff --git a/src/compiler.c b/src/compiler.c index 39cbb00..228cb74 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -36,7 +36,8 @@ static CHECK_RESULT int create_default_type(amal_compiler *compiler, const char lhsexpr_init(*lhs_expr, DECL_FLAG_EXTERN | DECL_FLAG_PUB | DECL_FLAG_CONST, create_buffer_view(name, strnlen(name, PATH_MAX))); return_if_error(ast_create(&compiler->allocator, struct_decl, AST_STRUCT_DECL, &(*lhs_expr)->rhs_expr)); return_if_error(ast_create(&compiler->allocator, *lhs_expr, AST_LHS, &expr)); - expr->resolve_data.type = *lhs_expr; + expr->resolve_data.type.type = RESOLVED_TYPE_LHS_EXPR; + expr->resolve_data.type.value.lhs_expr = *lhs_expr; expr->resolve_data.status = AST_RESOLVED; return scope_add_child(&compiler->root_scope, expr); } @@ -123,6 +124,7 @@ static CHECK_RESULT int amal_compiler_init(amal_compiler *self, const amal_compi amal_compiler_options_init(&self->options); self->program = program; self->started = bool_false; + self->work_failed = bool_false; self->generic_work_object_index = 0; amal_mutex_init(&self->mutex); @@ -204,7 +206,6 @@ static CHECK_RESULT int amal_compiler_load_in_this_thread(amal_compiler *compile return result; } -/* TODO: Handle errors (stop parsing in all other threads and report errors/warnings) */ static void* thread_callback_parse_file(void *userdata) { FileScopeReference *file_scope; CompilerParserThreadUserData compiler_parser_userdata; @@ -217,6 +218,11 @@ static void* thread_callback_parse_file(void *userdata) { result = (void*)AMAL_COMPILER_ERR; for(;;) { int has_next; + /* Abort job if another job failed */ + if(compiler_parser_userdata.compiler->work_failed) { + result = (void*)AMAL_COMPILER_WORK_FAIL_ABORT; + goto cleanup; + } cleanup_if_error(amal_compiler_load_in_this_thread(compiler_parser_userdata.compiler, file_scope, &compiler_parser_userdata.parser_thread_data->allocator)); @@ -324,6 +330,14 @@ static void* thread_callback_generic(void *userdata) { cleanup_if_error(thread_generate_bytecode(parser)); break; } + + /* Abort job if another job failed */ + if(compiler_userdata.compiler->work_failed) { + result = (void*)AMAL_COMPILER_WORK_FAIL_ABORT; + goto cleanup; + } + + /* Find next job */ cleanup_if_error(amal_mutex_lock(&compiler_userdata.compiler->mutex, "thread_callback_generic")); if(compiler_userdata.compiler->generic_work_object_index + 1 >= (int)buffer_get_size(&compiler_userdata.compiler->parsers, Parser*)) break; @@ -339,7 +353,7 @@ static void* thread_callback_generic(void *userdata) { and the other threads will stop when they are done with the work they are currently working on. */ if(result != NULL) { - cleanup_if_error(amal_mutex_lock(&compiler_userdata.compiler->mutex, "thread_callback_generic")); + ignore_result_int(amal_mutex_lock(&compiler_userdata.compiler->mutex, "thread_callback_generic")); compiler_userdata.compiler->generic_work_object_index = (int)buffer_get_size(&compiler_userdata.compiler->parsers, Parser*); } compiler_userdata.parser_thread_data->status = PARSER_THREAD_STATUS_IDLE; @@ -424,11 +438,9 @@ static CHECK_RESULT int amal_compiler_load_file_join_threads(amal_compiler *self int result; void *thread_return_data; ParserThreadData *parser_thread_data; - bool work_failed; assert(amal_thread_is_main()); thread_return_data = NULL; - work_failed = bool_false; for(;;) { bool done; /* @@ -442,12 +454,11 @@ static CHECK_RESULT int amal_compiler_load_file_join_threads(amal_compiler *self amal_mutex_tryunlock(&self->mutex); if(result != 0) goto cleanup; - /* TODO: Cleanup remaining threads if join fails */ - cleanup_if_error(parser_thread_data_join(parser_thread_data, &thread_return_data)); + /* TODO: What to do if join fails? */ + ignore_result_int(parser_thread_data_join(parser_thread_data, &thread_return_data)); if(thread_return_data != NULL) { - /* TODO: Somehow exit running jobs */ amal_log_error("Failed, waiting for jobs to finish"); - work_failed = bool_true; + self->work_failed = bool_true; } } @@ -458,7 +469,7 @@ static CHECK_RESULT int amal_compiler_load_file_join_threads(amal_compiler *self result = AMAL_COMPILER_OK; cleanup: - if(work_failed) + if(self->work_failed) result = AMAL_COMPILER_ERR; return result; } @@ -545,6 +556,9 @@ int amal_compiler_internal_load_file(amal_compiler *self, const char *filepath, bool main_job; bool new_entry; + if(self->work_failed) + return AMAL_COMPILER_WORK_FAIL_ABORT; + return_if_error(try_create_file_scope(self, filepath, file_scope, &new_entry)); assert(file_scope && *file_scope && (*file_scope)->canonical_path.data); if(!new_entry) { diff --git a/src/parser.c b/src/parser.c index e36790f..33e9146 100644 --- a/src/parser.c +++ b/src/parser.c @@ -24,6 +24,8 @@ static CHECK_RESULT Ast* parser_parse_rhs(Parser *self); static CHECK_RESULT Ast* parser_parse_body(Parser *self); static CHECK_RESULT Ast* parser_parse_struct_body(Parser *self); static CHECK_RESULT Ast* parser_parse_rhs_binop(Parser *self); +static void parser_parse_var_type(Parser *self, VariableType *result); +static void parser_parse_var_type_def(Parser *self, VariableType *result); static void parser_queue_file(Parser *self, BufferView path, FileScopeReference **file_scope); int parser_thread_data_init(ParserThreadData *self) { @@ -133,42 +135,119 @@ static void parser_parse_struct_body_loop(Parser *self, Scope *scope) { } /* -TODO: Implement params and return types -FUNC_SIGNATURE = 'fn' ('(' ')')? +FUNC_PARAM = TOK_IDENTIFIER VAR_TYPE_DEF +*/ +static CHECK_RESULT bool parser_parse_function_parameter(Parser *self, FunctionParameter *result) { + bool match; + + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_IDENTIFIER, &match)); + if(!match) + return bool_false; + + result->name = self->tokenizer.value.identifier; + parser_parse_var_type_def(self, &result->type); + if(result->type.type == VARIABLE_TYPE_NONE) { + self->error = tokenizer_create_error(&self->tokenizer, + tokenizer_get_error_index(&self->tokenizer), + "Expected ':' after parameter name"); + throw(PARSER_UNEXPECTED_TOKEN); + } + + return bool_true; +} + +/* +FUNC_PARAMS = FUNC_PARAM (',' FUNC_PARAM)* +*/ +static void parser_parse_function_parameters(Parser *self, FunctionSignature *func_sig) { + for(;;) { + FunctionParameter func_param; + bool match; + int result; + + function_parameter_init(&func_param); + if(!parser_parse_function_parameter(self, &func_param)) { + self->error = tokenizer_create_error(&self->tokenizer, + tokenizer_get_error_index(&self->tokenizer), + "Expected closure parameter"); + throw(PARSER_UNEXPECTED_TOKEN); + } + + result = function_signature_add_parameter(func_sig, &func_param); + if(result == AST_ERR_DEF_DUP) { + self->error = tokenizer_create_error(&self->tokenizer, + tokenizer_get_code_reference_index(&self->tokenizer, func_param.name.data), + "Parameter with name %.*s was defined twice for the function", func_param.name.size, func_param.name.data); + self->error_context = ERROR_CONTEXT_NONE; + throw(result); + } else if(result != 0) { + throw(result); + } + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_COMMA, &match)); + if(!match) + return; + } +} + +/* +FUNC_RETURN_TYPES = VAR_TYPE (',' VAR_TYPE)* +*/ +static void parser_parse_function_return_types(Parser *self, FunctionSignature *func_sig) { + for(;;) { + VariableType var_type; + bool match; + + parser_parse_var_type(self, &var_type); + if(var_type.type == VARIABLE_TYPE_NONE) { + self->error = tokenizer_create_error(&self->tokenizer, + tokenizer_get_error_index(&self->tokenizer), + "Expected closure return type"); + throw(PARSER_UNEXPECTED_TOKEN); + } + throw_if_error(function_signature_add_return_type(func_sig, &var_type)); + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_COMMA, &match)); + if(!match) + return; + } +} + +/* +FUNC_SIGNATURE = 'fn' ('(' FUNC_PARAMS? ') FUNC_RETURN_TYPES? ')? */ static CHECK_RESULT FunctionSignature* parser_parse_function_signature(Parser *self) { FunctionSignature *signature; bool match; + throw_if_error(arena_allocator_alloc(self->allocator, sizeof(FunctionSignature), (void**)&signature)); + throw_if_error(function_signature_init(signature, self->allocator)); + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_FN, &match)); if(!match) return NULL; throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_OPEN_PAREN, &match)); if(match) { - /* TODO: Parse parameters */ - throw_if_error(tokenizer_accept(&self->tokenizer, TOK_CLOSING_PAREN)); - /* TODO: Parse return types */ + bool has_closing_paren; + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_CLOSING_PAREN, &has_closing_paren)); + if(!has_closing_paren) { + parser_parse_function_parameters(self, signature); + throw_if_error(tokenizer_accept(&self->tokenizer, TOK_CLOSING_PAREN)); + } + parser_parse_function_return_types(self, signature); } - throw_if_error(arena_allocator_alloc(self->allocator, sizeof(FunctionSignature), (void**)&signature)); - function_signature_init(signature); return signature; } /* -VAR_TYPE_DEF = ':' TOK_IDENTIFIER|FUNC_SIGNATURE +VAR_TYPE = TOK_IDENTIFIER|FUNC_SIGNATURE */ -static void parser_parse_var_type_def(Parser *self, VariableType *result) { +void parser_parse_var_type(Parser *self, VariableType *result) { bool match; result->type = VARIABLE_TYPE_NONE; result->value.variable = NULL; - throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_COLON, &match)); - if(!match) - return; - throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_IDENTIFIER, &match)); if(match) { result->type = VARIABLE_TYPE_VARIABLE; @@ -181,12 +260,28 @@ static void parser_parse_var_type_def(Parser *self, VariableType *result) { result->value.signature = parser_parse_function_signature(self); if(!result->value.signature) { self->error = tokenizer_create_error(&self->tokenizer, - tokenizer_get_code_reference_index(&self->tokenizer, self->tokenizer.value.identifier.data), - "Expected type or closure signature"); + tokenizer_get_code_reference_index(&self->tokenizer, self->tokenizer.value.identifier.data), + "Expected type or closure signature"); throw(PARSER_UNEXPECTED_TOKEN); } } +/* +VAR_TYPE_DEF = ':' VAR_TYPE +*/ +void parser_parse_var_type_def(Parser *self, VariableType *result) { + bool match; + + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_COLON, &match)); + if(!match) { + result->type = VARIABLE_TYPE_NONE; + result->value.variable = NULL; + return; + } + + parser_parse_var_type(self, result); +} + /* LHS_DECLARATION = 'extern'? 'pub'? 'const'|'var' TOK_IDENTIFIER VAR_TYPE_DEF? */ @@ -263,6 +358,8 @@ static CHECK_RESULT FunctionDecl* parser_parse_closure(Parser *self) { throw_if_error(arena_allocator_alloc(self->allocator, sizeof(FunctionDecl), (void**)&result)); throw_if_error(funcdecl_init(result, signature, self->current_scope, self->allocator)); + signature->func_decl = result; + result->body.function_signature = signature; self->current_scope = &result->body; prev_has_func_parent = self->has_func_parent; @@ -492,7 +589,7 @@ static CHECK_RESULT Ast* parser_parse_number(Parser *self) { /* RHS_S = STRING | NUMBER | FUNC_CALL_OR_VARIABLE */ -static Ast* parser_parse_rhs_single_expr(Parser *self) { +static CHECK_RESULT Ast* parser_parse_rhs_single_expr(Parser *self) { Ast *result; bool match; result = NULL; @@ -633,22 +730,69 @@ static CHECK_RESULT Ast* parser_parse_conditional(Parser *self) { return NULL; } +/* +LHS_RHS = CLOSURE|STRUCT|IMPORT +*/ +static Ast* parser_parse_lhs_rhs(Parser *self, LhsExpr *lhs_expr) { + Ast *result; + FunctionDecl *func_decl; + StructDecl *struct_decl; + Import *import; + + func_decl = parser_parse_closure(self); + if(func_decl) { + throw_if_error(ast_create(self->allocator, func_decl, AST_FUNCTION_DECL, &result)); + func_decl->lhs_expr = lhs_expr; + return result; + } + + struct_decl = parser_parse_struct_decl(self); + if(struct_decl) { + throw_if_error(ast_create(self->allocator, struct_decl, AST_STRUCT_DECL, &result)); + return result; + } + + import = parser_parse_import(self); + if(import) { + parser_queue_file(self, import->path, &import->file_scope); + throw_if_error(ast_create(self->allocator, import, AST_IMPORT, &result)); + parser_parse_body_semicolon(self, result); + return result; + } + + return NULL; +} + +/* +RETURN = 'return' RHS +*/ +static ReturnExpr* parser_parse_return(Parser *self) { + bool match; + ReturnExpr *result; + + throw_if_error(tokenizer_consume_if(&self->tokenizer, TOK_RETURN, &match)); + if(!match) + return NULL; + + throw_if_error(arena_allocator_alloc(self->allocator, sizeof(ReturnExpr), (void**)&result)); + return_expr_init(result, parser_parse_rhs(self)); + return result; +} + /* BODY = (LHS_DECLARATION ';') | - (LHS_DECLARATION '=' CLOSURE|STRUCT|IMPORT|(RHS BODY_SEMICOLON)) | + (LHS_DECLARATION '=' LHS_RHS|(RHS BODY_SEMICOLON)) | CONDITIONAL | (RHS ';'|('=' RHS BODY_SEMICOLON)) */ Ast* parser_parse_body(Parser *self) { Ast *result; LhsExpr *lhs_expr; + ReturnExpr *return_expr; Ast *rhs_expr; lhs_expr = parser_parse_declaration_lhs(self); if(lhs_expr) { - FunctionDecl *func_decl; - StructDecl *struct_decl; - Import *import; bool match; throw_if_error(ast_create(self->allocator, lhs_expr, AST_LHS, &result)); @@ -675,25 +819,9 @@ Ast* parser_parse_body(Parser *self) { throw_if_error(tokenizer_accept(&self->tokenizer, TOK_EQUALS)); - func_decl = parser_parse_closure(self); - if(func_decl) { - throw_if_error(ast_create(self->allocator, func_decl, AST_FUNCTION_DECL, &lhs_expr->rhs_expr)); - return result; - } - - struct_decl = parser_parse_struct_decl(self); - if(struct_decl) { - throw_if_error(ast_create(self->allocator, struct_decl, AST_STRUCT_DECL, &lhs_expr->rhs_expr)); - return result; - } - - import = parser_parse_import(self); - if(import) { - parser_queue_file(self, import->path, &import->file_scope); - throw_if_error(ast_create(self->allocator, import, AST_IMPORT, &lhs_expr->rhs_expr)); - parser_parse_body_semicolon(self, lhs_expr->rhs_expr); + lhs_expr->rhs_expr = parser_parse_lhs_rhs(self, lhs_expr); + if(lhs_expr->rhs_expr) return result; - } } else { self->error_context = ERROR_CONTEXT_NO_LHS; result = parser_parse_conditional(self); @@ -701,6 +829,15 @@ Ast* parser_parse_body(Parser *self) { return result; } + if(self->has_func_parent) { + return_expr = parser_parse_return(self); + if(return_expr) { + throw_if_error(tokenizer_accept(&self->tokenizer, TOK_SEMICOLON)); + throw_if_error(ast_create(self->allocator, return_expr, AST_RETURN, &result)); + return result; + } + } + self->error_context = ERROR_CONTEXT_RHS_STANDALONE; rhs_expr = parser_parse_rhs(self); self->error_context = ERROR_CONTEXT_NONE; @@ -731,22 +868,26 @@ Ast* parser_parse_body(Parser *self) { } /* -STRUCT_BODY = TOK_IDENTIFIER ':' TOK_IDENTIFIER ';' +STRUCT_BODY = TOK_IDENTIFIER VAR_TYPE_DEF ';' */ Ast* parser_parse_struct_body(Parser *self) { Ast *result; BufferView var_name; - BufferView type_name; + VariableType var_type; StructField *struct_field; throw_if_error(tokenizer_accept(&self->tokenizer, TOK_IDENTIFIER)); var_name = self->tokenizer.value.identifier; - throw_if_error(tokenizer_accept(&self->tokenizer, TOK_COLON)); - throw_if_error(tokenizer_accept(&self->tokenizer, TOK_IDENTIFIER)); - type_name = self->tokenizer.value.identifier; + parser_parse_var_type_def(self, &var_type); + if(var_type.type == VARIABLE_TYPE_NONE) { + self->error = tokenizer_create_error(&self->tokenizer, + tokenizer_get_error_index(&self->tokenizer), + "Expected ':' after struct field name"); + throw(PARSER_UNEXPECTED_TOKEN); + } throw_if_error(tokenizer_accept(&self->tokenizer, TOK_SEMICOLON)); throw_if_error(arena_allocator_alloc(self->allocator, sizeof(LhsExpr), (void**)&struct_field)); - structfield_init(struct_field, var_name, type_name); + structfield_init(struct_field, var_name, &var_type); throw_if_error(ast_create(self->allocator, struct_field, AST_STRUCT_FIELD, &result)); return result; } @@ -767,7 +908,8 @@ int parser_parse_buffer(Parser *self, BufferView code_buffer, BufferView buffer_ tokenizer_print_error_object(&self->tokenizer, &self->error); break; case ERROR_CONTEXT_RHS_STANDALONE: - tokenizer_print_error(&self->tokenizer, self->tokenizer.prev_index, "Expected string, variable, closure, struct, function call or import"); + /* TODO: Only show "return" in error message if @self->has_func_parent */ + tokenizer_print_error(&self->tokenizer, self->tokenizer.prev_index, "Expected string, variable, closure, struct, function call, import or return"); break; case ERROR_CONTEXT_NO_LHS: tokenizer_print_error(&self->tokenizer, self->tokenizer.prev_index, "Expected variable declaration, string, variable or function call"); diff --git a/src/program.c b/src/program.c index 54709b1..a73cf72 100644 --- a/src/program.c +++ b/src/program.c @@ -25,6 +25,10 @@ typedef struct { NumberUnion value; } Number; +/*doc(Bytecode) +The layout of the full bytecode is: Header (Intermediates Strings Functions Instructions)* +*/ + static CHECK_RESULT int amal_program_append_header(amal_program *self) { /*doc(Bytecode header) # Header layout @@ -57,8 +61,9 @@ int amal_program_init(amal_program *self) { self->intermediates_start = NULL; self->strings_start = NULL; self->read_index = 0; - self->num_strings = 0; self->num_intermediates = 0; + self->num_strings = 0; + self->num_functions = 0; cleanup_if_error(amal_program_append_header(self)); return 0; @@ -189,6 +194,14 @@ static CHECK_RESULT int amal_program_read_strings(amal_program *self) { return AMAL_PROGRAM_OK; } +static CHECK_RESULT int amal_program_read_functions(amal_program *self) { + if(bytes_left_to_read(self) < sizeof(u16)) + return AMAL_PROGRAM_INVALID_FUNCTIONS; + am_memcpy(&self->num_functions, &self->data.data[self->read_index], sizeof(u16)); + self->read_index += sizeof(u16); + return AMAL_PROGRAM_OK; +} + static CHECK_RESULT int amal_program_get_intermediate_by_index(amal_program *self, u16 index, Number *result) { if(index >= self->num_intermediates) return AMAL_PROGRAM_INSTRUCTION_INVALID_INTERMEDIATE_INDEX; @@ -216,10 +229,12 @@ static CHECK_RESULT int amal_program_read_instructions(amal_program *self, amal_ u32 instructions_size; u32 read_start; u32 read_end; + u16 func_counter; bool inside_func; inside_func = bool_false; (void)inside_func; + func_counter = 0; if(bytes_left_to_read(self) < sizeof(instructions_size)) return AMAL_PROGRAM_INVALID_INSTRUCTIONS_SIZE; @@ -235,12 +250,15 @@ static CHECK_RESULT int amal_program_read_instructions(amal_program *self, amal_ on the type of the register. */ + return_if_error(amal_executor_instructions_start(executor, self->num_functions)); + read_start = self->read_index; read_end = read_start + instructions_size; while(self->read_index < read_end) { AmalOpcode opcode; opcode = self->data.data[self->read_index]; self->read_index += sizeof(AmalOpcodeType); + /* TODO: Check instruction length and check if that amount of bytes can be read */ switch(opcode) { case AMAL_OP_NOP: { return_if_error(amal_exec_nop(executor)); @@ -334,12 +352,17 @@ static CHECK_RESULT int amal_program_read_instructions(amal_program *self, amal_ self->read_index += 2; break; } - case AMAL_OP_CALL: - /*assert(bool_false && "TODO: Implement CALL");*/ + case AMAL_OP_CALL: { + u16 func_index; + u8 num_args; + am_memcpy(&func_index, self->data.data + self->read_index, sizeof(func_index)); + am_memcpy(&num_args, self->data.data + self->read_index + sizeof(func_index), sizeof(num_args)); + return_if_error(amal_exec_call(executor, func_index, num_args)); self->read_index += 3; break; + } case AMAL_OP_CALLR: - /*assert(bool_false && "TODO: Implement CALLR");*/ + assert(bool_false && "TODO: Implement CALLR"); self->read_index += 2; break; case AMAL_OP_CMP: { @@ -361,13 +384,19 @@ static CHECK_RESULT int amal_program_read_instructions(amal_program *self, amal_ self->read_index += 2; break; } - case AMAL_OP_RET: - return_if_error(amal_exec_ret(executor)); + case AMAL_OP_RET: { + const u8 reg = self->data.data[self->read_index]; + return_if_error(amal_exec_ret(executor, reg)); + self->read_index += 1; break; + } case AMAL_OP_FUNC_START: { u16 func_num_registers; assert(!inside_func); inside_func = bool_true; + assert(func_counter < self->num_functions); + ++func_counter; + am_memcpy(&func_num_registers, &self->data.data[self->read_index], sizeof(func_num_registers)); return_if_error(amal_exec_func_start(executor, func_num_registers)); self->read_index += 2; @@ -383,6 +412,8 @@ static CHECK_RESULT int amal_program_read_instructions(amal_program *self, amal_ } } + assert(self->read_index == read_end); + return_if_error(amal_executor_instructions_end(executor)); return AMAL_PROGRAM_OK; } @@ -396,6 +427,7 @@ int amal_program_run(amal_program *self) { while(bytes_left_to_read(self) > 0) { cleanup_if_error(amal_program_read_intermediates(self)); cleanup_if_error(amal_program_read_strings(self)); + cleanup_if_error(amal_program_read_functions(self)); cleanup_if_error(amal_program_read_instructions(self, executor)); } result = amal_executor_run(executor); diff --git a/src/ssa/ssa.c b/src/ssa/ssa.c index f3679aa..0aabc15 100644 --- a/src/ssa/ssa.c +++ b/src/ssa/ssa.c @@ -243,7 +243,7 @@ static CHECK_RESULT int ssa_ins_push(Ssa *self, SsaRegister reg) { return 0; } -static CHECK_RESULT int ssa_ins_call(Ssa *self, FunctionDecl *func_decl, SsaRegister *result) { +static CHECK_RESULT int ssa_ins_call(Ssa *self, FunctionDecl *func_decl, u8 num_args, SsaRegister *result) { usize index; index = self->instructions.size; @@ -251,12 +251,13 @@ static CHECK_RESULT int ssa_ins_call(Ssa *self, FunctionDecl *func_decl, SsaRegi if(self->reg_counter + 1 < self->reg_counter) return -1; - return_if_error(buffer_append_empty(&self->instructions, sizeof(u8) + sizeof(SsaRegister) + sizeof(func_decl))); + return_if_error(buffer_append_empty(&self->instructions, sizeof(u8) + sizeof(u8) + sizeof(SsaRegister) + sizeof(func_decl))); *result = self->reg_counter++; self->instructions.data[index + 0] = SSA_CALL; - am_memcpy(self->instructions.data + index + 1, result, sizeof(SsaRegister)); - am_memcpy(self->instructions.data + index + 1 + sizeof(SsaRegister), &func_decl, sizeof(func_decl)); - amal_log_debug("r%u = CALL %p", *result, func_decl); + self->instructions.data[index + 1] = num_args; + am_memcpy(self->instructions.data + index + 2, result, sizeof(SsaRegister)); + am_memcpy(self->instructions.data + index + 2 + sizeof(SsaRegister), &func_decl, sizeof(func_decl)); + amal_log_debug("r%u = CALL %d, %p", *result, num_args, func_decl); return 0; } @@ -286,6 +287,16 @@ static CHECK_RESULT int ssa_ins_jump(Ssa *self, JumpOffset jump_offset) { return 0; } +static CHECK_RESULT int ssa_ins_return(Ssa *self, SsaRegister reg) { + usize index; + index = self->instructions.size; + + return_if_error(buffer_append_empty(&self->instructions, sizeof(u8) + sizeof(SsaRegister))); + self->instructions.data[index + 0] = SSA_RET; + am_memcpy(self->instructions.data + index + 1, ®, sizeof(SsaRegister)); + return 0; +} + static usize ssa_ins_get_index(Ssa *self) { return self->instructions.size; } @@ -311,6 +322,28 @@ static CHECK_RESULT int ssa_ins_jump_set_target(Ssa *self, usize jump_ins_index) } static CHECK_RESULT SsaRegister ast_generate_ssa(Ast *self, SsaCompilerContext *context); +static CHECK_RESULT SsaRegister scope_named_object_generate_ssa(ScopeNamedObject *self, SsaCompilerContext *context); + +static bool ast_resolved_type_is_decl(AstResolvedType *self) { + /* TODO: Add more types as they are introduced */ + LhsExpr *lhs_expr; + if(self->type != RESOLVED_TYPE_LHS_EXPR) + return bool_false; + + lhs_expr = self->value.lhs_expr; + switch(lhs_expr->type.type) { + case VARIABLE_TYPE_NONE: + break; + case VARIABLE_TYPE_VARIABLE: + return bool_false; + case VARIABLE_TYPE_SIGNATURE: + /* TODO: This should return bool_false when it's possible to use signature in expressions */ + return bool_true; + } + + assert(lhs_expr->rhs_expr); + return lhs_expr->rhs_expr->type == AST_FUNCTION_DECL || lhs_expr->rhs_expr->type == AST_STRUCT_DECL; +} static CHECK_RESULT SsaRegister number_generate_ssa(Number *self, SsaCompilerContext *context) { SsaRegister reg; @@ -332,27 +365,23 @@ static CHECK_RESULT SsaRegister lhsexpr_extern_generate_ssa(LhsExpr *self, SsaCo return 0; } -static CHECK_RESULT SsaRegister lhsexpr_generate_ssa(Ast *self, SsaCompilerContext *context) { - LhsExpr *lhs_expr; +static CHECK_RESULT SsaRegister lhsexpr_generate_ssa(LhsExpr *self, AstResolveData *resolve_data, SsaCompilerContext *context) { SsaRegister reg; - assert(self->type == AST_LHS); - lhs_expr = self->value.lhs_expr; - - if(LHS_EXPR_IS_EXTERN(lhs_expr)) - return lhsexpr_extern_generate_ssa(lhs_expr, context); + if(LHS_EXPR_IS_EXTERN(self)) + return lhsexpr_extern_generate_ssa(self, context); - if(lhs_expr->rhs_expr) { - Ast *rhs_expr = lhs_expr->rhs_expr; + if(self->rhs_expr) { + Ast *rhs_expr = self->rhs_expr; SsaRegister rhs_reg; rhs_reg = ast_generate_ssa(rhs_expr, context); /* - Declarations (struct and function declaration) resolves to itself and in that case this expression - is just a compile-time name for the declaration. - Import expression also has no meaning in SSA until it's used. - TODO: Shouldn't lhsexpr that have struct/function declaration as rhs be different ast expression types? + Declarations (struct and function declaration) resolves to itself and in that case this expression + is just a compile-time name for the declaration. + Import expression also has no meaning in SSA until it's used. + TODO: Shouldn't lhsexpr that have struct/function declaration as rhs be different ast expression types? */ - if(self->resolve_data.type == lhs_expr || rhs_expr->type == AST_IMPORT) { + if(ast_resolved_type_is_decl(&resolve_data->type) || rhs_expr->type == AST_IMPORT) { /*assert(bool_false);*/ return 0; } @@ -364,32 +393,55 @@ static CHECK_RESULT SsaRegister lhsexpr_generate_ssa(Ast *self, SsaCompilerConte throw_if_error(ssa_ins_assign_reg(context->ssa, reg, rhs_reg)); } else { /* TODO: Do not assign if we dont want default value */ - SsaNumber number; - if(self->resolve_data.type == (LhsExpr*)context->compiler->default_types.i64) - number = create_ssa_integer(0); - else if(self->resolve_data.type == (LhsExpr*)context->compiler->default_types.f64) - number = create_ssa_float(0.0); - else - assert(bool_false && "TODO: assign default value to reg depending on LhsExpr type"); - throw_if_error(ssa_get_unique_reg(context->ssa, ®)); - throw_if_error(ssa_ins_assign_inter(context->ssa, reg, number)); + if(resolve_data->type.type == RESOLVED_TYPE_LHS_EXPR) { + SsaNumber number; + if(resolve_data->type.value.lhs_expr == (LhsExpr*)context->compiler->default_types.i64) + number = create_ssa_integer(0); + else if(resolve_data->type.value.lhs_expr == (LhsExpr*)context->compiler->default_types.f64) + number = create_ssa_float(0.0); + else + assert(bool_false && "TODO: assign default value to reg depending on LhsExpr type"); + throw_if_error(ssa_get_unique_reg(context->ssa, ®)); + throw_if_error(ssa_ins_assign_inter(context->ssa, reg, number)); + } else if(resolve_data->type.type == RESOLVED_TYPE_FUNC_SIG) { + assert(bool_false && "TODO: Implement this when variable can be null. Then the function pointer should be null"); + } else { + assert(bool_false); + } } return reg; } -static CHECK_RESULT SsaRegister assignmentexpr_generate_ssa(Ast *ast, SsaCompilerContext *context) { - AssignmentExpr *self; +static CHECK_RESULT SsaRegister assignmentexpr_generate_ssa(AssignmentExpr *self, SsaCompilerContext *context) { SsaRegister lhs_reg, rhs_reg; - - assert(ast->type == AST_ASSIGN); - self = ast->value.assign_expr; - lhs_reg = ast_generate_ssa(self->lhs_expr, context); rhs_reg = ast_generate_ssa(self->rhs_expr, context); throw_if_error(ssa_ins_assign_reg(context->ssa, lhs_reg, rhs_reg)); return lhs_reg; } +static CHECK_RESULT SsaRegister function_parameter_generate_ssa(FunctionParameter *self, SsaCompilerContext *context) { + SsaRegister reg; + if(self->resolve_data.status == AST_SSA_RESOLVED) + return self->resolve_data.ssa_reg; + + throw_if_error(ssa_get_unique_reg(context->ssa, ®)); + self->resolve_data.status = AST_SSA_RESOLVED; + self->resolve_data.ssa_reg = reg; + return reg; +} + +static CHECK_RESULT void function_signature_generate_params_ssa(FunctionSignature *self, SsaCompilerContext *context) { + FunctionParameter *param, *param_end; + param = buffer_begin(&self->parameters); + param_end = buffer_end(&self->parameters); + for(; param != param_end; ++param) { + SsaRegister reg; + reg = function_parameter_generate_ssa(param, context); + (void)reg; + } +} + /* TODO: Each function declaration should be in separate SSA instances so ast can be converted into ssa in any order. @@ -402,49 +454,54 @@ static CHECK_RESULT SsaRegister funcdecl_generate_ssa(FunctionDecl *self, SsaCom */ SsaRegister prev_reg_counter; usize func_metadata_index; + int num_params; prev_reg_counter = context->ssa->reg_counter; context->ssa->reg_counter = 0; + /* + Parameters need to have generated ssa so the first ssa registers belong to the function. + This way we can know if a register access is for a parameter or not by checking the number + */ + function_signature_generate_params_ssa(self->signature, context); + num_params = buffer_get_size(&self->signature->parameters, FunctionParameter); + amal_log_debug("SSA funcdecl %p", self); throw_if_error(ssa_ins_func_start(context->ssa, &self->ssa_func_index, &func_metadata_index)); scope_generate_ssa(&self->body, context); throw_if_error(ssa_ins_func_end(context->ssa)); /* Add the number of registers used to the function metadata (FUNC_START) */ + context->ssa->reg_counter -= num_params; am_memcpy(&context->ssa->instructions.data[func_metadata_index], &context->ssa->reg_counter, sizeof(u16)); context->ssa->reg_counter = prev_reg_counter; return 0; } -static CHECK_RESULT SsaRegister funccall_generate_ssa(Ast *self, SsaCompilerContext *context) { - /* TODO: Implement */ - FunctionCall *func_call; +static CHECK_RESULT SsaRegister funccall_generate_ssa(FunctionCall *self, AstResolveData *resolve_data, SsaCompilerContext *context) { Ast **ast; Ast **ast_end; SsaRegister reg; + FunctionDecl *func_decl; - assert(self->type == AST_FUNCTION_CALL); - func_call = self->value.func_call; - ast = buffer_begin(&func_call->args); - ast_end = buffer_end(&func_call->args); + ast = buffer_begin(&self->args); + ast_end = buffer_end(&self->args); for(; ast != ast_end; ++ast) { SsaRegister arg_reg; arg_reg = ast_generate_ssa(*ast, context); throw_if_error(ssa_ins_push(context->ssa, arg_reg)); } - assert((self->resolve_data.type->rhs_expr && self->resolve_data.type->rhs_expr->type == AST_FUNCTION_DECL) || - self->resolve_data.type->type.type == VARIABLE_TYPE_SIGNATURE); - if(LHS_EXPR_IS_EXTERN(self->resolve_data.type)) { - amal_log_error("TODO: Implement extern function call (extern function %.*s was called)", func_call->func.name.size, func_call->func.name.data); + func_decl = resolve_data->type.value.func_sig->func_decl; + assert(resolve_data->type.type == RESOLVED_TYPE_FUNC_SIG); + assert(func_decl && "TODO: Implement function call for anonymous closures"); + if(func_decl && func_decl->lhs_expr && LHS_EXPR_IS_EXTERN(func_decl->lhs_expr)) { + amal_log_error("TODO: Implement extern function call (extern function %.*s was called)", self->func.name.size, self->func.name.data); reg = 0; assert(bool_false && "TODO: Implement extern function call!"); } else { - FunctionDecl *func_to_call; /* rhs wont be null here because only extern variable can't have rhs */ - func_to_call = self->resolve_data.type->rhs_expr->value.func_decl; - amal_log_debug("SSA funccall %.*s, func index ptr: %p", func_call->func.name.size, func_call->func.name.data, func_to_call); - throw_if_error(ssa_ins_call(context->ssa, func_to_call, ®)); + amal_log_debug("SSA funccall %.*s, func index ptr: %p", self->func.name.size, self->func.name.data, func_decl); + throw_if_error(ssa_ins_call(context->ssa, func_decl, buffer_get_size(&self->args, Ast*), ®)); } return reg; @@ -475,8 +532,8 @@ static CHECK_RESULT SsaRegister string_generate_ssa(String *self, SsaCompilerCon static CHECK_RESULT SsaRegister variable_generate_ssa(Variable *self, SsaCompilerContext *context) { /* TODO: If resolved_var refers to a variable in another file, use a cross file reference that requires no locking (not yet implemented) */ /* This is not thread-safe:*/ - assert(self->resolved_var); - return ast_generate_ssa(self->resolved_var, context); + assert(self->resolved_var.type != NAMED_OBJECT_NONE); + return scope_named_object_generate_ssa(&self->resolved_var, context); } static SsaInstruction binop_type_to_ssa_type(BinopType binop_type, amal_default_type *type) { @@ -511,7 +568,8 @@ static CHECK_RESULT SsaRegister binop_generate_ssa(Binop *self, SsaCompilerConte } else { const SsaRegister lhs_reg = ast_generate_ssa(self->lhs, context); const SsaRegister rhs_reg = ast_generate_ssa(self->rhs, context); - throw_if_error(ssa_ins_binop(context->ssa, binop_type_to_ssa_type(self->type, (amal_default_type*)self->lhs->resolve_data.type), lhs_reg, rhs_reg, ®)); + assert(self->lhs->resolve_data.type.type == RESOLVED_TYPE_LHS_EXPR && "TODO: Implement binop_generate_ssa for function signature"); + throw_if_error(ssa_ins_binop(context->ssa, binop_type_to_ssa_type(self->type, (amal_default_type*)self->lhs->resolve_data.type.value.lhs_expr), lhs_reg, rhs_reg, ®)); } return reg; } @@ -567,63 +625,88 @@ static void while_statement_generate_ssa(WhileStatement *while_stmt, SsaCompiler throw_if_error(ssa_ins_jump_set_target(context->ssa, jump_condition_ins_index)); } -static CHECK_RESULT SsaRegister ast_generate_ssa(Ast *self, SsaCompilerContext *context) { - assert(self); - #ifdef DEBUG - if(self->resolve_data.status != AST_RESOLVED && self->resolve_data.status != AST_SSA_RESOLVED) { - amal_log_error("Ast type not resolved: %d", self->type); - assert(bool_false); - } - #endif +static void return_expr_generate_ssa(ReturnExpr *self, SsaCompilerContext *context) { + const SsaRegister reg = ast_generate_ssa(self->rhs_expr, context); + throw_if_error(ssa_ins_return(context->ssa, reg)); +} - if(self->resolve_data.status == AST_SSA_RESOLVED) - return self->ssa_reg; +static CHECK_RESULT SsaRegister ast_generate_ssa_resolve_data(void *ast_data, AstType ast_type, AstResolveData *resolve_data, SsaCompilerContext *context) { + if(resolve_data->status == AST_SSA_RESOLVED) + return resolve_data->ssa_reg; - switch(self->type) { + switch(ast_type) { case AST_NUMBER: - self->ssa_reg = number_generate_ssa(self->value.number, context); + resolve_data->ssa_reg = number_generate_ssa(ast_data, context); break; case AST_FUNCTION_DECL: - self->ssa_reg = funcdecl_generate_ssa(self->value.func_decl, context); + resolve_data->ssa_reg = funcdecl_generate_ssa(ast_data, context); break; case AST_FUNCTION_CALL: - self->ssa_reg = funccall_generate_ssa(self, context); + resolve_data->ssa_reg = funccall_generate_ssa(ast_data, resolve_data, context); break; case AST_STRUCT_DECL: - self->ssa_reg = structdecl_generate_ssa(self->value.struct_decl, context); + resolve_data->ssa_reg = structdecl_generate_ssa(ast_data, context); break; case AST_STRUCT_FIELD: - self->ssa_reg = structfield_generate_ssa(self->value.struct_field, context); + resolve_data->ssa_reg = structfield_generate_ssa(ast_data, context); break; case AST_LHS: - self->ssa_reg = lhsexpr_generate_ssa(self, context); + resolve_data->ssa_reg = lhsexpr_generate_ssa(ast_data, resolve_data, context); break; case AST_ASSIGN: - self->ssa_reg = assignmentexpr_generate_ssa(self, context); + resolve_data->ssa_reg = assignmentexpr_generate_ssa(ast_data, context); break; case AST_IMPORT: /* TODO: Implement cross file references */ - self->ssa_reg = 0; + resolve_data->ssa_reg = 0; break; case AST_STRING: - self->ssa_reg = string_generate_ssa(self->value.string, context); + resolve_data->ssa_reg = string_generate_ssa(ast_data, context); break; case AST_VARIABLE: - self->ssa_reg = variable_generate_ssa(self->value.variable, context); + resolve_data->ssa_reg = variable_generate_ssa(ast_data, context); break; case AST_BINOP: - self->ssa_reg = binop_generate_ssa(self->value.binop, context); + resolve_data->ssa_reg = binop_generate_ssa(ast_data, context); break; case AST_IF_STATEMENT: - if_statement_generate_ssa(self->value.if_stmt, context); + if_statement_generate_ssa(ast_data, context); break; case AST_WHILE_STATEMENT: - while_statement_generate_ssa(self->value.while_stmt, context); + while_statement_generate_ssa(ast_data, context); + break; + case AST_RETURN: + return_expr_generate_ssa(ast_data, context); + resolve_data->ssa_reg = 0; break; } - self->resolve_data.status = AST_SSA_RESOLVED; - return self->ssa_reg; + resolve_data->status = AST_SSA_RESOLVED; + return resolve_data->ssa_reg; +} + +CHECK_RESULT SsaRegister ast_generate_ssa(Ast *self, SsaCompilerContext *context) { + assert(self); + #ifdef DEBUG + if(self->resolve_data.status != AST_RESOLVED && self->resolve_data.status != AST_SSA_RESOLVED) { + amal_log_error("Ast type not resolved: %d", self->type); + assert(bool_false); + } + #endif + return ast_generate_ssa_resolve_data(self->value.data, self->type, &self->resolve_data, context); +} + +CHECK_RESULT SsaRegister scope_named_object_generate_ssa(ScopeNamedObject *self, SsaCompilerContext *context) { + switch(self->type) { + case NAMED_OBJECT_NONE: + assert(bool_false); + return 0; + case NAMED_OBJECT_LHS_EXPR: + return ast_generate_ssa_resolve_data(self->value.lhs_expr, AST_LHS, self->resolve_data, context); + case NAMED_OBJECT_FUNC_PARAM: + return function_parameter_generate_ssa(self->value.func_param, context); + } + return 0; } void scope_generate_ssa(Scope *self, SsaCompilerContext *context) { diff --git a/src/std/arena_allocator.c b/src/std/arena_allocator.c index 14787f1..73111dd 100644 --- a/src/std/arena_allocator.c +++ b/src/std/arena_allocator.c @@ -67,8 +67,7 @@ static CHECK_RESULT int arena_allocator_ensure_capacity_for(ArenaAllocator *self } static void* align_ptr_ceil(void *ptr, uintptr_t alignment) { - uintptr_t ptrval; - ptrval = (uintptr_t)ptr; + const uintptr_t ptrval = (uintptr_t)ptr; return (void*)((ptrval + alignment + 1) & ~(alignment - 1)); } @@ -76,7 +75,7 @@ static usize align_ptr_ceil_offset(void *ptr, uintptr_t alignment) { return (uintptr_t)align_ptr_ceil(ptr, alignment) - (uintptr_t)ptr; } -#define SCOPED_ALLOC_ALIGNMENT 8 +#define ALLOC_ALIGNMENT 8 int arena_allocator_alloc(ArenaAllocator *self, usize size, void **mem) { ArenaAllocatorNode *current; @@ -89,14 +88,14 @@ int arena_allocator_alloc(ArenaAllocator *self, usize size, void **mem) { return -1; } - alloc_size = size + align_ptr_ceil_offset(self->current->data + self->current->size, SCOPED_ALLOC_ALIGNMENT); + alloc_size = size + align_ptr_ceil_offset(self->current->data + self->current->size, ALLOC_ALIGNMENT); return_if_error(arena_allocator_ensure_capacity_for(self, alloc_size)); /* Reallocated (new node created) */ if(self->current != current) { *mem = self->current->data; self->current->size += size; } else { - *mem = align_ptr_ceil(self->current->data + self->current->size, SCOPED_ALLOC_ALIGNMENT); + *mem = align_ptr_ceil(self->current->data + self->current->size, ALLOC_ALIGNMENT); self->current->size += alloc_size; } return 0; diff --git a/src/std/buffer.c b/src/std/buffer.c index 0e4ca89..93e8558 100644 --- a/src/std/buffer.c +++ b/src/std/buffer.c @@ -48,6 +48,7 @@ static CHECK_RESULT int buffer_ensure_capacity(Buffer *self, usize new_capacity) self->data = new_mem; self->capacity = capacity; + /* Update list of buffers in the allocator with the new address of the buffer data */ if(self->allocator) am_memcpy(self->allocator->mems.data + sizeof(void*) * self->allocator_index, &self->data, sizeof(void*)); return BUFFER_OK; diff --git a/src/std/buffer_view.c b/src/std/buffer_view.c index 8ddfab9..f2d79c0 100644 --- a/src/std/buffer_view.c +++ b/src/std/buffer_view.c @@ -1,4 +1,5 @@ #include "../../include/std/buffer_view.h" +#include "../../include/std/mem.h" BufferView create_buffer_view_null() { BufferView buffer_view; @@ -13,3 +14,7 @@ BufferView create_buffer_view(const char *data, usize size) { buffer_view.size = size; return buffer_view; } + +bool buffer_view_equals(const BufferView *self, const BufferView *other) { + return self->size == other->size && am_memeql(self->data, other->data, self->size); +} diff --git a/src/tokenizer.c b/src/tokenizer.c index 7620fc0..1586691 100644 --- a/src/tokenizer.c +++ b/src/tokenizer.c @@ -216,6 +216,9 @@ static CHECK_RESULT int __tokenizer_next(Tokenizer *self, Token *token) { } else if(am_memeql(self->value.identifier.data, "extern", 6)) { *token = TOK_EXTERN; return TOKENIZER_OK; + } else if(am_memeql(self->value.identifier.data, "return", 6)) { + *token = TOK_RETURN; + return TOKENIZER_OK; } break; } @@ -346,6 +349,8 @@ static CHECK_RESULT int __tokenizer_next(Tokenizer *self, Token *token) { } self->index += 6; + /* TODO: This should be moved to the parser */ + result = tokenizer_next(self, &last_token); if(result != 0 || last_token != TOK_OPEN_PAREN) { err_msg = "Expected '(' after @import"; @@ -490,6 +495,9 @@ static BufferView tokenizer_expected_token_as_string(Token token) { case TOK_EXTERN: str = "extern"; break; + case TOK_RETURN: + str = "return"; + break; } return create_buffer_view(str, strlen(str)); } diff --git a/tests/bytecode.amal b/tests/bytecode.amal index 799733b..dd9bd3d 100644 --- a/tests/bytecode.amal +++ b/tests/bytecode.amal @@ -7,8 +7,14 @@ const main = fn { const value3 = 2 + 5 - 1 * 10 / 2; const str_value = "hello, world"; //printf(); + print(); + const result = print_num(value3); } const print = fn { +} + +const print_num = fn(num: i32) i32 { + return num; } \ No newline at end of file diff --git a/tests/errors/closure_duplicate_param_name.amal b/tests/errors/closure_duplicate_param_name.amal new file mode 100644 index 0000000..4e74eec --- /dev/null +++ b/tests/errors/closure_duplicate_param_name.amal @@ -0,0 +1 @@ +const func = fn(value: i32, value: i32) {} \ No newline at end of file diff --git a/tests/main.c b/tests/main.c index 63f72d5..3336c46 100644 --- a/tests/main.c +++ b/tests/main.c @@ -251,6 +251,7 @@ static void run_all_tests() { " extern const a;\n" " ^\n"); test_load_error("tests/errors/no_main_func.amal", NULL); + test_load_error("tests/errors/closure_duplicate_param_name.amal", "TODO: Add expected error here"); } /* TODO: Restrict variables in global scope to const */ -- cgit v1.2.3