text: /*
   *  Licensed to the Apache Software Foundation (ASF) under one or more
   *  contributor license agreements.  See the NOTICE file distributed with
   *  this work for additional information regarding copyright ownership.
   *  The ASF licenses this file to You under the Apache License, Version 2.0
   *  (the "License"); you may not use this file except in compliance with
   *  the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing, software
   *  distributed under the License is distributed on an "AS IS" BASIS,
   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *  See the License for the specific language governing permissions and
   *  limitations under the License.
   */
  /**
   * @author Alexander Astapchuk
   */
  /**
   * @file
   * @brief Code generation routines for various branches - GOTO, IF_*, 
   * TABLE- and LOOKUP switches, JSR and RET.
   */
   
  #include "compiler.h"
  
  
  namespace Jitrino {
  namespace Jet {
  
  /**
   * @brief Maps conditional branches #JavaByteCodes to appropriate 
   *        #ConditionMnemonic.
   *
   * Accepts only opcode in ranges: [OPCODE_IFEQ; OPCODE_IFLE] and 
   * [OPCODE_IF_ICMPEQ; OPCODE_IF_ICMPLE].
   *
   * @param opcod - a byte code
   * @return appropriate ConditionMnemonic 
   */
  static COND to_cond(JavaByteCodes opcod)
  {
      if (opcod==OPCODE_GOTO) {
          return cond_none;
      }
      unsigned cod;
      if (OPCODE_IFEQ <= opcod && opcod <= OPCODE_IFLE) {
          cod = opcod - OPCODE_IFEQ;
      }
      else {
          assert(OPCODE_IF_ICMPEQ <= opcod && opcod <= OPCODE_IF_ICMPLE);
          cod = opcod - OPCODE_IF_ICMPEQ;
      }
      
      switch (cod) {
      case 0:     return eq;
      case 1:     return ne;
      case 2:     return lt;
      case 3:     return ge;
      case 4:     return gt;
      case 5:     return le;
      default:
          break;
      }
      assert(false);
      return (COND)NOTHING;
  }
  
  /**
   * @brief Reverts a condition so operands in comparation may be swapped.
   *
   * Eg. 'op0 <= op1' may become 'op1 >= op0', which is 'lt' changed to 'gt'.
   *
   * Used to swap operands in IF_ICMPx operations when we have an immediate
   * operand as a first operand.
   */
  static COND flip(COND cond)
  {
      switch(cond) {
      case eq:
      case ne:    return cond;
      case lt:    return gt;
      case ge:    return le;
      case gt:    return lt;
      case le:    return ge;
      default:
          break;
      }
      assert(false);
      return (COND)NOTHING;
  }
  
  void Compiler::gen_goto(unsigned target)
  {
      if (target <= m_pc) {
          // Back branch
          gen_prof_be();
          gen_gc_safe_point();
      }
      gen_bb_leave(target);
      br(cond_none, target, m_bbinfo->start);
  }
  
  void Compiler::gen_if(JavaByteCodes opcod, unsigned target)
  {
      if (target <= m_pc) {
          // have back branch here
          gen_prof_be();
          gen_gc_safe_point();
      }
      jtype jt = i32;
      if (opcod == OPCODE_IFNULL) {
          opcod = OPCODE_IFEQ;
          jt = jobj;
      }
      else if (opcod == OPCODE_IFNONNULL) {
          opcod = OPCODE_IFNE;
          jt = jobj;
      }
      OpndKind kind = m_jframe->dip(0).kind();
      bool forceReg = (kind == opnd_imm) || (jt == jobj && g_refs_squeeze);
      Opnd op1 = vstack(0, forceReg).as_opnd();
      vpop();
      rlock(op1);
      COND cond = to_cond(opcod);
      static const Opnd zero((int)0);
      if (jt == jobj && g_refs_squeeze) {
          AR ar = valloc(jobj);
          movp(ar, NULL_REF);
          alu(alu_cmp, Opnd(jobj, ar), op1);
      }
      else if (opcod == OPCODE_IFEQ || opcod == OPCODE_IFNE) {
          if (op1.is_reg()) {
              alu(alu_test, op1, op1);
          }
          else {
              alu(alu_cmp, op1, zero);
          }
      }
      else {
          alu(alu_cmp, op1, zero);
      }
      runlock(op1);
      gen_bb_leave(target);
      br(cond, target, m_bbinfo->start);
  }
  
  void Compiler::gen_if_icmp(JavaByteCodes opcod, unsigned target)
  {
      if (target <= m_pc) {
          // have back branch here
          gen_prof_be();
          gen_gc_safe_point();
      }
      if (opcod == OPCODE_IF_ACMPEQ) {
          opcod = OPCODE_IF_ICMPEQ;
      }
      else if (opcod == OPCODE_IF_ACMPNE) {
          opcod = OPCODE_IF_ICMPNE;
      }
      Opnd op2 = vstack(0).as_opnd();
      vpop();
      rlock(op2);
      OpndKind kind = m_jframe->dip(0).kind();
      // 'Bad' combinations are 'm,m' and 'imm,<any, but imm>' - have to force
      // an item into a register
      bool forceReg = (op2.is_mem() && kind == opnd_mem) || 
                      (op2.is_imm() && kind == opnd_imm);
      Opnd op1 = vstack(0, forceReg).as_opnd();
      vpop();
      rlock(op1);
      
      COND cond = to_cond(opcod);
      if ( (op1.is_mem() && op2.is_reg()) || op1.is_imm()) {
          // here we have 'mem, reg' or 'imm, mem-or-reg' - swap them so it
          // become 'reg, mem' (more efficient) or 'mem-or-reg, imm' (existent)
          // operations. change the branch condition appropriately.
          alu(alu_cmp, op2, op1);
          cond = flip(cond);
      }
      else {
          alu(alu_cmp, op1, op2);
      }
      
      runlock(op1);
      runlock(op2);
      gen_bb_leave(target);
      br(cond, target, m_bbinfo->start);
  }
  
  void Compiler::gen_switch(const JInst & jinst)
  {
      assert(jinst.opcode == OPCODE_LOOKUPSWITCH 
             || jinst.opcode == OPCODE_TABLESWITCH);
      Opnd val = vstack(0, true).as_opnd();
      vpop();
      rlock(val);
      gen_bb_leave(NOTHING);
  
      if (jinst.opcode == OPCODE_LOOKUPSWITCH) {
          unsigned n = jinst.get_num_targets();
          for (unsigned i = 0; i < n; i++) {
              Opnd key(jinst.key(i));
              unsigned pc = jinst.get_target(i);
              alu(alu_cmp, val, key);
              br(eq, pc, m_bbinfo->start);
          }
          runlock(val);
          br(cond_none, jinst.get_def_target(), m_bbinfo->start);
          return;
      }
      //
      // TABLESWITCH
      //
      alu(alu_cmp, val, jinst.high());
      br(gt, jinst.get_def_target(), m_bbinfo->start);
      
      alu(alu_cmp, val, jinst.low());
      br(lt, jinst.get_def_target(), m_bbinfo->start);
      
      AR gr_tabl = valloc(jobj);
      movp(gr_tabl, DATA_SWITCH_TABLE | m_curr_inst->pc, m_bbinfo->start);
  #ifdef _EM64T_
      // On EM64T, we operate with I_32 value in a register, but the 
      // register will be used as 64 bit in address form - have to extend
      sx(Opnd(i64, val.reg()), Opnd(i32, val.reg()));
  #endif
      // Here, we need to extract 'index-=low()' - can pack this into 
      // complex address form:
      //      [table + index*sizeof(void*) - low()*sizeof(void*)],
      // but only if low()*sizeof(void*) does fit into displacement ...
      int tmp = -jinst.low();
      const int LO_BOUND = INT_MIN/(int)sizeof(void*);
      const int UP_BOUND = INT_MAX/(int)sizeof(void*);
      if (LO_BOUND<=tmp && tmp<=UP_BOUND) {
          ld(jobj, gr_tabl, gr_tabl, -jinst.low()*sizeof(void*), 
          val.reg(), sizeof(void*));
      }
      else {
          // ... otherwise subtract explicitly, but only if the register
          // is not used anywhere else
          if (rrefs(val.reg()) !=0) {
              Opnd vtmp(i32, valloc(i32));
              mov(vtmp, val); // make a copy of val
              runlock(val);
              val = vtmp;
              rlock(val);
          }
          alu(alu_sub, val, jinst.low());
          ld(jobj, gr_tabl, gr_tabl, 0, val.reg(), sizeof(void*));
      }
      runlock(val);
      br(gr_tabl);
  }
  
  void Compiler::gen_jsr(unsigned target)
  {
      AR gr = valloc(jobj);
      const JInst& jinst = *m_curr_inst;
      movp(gr, jinst.next, m_bbinfo->start);
      vpush(Val(jretAddr, gr));
      gen_bb_leave(target);
      br(cond_none, target, m_bbinfo->start);
  }
  
  void Compiler::gen_ret(unsigned idx)
  {
      Opnd ret = vlocal(jretAddr, idx, false).as_opnd();
      gen_bb_leave(NOTHING);
      br(ret);
  }
  
  
  }}; // ~namespace Jitrino::Jet
  