/* src/vm/jit/s390/emit.c - s390 code emitter functions Copyright (C) 1996-2005, 2006, 2007 R. Grafl, A. Krall, C. Kruegel, C. Oates, R. Obermaisser, M. Platter, M. Probst, S. Ring, E. Steiner, C. Thalinger, D. Thuernbeck, P. Tomsich, C. Ullrich, J. Wenninger, Institut f. Computersprachen - TU Wien This file is part of CACAO. 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config.h" #include #include #include "mm/memory.h" #if defined(ENABLE_THREADS) # include "threads/native/lock.h" #endif #include "vm/builtin.h" #include "vm/exceptions.h" #include "vm/global.h" #include "vm/jit/abi.h" #include "vm/jit/abi-asm.h" #include "vm/jit/asmpart.h" #include "vm/jit/codegen-common.h" #include "vm/jit/emit-common.h" #include "vm/jit/jit.h" #include "vm/jit/patcher-common.h" #include "vm/jit/replace.h" #include "vm/jit/trace.h" #include "vm/jit/s390/codegen.h" #include "vm/jit/s390/emit.h" #include "vm/jit/s390/md-abi.h" #include "vm/types.h" #include "vmcore/options.h" /* emit_load ******************************************************************* Emits a possible load of an operand. *******************************************************************************/ s4 emit_load(jitdata *jd, instruction *iptr, varinfo *src, s4 tempreg) { codegendata *cd; s4 disp; s4 reg; /* get required compiler data */ cd = jd->cd; if (IS_INMEMORY(src->flags)) { COUNT_SPILLS; disp = src->vv.regoff; if (IS_FLT_DBL_TYPE(src->type)) { if (IS_2_WORD_TYPE(src->type)) M_DLD(tempreg, REG_SP, disp); else M_FLD(tempreg, REG_SP, disp); } else { if (IS_2_WORD_TYPE(src->type)) M_LLD(tempreg, REG_SP, disp); else M_ILD(tempreg, REG_SP, disp); } reg = tempreg; } else reg = src->vv.regoff; return reg; } /* emit_store ****************************************************************** This function generates the code to store the result of an operation back into a spilled pseudo-variable. If the pseudo-variable has not been spilled in the first place, this function will generate nothing. *******************************************************************************/ void emit_store(jitdata *jd, instruction *iptr, varinfo *dst, s4 d) { codegendata *cd; /* get required compiler data */ cd = jd->cd; if (IS_INMEMORY(dst->flags)) { COUNT_SPILLS; if (IS_FLT_DBL_TYPE(dst->type)) { if (IS_2_WORD_TYPE(dst->type)) M_DST(d, REG_SP, dst->vv.regoff); else M_FST(d, REG_SP, dst->vv.regoff); } else { if (IS_2_WORD_TYPE(dst->type)) M_LST(d, REG_SP, dst->vv.regoff); else M_IST(d, REG_SP, dst->vv.regoff); } } } /* emit_copy ******************************************************************* Generates a register/memory to register/memory copy. *******************************************************************************/ void emit_copy(jitdata *jd, instruction *iptr) { codegendata *cd; varinfo *src; varinfo *dst; s4 s1, d; /* get required compiler data */ cd = jd->cd; /* get source and destination variables */ src = VAROP(iptr->s1); dst = VAROP(iptr->dst); if ((src->vv.regoff != dst->vv.regoff) || ((src->flags ^ dst->flags) & INMEMORY)) { if ((src->type == TYPE_RET) || (dst->type == TYPE_RET)) { /* emit nothing, as the value won't be used anyway */ return; } if (IS_INMEMORY(src->flags) && IS_INMEMORY(dst->flags)) { if (IS_2_WORD_TYPE(src->type)) { N_MVC(dst->vv.regoff, 8, REG_SP, src->vv.regoff, REG_SP); } else { N_MVC(dst->vv.regoff, 4, REG_SP, src->vv.regoff, REG_SP); } } else { /* If one of the variables resides in memory, we can eliminate the register move from/to the temporary register with the order of getting the destination register and the load. */ if (IS_INMEMORY(src->flags)) { if (IS_FLT_DBL_TYPE(dst->type)) { d = codegen_reg_of_var(iptr->opc, dst, REG_FTMP1); } else { if (IS_2_WORD_TYPE(dst->type)) { d = codegen_reg_of_var(iptr->opc, dst, REG_ITMP12_PACKED); } else { d = codegen_reg_of_var(iptr->opc, dst, REG_ITMP1); } } s1 = emit_load(jd, iptr, src, d); } else { if (IS_FLT_DBL_TYPE(src->type)) { s1 = emit_load(jd, iptr, src, REG_FTMP1); } else { if (IS_2_WORD_TYPE(src->type)) { s1 = emit_load(jd, iptr, src, REG_ITMP12_PACKED); } else { s1 = emit_load(jd, iptr, src, REG_ITMP1); } } d = codegen_reg_of_var(iptr->opc, dst, s1); } if (s1 != d) { if (IS_FLT_DBL_TYPE(src->type)) { M_FMOV(s1, d); } else { if (IS_2_WORD_TYPE(src->type)) { M_LNGMOVE(s1, d); } else { M_MOV(s1, d); } } } emit_store(jd, iptr, dst, d); } } } /* emit_trap ******************************************************************* Emit a trap instruction and return the original machine code. *******************************************************************************/ uint32_t emit_trap(codegendata *cd) { uint32_t mcode; /* Get machine code which is patched back in later. The trap is 2 bytes long. */ mcode = *((u2 *) cd->mcodeptr); M_ILL(EXCEPTION_HARDWARE_PATCHER); return mcode; } /* emit_verbosecall_enter ****************************************************** Generates the code for the call trace. *******************************************************************************/ #if !defined(NDEBUG) void emit_verbosecall_enter(jitdata *jd) { methodinfo *m; codegendata *cd; methoddesc *md; s4 stackframesize; s4 i, off, disp, s; m = jd->m; cd = jd->cd; md = m->parseddesc; /* mark trace code */ M_NOP; /* allocate stack frame */ stackframesize = 96 + (md->paramcount * 8); /* for leaf methods we need to store unused argument and temporary registers */ if (jd->isleafmethod) { stackframesize += (ARG_CNT + TMP_CNT) * 8; } /* allocate stack frame */ M_ASUB_IMM(stackframesize, REG_SP); /* store argument registers in array */ off = 96; for (i = 0; i < md->paramcount; i++) { if (! md->params[i].inmemory) { s = md->params[i].regoff; switch (md->paramtypes[i].type) { case TYPE_INT: case TYPE_ADR: M_IST(s, REG_SP, off); break; case TYPE_LNG: M_LST(s, REG_SP, off); break; case TYPE_FLT: M_FST(s, REG_SP, off); break; case TYPE_DBL: M_DST(s, REG_SP, off); break; } } off += 8; } /* save unused (currently all) argument registers for leaf methods */ /* save temporary registers for leaf methods */ if (jd->isleafmethod) { for (i = 0; i < INT_ARG_CNT; ++i, off += 8) { M_IST(abi_registers_integer_argument[i], REG_SP, off); } for (i = 0; i < FLT_ARG_CNT; ++i, off += 8) { M_DST(abi_registers_float_argument[i], REG_SP, off); } for (i = 0; i < INT_TMP_CNT; ++i, off += 8) { M_IST(abi_registers_integer_temporary[i], REG_SP, off); } for (i = 0; i < FLT_TMP_CNT; ++i, off += 8) { M_DST(abi_registers_float_temporary[i], REG_SP, off); } } /* load arguments for trace_java_call_enter */ /* methodinfo */ disp = dseg_add_address(cd, m); M_ALD_DSEG(REG_A0, disp); /* pointer to argument registers array */ M_LDA(REG_A1, REG_SP, 96); /* pointer to on stack arguments */ M_LDA(REG_A2, REG_SP, stackframesize + (cd->stackframesize * 8)); /* call trace_java_call_enter */ disp = dseg_add_functionptr(cd, trace_java_call_enter); M_ALD_DSEG(REG_ITMP2, disp); M_CALL(REG_ITMP2); /* restore used argument registers */ /* for leaf methods restore all argument and temporary registers */ if (jd->isleafmethod) { off = 96 + (8 * md->paramcount); for (i = 0; i < INT_ARG_CNT; ++i, off += 8) { M_ILD(abi_registers_integer_argument[i], REG_SP, off); } for (i = 0; i < FLT_ARG_CNT; ++i, off += 8) { M_DLD(abi_registers_float_argument[i], REG_SP, off); } for (i = 0; i < INT_TMP_CNT; ++i, off += 8) { M_ILD(abi_registers_integer_temporary[i], REG_SP, off); } for (i = 0; i < FLT_TMP_CNT; ++i, off += 8) { M_DLD(abi_registers_float_temporary[i], REG_SP, off); } } else { off = 96; for (i = 0; i < md->paramcount; i++) { if (! md->params[i].inmemory) { s = md->params[i].regoff; switch (md->paramtypes[i].type) { case TYPE_INT: case TYPE_ADR: M_ILD(s, REG_SP, off); break; case TYPE_LNG: M_LLD(s, REG_SP, off); break; case TYPE_FLT: M_FLD(s, REG_SP, off); break; case TYPE_DBL: M_DLD(s, REG_SP, off); break; } } off += 8; } } /* remove stack frame */ M_AADD_IMM(stackframesize, REG_SP); /* mark trace code */ M_NOP; } #endif /* !defined(NDEBUG) */ /* emit_verbosecall_exit ******************************************************* Generates the code for the call trace. *******************************************************************************/ #if !defined(NDEBUG) void emit_verbosecall_exit(jitdata *jd) { methodinfo *m; codegendata *cd; s4 disp; s4 stackframesize; s4 off; s4 t; m = jd->m; cd = jd->cd; t = m->parseddesc->returntype.type; /* mark trace code */ M_NOP; /* allocate stackframe */ stackframesize = 96 + (1 * 8); M_ASUB_IMM(stackframesize, REG_SP); off = 96; /* store return values in array */ if (IS_INT_LNG_TYPE(t)) { if (IS_2_WORD_TYPE(t)) { M_LST(REG_RESULT_PACKED, REG_SP, off); } else { M_IST(REG_RESULT, REG_SP, off); } } else { M_DST(REG_FRESULT, REG_SP, off); } /* call trace_java_call_exit */ disp = dseg_add_address(cd, m); M_ALD_DSEG(REG_A0, disp); M_LDA(REG_A1, REG_SP, off); disp = dseg_add_functionptr(cd, trace_java_call_exit); M_ALD_DSEG(REG_ITMP2, disp); M_CALL(REG_ITMP2); /* restore return value */ if (IS_INT_LNG_TYPE(t)) { if (IS_2_WORD_TYPE(t)) { M_LLD(REG_RESULT_PACKED, REG_SP, off); } else { M_ILD(REG_RESULT, REG_SP, off); } } else { M_DLD(REG_FRESULT, REG_SP, off); } /* remove stackframe */ M_AADD_IMM(stackframesize, REG_SP); /* mark trace code */ M_NOP; } #endif /* !defined(NDEBUG) */ /* emit_load_high ************************************************************** Emits a possible load of the high 32-bits of an operand. *******************************************************************************/ s4 emit_load_high(jitdata *jd, instruction *iptr, varinfo *src, s4 tempreg) { codegendata *cd; s4 disp; s4 reg; assert(src->type == TYPE_LNG); /* get required compiler data */ cd = jd->cd; if (IS_INMEMORY(src->flags)) { COUNT_SPILLS; disp = src->vv.regoff; M_ILD(tempreg, REG_SP, disp); reg = tempreg; } else reg = GET_HIGH_REG(src->vv.regoff); return reg; } /* emit_load_low *************************************************************** Emits a possible load of the low 32-bits of an operand. *******************************************************************************/ s4 emit_load_low(jitdata *jd, instruction *iptr, varinfo *src, s4 tempreg) { codegendata *cd; s4 disp; s4 reg; assert(src->type == TYPE_LNG); /* get required compiler data */ cd = jd->cd; if (IS_INMEMORY(src->flags)) { COUNT_SPILLS; disp = src->vv.regoff; M_ILD(tempreg, REG_SP, disp + 4); reg = tempreg; } else reg = GET_LOW_REG(src->vv.regoff); return reg; } s4 emit_load_s1_but(jitdata *jd, instruction *iptr, s4 tempreg, s4 notreg) { codegendata *cd = jd->cd; s4 reg = emit_load_s1(jd, iptr, tempreg); if (reg == notreg) { if (IS_FLT_DBL_TYPE(VAROP(iptr->s1)->type)) { M_FMOV(reg, tempreg); } else { M_MOV(reg, tempreg); } return tempreg; } else { return reg; } } s4 emit_load_s2_but(jitdata *jd, instruction *iptr, s4 tempreg, s4 notreg) { codegendata *cd = jd->cd; s4 reg = emit_load_s2(jd, iptr, tempreg); if (reg == notreg) { if (IS_FLT_DBL_TYPE(VAROP(iptr->sx.s23.s2)->type)) { M_FMOV(reg, tempreg); } else { M_MOV(reg, tempreg); } return tempreg; } else { return reg; } } void emit_copy_dst(jitdata *jd, instruction *iptr, s4 dtmpreg) { codegendata *cd; varinfo *dst; cd = jd->cd; dst = VAROP(iptr->dst); if (! IS_INMEMORY(dst->flags)) { if (dst->vv.regoff != dtmpreg) { if (IS_FLT_DBL_TYPE(dst->type)) { M_FLTMOVE(dtmpreg, dst->vv.regoff); } else if (IS_2_WORD_TYPE(dst->type)) { M_LNGMOVE(dtmpreg, dst->vv.regoff); } else { M_INTMOVE(dtmpreg, dst->vv.regoff); } } } } void emit_branch(codegendata *cd, s4 disp, s4 condition, s4 reg, u4 opt) { s4 branchdisp = disp; s4 branchmpc; u1 *ref; if (N_VALID_BRANCH(branchdisp)) { /* valid displacement */ switch (condition) { case BRANCH_EQ: M_BEQ(branchdisp); break; case BRANCH_NE: M_BNE(branchdisp); break; case BRANCH_LT: M_BLT(branchdisp); break; case BRANCH_GE: M_BGE(branchdisp); break; case BRANCH_GT: M_BGT(branchdisp); break; case BRANCH_LE: M_BLE(branchdisp); break; case BRANCH_UNCONDITIONAL: M_BR(branchdisp); break; default: vm_abort("emit_branch: unknown condition %d", condition); } } else { /* If LONGBRANCHES is not set, the flag and the error flag */ if (!CODEGENDATA_HAS_FLAG_LONGBRANCHES(cd)) { cd->flags |= (CODEGENDATA_FLAG_ERROR | CODEGENDATA_FLAG_LONGBRANCHES); } /* If error flag is set, do nothing. The method has to be recompiled. */ if (CODEGENDATA_HAS_FLAG_LONGBRANCHES(cd) && CODEGENDATA_HAS_FLAG_ERROR(cd)) { return; } /* Patch the displacement to branch over the actual branch manually * to not get yet more nops. */ branchmpc = cd->mcodeptr - cd->mcodebase; ref = cd->mcodeptr; switch (condition) { case BRANCH_EQ: M_BNE(0); break; case BRANCH_NE: M_BEQ(0); break; case BRANCH_LT: M_BGE(0); break; case BRANCH_GE: M_BLT(0); break; case BRANCH_GT: M_BLE(0); break; case BRANCH_LE: M_BGT(0); break; case BRANCH_UNCONDITIONAL: /* fall through, no displacement to patch */ ref = NULL; break; default: vm_abort("emit_branch: unknown condition %d", condition); } /* The actual long branch */ disp = dseg_add_s4(cd, branchmpc + disp - N_PV_OFFSET); M_ILD_DSEG(REG_ITMP2, disp); M_AADD(REG_PV, REG_ITMP2); M_JMP(RN, REG_ITMP2); /* Patch back the displacement */ if (ref != NULL) { *(u4 *)ref |= (u4)((cd->mcodeptr - ref) / 2); } } } void emit_arithmetic_check(codegendata *cd, instruction *iptr, s4 reg) { if (INSTRUCTION_MUST_CHECK(iptr)) { M_TEST(reg); M_BNE(SZ_BRC + SZ_ILL); M_ILL(EXCEPTION_HARDWARE_ARITHMETIC); } } /* emit_arrayindexoutofbounds_check ******************************************** Emit a ArrayIndexOutOfBoundsException check. *******************************************************************************/ void emit_arrayindexoutofbounds_check(codegendata *cd, instruction *iptr, s4 s1, s4 s2) { if (INSTRUCTION_MUST_CHECK(iptr)) { /* Size is s4, >= 0 * Do unsigned comparison to catch negative indexes. */ N_CL(s2, OFFSET(java_array_t, size), RN, s1); M_BLT(SZ_BRC + SZ_ILL); M_ILL2(s2, EXCEPTION_HARDWARE_ARRAYINDEXOUTOFBOUNDS); } } /* emit_arraystore_check ******************************************************* Emit an ArrayStoreException check. *******************************************************************************/ void emit_arraystore_check(codegendata *cd, instruction *iptr) { if (INSTRUCTION_MUST_CHECK(iptr)) { M_TEST(REG_RESULT); M_BNE(SZ_BRC + SZ_ILL); M_ILL(EXCEPTION_HARDWARE_ARRAYSTORE); } } void emit_classcast_check(codegendata *cd, instruction *iptr, s4 condition, s4 reg, s4 s1) { if (INSTRUCTION_MUST_CHECK(iptr)) { if (reg != RN) { M_TEST(reg); } switch (condition) { case BRANCH_LE: M_BGT(SZ_BRC + SZ_ILL); break; case BRANCH_EQ: M_BNE(SZ_BRC + SZ_ILL); break; case BRANCH_GT: M_BLE(SZ_BRC + SZ_ILL); break; default: vm_abort("emit_classcast_check: unknown condition %d", condition); } M_ILL2(s1, EXCEPTION_HARDWARE_CLASSCAST); } } void emit_nullpointer_check(codegendata *cd, instruction *iptr, s4 reg) { if (INSTRUCTION_MUST_CHECK(iptr)) { M_TEST(reg); M_BNE(SZ_BRC + SZ_ILL); M_ILL(EXCEPTION_HARDWARE_NULLPOINTER); } } void emit_exception_check(codegendata *cd, instruction *iptr) { if (INSTRUCTION_MUST_CHECK(iptr)) { M_TEST(REG_RESULT); M_BNE(SZ_BRC + SZ_ILL); M_ILL(EXCEPTION_HARDWARE_EXCEPTION); } } void emit_restore_pv(codegendata *cd) { s4 offset, offset_imm; /* N_BASR(REG_PV, RN); disp = (s4) (cd->mcodeptr - cd->mcodebase); M_ASUB_IMM32(disp, REG_ITMP1, REG_PV); */ /* If the offset from the method start does not fit into an immediate * value, we can't put it into the data segment! */ /* Displacement from start of method to here */ offset = (s4) (cd->mcodeptr - cd->mcodebase); offset_imm = -offset - SZ_BASR + N_PV_OFFSET; if (N_VALID_IMM(offset_imm)) { /* Get program counter */ N_BASR(REG_PV, RN); /* Substract displacement */ M_AADD_IMM(offset_imm, REG_PV); } else { /* Save program counter and jump over displacement in instruction flow */ N_BRAS(REG_PV, SZ_BRAS + SZ_LONG); /* Place displacement here */ /* REG_PV points now exactly to this position */ N_LONG(-offset - SZ_BRAS + N_PV_OFFSET); /* Substract *(REG_PV) from REG_PV */ N_A(REG_PV, 0, RN, REG_PV); } } /* * These are local overrides for various environment variables in Emacs. * Please do not remove this and leave it at the end of the file, where * Emacs will automagically detect them. * --------------------------------------------------------------------- * Local variables: * mode: c * indent-tabs-mode: t * c-basic-offset: 4 * tab-width: 4 * End: */