From d9f652919961a2947452ad3c4af4659f3d2fb330 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 24 Aug 2019 23:31:14 +0200 Subject: Add if/else/elseif/while, including the final assembly --- README.md | 4 +- doc/Documentation.md | 6 +- executor/executor.h | 5 +- executor/x86_64/asm.c | 112 +++++++++++++++++++++++++++++- executor/x86_64/asm.h | 24 +++++++ executor/x86_64/executor.c | 151 +++++++++++++++++++++++++++++++---------- include/bytecode/bytecode.h | 15 ++-- include/ssa/ssa.h | 9 ++- src/bytecode/bytecode.c | 20 ++++-- src/parser.c | 22 ++++-- src/program.c | 20 ++++-- src/ssa/ssa.c | 162 ++++++++++++++++++++++++++++++-------------- tests/bytecode.amal | 5 +- 13 files changed, 429 insertions(+), 126 deletions(-) diff --git a/README.md b/README.md index 6dc4086..2760c93 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,9 @@ 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.\ Show compile error if variables are assigned to but not used.\ Push arguments in reverse order (right-to-left, cdecl) (in program.c, since on windows we will need to support stdcall which is left-to-right).\ -After tokenizing files, unload file data to disk. Otherwise large programs (as large as chromium) will need gigabytes of ram to compile. +After tokenizing files, unload file data to disk if possible.\ +Parallelize program decoding, if there is an advantage to doing it.\ +Logical AND binop should skip expressions the moment the result becomes false. # 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/doc/Documentation.md b/doc/Documentation.md index 8b2133e..adb0bef 100644 --- a/doc/Documentation.md +++ b/doc/Documentation.md @@ -8,18 +8,18 @@ Instructions can be in 7 different formats: 4. 3 bytes:\ 4.1 Opcode(u8) + intermediate(u16)\ 4.2 Opcode(u8) + data(u16)\ -4.3 Opcode(u8) + offset(i16)\ +4.3 Opcode(u8) + label(i16)\ 4.4 Opcode(u8) + register(i8) + num_args(u8) 5. 4 bytes: Opcode(u8) + register(i8) + register(i8) + register(i8) 6. 4 bytes:\ -6.1 Opcode(u8) + register(i8) + offset(i16)\ +6.1 Opcode(u8) + register(i8) + label(i16)\ 6.2 Opcode(u8) + register(i8) + intermediate(u16)\ 6.3 Opcode(u8) + register(i8) + data(u16)\ 6.4 Opcode(u8) + flags(u8) + num_local_var_reg(u16) 7. 5 bytes: Opcode(u8) + index(u16) + num_args(u8) + register(i8) ## Registers Registers have a range of 128. Local variables start from register 0 and increment while parameters start from -1 -and decrement. +and decrement. Registers have the scope of functions and reset after instructions reach a new function (AMAL_OP_FUNC_START). # Compiler flow (Tokenize&parse -> Resolve AST -> Generate SSA -> Generate bytecode) -> Generate program\ diff --git a/executor/executor.h b/executor/executor.h index df6c0d8..a0be5c9 100644 --- a/executor/executor.h +++ b/executor/executor.h @@ -43,10 +43,11 @@ CHECK_RESULT int amal_exec_call(amal_executor *self, u16 func_index, u8 num_args /*CHECK_RESULT int amal_exec_callr(i8 dst_reg, BufferView data);*/ CHECK_RESULT int amal_exec_calle(amal_executor *self, void *func, u8 num_args, i8 dst_reg); CHECK_RESULT int amal_exec_cmp(amal_executor *self, i8 dst_reg, i8 src_reg1, i8 src_reg2); -CHECK_RESULT int amal_exec_jz(amal_executor *self, i8 dst_reg, i16 offset); -CHECK_RESULT int amal_exec_jmp(amal_executor *self, i16 offset); +CHECK_RESULT int amal_exec_jz(amal_executor *self, i8 reg, u16 target_label); +CHECK_RESULT int amal_exec_jmp(amal_executor *self, u16 target_label); CHECK_RESULT int amal_exec_ret(amal_executor *self, i8 reg); CHECK_RESULT int amal_exec_func_start(amal_executor *self, u16 num_regs); CHECK_RESULT int amal_exec_func_end(amal_executor *self); +CHECK_RESULT int amal_exec_label(amal_executor *self); #endif diff --git a/executor/x86_64/asm.c b/executor/x86_64/asm.c index f2bb801..a6bf274 100644 --- a/executor/x86_64/asm.c +++ b/executor/x86_64/asm.c @@ -480,6 +480,112 @@ int asm_call_rel32(Asm *self, i32 relative) { } void asm_override_call_rel32(Asm *self, u32 asm_index, i32 new_relative) { + assert(*(u8*)(self->code + asm_index) == 0xE8); + 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)); +} + +int asm_cmp_rm(Asm *self, Reg64 reg1, AsmPtr *reg2) { + ins_start(self); + /* 8 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, 8)); + *self->code_it++ = REX_W; + *self->code_it++ = 0x3B; + asm_rm(self, reg2, reg1); + ins_end(self, "cmp %s, %s", reg64_to_str(reg1), asm_ptr_to_string(reg2)); + return 0; +} + +int asm_sete_m(Asm *self, AsmPtr *dst) { + assert(dst->base != RSP && dst->base != RBP && dst->base != RSI && dst->base != RDI); + ins_start(self); + /* 8 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, 8)); + *self->code_it++ = 0x0F; + *self->code_it++ = 0x94; + asm_rm(self, dst, 0x0); /* the @src bits are not used */ + ins_end(self, "sete %s", asm_ptr_to_string(dst)); + return 0; +} + +int asm_sete_r(Asm *self, Reg64 dst) { + assert(dst != RSP && dst != RBP && dst != RSI && dst != RDI); + ins_start(self); + /* 8 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, 8)); + *self->code_it++ = 0x0F; + *self->code_it++ = 0x94; + asm_rr(self, dst, 0x0); /* the @src bits are not used */ + ins_end(self, "sete %s", reg64_to_str(dst)); + return 0; +} + +/* + Note: This is sometimes called with @relative INT32_MAX-(2 or 6) (will print jz 0x7ffffff9), in which case it's most likely a dummy + jump until the relative position is later changed with @asm_override_jcc_rel32. + TODO: Update the ins_end debug print to take that into account somehow +*/ +int asm_jz(Asm *self, i32 relative) { + /* + Note: We dont use the 16-bit relative variant, as it will clear the upper two bytes of the EIP registers, resulting + in a maximum instruction pointer size of 16 bits + */ + ins_start(self); + if(abs(relative - 2) <= INT8_MAX) { + relative -= 2; + return_if_error(asm_ensure_capacity(self, 2)); + *self->code_it++ = 0x74; + *self->code_it++ = (i8)relative; + } else { + relative -= 6; + return_if_error(asm_ensure_capacity(self, 6)); + *self->code_it++ = 0x0F; + *self->code_it++ = 0x84; + am_memcpy(self->code_it, &relative, sizeof(relative)); + self->code_it += sizeof(relative); + } + ins_end(self, "jz 0x%x", relative); + return 0; +} + +void asm_override_jcc_rel32(Asm *self, u32 asm_index, i32 new_relative) { + /* +2 because rel32 variant of the jump instruction opcode is 2 bytes */ + assert(*(u8*)(self->code + asm_index) == 0x0F); + assert(*(u8*)(self->code + asm_index + 1) == 0x84); + new_relative -= 6; /* In x86, the relative position starts from the next instruction */ + am_memcpy((u8*)self->code + asm_index + 2, &new_relative, sizeof(new_relative)); +} + +/* + Note: This is sometimes called with @relative INT32_MAX-(2 or 5) (will print jmp 0x7ffffffa), in which case it's most likely a dummy + jump until the relative position is later changed with @asm_override_jmp_rel32. + TODO: Update the ins_end debug print to take that into account somehow +*/ +int asm_jmp(Asm *self, i32 relative) { + /* + Note: We dont use the 16-bit relative variant, as it will clear the upper two bytes of the EIP registers, resulting + in a maximum instruction pointer size of 16 bits + */ + ins_start(self); + if(abs(relative - 2) <= INT8_MAX) { + relative -= 2; + return_if_error(asm_ensure_capacity(self, 2)); + *self->code_it++ = 0xEB; + *self->code_it++ = (i8)relative; + } else { + relative -= 5; + return_if_error(asm_ensure_capacity(self, 5)); + *self->code_it++ = 0xE9; + am_memcpy(self->code_it, &relative, sizeof(relative)); + self->code_it += sizeof(relative); + } + ins_end(self, "jmp 0x%x", relative); + return 0; +} + +void asm_override_jmp_rel32(Asm *self, u32 asm_index, i32 new_relative) { + /* +1 to skip instruction opcode */ + assert(*(u8*)(self->code + asm_index) == 0xE9); 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)); } @@ -488,8 +594,7 @@ void asm_override_call_rel32(Asm *self, u32 asm_index, i32 new_relative) { /* /r */ #define DEFINE_INS_RM(mnemonic, opcode) \ -int asm_##mnemonic##_rmb(Asm *self, Reg32 dst, Reg32 src) { \ - return_if_error(asm_ensure_capacity(self, 2)); \ +int asm_##mnemonic##_rmb(Asm *self, Reg32 dst, Reg32 src) { \ *self->code_it++ = opcode; \ *self->code_it++ = 0xC0 + 8*dst + src; \ return 0; \ @@ -498,6 +603,7 @@ int asm_##mnemonic##_rmb(Asm *self, Reg32 dst, Reg32 src) { \ int asm_##mnemonic##_rm32(Asm *self, Reg32 dst, Reg32 src) { \ int result; \ ins_start(self); \ + return_if_error(asm_ensure_capacity(self, 2)); \ result = asm_##mnemonic##_rmb(self, (Reg32)dst, (Reg32)src); \ ins_end(self, #mnemonic" %s, %s", reg32_to_str(dst), reg32_to_str(src)); \ return result; \ @@ -506,7 +612,7 @@ int asm_##mnemonic##_rm32(Asm *self, Reg32 dst, Reg32 src) { \ int asm_##mnemonic##_rm64(Asm *self, Reg64 dst, Reg64 src) { \ int result; \ ins_start(self); \ - return_if_error(asm_ensure_capacity(self, 1)); \ + return_if_error(asm_ensure_capacity(self, 3)); \ *self->code_it++ = REX_W; \ result = asm_##mnemonic##_rmb(self, (Reg32)dst, (Reg32)src); \ ins_end(self, #mnemonic" %s, %s", reg64_to_str(dst), reg64_to_str(src)); \ diff --git a/executor/x86_64/asm.h b/executor/x86_64/asm.h index ace1ecf..ac519e9 100644 --- a/executor/x86_64/asm.h +++ b/executor/x86_64/asm.h @@ -89,6 +89,30 @@ CHECK_RESULT int asm_callr(Asm *self, Reg64 reg); CHECK_RESULT int asm_call_rel32(Asm *self, i32 relative); void asm_override_call_rel32(Asm *self, u32 asm_index, i32 new_relative); +CHECK_RESULT int asm_cmp_rm(Asm *self, Reg64 reg1, AsmPtr *reg2); +/* + Sets the 8 bit memory operand to 1 if the last cmp was equals, otherwise set it to 0. + Note: this instruction doesn't work with AH (RSP), CH (RBP), DH (RSI) and BH (RDI). + TODO: When ST, MM AND XMM registers are implemented, also check for them as they are also invalid +*/ +CHECK_RESULT int asm_sete_m(Asm *self, AsmPtr *dst); +CHECK_RESULT int asm_sete_r(Asm *self, Reg64 dst); +/* + 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_jz(Asm *self, i32 relative); +/* Override conditional jump target */ +void asm_override_jcc_rel32(Asm *self, u32 asm_index, i32 new_relative); +/* + 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_jmp(Asm *self, i32 relative); +void asm_override_jmp_rel32(Asm *self, u32 asm_index, i32 new_relative); + diff --git a/executor/x86_64/executor.c b/executor/x86_64/executor.c index 335790a..d35dbdc 100644 --- a/executor/x86_64/executor.c +++ b/executor/x86_64/executor.c @@ -11,22 +11,35 @@ TODO: Operations with memory registers could access outside the stack. Should this be checked? */ +/* + TODO: Allow this to dynamically change up to 1<<16, to match jump instruction. + This is a sane limit for now +*/ +#define MAX_LABELS 128 + typedef struct { u32 asm_index; u16 func_index; } CallDefer; +typedef struct { + u32 asm_index; + u16 target_label; + bool condition; +} JumpDefer; + typedef struct { Asm asm; usize *function_indices; u16 num_functions; u16 func_counter; Buffer/*CallDefer*/ call_defer; + Buffer/*JumpDefer*/ jump_defer; + u32 label_asm_index[MAX_LABELS]; + int label_counter; } amal_executor_impl; -#define IMPL \ - amal_executor_impl *impl; \ - impl = (amal_executor_impl*)self; +#define IMPL amal_executor_impl *impl = (amal_executor_impl*)self; /* @reg will be a positive value when accessing local variables, in which case the first @@ -53,11 +66,14 @@ int amal_executor_init(amal_executor **self) { (*impl)->num_functions = 0; (*impl)->func_counter = 0; ignore_result_int(buffer_init(&(*impl)->call_defer, NULL)); + ignore_result_int(buffer_init(&(*impl)->jump_defer, NULL)); + (*impl)->label_counter = 0; return asm_init(&(*impl)->asm); } void amal_executor_deinit(amal_executor *self) { IMPL + buffer_deinit(&impl->jump_defer); buffer_deinit(&impl->call_defer); am_free(impl->function_indices); asm_deinit(&impl->asm); @@ -75,26 +91,28 @@ u32 amal_exec_get_code_offset(amal_executor *self) { } 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; + return_if_error(am_realloc(impl->function_indices, num_functions * sizeof(usize), (void**)&impl->function_indices)); 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); + CallDefer *call_defer = buffer_begin(&impl->call_defer); + CallDefer *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; + i32 func_offset; + if(call_defer->func_index >= impl->num_functions) { + amal_log_error("Program attempted to call a function that doesn't exist (index %u, while there are only %u functions)", call_defer->func_index, impl->num_functions); + return -1; + } + 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); } + buffer_clear(&impl->call_defer); + impl->func_counter = 0; return 0; } @@ -272,25 +290,26 @@ int amal_exec_pushd(amal_executor *self, BufferView data) { } int amal_exec_call(amal_executor *self, u16 func_index, u8 num_args, i8 dst_reg) { - isize asm_size; IMPL /* TODO: Preserve necessary registers before call? */ /* TODO: This assumes all arguments are isize */ - asm_size = asm_get_size(&impl->asm); + /* Do the function call */ + isize asm_offset = 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)); + return_if_error(asm_call_rel32(&impl->asm, (isize)impl->function_indices[func_index] - asm_offset)); } 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.asm_index = asm_offset; 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)); } + /* Handle function result and cleanup */ { AsmPtr dst; asm_ptr_init_disp(&dst, RBP, get_register_stack_offset(dst_reg)); @@ -350,30 +369,65 @@ int amal_exec_callr(i8 dst_reg, BufferView data) { */ int amal_exec_cmp(amal_executor *self, i8 dst_reg, i8 src_reg1, i8 src_reg2) { - (void)self; - (void)dst_reg; - (void)src_reg1; - (void)src_reg2; - /* TODO: Implement! */ - assert(bool_false && "TODO: Implement!"); - return 0; + IMPL + + AsmPtr dst, src1, src2; + asm_ptr_init_disp(&dst, RBP, get_register_stack_offset(dst_reg)); + asm_ptr_init_disp(&src1, RBP, get_register_stack_offset(src_reg1)); + asm_ptr_init_disp(&src2, RBP, get_register_stack_offset(src_reg2)); + + return_if_error(asm_mov_rm(&impl->asm, RCX, &dst)); + return_if_error(asm_xor_rm64(&impl->asm, RCX, RCX)); + + return_if_error(asm_mov_rm(&impl->asm, RAX, &src1)); + return_if_error(asm_cmp_rm(&impl->asm, RAX, &src2)); + return_if_error(asm_sete_r(&impl->asm, RCX)); + return asm_mov_mr(&impl->asm, &dst, RCX); } -int amal_exec_jz(amal_executor *self, i8 dst_reg, i16 offset) { - (void)self; - (void)dst_reg; - (void)offset; - /* TODO: Implement! */ - assert(bool_false && "TODO: Implement!"); - return 0; +int amal_exec_jz(amal_executor *self, i8 reg, u16 target_label) { + AsmPtr ptr; + u32 asm_offset; + IMPL + + asm_ptr_init_disp(&ptr, RBP, get_register_stack_offset(reg)); + return_if_error(asm_mov_rm(&impl->asm, RAX, &ptr)); + return_if_error(asm_cmp_rm64_imm(&impl->asm, RAX, 0)); + + asm_offset = asm_get_size(&impl->asm); + if(target_label < impl->label_counter) { + return asm_jz(&impl->asm, (i32)impl->label_asm_index[target_label] - (i32)asm_offset); + } else { + JumpDefer jump_defer; + jump_defer.asm_index = asm_offset; + jump_defer.target_label = target_label; + jump_defer.condition = bool_true; + /* + Insert dummy target, but it has to be above INT16_MAX, so the target can be replaced + no matter how large the jump will be + */ + return_if_error(asm_jz(&impl->asm, INT32_MAX)); + return buffer_append(&impl->jump_defer, &jump_defer, sizeof(jump_defer)); + } } -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_jmp(amal_executor *self, u16 target_label) { + IMPL + u32 asm_offset = asm_get_size(&impl->asm); + if(target_label < impl->label_counter) { + return asm_jmp(&impl->asm, (i32)impl->label_asm_index[target_label] - (i32)asm_offset); + } else { + JumpDefer jump_defer; + jump_defer.asm_index = asm_offset; + jump_defer.target_label = target_label; + jump_defer.condition = bool_false; + /* + Insert dummy target, but it has to be above INT16_MAX, so the target can be replaced + no matter how large the jump will be + */ + return_if_error(asm_jmp(&impl->asm, INT32_MAX)); + return buffer_append(&impl->jump_defer, &jump_defer, sizeof(jump_defer)); + } } int amal_exec_ret(amal_executor *self, i8 reg) { @@ -408,8 +462,33 @@ int amal_exec_func_start(amal_executor *self, u16 num_regs) { int amal_exec_func_end(amal_executor *self) { IMPL + + JumpDefer *jump_defer = buffer_begin(&impl->jump_defer); + JumpDefer *jump_defer_end = buffer_end(&impl->jump_defer); + for(; jump_defer != jump_defer_end; ++jump_defer) { + i32 jump_offset; + if(jump_defer->target_label >= impl->label_counter) { + amal_log_error("Program attempted to jump to a label that doesn't exist (label %u, while there are only %u labels)", jump_defer->target_label, impl->label_counter); + return -1; + } + jump_offset = (isize)impl->label_asm_index[jump_defer->target_label] - (isize)jump_defer->asm_index; + if(jump_defer->condition) + asm_override_jcc_rel32(&impl->asm, jump_defer->asm_index, jump_offset); + else + asm_override_jmp_rel32(&impl->asm, jump_defer->asm_index, jump_offset); + } + buffer_clear(&impl->jump_defer); + impl->label_counter = 0; + return_if_error(asm_mov_rr(&impl->asm, RSP, RBP)); return_if_error(asm_popr(&impl->asm, RBP)); return_if_error(asm_popr(&impl->asm, RBX)); return asm_ret(&impl->asm, 0); } + +int amal_exec_label(amal_executor *self) { + IMPL + assert(impl->label_counter < MAX_LABELS); + impl->label_asm_index[impl->label_counter++] = asm_get_size(&impl->asm); + return 0; +} diff --git a/include/bytecode/bytecode.h b/include/bytecode/bytecode.h index 64b54b4..b1b5dda 100644 --- a/include/bytecode/bytecode.h +++ b/include/bytecode/bytecode.h @@ -18,18 +18,18 @@ 4. 3 bytes:\ 4.1 Opcode(u8) + intermediate(u16)\ 4.2 Opcode(u8) + data(u16)\ - 4.3 Opcode(u8) + offset(i16)\ + 4.3 Opcode(u8) + label(i16)\ 4.4 Opcode(u8) + register(i8) + num_args(u8) 5. 4 bytes: Opcode(u8) + register(i8) + register(i8) + register(i8) 6. 4 bytes:\ - 6.1 Opcode(u8) + register(i8) + offset(i16)\ + 6.1 Opcode(u8) + register(i8) + label(i16)\ 6.2 Opcode(u8) + register(i8) + intermediate(u16)\ 6.3 Opcode(u8) + register(i8) + data(u16)\ 6.4 Opcode(u8) + flags(u8) + num_local_var_reg(u16) 7. 5 bytes: Opcode(u8) + index(u16) + num_args(u8) + register(i8) # Registers Registers have a range of 128. Local variables start from register 0 and increment while parameters start from -1 - and decrement. + and decrement. Registers have the scope of functions and reset after instructions reach a new function (AMAL_OP_FUNC_START). */ typedef enum { AMAL_OP_NOP, /* No operation (do nothing). This can be used for patching code */ @@ -50,11 +50,12 @@ typedef enum { AMAL_OP_CALLR, /* callr reg, num_args - Call a function using a register. Used for function pointers. num_args is u8 */ AMAL_OP_CALLE, /* calle efi, num_args, dst - Call an extern function using extern function index (efi) and num_args arguments. The result is stored in register dst. efi is u16 and num_args is u8 */ 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_JZ, /* jz reg, label - Jump to label in the current function if reg is zero. label is u16 */ + AMAL_OP_JMP, /* jmp label - Unconditional jump to label in the current function. label is u16 */ AMAL_OP_RET, /* ret reg - Return from the function with reg result */ AMAL_OP_FUNC_START, /* func_start flags, num_local_var_reg - Start of a function which has @num_local_var_reg local variable registers allocated and has the flag @flag. @flag is u8 and @num_local_var_reg is u16 */ - AMAL_OP_FUNC_END /* func_end - End of a function. Implementation should do a ret here */ + AMAL_OP_FUNC_END, /* func_end - End of a function. Implementation should do a ret here */ + AMAL_OP_LABEL, /* label - Label. This is the target of a jump instruction. Jump instructions only jump to labels in the same function scope */ } AmalOpcode; #define AMAL_BYTECODE_MAGIC_NUMBER (u32)0xdec05eba @@ -66,7 +67,7 @@ typedef enum { typedef enum { FUNC_FLAG_NONE = 0, - FUNC_FLAG_EXPORTED = (1 << 1) + FUNC_FLAG_EXPORTED = 1 << 0 } amal_func_flag; typedef u8 AmalOpcodeType; diff --git a/include/ssa/ssa.h b/include/ssa/ssa.h index ed147bf..9d6949d 100644 --- a/include/ssa/ssa.h +++ b/include/ssa/ssa.h @@ -28,7 +28,8 @@ typedef enum { SSA_CALL_EXTERN, SSA_JUMP_ZERO, SSA_JUMP, - SSA_RET + SSA_RET, + SSA_LABEL } SsaInstruction; typedef enum { @@ -61,6 +62,7 @@ typedef u16 SsaStringIndex; typedef u16 SsaExternFuncIndex; typedef u16 SsaExportFuncIndex; typedef u16 SsaFuncIndex; +typedef u16 SsaLabelIndex; typedef struct { Buffer/*instruction data*/ instructions; @@ -78,6 +80,7 @@ typedef struct { SsaRegister reg_counter; SsaRegister param_counter; SsaFuncIndex func_counter; + SsaLabelIndex label_counter; Parser *parser; /* Borrowed */ } Ssa; @@ -111,11 +114,11 @@ typedef struct { typedef struct { SsaRegister condition_reg; - JumpOffset jump_offset; + SsaLabelIndex target_label; } SsaInsJumpZero; typedef struct { - JumpOffset jump_offset; + SsaLabelIndex target_label; } SsaInsJump; /* None of these functions are thread-safe */ diff --git a/src/bytecode/bytecode.c b/src/bytecode/bytecode.c index fe3dc0f..47d492c 100644 --- a/src/bytecode/bytecode.c +++ b/src/bytecode/bytecode.c @@ -77,6 +77,7 @@ static void add_intermediates(BytecodeCompilerContext *self) { Buffer *instructions = &self->bytecode.data; SsaNumber *intermediate = buffer_begin(&ssa->intermediates); SsaNumber *intermediates_end = buffer_end(&ssa->intermediates); + int i = 0; u32 intemediates_size = (sizeof(u8) + sizeof(u64)) * buffer_get_size(&ssa->intermediates, SsaNumber); throw_if_error(buffer_expand(instructions, sizeof(u32) + intemediates_size)); @@ -85,6 +86,7 @@ static void add_intermediates(BytecodeCompilerContext *self) { throw_if_error(buffer_append(instructions, &intermediate->type, sizeof(u8))); /* TODO: Store value using an encoding that will save space when using low numbers */ throw_if_error(buffer_append(instructions, &intermediate->value.integer, sizeof(u64))); + fprintf(stderr, "i%d = %ld\n", i++, intermediate->value.integer); } } @@ -228,11 +230,12 @@ static void add_export_functions(BytecodeCompilerContext *self) { 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); - fputc('\n', stderr); + if(fmt) { + fprintf(stderr, fmt); + fputc('\n', stderr); + } } - static void add_ins2(BytecodeCompilerContext *self, AmalOpcode opcode, i8 reg, const char *fmt) { Buffer *instructions = &self->bytecode.data; size_t index = instructions->size; @@ -325,6 +328,7 @@ static void add_instructions(BytecodeCompilerContext *self) { Ssa *ssa = self->parser->ssa; u8 *instruction = buffer_begin(&ssa->instructions); u8 *instructions_end = buffer_end(&ssa->instructions); + u16 label_counter = 0; u32 num_instructions_index = self->bytecode.data.size; throw_if_error(buffer_append_empty(&self->bytecode.data, sizeof(num_instructions_index))); @@ -386,6 +390,7 @@ static void add_instructions(BytecodeCompilerContext *self) { case SSA_FUNC_START: { instruction += ssa_extract_data(instruction, &ssa_ins_func_start, sizeof(ssa_ins_func_start)); add_ins6(self, AMAL_OP_FUNC_START, ssa_ins_func_start.flags, ssa_ins_func_start.num_local_vars_regs, "func_start 0x%02x, %u"); + label_counter = 0; break; } case SSA_FUNC_END: { @@ -419,12 +424,12 @@ static void add_instructions(BytecodeCompilerContext *self) { } case SSA_JUMP_ZERO: { instruction += ssa_extract_data(instruction, &ssa_ins_jump_zero, sizeof(ssa_ins_jump_zero)); - add_ins6(self, AMAL_OP_JZ, ssa_ins_jump_zero.condition_reg, ssa_ins_jump_zero.jump_offset, "jz r%d, %d"); + add_ins6(self, AMAL_OP_JZ, ssa_ins_jump_zero.condition_reg, ssa_ins_jump_zero.target_label, "jz r%d, l%d"); break; } case SSA_JUMP: { instruction += ssa_extract_data(instruction, &ssa_ins_jump, sizeof(ssa_ins_jump)); - add_ins4(self, AMAL_OP_JMP, ssa_ins_jump.jump_offset, "jmp %d"); + add_ins4(self, AMAL_OP_JMP, ssa_ins_jump.target_label, "jmp l%d"); break; } case SSA_RET: { @@ -434,6 +439,11 @@ static void add_instructions(BytecodeCompilerContext *self) { add_ins2(self, AMAL_OP_RET, reg, "ret r%d"); break; } + case SSA_LABEL: { + add_ins1(self, AMAL_OP_LABEL, NULL); + fprintf(stderr, "label l%d\n", label_counter++); + break; + } } } diff --git a/src/parser.c b/src/parser.c index ff34663..a6b4ecf 100644 --- a/src/parser.c +++ b/src/parser.c @@ -48,6 +48,10 @@ int parser_init(Parser *self, amal_compiler *compiler, ArenaAllocator *allocator return PARSER_OK; } +static bool parser_is_current_scope_file_scope(Parser *self) { + return self->current_scope == &self->struct_decl.body; +} + /* BODY_LOOP = BODY* @end_token */ @@ -359,6 +363,12 @@ static CHECK_RESULT FunctionDecl* parser_parse_closure(Parser *self) { if(!signature) return NULL; + /* + TODO: Implement function declaration inside other functions. + Such functions should be moved to the file scope in the bytecode generation + */ + assert(parser_is_current_scope_file_scope(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; @@ -500,16 +510,20 @@ static CHECK_RESULT ElseIfStatement* parser_parse_else_if_statement(Parser *self } static void parser_parse_else_if_statement_loop(Parser *self, IfStatement *if_stmt) { - ElseIfStatement *else_if_stmt; - else_if_stmt = if_stmt->else_if_stmt; + ElseIfStatement *else_if_stmt = NULL; for(;;) { ElseIfStatement *next_else_if; next_else_if = parser_parse_else_if_statement(self); if(!next_else_if) break; - else_if_stmt->next_else_if_stmt = next_else_if; + + if(!else_if_stmt) + if_stmt->else_if_stmt = next_else_if; + else + else_if_stmt->next_else_if_stmt = next_else_if; + /* else statement that has no condition can't be followed by another else statement */ - if(!else_if_stmt->condition) + if(!next_else_if->condition) break; else_if_stmt = next_else_if; } diff --git a/src/program.c b/src/program.c index 55bfda4..eef49b6 100644 --- a/src/program.c +++ b/src/program.c @@ -125,7 +125,7 @@ static CHECK_RESULT int amal_program_set_exported_function_instruction_offset_ad num_args = self->exported_funcs[sizeof(instruction_offset)]; func_name_size = self->exported_funcs[sizeof(instruction_offset) + sizeof(num_args)]; self->exported_funcs += sizeof(instruction_offset) + sizeof(num_args) + sizeof(func_name_size); - if(self->main_func_instruction_offset == ~0U && am_memeql(self->exported_funcs, "main", 4)) + if(self->main_func_instruction_offset == ~0U && func_name_size == 4 && am_memeql(self->exported_funcs, "main", 4)) self->main_func_instruction_offset = instruction_offset; /* +1 to skip null-termination character */ @@ -521,16 +521,18 @@ static CHECK_RESULT int amal_program_read_instructions(amal_program *self, amal_ break; } case AMAL_OP_JZ: { - i16 jump_offset; - am_memcpy(&jump_offset, &self->data.data[self->read_index + sizeof(i8)], sizeof(jump_offset)); - return_if_error(amal_exec_jz(executor, self->data.data[self->read_index], jump_offset)); + i8 reg; + u16 target_label; + reg = self->data.data[self->read_index]; + am_memcpy(&target_label, self->data.data + self->read_index + sizeof(reg), sizeof(target_label)); + return_if_error(amal_exec_jz(executor, reg, target_label)); self->read_index += 3; break; } case AMAL_OP_JMP: { - i16 jump_offset; - am_memcpy(&jump_offset, &self->data.data[self->read_index], sizeof(jump_offset)); - return_if_error(amal_exec_jmp(executor, jump_offset)); + u16 target_label; + am_memcpy(&target_label, self->data.data + self->read_index, sizeof(target_label)); + return_if_error(amal_exec_jmp(executor, target_label)); self->read_index += 2; break; } @@ -564,6 +566,10 @@ static CHECK_RESULT int amal_program_read_instructions(amal_program *self, amal_ return_if_error(amal_exec_func_end(executor)); break; } + case AMAL_OP_LABEL: { + return_if_error(amal_exec_label(executor)); + break; + } } } diff --git a/src/ssa/ssa.c b/src/ssa/ssa.c index 940f636..5c625b2 100644 --- a/src/ssa/ssa.c +++ b/src/ssa/ssa.c @@ -69,6 +69,7 @@ int ssa_init(Ssa *self, Parser *parser) { self->reg_counter = 0; self->param_counter = 0; self->func_counter = 0; + self->label_counter = 0; self->parser = parser; return 0; } @@ -80,6 +81,7 @@ static CHECK_RESULT int ssa_get_unique_reg(Ssa *self, SsaRegister *result) { amal_log_error("Ssa too many registers!"); return -1; } + assert(self->reg_counter <= INT8_MAX && "TODO: Implement usage of reg higher than 128"); *result = self->reg_counter++; return 0; } @@ -331,24 +333,33 @@ static CHECK_RESULT int ssa_ins_call_extern(Ssa *self, SsaExternFuncIndex extern return buffer_append(&self->instructions, &ins_func_call_extern, sizeof(ins_func_call_extern)); } -static CHECK_RESULT int ssa_ins_jumpzero(Ssa *self, SsaRegister condition_reg, JumpOffset jump_offset) { +static CHECK_RESULT int ssa_ins_jumpzero(Ssa *self, SsaRegister condition_reg, SsaLabelIndex target_label, usize *instruction_offset) { const u8 ins_type = SSA_JUMP_ZERO; SsaInsJumpZero ins_jump_zero; ins_jump_zero.condition_reg = condition_reg; - ins_jump_zero.jump_offset = jump_offset; - if(jump_offset == 0) + ins_jump_zero.target_label = target_label; + if(target_label == 0) amal_log_debug("JUMP_ZERO r%d, DUMMY", condition_reg); else - amal_log_debug("JUMP_ZERO r%d, %d", condition_reg, jump_offset); + amal_log_debug("JUMP_ZERO r%d, l%d", condition_reg, target_label); + *instruction_offset = self->instructions.size; return_if_error(buffer_append(&self->instructions, &ins_type, 1)); return buffer_append(&self->instructions, &ins_jump_zero, sizeof(ins_jump_zero)); } -static CHECK_RESULT int ssa_ins_jump(Ssa *self, JumpOffset jump_offset) { +static CHECK_RESULT int ssa_ins_jump(Ssa *self, SsaLabelIndex target_label, usize *instruction_offset) { const u8 ins_type = SSA_JUMP; SsaInsJump ins_jump; - ins_jump.jump_offset = jump_offset; - amal_log_debug("JUMP %d", jump_offset); + ins_jump.target_label = target_label; + + if(target_label == 0) + amal_log_debug("JUMP DUMMY"); + else + amal_log_debug("JUMP l%d", target_label); + + if(instruction_offset) + *instruction_offset = self->instructions.size; + return_if_error(buffer_append(&self->instructions, &ins_type, 1)); return buffer_append(&self->instructions, &ins_jump, sizeof(ins_jump)); } @@ -359,21 +370,28 @@ static CHECK_RESULT int ssa_ins_return(Ssa *self, SsaRegister reg) { return buffer_append(&self->instructions, ®, sizeof(reg)); } -static usize ssa_ins_get_index(Ssa *self) { - return self->instructions.size; +static CHECK_RESULT int ssa_ins_label(Ssa *self, u16 *label_index) { + const u8 ins_type = SSA_LABEL; + /* Overflow */ + if(self->label_counter + 1 <= self->label_counter) { + amal_log_error("Ssa too many labels!"); + return -1; + } + *label_index = self->label_counter; + ++self->label_counter; + return buffer_append(&self->instructions, &ins_type, 1); } -/* Set target of jump instruction to current location */ -static CHECK_RESULT int ssa_ins_jump_set_target(Ssa *self, usize jump_ins_index) { +static CHECK_RESULT int ssa_set_jump_label(Ssa *self, usize jump_ins_index, SsaLabelIndex label) { switch(self->instructions.data[jump_ins_index]) { case SSA_JUMP_ZERO: { - isize jump_offset = (isize)ssa_ins_get_index(self) - (isize)jump_ins_index; - /* TODO: Should something be done about this? */ - if(jump_offset < -0x7FFF || jump_offset > 0x7FFF) { - amal_log_error("Unexpected error. Jump offset has to be less than +-32767, was %d", jump_offset); - return -1; - } - am_memcpy(self->instructions.data + jump_ins_index + 1 + offsetof(SsaInsJumpZero, jump_offset), &jump_offset, sizeof(JumpOffset)); + /* +1 to skip instruction opcode */ + am_memcpy(self->instructions.data + jump_ins_index + 1 + offsetof(SsaInsJumpZero, target_label), &label, sizeof(SsaLabelIndex)); + break; + } + case SSA_JUMP: { + /* +1 to skip instruction opcode */ + am_memcpy(self->instructions.data + jump_ins_index + 1 + offsetof(SsaInsJump, target_label), &label, sizeof(SsaLabelIndex)); break; } default: @@ -386,6 +404,7 @@ 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); +#if 0 static bool ast_resolved_type_is_decl(AstResolvedType *self) { /* TODO: Add more types as they are introduced */ LhsExpr *lhs_expr; @@ -406,6 +425,24 @@ static bool ast_resolved_type_is_decl(AstResolvedType *self) { assert(lhs_expr->rhs_expr); return lhs_expr->rhs_expr->type == AST_FUNCTION_DECL || lhs_expr->rhs_expr->type == AST_STRUCT_DECL; } +#endif +static bool lhs_expr_is_decl(LhsExpr *self) { + if(self->rhs_expr) { + return self->rhs_expr->type == AST_FUNCTION_DECL || self->rhs_expr->type == AST_STRUCT_DECL; + } else { + switch(self->type.type) { + case VARIABLE_TYPE_NONE: + assert(bool_false); + return 0; + 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; + } + return 0; + } +} static CHECK_RESULT SsaRegister number_generate_ssa(Number *self, SsaCompilerContext *context) { SsaRegister reg; @@ -482,7 +519,7 @@ static CHECK_RESULT SsaRegister lhsexpr_generate_ssa(LhsExpr *self, AstResolveDa 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(ast_resolved_type_is_decl(&resolve_data->type) || rhs_expr->type == AST_IMPORT) { + if(lhs_expr_is_decl(self) || rhs_expr->type == AST_IMPORT) { /*assert(bool_false);*/ return 0; } @@ -528,6 +565,7 @@ static CHECK_RESULT SsaRegister function_parameter_generate_ssa(FunctionParamete throw(-1); } reg = -1 - reg; + assert(reg >= INT8_MIN && "TODO: Implement more than 128 params"); self->resolve_data.status = AST_SSA_RESOLVED; self->resolve_data.ssa_reg = reg; return reg; @@ -555,10 +593,9 @@ static CHECK_RESULT SsaRegister funcdecl_generate_ssa(FunctionDecl *self, SsaCom that is reset after function end */ usize func_metadata_index; - SsaRegister prev_reg_counter = context->ssa->reg_counter; - SsaRegister prev_param_counter = context->ssa->param_counter; u8 func_flags = 0; context->ssa->reg_counter = 0; + context->ssa->label_counter = 0; /* Parameters need to have generated ssa so the first ssa registers belong to the function. @@ -580,9 +617,6 @@ static CHECK_RESULT SsaRegister funcdecl_generate_ssa(FunctionDecl *self, SsaCom /* Add the number of registers used to the function metadata (FUNC_START) */ am_memcpy(context->ssa->instructions.data + func_metadata_index, &context->ssa->reg_counter, sizeof(u16)); - - context->ssa->param_counter = prev_param_counter; - context->ssa->reg_counter = prev_reg_counter; return 0; } @@ -684,47 +718,71 @@ static CHECK_RESULT SsaRegister binop_generate_ssa(Binop *self, SsaCompilerConte return reg; } -static void else_if_statement_generate_ssa(ElseIfStatement *else_if_stmt, SsaCompilerContext *context) { - usize jump_ins_index = 0; +static void else_if_statement_generate_ssa(ElseIfStatement *else_if_stmt, SsaCompilerContext *context, SsaLabelIndex *skip_other_else_statements_label) { if(else_if_stmt->condition) { - SsaRegister condition_reg; - condition_reg = ast_generate_ssa(else_if_stmt->condition, context); - jump_ins_index = ssa_ins_get_index(context->ssa); - throw_if_error(ssa_ins_jumpzero(context->ssa, condition_reg, 0)); + usize jump_ins_index; + usize jump_skip_else_index; + SsaLabelIndex skip_body_label; + SsaRegister condition_reg = ast_generate_ssa(else_if_stmt->condition, context); + throw_if_error(ssa_ins_jumpzero(context->ssa, condition_reg, 0, &jump_ins_index)); + scope_generate_ssa(&else_if_stmt->body, context); + if(else_if_stmt->next_else_if_stmt) + throw_if_error(ssa_ins_jump(context->ssa, 0, &jump_skip_else_index)); + + throw_if_error(ssa_ins_label(context->ssa, &skip_body_label)); + throw_if_error(ssa_set_jump_label(context->ssa, jump_ins_index, skip_body_label)); + if(else_if_stmt->next_else_if_stmt) { + else_if_statement_generate_ssa(else_if_stmt->next_else_if_stmt, context, skip_other_else_statements_label); + /* Skip over all other else if statements, since else_if_statement_generate_ssa is recursive */ + throw_if_error(ssa_set_jump_label(context->ssa, jump_skip_else_index, *skip_other_else_statements_label)); + return; + } + } else { + assert(!else_if_stmt->next_else_if_stmt); + scope_generate_ssa(&else_if_stmt->body, context); } - scope_generate_ssa(&else_if_stmt->body, context); - if(else_if_stmt->condition) - throw_if_error(ssa_ins_jump_set_target(context->ssa, jump_ins_index)); - if(else_if_stmt->next_else_if_stmt) - else_if_statement_generate_ssa(else_if_stmt->next_else_if_stmt, context); + + /* Note: The last else if statement doesn't need a jump */ + throw_if_error(ssa_ins_label(context->ssa, skip_other_else_statements_label)); } static void if_statement_generate_ssa(IfStatement *if_stmt, SsaCompilerContext *context) { + usize jump_ins_index; + usize jump_skip_else_index; + SsaLabelIndex skip_body_label; + SsaLabelIndex skip_else_statements_label; + SsaRegister condition_reg = ast_generate_ssa(if_stmt->condition, context); - usize jump_ins_index = ssa_ins_get_index(context->ssa); - throw_if_error(ssa_ins_jumpzero(context->ssa, condition_reg, 0)); + throw_if_error(ssa_ins_jumpzero(context->ssa, condition_reg, 0, &jump_ins_index)); scope_generate_ssa(&if_stmt->body, context); - throw_if_error(ssa_ins_jump_set_target(context->ssa, jump_ins_index)); if(if_stmt->else_if_stmt) - else_if_statement_generate_ssa(if_stmt->else_if_stmt, context); + throw_if_error(ssa_ins_jump(context->ssa, 0, &jump_skip_else_index)); + + throw_if_error(ssa_ins_label(context->ssa, &skip_body_label)); + throw_if_error(ssa_set_jump_label(context->ssa, jump_ins_index, skip_body_label)); + if(if_stmt->else_if_stmt) { + else_if_statement_generate_ssa(if_stmt->else_if_stmt, context, &skip_else_statements_label); + /* + Skip over all else if statements, since else_if_statement_generate_ssa is recursive. + We want to jump since we want to skip the else if statements if we are inside the first if-statement + */ + throw_if_error(ssa_set_jump_label(context->ssa, jump_skip_else_index, skip_else_statements_label)); + } } static void while_statement_generate_ssa(WhileStatement *while_stmt, SsaCompilerContext *context) { - isize jump_offset; - usize jump_back_ins_index = ssa_ins_get_index(context->ssa); + SsaLabelIndex before_condition_label; + SsaLabelIndex skip_body_label; + usize jump_after_condition_index; + + throw_if_error(ssa_ins_label(context->ssa, &before_condition_label)); SsaRegister condition_reg = ast_generate_ssa(while_stmt->condition, context); - usize jump_condition_ins_index = ssa_ins_get_index(context->ssa); - throw_if_error(ssa_ins_jumpzero(context->ssa, condition_reg, 0)); + throw_if_error(ssa_ins_jumpzero(context->ssa, condition_reg, 0, &jump_after_condition_index)); scope_generate_ssa(&while_stmt->body, context); - /* Jump back and check condition again before running the content of the loop again */ - jump_offset = (isize)jump_back_ins_index - (isize)ssa_ins_get_index(context->ssa); - /* TODO: Should something be done about this? */ - if(jump_offset < -0x7FFF || jump_offset > 0x7FFF) { - amal_log_error("Unexpected error. Jump offset has to be less than +-32767, was %d", jump_offset); - throw(1); - } - throw_if_error(ssa_ins_jump(context->ssa, (JumpOffset)jump_offset)); - throw_if_error(ssa_ins_jump_set_target(context->ssa, jump_condition_ins_index)); + + throw_if_error(ssa_ins_jump(context->ssa, before_condition_label, NULL)); + throw_if_error(ssa_ins_label(context->ssa, &skip_body_label)); + throw_if_error(ssa_set_jump_label(context->ssa, jump_after_condition_index, skip_body_label)); } static void return_expr_generate_ssa(ReturnExpr *self, SsaCompilerContext *context) { diff --git a/tests/bytecode.amal b/tests/bytecode.amal index 32c1ccb..ef255cd 100644 --- a/tests/bytecode.amal +++ b/tests/bytecode.amal @@ -12,9 +12,8 @@ const main = fn { const value3 = 2 + 5 - 1 * 10 / 2; const str_value = "hello, world"; print(); - //if value2 == 54 - // const result = print_num(1337); - const result = print_num(1337); + if value2 == 54 + const result = print_num(1337); } const print_num = fn(num: i32) i32 { -- cgit v1.2.3