/*
 * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2020, 2022, Huawei Technologies Co., Ltd. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

#include "asm/assembler.hpp"
#include "c1/c1_LIRAssembler.hpp"
#include "c1/c1_MacroAssembler.hpp"

#ifndef PRODUCT
#define COMMENT(x)   do { __ block_comment(x); } while (0)
#else
#define COMMENT(x)
#endif

#define __ _masm->

void LIR_Assembler::arithmetic_idiv(LIR_Code code, LIR_Opr left, LIR_Opr right, LIR_Opr illegal,
                                    LIR_Opr result, CodeEmitInfo* info) {
  // opcode check
  assert((code == lir_idiv) || (code == lir_irem), "opcode must be idiv or irem");
  bool is_irem = (code == lir_irem);
  // opreand check
  assert(left->is_single_cpu(), "left must be a register");
  assert(right->is_single_cpu() || right->is_constant(), "right must be a register or constant");
  assert(result->is_single_cpu(), "result must be a register");
  Register lreg = left->as_register();
  Register dreg = result->as_register();

  // power-of-2 constant check and codegen
  if (right->is_constant()) {
    int c = right->as_constant_ptr()->as_jint();
    assert(c > 0 && is_power_of_2(c), "divisor must be power-of-2 constant");
    if (is_irem) {
      if (c == 1) {
        // move 0 to dreg if divisor is 1
        __ mv(dreg, zr);
      } else {
        unsigned int shift = exact_log2(c);
        __ sraiw(t0, lreg, 0x1f);
        __ srliw(t0, t0, BitsPerInt - shift);
        __ addw(t1, lreg, t0);
        if (Assembler::is_simm12(c - 1)) {
          __ andi(t1, t1, c - 1);
        } else {
          __ zext(t1, t1, shift);
        }
        __ subw(dreg, t1, t0);
      }
    } else {
      if (c == 1) {
        // move lreg to dreg if divisor is 1
        __ mv(dreg, lreg);
      } else {
        unsigned int shift = exact_log2(c);
        __ sraiw(t0, lreg, 0x1f);
        if (Assembler::is_simm12(c - 1)) {
          __ andi(t0, t0, c - 1);
        } else {
          __ zext(t0, t0, shift);
        }
        __ addw(dreg, t0, lreg);
        __ sraiw(dreg, dreg, shift);
      }
    }
  } else {
    Register rreg = right->as_register();
    if (is_irem) {
      __ remw(dreg, lreg, rreg);
    } else {
      __ divw(dreg, lreg, rreg);
    }
  }
}

void LIR_Assembler::arith_op_single_cpu_right_constant(LIR_Code code, LIR_Opr left, LIR_Opr right,
                                                       Register lreg, Register dreg) {
  // cpu register - constant
  jlong c;

  switch (right->type()) {
    case T_LONG:
      c = right->as_constant_ptr()->as_jlong(); break;
    case T_INT:     // fall through
    case T_ADDRESS:
      c = right->as_constant_ptr()->as_jint(); break;
    default:
      ShouldNotReachHere();
      c = 0;   // unreachable
  }

  assert(code == lir_add || code == lir_sub, "mismatched arithmetic op");
  if (c == 0 && dreg == lreg) {
    COMMENT("effective nop elided");
    return;
  }
  switch (left->type()) {
    case T_INT:
      switch (code) {
        case lir_add: __ addw(dreg, lreg, c); break;
        case lir_sub: __ subw(dreg, lreg, c); break;
        default:      ShouldNotReachHere();
      }
    break;
    case T_OBJECT:  // fall through
    case T_ADDRESS:
      switch (code) {
        case lir_add: __ add(dreg, lreg, c); break;
        case lir_sub: __ sub(dreg, lreg, c); break;
        default:      ShouldNotReachHere();
      }
    break;
    default:
      ShouldNotReachHere();
  }
}

void LIR_Assembler::arith_op_single_cpu(LIR_Code code, LIR_Opr left, LIR_Opr right, LIR_Opr dest) {
  Register lreg = left->as_register();
  Register dreg = as_reg(dest);

  if (right->is_single_cpu()) {
    // cpu register - cpu register
    assert(left->type() == T_INT && right->type() == T_INT && dest->type() == T_INT, "should be");
    Register rreg = right->as_register();
    switch (code) {
      case lir_add: __ addw(dest->as_register(), lreg, rreg); break;
      case lir_sub: __ subw(dest->as_register(), lreg, rreg); break;
      case lir_mul: __ mulw(dest->as_register(), lreg, rreg); break;
      default:      ShouldNotReachHere();
    }
  } else if (right->is_double_cpu()) {
    Register rreg = right->as_register_lo();
    // sigle_cpu + double_cpu; can happen with obj_long
    assert(code == lir_add || code == lir_sub, "mismatched arithmetic op");
    switch (code) {
      case lir_add: __ add(dreg, lreg, rreg); break;
      case lir_sub: __ sub(dreg, lreg, rreg); break;
      default:      ShouldNotReachHere();
    }
  } else if (right->is_constant()) {
    arith_op_single_cpu_right_constant(code, left, right, lreg, dreg);
  } else {
    ShouldNotReachHere();
  }
}

void LIR_Assembler::arith_op_double_cpu(LIR_Code code, LIR_Opr left, LIR_Opr right, LIR_Opr dest) {
  Register lreg_lo = left->as_register_lo();

  if (right->is_double_cpu()) {
    // cpu register - cpu register
    Register rreg_lo = right->as_register_lo();
    switch (code) {
      case lir_add: __ add(dest->as_register_lo(), lreg_lo, rreg_lo); break;
      case lir_sub: __ sub(dest->as_register_lo(), lreg_lo, rreg_lo); break;
      case lir_mul: __ mul(dest->as_register_lo(), lreg_lo, rreg_lo); break;
      case lir_div: __ div(dest->as_register_lo(), lreg_lo, rreg_lo); break;
      case lir_rem: __ rem(dest->as_register_lo(), lreg_lo, rreg_lo); break;
      default:
        ShouldNotReachHere();
    }
  } else if (right->is_constant()) {
    jlong c = right->as_constant_ptr()->as_jlong();
    Register dreg = as_reg(dest);
    switch (code) {
      case lir_add: // fall through
      case lir_sub:
        if (c == 0 && dreg == lreg_lo) {
          COMMENT("effective nop elided");
          return;
        }
        code == lir_add ? __ add(dreg, lreg_lo, c) : __ sub(dreg, lreg_lo, c);
        break;
      case lir_div:
        assert(c > 0 && is_power_of_2(c), "divisor must be power-of-2 constant");
        if (c == 1) {
          // move lreg_lo to dreg if divisor is 1
          __ mv(dreg, lreg_lo);
        } else {
          unsigned int shift = exact_log2_long(c);
          // use t0 as intermediate result register
          __ srai(t0, lreg_lo, 0x3f);
          if (Assembler::is_simm12(c - 1)) {
            __ andi(t0, t0, c - 1);
          } else {
            __ zext(t0, t0, shift);
          }
          __ add(dreg, t0, lreg_lo);
          __ srai(dreg, dreg, shift);
        }
        break;
      case lir_rem:
        assert(c > 0 && is_power_of_2(c), "divisor must be power-of-2 constant");
        if (c == 1) {
          // move 0 to dreg if divisor is 1
          __ mv(dreg, zr);
        } else {
          unsigned int shift = exact_log2_long(c);
          __ srai(t0, lreg_lo, 0x3f);
          __ srli(t0, t0, BitsPerLong - shift);
          __ add(t1, lreg_lo, t0);
          if (Assembler::is_simm12(c - 1)) {
            __ andi(t1, t1, c - 1);
          } else {
            __ zext(t1, t1, shift);
          }
          __ sub(dreg, t1, t0);
        }
        break;
      default:
        ShouldNotReachHere();
    }
  } else {
    ShouldNotReachHere();
  }
}

void LIR_Assembler::arith_op_single_fpu(LIR_Code code, LIR_Opr left, LIR_Opr right, LIR_Opr dest) {
  assert(right->is_single_fpu(), "right hand side of float arithmetics needs to be float register");
  switch (code) {
    case lir_add: __ fadd_s(dest->as_float_reg(), left->as_float_reg(), right->as_float_reg()); break;
    case lir_sub: __ fsub_s(dest->as_float_reg(), left->as_float_reg(), right->as_float_reg()); break;
    case lir_mul: __ fmul_s(dest->as_float_reg(), left->as_float_reg(), right->as_float_reg()); break;
    case lir_div: __ fdiv_s(dest->as_float_reg(), left->as_float_reg(), right->as_float_reg()); break;
    default:
      ShouldNotReachHere();
  }
}

void LIR_Assembler::arith_op_double_fpu(LIR_Code code, LIR_Opr left, LIR_Opr right, LIR_Opr dest) {
  if (right->is_double_fpu()) {
    // fpu register - fpu register
    switch (code) {
      case lir_add: __ fadd_d(dest->as_double_reg(), left->as_double_reg(), right->as_double_reg()); break;
      case lir_sub: __ fsub_d(dest->as_double_reg(), left->as_double_reg(), right->as_double_reg()); break;
      case lir_mul: __ fmul_d(dest->as_double_reg(), left->as_double_reg(), right->as_double_reg()); break;
      case lir_div: __ fdiv_d(dest->as_double_reg(), left->as_double_reg(), right->as_double_reg()); break;
      default:
        ShouldNotReachHere();
    }
  } else {
    ShouldNotReachHere();
  }
}

void LIR_Assembler::arith_op(LIR_Code code, LIR_Opr left, LIR_Opr right, LIR_Opr dest,
                             CodeEmitInfo* info) {
  assert(info == nullptr, "should never be used, idiv/irem and ldiv/lrem not handled by this method");

  if (left->is_single_cpu()) {
    arith_op_single_cpu(code, left, right, dest);
  } else if (left->is_double_cpu()) {
    arith_op_double_cpu(code, left, right, dest);
  } else if (left->is_single_fpu()) {
    arith_op_single_fpu(code, left, right, dest);
  } else if (left->is_double_fpu()) {
    arith_op_double_fpu(code, left, right, dest);
  } else {
    ShouldNotReachHere();
  }
}

#undef __
