/* * jit-rules-alpha.c - Rules that define the characteristics of the alpha. * * Copyright (C) 2006 Southern Storm Software, Pty Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "jit-internal.h" #include "jit-rules.h" #include "jit-apply-rules.h" #if defined(JIT_BACKEND_ALPHA) #include "jit-elf-defs.h" #include "jit-gen-alpha.h" #include "jit-reg-alloc.h" #include "jit-setjmp.h" #include /* * _alpha_has_ieeefp() * * When the Alpha architecture's floating point unit was first designed, * the designers traded performance for functionality. As a result, all * Alpha systems below EV6 do not fully implement the IEEE floating * point standard. For those earlier systems, there is no hardware * support for denormalized numbers or exceptional IEEE values like not * a number and positive/negative infinity. For systems without hardware * support, the kernal can assist, but more you'll need to add * instructions to trap into the kernel. Use this function to determine * if hardware ieeefp is available. * * To get the kernel to assist when needed, use the following code: * * if (!_alpha_has_ieeefp()) * alpha_trapb(inst); * * RETURN VALUE: * - TRUE if the CPU fully supports IEEE floating-point (i.e. >=ev6) * - FALSE if the CPU needs kernel assistance */ int _alpha_has_ieeefp() { unsigned long __implver; /* * __implver - major version number of the processor * * (__implver == 0) ev4 class processors * (__implver == 1) ev5 class processors * (__implver == 2) ev6 class processors */ __asm__ ("implver %0" : "=r"(__implver)); return (__implver >= 2); } /* * Round a size up to a multiple of the stack word size. */ #define ROUND_STACK(size) \ (((size) + (sizeof(alpha_inst) - 1)) & ~(sizeof(alpha_inst) - 1)) /* * Setup or teardown the alpha code output process. */ #define jit_cache_setup_output(needed) \ alpha_inst inst = (alpha_inst) gen->posn.ptr; \ if(!jit_cache_check_for_n(&(gen->posn), (needed))) { \ jit_cache_mark_full(&(gen->posn)); \ return; \ } \ #define jit_cache_end_output() \ gen->posn.ptr = (unsigned char*) inst /* * Load the instruction pointer from the generation context. */ #define jit_gen_load_inst_ptr(gen,inst) \ inst = (alpha_inst) (gen)->posn.ptr; /* * Save the instruction pointer back to the generation context. */ #define jit_gen_save_inst_ptr(gen,inst) \ (gen)->posn.ptr = (unsigned char *) inst; static _jit_regclass_t *alpha_reg; static _jit_regclass_t *alpha_freg; /* * Initialize the backend. This is normally used to configure registers * that may not appear on all CPU's in a given family. For example, only * some ARM cores have floating-point registers. */ void _jit_init_backend(void) { alpha_reg = _jit_regclass_create("reg", JIT_REG_WORD | JIT_REG_LONG, 32, ALPHA_R0, ALPHA_R1, ALPHA_R2, ALPHA_R3, ALPHA_R4, ALPHA_R5, ALPHA_R6, ALPHA_R7, ALPHA_R8, ALPHA_R9, ALPHA_R10, ALPHA_R11, ALPHA_R12, ALPHA_R13, ALPHA_R14, ALPHA_R15, ALPHA_R16, ALPHA_R17, ALPHA_R18, ALPHA_R19, ALPHA_R20, ALPHA_R21, ALPHA_R22, ALPHA_R23, ALPHA_R24, ALPHA_R25, ALPHA_R26, ALPHA_R27, ALPHA_R28, ALPHA_R29, ALPHA_R30, ALPHA_R31 ); alpha_freg = _jit_regclass_create("freg", JIT_REG_FLOAT32 | JIT_REG_FLOAT64 | JIT_REG_NFLOAT, 32, ALPHA_F0, ALPHA_F1, ALPHA_F2, ALPHA_F3, ALPHA_F4, ALPHA_F5, ALPHA_F6, ALPHA_F7, ALPHA_F8, ALPHA_F9, ALPHA_F10, ALPHA_F11, ALPHA_F12, ALPHA_F13, ALPHA_F14, ALPHA_F15, ALPHA_F16, ALPHA_F17, ALPHA_F18, ALPHA_F19, ALPHA_F20, ALPHA_F21, ALPHA_F22, ALPHA_F23, ALPHA_F24, ALPHA_F25, ALPHA_F26, ALPHA_F27, ALPHA_F28, ALPHA_F29, ALPHA_F30, ALPHA_F31 ); } #define TODO() \ do { \ fprintf(stderr, "TODO at %s, %d\n", __FILE__, (int)__LINE__); \ } while (0) /* * Get the ELF machine and ABI type information for this platform. The * machine field should be set to one of the EM_* values in * jit-elf-defs.h. The abi field should be set to one of the ELFOSABI_* * values in jit-elf-defs.h (ELFOSABI_SYSV will normally suffice if you * are unsure). The abi_version field should be set to the ABI version, * which is usually zero. */ void _jit_gen_get_elf_info(jit_elf_info_t *info) { info->machine = EM_ALPHA; info->abi = ELFOSABI_SYSV; info->abi_version = 0; } /* * Generate the prolog for a function into a previously-prepared buffer * area of JIT_PROLOG_SIZE bytes in size. Returns the start of the * prolog, which may be different than buf. * * This function is called at the end of the code generation process, * not the beginning. At this point, it is known which callee save * registers must be preserved, allowing the back end to output the * most compact prolog possible. */ void *_jit_gen_prolog(jit_gencode_t gen, jit_function_t func, void *buf) { unsigned int prolog[JIT_PROLOG_SIZE]; alpha_inst inst = prolog; /* Compute and load the global pointer (2 instruction) */ alpha_ldah(inst,ALPHA_GP,ALPHA_PV,0); alpha_lda( inst,ALPHA_GP,ALPHA_GP,0); /* Allocate space for a new stack frame. (1 instruction) */ alpha_lda(inst,ALPHA_SP,ALPHA_SP,-(2*8)); /* Save the return address. (1 instruction) */ alpha_stq(inst,ALPHA_RA,ALPHA_SP,0*8); /* Save the frame pointer. (1 instruction) */ alpha_stq(inst,ALPHA_FP,ALPHA_SP,1*8); /* Set the frame pointer (1 instruction) */ alpha_mov(inst,ALPHA_SP,ALPHA_FP); /* Force any pending hardware exceptions to be raised. (1 instruction) */ alpha_trapb(inst); /* Copy the prolog into place and return the entry position */ jit_memcpy(buf, prolog, JIT_PROLOG_SIZE); return (void *) buf; } /* * Generate a function epilog, restoring the registers that were saved on entry to the * function, and then returning. * * Only one epilog is generated per function. Functions with multiple jit_insn_return * instructions will all jump to the common epilog. This is needed because the code * generator may not know which callee save registers need to be restored by the * epilog until the full function has been processed. */ void _jit_gen_epilog(jit_gencode_t gen, jit_function_t func) { void **fixup, **next; jit_cache_setup_output(20); /* Perform fixups on any blocks that jump to the epilog */ fixup = (void **)(gen->epilog_fixup); while (fixup) { alpha_inst code = (alpha_inst) fixup; next = (void **)(fixup[0]); _alpha_li64(code,ALPHA_AT,(long int)inst); alpha_jmp(code,ALPHA_ZERO,ALPHA_AT,1); fixup = next; } /* Set the stack pointer (1 instruction) */ alpha_mov(inst,ALPHA_FP,ALPHA_SP); /* Restore the return address. (1 instruction) */ alpha_ldq(inst,ALPHA_RA,ALPHA_SP,0*8); /* Restore the frame pointer. (1 instruction) */ alpha_ldq(inst,ALPHA_FP,ALPHA_SP,1*8); /* Restore the stack pointer (1 instruction) */ alpha_lda(inst,ALPHA_SP,ALPHA_SP,16*8); /* Force any pending hardware exceptions to be raised. (1 instruction) */ alpha_trapb(inst); /* Return from the current function (1 instruction) */ alpha_ret(inst,ALPHA_RA,1); jit_cache_end_output(); } /* * Create instructions within func to clean up after a function call and * to place the function's result into return_value. This should use * jit_insn_pop_stack to pop values off the system stack and * jit_insn_return_reg to tell libjit which register contains the return * value. In the case of a void function, return_value will be NULL. * * Note: the argument values are passed again because it may not be * possible to determine how many bytes to pop from the stack from the * signature alone; especially if the called function is vararg. */ int _jit_create_call_return_insns(jit_function_t func, jit_type_t signature, jit_value_t *args, unsigned int num_args, jit_value_t return_value, int is_nested) { jit_type_t return_type; int ptr_return; return_type = jit_type_normalize(jit_type_get_return(signature)); ptr_return = jit_type_return_via_pointer(return_type); /* Bail out now if we don't need to worry about return values */ if (!return_value || ptr_return) { return 0; } /* * Structure values must be flushed into the frame, and * everything else ends up in a register */ if (jit_type_is_struct(return_type) || jit_type_is_union(return_type)) { if (!jit_insn_flush_struct(func, return_value)) { return 0; } } else if (return_type->kind == JIT_TYPE_FLOAT32 || return_type->kind == JIT_TYPE_FLOAT64 || return_type->kind == JIT_TYPE_NFLOAT) { if (!jit_insn_return_reg(func, return_value, 32 /* fv0 */)) { return 0; } } else if (return_type->kind != JIT_TYPE_VOID) { if (!jit_insn_return_reg(func, return_value, 0 /* v0 */)) { return 0; } } /* Everything is back where it needs to be */ return 1; } /* * Place the indirect function pointer value into a suitable register or * stack location for a subsequent indirect call. */ int _jit_setup_indirect_pointer(jit_function_t func, jit_value_t value) { TODO(); return 0; } void _jit_gen_exch_top(jit_gencode_t gen, int reg) { /* not used by alpha */; } void _jit_gen_move_top(jit_gencode_t gen, int reg) { /* not used by alpha */; } void _jit_gen_spill_top(jit_gencode_t gen, int reg, jit_value_t value, int pop) { /* not used by alpha */; } void _jit_gen_spill_global(jit_gencode_t gen, int reg, jit_value_t value) { /* not used by alpha */; } /* * Generate instructions to spill a pseudo register to the local variable frame. If * other_reg is not -1, then it indicates the second register in a 64-bit register pair. * * This function will typically call _jit_gen_fix_value to fix the value's frame * position, and will then generate the appropriate spill instructions. */ void _jit_gen_spill_reg(jit_gencode_t gen, int reg, int other_reg, jit_value_t value) { int offset; /* Make sure that we have sufficient space */ jit_cache_setup_output(32); /* If the value is associated with a global register, then copy to that */ if (value->has_global_register) { alpha_mov(inst,_jit_reg_info[reg].cpu_reg,_jit_reg_info[value->global_reg].cpu_reg); jit_cache_end_output(); return; } /* Fix the value in place within the local variable frame */ _jit_gen_fix_value(value); /* Output an appropriate instruction to spill the value */ offset = (int)(value->frame_offset); if (reg < 32) { /* if integer register */ alpha_stq(inst,reg,ALPHA_FP,offset); if (other_reg != -1) { offset += sizeof(void *); alpha_stq(inst,other_reg,ALPHA_FP,offset); } } else /* floating point register */ { /* TODO requires floating point support */ } jit_cache_end_output(); return; } /* * Generate instructions to free a register without spilling its value. This is called * when a register's contents become invalid, or its value is no longer required. If * value_used is set to a non-zero value, then it indicates that the register's value * was just used. Otherwise, there is a value in the register but it was never used. * * On most platforms, this function won't need to do anything to free the register. * But some do need to take explicit action. For example, x86 needs an explicit * instruction to remove a floating-point value from the FPU's stack if its value has * not been used yet. */ void _jit_gen_free_reg(jit_gencode_t gen, int reg, int other_reg, int value_used) { /* Nothing to do here */; } /* * Not all CPU's support all arithmetic, conversion, bitwise, or comparison operators * natively. For example, most ARM platforms need to call out to helper functions to * perform floating-point. * * If this function returns zero, then jit-insn.c will output a call to an intrinsic * function that is equivalent to the desired opcode. This is how you tell libjit that * you cannot handle the opcode natively. * * This function can also help you develop your back end incrementally. Initially, you * can report that only integer operations are supported, and then once you have them * working you can move on to the floating point operations. * * Since alpha processors below ev6 need help with floating-point, we'll use the * intrinsic floating-point functions on this systems. */ int _jit_opcode_is_supported(int opcode) { switch(opcode) { #define JIT_INCLUDE_SUPPORTED #include "jit-rules-alpha.inc" #undef JIT_INCLUDE_SUPPORTED } return 0; } /* * Determine if type is a candidate for allocation within global registers. */ int _jit_gen_is_global_candidate(jit_type_t type) { switch(jit_type_remove_tags(type)->kind) { case JIT_TYPE_INT: case JIT_TYPE_UINT: case JIT_TYPE_NINT: case JIT_TYPE_NUINT: case JIT_TYPE_PTR: case JIT_TYPE_SIGNATURE: return 1; } return 0; } /* * Called to notify the back end that the start of a basic block has been reached. */ void _jit_gen_start_block(jit_gencode_t gen, jit_block_t block) { void **fixup, **next; /* Set the address of this block */ block->address = (void *)(gen->posn.ptr); /* If this block has pending fixups, then apply them now */ fixup = (void **)(block->fixup_list); while (fixup) { alpha_inst code = (alpha_inst) fixup; next = (void **)(fixup[0]); _alpha_li64(code,ALPHA_AT,(long int)(gen->posn.ptr)); alpha_jmp(code,ALPHA_ZERO,ALPHA_AT,1); fixup = next; } block->fixup_list = 0; } /* * Called to notify the back end that the end of a basic block has been reached. */ void _jit_gen_end_block(jit_gencode_t gen, jit_block_t block) { /* Nothing to do here */; } /* * Generate instructions to load a value into a register. The value will either be a * constant or a slot in the frame. You should fix frame slots with * _jit_gen_fix_value. */ void _jit_gen_load_value(jit_gencode_t gen, int reg, int other_reg, jit_value_t value) { short int offset; /* Make sure that we have sufficient space */ jit_cache_setup_output(32); if (value->is_constant) { /* Determine the type of constant to be loaded */ switch(jit_type_normalize(value->type)->kind) { case JIT_TYPE_SBYTE: case JIT_TYPE_UBYTE: case JIT_TYPE_SHORT: case JIT_TYPE_USHORT: case JIT_TYPE_INT: case JIT_TYPE_UINT: case JIT_TYPE_LONG: case JIT_TYPE_ULONG: alpha_li(inst,_jit_reg_info[reg].cpu_reg,(jit_nint)(value->address)); break; /* TODO (needs floating-point case JIT_TYPE_FLOAT32: case JIT_TYPE_FLOAT64: case JIT_TYPE_NFLOAT: break; */ default: break; } } else if (value->in_register || value->in_global_register) { /* mov from value->reg to _jit_reg_info[reg].cpu_reg */ alpha_mov(inst,value->reg,_jit_reg_info[reg].cpu_reg); } else { /* Fix the position of the value in the stack frame */ _jit_gen_fix_value(value); offset = (int)(value->frame_offset); /* Load the value into the specified register */ switch (jit_type_normalize(value->type)->kind) { case JIT_TYPE_SBYTE: /* TODO add alpha_ldb alpha_ldb(inst,_jit_reg_info[reg].cpu_reg,ALPHA_SP,offset); */ break; case JIT_TYPE_UBYTE: alpha_ldbu(inst,_jit_reg_info[reg].cpu_reg,ALPHA_SP,offset); break; case JIT_TYPE_SHORT: /* TODO add alpha_ldw alpha_ldw(inst,_jit_reg_info[reg].cpu_reg,ALPHA_SP,offset); */ break; case JIT_TYPE_USHORT: alpha_ldwu(inst,_jit_reg_info[reg].cpu_reg,ALPHA_SP,offset); break; case JIT_TYPE_INT: alpha_ldl(inst,_jit_reg_info[reg].cpu_reg,ALPHA_SP,offset); break; case JIT_TYPE_UINT: /* TODO add alpha_ldlu alpha_ldlu(inst,_jit_reg_info[reg].cpu_reg,ALPHA_SP,offset); */ break; case JIT_TYPE_LONG: alpha_ldq(inst,_jit_reg_info[reg].cpu_reg,ALPHA_SP,offset); break; case JIT_TYPE_ULONG: /* TODO add alpha_ldqu alpha_ldqu(inst,_jit_reg_info[reg].cpu_reg,ALPHA_SP,offset); */ break; /* TODO requires floating-point support */ case JIT_TYPE_FLOAT32: case JIT_TYPE_FLOAT64: case JIT_TYPE_NFLOAT: break; } } jit_cache_end_output(); } void _jit_gen_load_global(jit_gencode_t gen, int reg, jit_value_t value) { TODO(); } /* * Generate code for a redirector, which makes an indirect jump to the contents of * func->entry_point. Redirectors are used on recompilable functions in place of the * regular entry point. This allows libjit to redirect existing calls to the new * version after recompilation. */ void *_jit_gen_redirector(jit_gencode_t gen, jit_function_t func) { void *ptr, *entry; alpha_inst inst = (alpha_inst) gen->posn.ptr; if (!jit_cache_check_for_n(&(gen->posn), 8*6)) { jit_cache_mark_full(&(gen->posn)); return NULL; } ptr = (void *)&(func->entry_point); entry = gen->posn.ptr; alpha_call(inst, ptr); return entry; } /* * Generate native code for the specified insn. This function should call the * appropriate register allocation routines, output the instruction, and then arrange * for the result to be placed in an appropriate register or memory destination. */ void _jit_gen_insn(jit_gencode_t gen, jit_function_t func, jit_block_t block, jit_insn_t insn) { switch (insn->opcode) { #define JIT_INCLUDE_RULES #include "jit-rules-alpha.inc" #undef JIT_INCLUDE_RULES default: fprintf(stderr, "TODO(%x) at %s, %d\n", (int)(insn->opcode), __FILE__, (int)__LINE__); break; } } void _jit_gen_fix_value(jit_value_t value) { if (!(value->has_frame_offset) && !(value->is_constant)) { jit_nint size = (jit_nint)(ROUND_STACK(jit_type_get_size(value->type))); value->block->func->builder->frame_size += size; value->frame_offset = -(value->block->func->builder->frame_size); value->has_frame_offset = 1; } } /* * Output a branch instruction. */ void alpha_output_branch(jit_function_t func, alpha_inst inst, int opcode, jit_insn_t insn, int reg) { jit_block_t block; if (!(block = jit_block_from_label(func, (jit_label_t)(insn->dest)))) return; if (block->address) { /* We already know the address of the block */ short offset = ((unsigned long) block->address - (unsigned long) inst); alpha_encode_branch(inst,opcode,reg,offset); } else { long *addr = (void*) inst; /* Output a placeholder and record on the block's fixup list */ *addr = (long) block->fixup_list; inst++; inst++; _jit_pad_buffer((unsigned char*)inst,6); } } /* * Jump to the current function's epilog. */ void jump_to_epilog(jit_gencode_t gen, alpha_inst inst, jit_block_t block) { long *addr = (void*) inst; /* * If the epilog is the next thing that we will output, * then fall through to the epilog directly. */ block = block->next; while (!block && block->first_insn > block->last_insn) block = block->next; if (!block) return; /* * fixups are slightly strange for the alpha port. On alpha you * cannot use an address stored in memory for jumps. The address * has to stored in a register. * * The fixups need the address stored in memory so that they can * be 'fixed up' later. So what we do here is output the address * and some nops. When it gets 'fixed up' we replace the address * and 4 no-ops with opcodes to load the address into a register. * Then we overwrite the last no-op with a jump opcode. */ /* Output a placeholder for the jump and add it to the fixup list */ *addr = (long) gen->epilog_fixup; inst++; inst++; _jit_pad_buffer((unsigned char*)inst,6); /* to be overwritten later with jmp */ (gen)->posn.ptr = (unsigned char*) inst; } #endif /* JIT_BACKEND_ALPHA */