#include "../executor.h" #include "../../include/std/alloc.h" #include "../../include/std/buffer.h" #include "asm.h" #include /* TODO: Currently almost all operations are performed on memory. This should be optimized to take advantage of registers. 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_START \ amal_executor_impl *impl = (amal_executor_impl*)self; \ return_if_error(asm_ensure_capacity(&impl->asm, 256)); /* @reg will be a positive value when accessing local variables, in which case the first local variable is located at -sizeof(usize) and the next one is at -(2 * sizeof(usize)). @reg will be a negative value starting at -1 when accessing parameters. The first parameter is located at 3*sizeof(usize) and the next one is at 4*sizeof(usize). Parameter starts at 3*sizeof(usize) because offset 0 is the return address, offset 1*sizeof(usize) is the saved RBP and 2*sizeof(usize) is saved RBX. TODO: Use different offset when saving more registers, for example on Microsoft Windows. */ #define get_register_stack_offset(reg) \ (reg >= 0 ? (i32)(-reg * (int)sizeof(usize) - sizeof(usize)) : (i32)(-reg * (int)sizeof(usize) + 2*sizeof(usize))) static i64 abs_i64(i64 value) { return value >= 0 ? value : -value; } 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)->function_indices = NULL; (*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) { amal_executor_impl *impl = (amal_executor_impl*)self; buffer_deinit(&impl->jump_defer); buffer_deinit(&impl->call_defer); am_free(impl->function_indices); asm_deinit(&impl->asm); am_free(impl); } int amal_executor_run(amal_executor *self, u32 offset) { amal_executor_impl *impl = (amal_executor_impl*)self; return asm_execute(&impl->asm, offset); } u32 amal_exec_get_code_offset(amal_executor *self) { amal_executor_impl *impl = (amal_executor_impl*)self; return asm_get_size(&impl->asm); } int amal_executor_instructions_start(amal_executor *self, u16 num_functions) { amal_executor_impl *impl = (amal_executor_impl*)self; return_if_error(am_realloc(impl->function_indices, num_functions * sizeof(usize), (void**)&impl->function_indices)); impl->num_functions = num_functions; return 0; } int amal_executor_instructions_end(amal_executor *self) { amal_executor_impl *impl = (amal_executor_impl*)self; 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) { 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; } int amal_exec_nop(amal_executor *self) { IMPL_START asm_nop(&impl->asm); return 0; } int amal_exec_setz(amal_executor *self, i8 dst_reg) { AsmPtr dst; IMPL_START asm_ptr_init_disp(&dst, RBP, get_register_stack_offset(dst_reg)); asm_mov_mi(&impl->asm, &dst, 0); return 0; } int amal_exec_mov(amal_executor *self, i8 dst_reg, i8 src_reg) { AsmPtr ptr; IMPL_START asm_ptr_init_disp(&ptr, RBP, get_register_stack_offset(src_reg)); asm_mov_rm(&impl->asm, RAX, &ptr); asm_ptr_init_disp(&ptr, RBP, get_register_stack_offset(dst_reg)); asm_mov_mr(&impl->asm, &ptr, RAX); return 0; } int amal_exec_movi(amal_executor *self, i8 dst_reg, i64 imm) { IMPL_START /* TODO: if @number is a float then use float instructions */ if(abs_i64(imm) <= INT32_MAX) { AsmPtr dst; asm_ptr_init_disp(&dst, RBP, get_register_stack_offset(dst_reg)); asm_mov_mi(&impl->asm, &dst, imm); } else { AsmPtr dst; asm_ptr_init_disp(&dst, RBP, get_register_stack_offset(dst_reg)); asm_mov_ri(&impl->asm, RAX, imm); asm_mov_mr(&impl->asm, &dst, RAX); } return 0; } int amal_exec_movd(amal_executor *self, i8 dst_reg, BufferView data) { AsmPtr dst; IMPL_START asm_ptr_init_disp(&dst, RBP, get_register_stack_offset(dst_reg)); asm_mov_ri(&impl->asm, RAX, (uintptr_t)data.data); asm_mov_mr(&impl->asm, &dst, RAX); return 0; } int amal_exec_add(amal_executor *self, i8 dst_reg, i8 src_reg1, i8 src_reg2) { AsmPtr dst; AsmPtr reg1; AsmPtr reg2; IMPL_START 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)); asm_mov_rm(&impl->asm, RAX, ®1); asm_mov_rm(&impl->asm, RCX, ®2); asm_add_rr(&impl->asm, RAX, RCX); asm_mov_mr(&impl->asm, &dst, RAX); return 0; } int amal_exec_sub(amal_executor *self, i8 dst_reg, i8 src_reg1, i8 src_reg2) { AsmPtr dst; AsmPtr reg1; AsmPtr reg2; IMPL_START 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)); asm_mov_rm(&impl->asm, RAX, ®1); asm_mov_rm(&impl->asm, RCX, ®2); asm_sub_rr(&impl->asm, RAX, RCX); asm_mov_mr(&impl->asm, &dst, RAX); return 0; } int amal_exec_imul(amal_executor *self, i8 dst_reg, i8 src_reg1, i8 src_reg2) { AsmPtr dst; AsmPtr reg1; AsmPtr reg2; IMPL_START 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)); asm_mov_rm(&impl->asm, RAX, ®1); asm_mov_rm(&impl->asm, RCX, ®2); asm_imul_rr(&impl->asm, RAX, RCX); asm_mov_mr(&impl->asm, &dst, RAX); return 0; } int amal_exec_mul(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! */ #if 0 AsmPtr dst; AsmPtr reg1; AsmPtr reg2; asm_ptr_init_disp(&dst, RBP, -(i32)get_register_at_offset(0)); asm_ptr_init_disp(®1, RBP, -(i32)get_register_at_offset(1)); asm_ptr_init_disp(®2, RBP, -(i32)get_register_at_offset(2)); return_if_error(asm_mov_rm(&self->asm, RAX, ®1)); return_if_error(asm_mov_rm(&self->asm, RCX, ®2)); 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, i8 dst_reg, i8 src_reg1, i8 src_reg2) { AsmPtr dst; AsmPtr reg1; AsmPtr reg2; IMPL_START 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)); asm_mov_rm(&impl->asm, RAX, ®1); asm_mov_rm(&impl->asm, RCX, ®2); asm_cqo(&impl->asm); /* TODO: Preserve RDX if needed, since it's also used as a parameter in system-v x86_64 abi */ asm_idiv_rr(&impl->asm, RCX); asm_mov_mr(&impl->asm, &dst, RAX); return 0; } int amal_exec_div(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; } int amal_exec_push(amal_executor *self, i8 reg) { AsmPtr reg_ptr; IMPL_START asm_ptr_init_disp(®_ptr, RBP, get_register_stack_offset(reg)); asm_mov_rm(&impl->asm, RAX, ®_ptr); asm_pushr(&impl->asm, RAX); return 0; } int amal_exec_pushi(amal_executor *self, i64 imm) { (void)self; (void)imm; /* TODO: Implement! */ assert(bool_false && "TODO: Implement!"); return 0; } 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, i8 dst_reg) { IMPL_START /* TODO: Preserve necessary registers before call? */ /* TODO: This assumes all arguments are isize */ /* Do the function call */ isize asm_offset = asm_get_size(&impl->asm); if(func_index < impl->func_counter) { 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_offset; call_defer.func_index = func_index; return_if_error(buffer_append(&impl->call_defer, &call_defer, sizeof(call_defer))); 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)); /* TODO: Make this work when result is not stored in RAX (multiple return results) */ asm_mov_mr(&impl->asm, &dst, RAX); } if(num_args > 0) asm_add_rm64_imm(&impl->asm, RSP, num_args * sizeof(isize)); return 0; } const Reg64 SYS_V_PARAM_REGS[] = { RDI, RSI, RDX, RCX }; /* TODO: Make argument passing work for different calling conventions and different ABI. This currently assumes x86_64 system v abi. System-V ABI parameters: RDI, RSI, RDX, RCX, R8, R9, XMM0–7. The rest are passed in the stack. */ /* TODO: Make this work when function returns something else than a POD */ int amal_exec_calle(amal_executor *self, void *func, u8 num_args, i8 dst_reg) { AsmPtr dst; IMPL_START /* TODO: Support R and XMM registers so more than 5 arguments can be used for functions */ assert(num_args < 5); { /* TODO: Do this directly in @PUSH instruction instead. For now we copy the pushed data to the registers that need to be set for the specific abi for parameters */ int i; AsmPtr src; asm_ptr_init_disp(&src, RSP, 0); for(i = num_args - 1; i >= 0; --i) { asm_mov_rm(&impl->asm, SYS_V_PARAM_REGS[i], &src); src.disp += 0x8; } } /* TODO: Preserve necessary registers before call? */ /* TODO: This assumes all arguments are isize */ asm_mov_ri(&impl->asm, RAX, (intptr_t)func); asm_callr(&impl->asm, RAX); asm_ptr_init_disp(&dst, RBP, get_register_stack_offset(dst_reg)); asm_mov_mr(&impl->asm, &dst, RAX); if(num_args > 0) asm_add_rm64_imm(&impl->asm, RSP, num_args * sizeof(isize)); return 0; } /* 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) { IMPL_START 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)); asm_mov_rm(&impl->asm, RCX, &dst); asm_xor_rm64(&impl->asm, RCX, RCX); asm_mov_rm(&impl->asm, RAX, &src1); asm_cmp_rm(&impl->asm, RAX, &src2); asm_sete_r(&impl->asm, RCX); asm_mov_mr(&impl->asm, &dst, RCX); return 0; } int amal_exec_jz(amal_executor *self, i8 reg, u16 target_label) { AsmPtr ptr; u32 asm_offset; IMPL_START asm_ptr_init_disp(&ptr, RBP, get_register_stack_offset(reg)); asm_mov_rm(&impl->asm, RAX, &ptr); asm_cmp_rm64_imm(&impl->asm, RAX, 0); asm_offset = asm_get_size(&impl->asm); if(target_label < impl->label_counter) { asm_jz(&impl->asm, (i32)impl->label_asm_index[target_label] - (i32)asm_offset); return 0; } 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 */ asm_jz(&impl->asm, INT32_MAX); return buffer_append(&impl->jump_defer, &jump_defer, sizeof(jump_defer)); } } int amal_exec_jmp(amal_executor *self, u16 target_label) { IMPL_START u32 asm_offset = asm_get_size(&impl->asm); if(target_label < impl->label_counter) { asm_jmp(&impl->asm, (i32)impl->label_asm_index[target_label] - (i32)asm_offset); return 0; } 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 */ 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) { AsmPtr ret_reg; IMPL_START asm_ptr_init_disp(&ret_reg, RBP, get_register_stack_offset(reg)); /* Result is returned in RAX register. TODO: Make this work when returning more than one result */ 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) { /* 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 */ IMPL_START impl->function_indices[impl->func_counter++] = asm_get_size(&impl->asm); asm_pushr(&impl->asm, RBX); asm_pushr(&impl->asm, RBP); asm_mov_rr(&impl->asm, RBP, RSP); asm_sub_rm64_imm(&impl->asm, RSP, num_regs * sizeof(isize)); return 0; } int amal_exec_func_end(amal_executor *self) { IMPL_START 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; asm_mov_rr(&impl->asm, RSP, RBP); asm_popr(&impl->asm, RBP); asm_popr(&impl->asm, RBX); asm_ret(&impl->asm, 0); return 0; } int amal_exec_label(amal_executor *self) { IMPL_START assert(impl->label_counter < MAX_LABELS); impl->label_asm_index[impl->label_counter++] = asm_get_size(&impl->asm); return 0; }