#include "../executor.h" #include "../../include/std/alloc.h" #include "../../include/std/buffer.h" #include "../../include/std/log.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 target_label; bool condition; } JumpDefer; typedef struct { Asm asm; u16 func_counter; Buffer/*JumpDefer*/ jump_defer; u32 label_asm_index[MAX_LABELS]; int label_counter; int num_args; int num_pushed_values; int num_saved_params_for_call; } amal_executor_impl; #define ASM_ENSURE_CAPACITY return_if_error(asm_ensure_capacity(&impl->asm, 256)); const Reg64 SYS_V_REG_PARAMS[] = { RDI, RSI, RDX, RCX, R8, R9, R10, R11 }; const int NUM_REG_PARAMS = 8; static i64 abs_i64(i64 value) { return value >= 0 ? value : -value; } typedef enum { OPERAND_TYPE_REG, OPERAND_TYPE_MEM } AsmOperandType; typedef struct { AsmOperandType type; union { AsmPtr mem; Reg64 reg; } value; } AsmOperand; static AsmOperand amal_reg_to_asm_operand(AmalReg reg) { AsmOperand result; AmalReg reg_value = AMAL_REG_VALUE(reg); if(reg & REG_FLAG_PARAM) { if(reg_value < NUM_REG_PARAMS) { result.type = OPERAND_TYPE_REG; result.value.reg = SYS_V_REG_PARAMS[reg_value]; } else { result.type = OPERAND_TYPE_MEM; asm_ptr_init_disp(&result.value.mem, RBP, (i32)reg_value * sizeof(usize) + 2 * sizeof(usize)); } } else { result.type = OPERAND_TYPE_MEM; asm_ptr_init_disp(&result.value.mem, RBP, (i32)-reg_value * sizeof(usize) - sizeof(usize)); } return result; } static AsmOperand asm_reg_to_operand(Reg64 reg) { AsmOperand result; result.type = OPERAND_TYPE_REG; result.value.reg = reg; return result; } /* Note: both operands can't be memory operands */ static void asm_mov(Asm *self, AsmOperand *dst, AsmOperand *src) { switch(dst->type) { case OPERAND_TYPE_REG: { switch(src->type) { case OPERAND_TYPE_REG: asm_mov_rr(self, dst->value.reg, src->value.reg); break; case OPERAND_TYPE_MEM: asm_mov_rm(self, dst->value.reg, &src->value.mem); break; } break; } case OPERAND_TYPE_MEM: { assert(src->type == OPERAND_TYPE_REG && "Both operands can't be memory operands"); asm_mov_mr(self, &dst->value.mem, src->value.reg); break; } } } static void asm_movi(Asm *self, AsmOperand *dst, i64 immediate) { switch(dst->type) { case OPERAND_TYPE_REG: asm_mov_ri(self, dst->value.reg, immediate); break; case OPERAND_TYPE_MEM: asm_mov_mi(self, &dst->value.mem, immediate); break; } } static void asm_cmp(Asm *self, AsmOperand *op1, AsmOperand *op2) { switch(op1->type) { case OPERAND_TYPE_REG: { switch(op2->type) { case OPERAND_TYPE_REG: asm_cmp_rm64(self, op1->value.reg, op2->value.reg); break; case OPERAND_TYPE_MEM: asm_cmp_rm(self, op1->value.reg, &op2->value.mem); break; } break; } case OPERAND_TYPE_MEM: { assert(op2->type == OPERAND_TYPE_REG && "Both operands can't be memory operands"); asm_cmp_rm(self, op2->value.reg, &op1->value.mem); break; } } } static void asm_call(Asm *self, AsmOperand *op) { switch(op->type) { case OPERAND_TYPE_REG: asm_callr(self, op->value.reg); break; case OPERAND_TYPE_MEM: asm_callm(self, &op->value.mem); break; } } 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)->func_counter = 0; (*impl)->label_counter = 0; (*impl)->num_args = 0; (*impl)->num_pushed_values = 0; (*impl)->num_saved_params_for_call = 0; ignore_result_int(buffer_init(&(*impl)->jump_defer, NULL)); 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); 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) { (void)self; (void)num_functions; return 0; } int amal_executor_instructions_end(amal_executor *self) { amal_executor_impl *impl = (amal_executor_impl*)self; impl->func_counter = 0; return 0; } int amal_exec_nop(amal_executor *self) { amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_nop(&impl->asm); return 0; } int amal_exec_setz(amal_executor *self, AmalReg dst_reg) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_movi(&impl->asm, &dst_op, 0); return 0; } int amal_exec_mov(amal_executor *self, AmalReg dst_reg, AmalReg src_reg) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand src_op = amal_reg_to_asm_operand(src_reg); AsmOperand rax_op = asm_reg_to_operand(RAX); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_mov(&impl->asm, &rax_op, &src_op); asm_mov(&impl->asm, &dst_op, &rax_op); return 0; } int amal_exec_movi(amal_executor *self, AmalReg dst_reg, i64 imm) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY /* TODO: if @number is a float then use float instructions */ if(abs_i64(imm) <= INT32_MAX) { asm_movi(&impl->asm, &dst_op, imm); } else { AsmOperand rax_op = asm_reg_to_operand(RAX); asm_movi(&impl->asm, &rax_op, imm); asm_mov(&impl->asm, &dst_op, &rax_op); } return 0; } int amal_exec_movd(amal_executor *self, AmalReg dst_reg, BufferView data) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand rax_op = asm_reg_to_operand(RAX); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_mov_ri(&impl->asm, rax_op.value.reg, (uintptr_t)data.data); asm_mov(&impl->asm, &dst_op, &rax_op); return 0; } int amal_exec_add(amal_executor *self, AmalReg dst_reg, AmalReg src_reg1, AmalReg src_reg2) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand src_op1 = amal_reg_to_asm_operand(src_reg1); AsmOperand src_op2 = amal_reg_to_asm_operand(src_reg2); AsmOperand rax_op = asm_reg_to_operand(RAX); AsmOperand rcx_op = asm_reg_to_operand(RCX); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_mov(&impl->asm, &rax_op, &src_op1); asm_mov(&impl->asm, &rcx_op, &src_op2); asm_add_rr(&impl->asm, RAX, RCX); asm_mov(&impl->asm, &dst_op, &rax_op); return 0; } int amal_exec_sub(amal_executor *self, AmalReg dst_reg, AmalReg src_reg1, AmalReg src_reg2) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand src_op1 = amal_reg_to_asm_operand(src_reg1); AsmOperand src_op2 = amal_reg_to_asm_operand(src_reg2); AsmOperand rax_op = asm_reg_to_operand(RAX); AsmOperand rcx_op = asm_reg_to_operand(RCX); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_mov(&impl->asm, &rax_op, &src_op1); asm_mov(&impl->asm, &rcx_op, &src_op2); asm_sub_rr(&impl->asm, RAX, RCX); asm_mov(&impl->asm, &dst_op, &rax_op); return 0; } int amal_exec_imul(amal_executor *self, AmalReg dst_reg, AmalReg src_reg1, AmalReg src_reg2) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand src_op1 = amal_reg_to_asm_operand(src_reg1); AsmOperand src_op2 = amal_reg_to_asm_operand(src_reg2); AsmOperand rax_op = asm_reg_to_operand(RAX); AsmOperand rcx_op = asm_reg_to_operand(RCX); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_mov(&impl->asm, &rax_op, &src_op1); asm_mov(&impl->asm, &rcx_op, &src_op2); asm_imul_rr(&impl->asm, RAX, RCX); asm_mov(&impl->asm, &dst_op, &rax_op); return 0; } int amal_exec_mul(amal_executor *self, AmalReg dst_reg, AmalReg src_reg1, AmalReg 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, AmalReg dst_reg, AmalReg src_reg1, AmalReg src_reg2) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand src_op1 = amal_reg_to_asm_operand(src_reg1); AsmOperand src_op2 = amal_reg_to_asm_operand(src_reg2); AsmOperand rax_op = asm_reg_to_operand(RAX); AsmOperand rcx_op = asm_reg_to_operand(RCX); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_mov(&impl->asm, &rax_op, &src_op1); asm_mov(&impl->asm, &rcx_op, &src_op2); asm_cqo(&impl->asm); asm_idiv_rax_r(&impl->asm, RCX); asm_mov(&impl->asm, &dst_op, &rax_op); return 0; } int amal_exec_div(amal_executor *self, AmalReg dst_reg, AmalReg src_reg1, AmalReg 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, AmalReg reg) { AsmOperand op = amal_reg_to_asm_operand(reg); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY if(impl->num_pushed_values < NUM_REG_PARAMS) { ++impl->num_saved_params_for_call; /* TODO: If the arguments to the function are taken from the parameter of the current function, then this can be optimized to either swap registers or no-op */ AsmOperand dst_reg = asm_reg_to_operand(SYS_V_REG_PARAMS[impl->num_pushed_values]); /* Backup parameter. TODO: Remove this, copy it to a temporary register instead. This should also only be done if the parameter is actually used in the current function and only if it is used after this point */ asm_pushr(&impl->asm, dst_reg.value.reg); asm_mov(&impl->asm, &dst_reg, &op); } else { AsmOperand rax_op = asm_reg_to_operand(RAX); asm_mov(&impl->asm, &rax_op, &op); asm_pushr(&impl->asm, RAX); } ++impl->num_pushed_values; 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_start(amal_executor *self, u8 num_args) { amal_executor_impl *impl = (amal_executor_impl*)self; impl->num_args = num_args; return 0; } int amal_exec_call(amal_executor *self, u32 code_offset, AmalReg dst_reg) { amal_executor_impl *impl = (amal_executor_impl*)self; /* TODO: Preserve necessary registers before call? */ /* TODO: This assumes all arguments are isize */ /* Do the function call */ isize asm_offset; /* TODO: Do not push */ int num_pushed_stack = impl->num_pushed_values;/* + impl->num_saved_params_for_call - (int)NUM_REG_PARAMS;*/ ASM_ENSURE_CAPACITY /*assert((num_pushed_stack <= 0 || num_pushed_stack % 2 == 0) && "TODO: Align stack to 16-bytes before calling functions");*/ if(num_pushed_stack & 1) { ++num_pushed_stack; asm_sub_rm64_imm(&impl->asm, RSP, sizeof(isize)); } asm_offset = asm_get_size(&impl->asm); assert(code_offset < asm_offset); asm_call_rel32(&impl->asm, (isize)code_offset - asm_offset); /* Function result */ { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand rax_op = asm_reg_to_operand(RAX); /* TODO: Make this work when result is not stored in RAX (multiple return results) */ asm_mov(&impl->asm, &dst_op, &rax_op); } /* Function cleanup */ if(num_pushed_stack > 0) asm_add_rm64_imm(&impl->asm, RSP, num_pushed_stack * sizeof(isize)); impl->num_pushed_values = 0; return 0; } void amal_exec_call_overwrite(amal_executor *self, u32 call_code_offset, i32 new_target_rel32) { amal_executor_impl *impl = (amal_executor_impl*)self; asm_overwrite_call_rel32(&impl->asm, call_code_offset, new_target_rel32); } /* 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, AmalReg dst_reg) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand rax_op = asm_reg_to_operand(RAX); amal_executor_impl *impl = (amal_executor_impl*)self; /* TODO: Do not push */ int num_pushed_stack = impl->num_pushed_values;/* + impl->num_saved_params_for_call - (int)NUM_REG_PARAMS;*/ ASM_ENSURE_CAPACITY /*assert((num_pushed_stack <= 0 || num_pushed_stack % 2 == 0) && "TODO: Align stack to 16-bytes before calling functions");*/ if(num_pushed_stack & 1) { ++num_pushed_stack; asm_sub_rm64_imm(&impl->asm, RSP, sizeof(isize)); } /* 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_mov(&impl->asm, &dst_op, &rax_op); if(num_pushed_stack > 0) asm_add_rm64_imm(&impl->asm, RSP, num_pushed_stack * sizeof(isize)); impl->num_pushed_values = 0; return 0; } int amal_exec_callr(amal_executor *self, AmalReg reg, AmalReg dst_reg) { amal_executor_impl *impl = (amal_executor_impl*)self; /* TODO: Preserve necessary registers before call? */ /* TODO: This assumes all arguments are isize */ /* TODO: Do not push */ int num_pushed_stack = impl->num_pushed_values;/* + impl->num_saved_params_for_call - (int)NUM_REG_PARAMS;*/ AsmOperand func_ptr_op; ASM_ENSURE_CAPACITY /*assert((num_pushed_stack <= 0 || num_pushed_stack % 2 == 0) && "TODO: Align stack to 16-bytes before calling functions");*/ if(num_pushed_stack & 1) { ++num_pushed_stack; asm_sub_rm64_imm(&impl->asm, RSP, sizeof(isize)); } func_ptr_op = amal_reg_to_asm_operand(reg); asm_call(&impl->asm, &func_ptr_op); /* Function result */ { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand rax_op = asm_reg_to_operand(RAX); /* TODO: Make this work when result is not stored in RAX (multiple return results) */ asm_mov(&impl->asm, &dst_op, &rax_op); } /* Function cleanup */ if(num_pushed_stack > 0) asm_add_rm64_imm(&impl->asm, RSP, num_pushed_stack * sizeof(isize)); impl->num_pushed_values = 0; return 0; } int amal_exec_eq(amal_executor *self, AmalReg dst_reg, AmalReg src_reg1, AmalReg src_reg2) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand src_op1 = amal_reg_to_asm_operand(src_reg1); AsmOperand src_op2 = amal_reg_to_asm_operand(src_reg2); AsmOperand rax_op = asm_reg_to_operand(RAX); AsmOperand rcx_op = asm_reg_to_operand(RCX); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_xor_rm64(&impl->asm, rcx_op.value.reg, rcx_op.value.reg); asm_mov(&impl->asm, &rax_op, &src_op1); asm_cmp(&impl->asm, &rax_op, &src_op2); asm_sete_r(&impl->asm, rcx_op.value.reg); asm_mov(&impl->asm, &dst_op, &rcx_op); return 0; } int amal_exec_neq(amal_executor *self, AmalReg dst_reg, AmalReg src_reg1, AmalReg src_reg2) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand src_op1 = amal_reg_to_asm_operand(src_reg1); AsmOperand src_op2 = amal_reg_to_asm_operand(src_reg2); AsmOperand rax_op = asm_reg_to_operand(RAX); AsmOperand rcx_op = asm_reg_to_operand(RCX); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_xor_rm64(&impl->asm, rcx_op.value.reg, rcx_op.value.reg); asm_mov(&impl->asm, &rax_op, &src_op1); asm_cmp(&impl->asm, &rax_op, &src_op2); asm_setne_r(&impl->asm, rcx_op.value.reg); asm_mov(&impl->asm, &dst_op, &rcx_op); return 0; } int amal_exec_ilt(amal_executor *self, AmalReg dst_reg, AmalReg src_reg1, AmalReg src_reg2) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand src_op1 = amal_reg_to_asm_operand(src_reg1); AsmOperand src_op2 = amal_reg_to_asm_operand(src_reg2); AsmOperand rax_op = asm_reg_to_operand(RAX); AsmOperand rcx_op = asm_reg_to_operand(RCX); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_xor_rm64(&impl->asm, rcx_op.value.reg, rcx_op.value.reg); asm_mov(&impl->asm, &rax_op, &src_op1); asm_cmp(&impl->asm, &rax_op, &src_op2); asm_setl_r(&impl->asm, rcx_op.value.reg); asm_mov(&impl->asm, &dst_op, &rcx_op); return 0; } int amal_exec_ile(amal_executor *self, AmalReg dst_reg, AmalReg src_reg1, AmalReg src_reg2) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand src_op1 = amal_reg_to_asm_operand(src_reg1); AsmOperand src_op2 = amal_reg_to_asm_operand(src_reg2); AsmOperand rax_op = asm_reg_to_operand(RAX); AsmOperand rcx_op = asm_reg_to_operand(RCX); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_xor_rm64(&impl->asm, rcx_op.value.reg, rcx_op.value.reg); asm_mov(&impl->asm, &rax_op, &src_op1); asm_cmp(&impl->asm, &rax_op, &src_op2); asm_setle_r(&impl->asm, rcx_op.value.reg); asm_mov(&impl->asm, &dst_op, &rcx_op); return 0; } int amal_exec_igt(amal_executor *self, AmalReg dst_reg, AmalReg src_reg1, AmalReg src_reg2) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand src_op1 = amal_reg_to_asm_operand(src_reg1); AsmOperand src_op2 = amal_reg_to_asm_operand(src_reg2); AsmOperand rax_op = asm_reg_to_operand(RAX); AsmOperand rcx_op = asm_reg_to_operand(RCX); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_xor_rm64(&impl->asm, rcx_op.value.reg, rcx_op.value.reg); asm_mov(&impl->asm, &rax_op, &src_op1); asm_cmp(&impl->asm, &rax_op, &src_op2); asm_setg_r(&impl->asm, rcx_op.value.reg); asm_mov(&impl->asm, &dst_op, &rcx_op); return 0; } int amal_exec_ige(amal_executor *self, AmalReg dst_reg, AmalReg src_reg1, AmalReg src_reg2) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand src_op1 = amal_reg_to_asm_operand(src_reg1); AsmOperand src_op2 = amal_reg_to_asm_operand(src_reg2); AsmOperand rax_op = asm_reg_to_operand(RAX); AsmOperand rcx_op = asm_reg_to_operand(RCX); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_xor_rm64(&impl->asm, rcx_op.value.reg, rcx_op.value.reg); asm_mov(&impl->asm, &rax_op, &src_op1); asm_cmp(&impl->asm, &rax_op, &src_op2); asm_setge_r(&impl->asm, rcx_op.value.reg); asm_mov(&impl->asm, &dst_op, &rcx_op); return 0; } int amal_exec_lt(amal_executor *self, AmalReg dst_reg, AmalReg src_reg1, AmalReg src_reg2) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand src_op1 = amal_reg_to_asm_operand(src_reg1); AsmOperand src_op2 = amal_reg_to_asm_operand(src_reg2); AsmOperand rax_op = asm_reg_to_operand(RAX); AsmOperand rcx_op = asm_reg_to_operand(RCX); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_xor_rm64(&impl->asm, rcx_op.value.reg, rcx_op.value.reg); asm_mov(&impl->asm, &rax_op, &src_op1); asm_cmp(&impl->asm, &rax_op, &src_op2); asm_setb_r(&impl->asm, rcx_op.value.reg); asm_mov(&impl->asm, &dst_op, &rcx_op); return 0; } int amal_exec_le(amal_executor *self, AmalReg dst_reg, AmalReg src_reg1, AmalReg src_reg2) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand src_op1 = amal_reg_to_asm_operand(src_reg1); AsmOperand src_op2 = amal_reg_to_asm_operand(src_reg2); AsmOperand rax_op = asm_reg_to_operand(RAX); AsmOperand rcx_op = asm_reg_to_operand(RCX); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_xor_rm64(&impl->asm, rcx_op.value.reg, rcx_op.value.reg); asm_mov(&impl->asm, &rax_op, &src_op1); asm_cmp(&impl->asm, &rax_op, &src_op2); asm_setbe_r(&impl->asm, rcx_op.value.reg); asm_mov(&impl->asm, &dst_op, &rcx_op); return 0; } int amal_exec_gt(amal_executor *self, AmalReg dst_reg, AmalReg src_reg1, AmalReg src_reg2) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand src_op1 = amal_reg_to_asm_operand(src_reg1); AsmOperand src_op2 = amal_reg_to_asm_operand(src_reg2); AsmOperand rax_op = asm_reg_to_operand(RAX); AsmOperand rcx_op = asm_reg_to_operand(RCX); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_xor_rm64(&impl->asm, rcx_op.value.reg, rcx_op.value.reg); asm_mov(&impl->asm, &rax_op, &src_op1); asm_cmp(&impl->asm, &rax_op, &src_op2); asm_seta_r(&impl->asm, rcx_op.value.reg); asm_mov(&impl->asm, &dst_op, &rcx_op); return 0; } int amal_exec_ge(amal_executor *self, AmalReg dst_reg, AmalReg src_reg1, AmalReg src_reg2) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand src_op1 = amal_reg_to_asm_operand(src_reg1); AsmOperand src_op2 = amal_reg_to_asm_operand(src_reg2); AsmOperand rax_op = asm_reg_to_operand(RAX); AsmOperand rcx_op = asm_reg_to_operand(RCX); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_xor_rm64(&impl->asm, rcx_op.value.reg, rcx_op.value.reg); asm_mov(&impl->asm, &rax_op, &src_op1); asm_cmp(&impl->asm, &rax_op, &src_op2); asm_setae_r(&impl->asm, rcx_op.value.reg); asm_mov(&impl->asm, &dst_op, &rcx_op); return 0; } int amal_exec_and(amal_executor *self, AmalReg dst_reg, AmalReg src_reg1, AmalReg src_reg2) { AsmOperand dst_op = amal_reg_to_asm_operand(dst_reg); AsmOperand src_op1 = amal_reg_to_asm_operand(src_reg1); AsmOperand src_op2 = amal_reg_to_asm_operand(src_reg2); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_and_rm64(&impl->asm, src_op1.value.reg, src_op2.value.reg); asm_mov(&impl->asm, &dst_op, &src_op1); return 0; } int amal_exec_jz(amal_executor *self, AmalReg reg, u16 target_label) { AsmOperand op = amal_reg_to_asm_operand(reg); AsmOperand rax_op = asm_reg_to_operand(RAX); u32 asm_offset; amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY asm_mov(&impl->asm, &rax_op, &op); asm_cmp_rm64_imm(&impl->asm, rax_op.value.reg, 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) { amal_executor_impl *impl = (amal_executor_impl*)self; u32 asm_offset = asm_get_size(&impl->asm); ASM_ENSURE_CAPACITY 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, AmalReg reg) { AsmOperand op = amal_reg_to_asm_operand(reg); AsmOperand rax_op = asm_reg_to_operand(RAX); amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY /* Result is returned in RAX register. TODO: Make this work when returning more than one result */ asm_mov(&impl->asm, &rax_op, &op); return amal_exec_func_end(self); } static u32 get_next_uneven_number(u32 value) { return value + !(value & 1); } int amal_exec_func_start(amal_executor *self, u8 num_params, 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 */ amal_executor_impl *impl = (amal_executor_impl*)self; ASM_ENSURE_CAPACITY (void)num_params; /* TODO: Allow use of parameter registers that do not need to be preserved since they are not used */ asm_pushr(&impl->asm, RBX); asm_pushr(&impl->asm, RBP); asm_mov_rr(&impl->asm, RBP, RSP); /* Functions are entered with a stack alignment of 8 (because of call return address is pushed to stack). Make sure to align to to next 16-byte even if the extra bytes are not used. */ asm_sub_rm64_imm(&impl->asm, RSP, get_next_uneven_number(num_regs) * sizeof(isize)); return 0; } int amal_exec_func_end(amal_executor *self) { amal_executor_impl *impl = (amal_executor_impl*)self; JumpDefer *jump_defer = buffer_begin(&impl->jump_defer); JumpDefer *jump_defer_end = buffer_end(&impl->jump_defer); ASM_ENSURE_CAPACITY 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_overwrite_jcc_rel32(&impl->asm, jump_defer->asm_index, jump_offset); else asm_overwrite_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) { amal_executor_impl *impl = (amal_executor_impl*)self; assert(impl->label_counter < MAX_LABELS); impl->label_asm_index[impl->label_counter++] = asm_get_size(&impl->asm); return 0; }