/*
 * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2024, Rivos Inc. 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.
 */

#if (defined(RISCV) || defined(RISCV64)) && !defined(ZERO)

#include "asm/assembler.inline.hpp"
#include "asm/macroAssembler.hpp"
#include "cppstdlib/limits.hpp"
#include "memory/resourceArea.hpp"
#include "metaprogramming/enableIf.hpp"
#include "runtime/orderAccess.hpp"
#include "threadHelper.inline.hpp"
#include "unittest.hpp"

typedef int64_t (*zicond_func)(int64_t cmp1, int64_t cmp2, int64_t dst, int64_t src);
typedef void (MacroAssembler::*cmov_func)(Register cmp1, Register cmp2, Register dst, Register src);

class CmovTester {
 public:
  static void test(cmov_func func, int64_t a0, int64_t a1, int64_t a2, int64_t a3, int64_t result) {
    BufferBlob* bb = BufferBlob::create("riscvTest", 128);
    CodeBuffer code(bb);
    MacroAssembler _masm(&code);
    address entry = _masm.pc();
    {
      ((&_masm)->*func)(c_rarg0, c_rarg1, c_rarg2, c_rarg3);
      _masm.mv(c_rarg0, c_rarg2);
      _masm.ret();
    }
    _masm.flush(); // icache invalidate
    int64_t ret = ((zicond_func)entry)(a0, a1, a2, a3);
    ASSERT_EQ(ret, result);
    BufferBlob::free(bb);
  }
};

static void run_cmov_tests() {
  // If 42(a0) eq 42(a1): assign dest(a2/66) the src(a3/77), expect result: 77
  CmovTester::test(&MacroAssembler::cmov_eq, 42, 42, 66, 77, 77);
  // If 41(a0) eq 42(a1): assign dest(a2/66) the src(a3/77), expect result: 66
  CmovTester::test(&MacroAssembler::cmov_eq, 41, 42, 66, 77, 66);

  CmovTester::test(&MacroAssembler::cmov_ne, 41, 42, 66, 77, 77);
  CmovTester::test(&MacroAssembler::cmov_ne, 42, 42, 66, 77, 66);

  CmovTester::test(&MacroAssembler::cmov_le, 41, 42, 66, 77, 77);
  CmovTester::test(&MacroAssembler::cmov_le, 42, 42, 66, 77, 77);
  CmovTester::test(&MacroAssembler::cmov_le, 42, -1, 66, 77, 66);

  CmovTester::test(&MacroAssembler::cmov_leu, 41, 42, 66, 77, 77);
  CmovTester::test(&MacroAssembler::cmov_leu, 42, 42, 66, 77, 77);
  CmovTester::test(&MacroAssembler::cmov_leu, -1, 42, 66, 77, 66);

  CmovTester::test(&MacroAssembler::cmov_ge, 43, 42, 66, 77, 77);
  CmovTester::test(&MacroAssembler::cmov_ge, 42, 42, 66, 77, 77);
  CmovTester::test(&MacroAssembler::cmov_ge, -1, 42, 66, 77, 66);

  CmovTester::test(&MacroAssembler::cmov_geu, 43, 42, 66, 77, 77);
  CmovTester::test(&MacroAssembler::cmov_geu, 42, 42, 66, 77, 77);
  CmovTester::test(&MacroAssembler::cmov_geu, 42, -1, 66, 77, 66);

  CmovTester::test(&MacroAssembler::cmov_lt, 41, 42, 66, 77, 77);
  CmovTester::test(&MacroAssembler::cmov_lt, 42, 42, 66, 77, 66);
  CmovTester::test(&MacroAssembler::cmov_lt, 42, -1, 66, 77, 66);

  CmovTester::test(&MacroAssembler::cmov_ltu, 41, 42, 66, 77, 77);
  CmovTester::test(&MacroAssembler::cmov_ltu, 42, 42, 66, 77, 66);
  CmovTester::test(&MacroAssembler::cmov_ltu, -1, 42, 66, 77, 66);

  CmovTester::test(&MacroAssembler::cmov_gt, 43, 42, 66, 77, 77);
  CmovTester::test(&MacroAssembler::cmov_gt, 42, 42, 66, 77, 66);
  CmovTester::test(&MacroAssembler::cmov_gt, -1, 42, 66, 77, 66);

  CmovTester::test(&MacroAssembler::cmov_gtu, 43, 42, 66, 77, 77);
  CmovTester::test(&MacroAssembler::cmov_gtu, 42, 42, 66, 77, 66);
  CmovTester::test(&MacroAssembler::cmov_gtu, 42, -1, 66, 77, 66);
}

TEST_VM(RiscV, cmov) {
  run_cmov_tests();
  if (UseZicond) {
    UseZicond = false;
    run_cmov_tests();
    UseZicond = true;
  }
}

template <Assembler::operand_size ASMSIZE>
bool using_narrow() {
  if (ASMSIZE == Assembler::int8 || ASMSIZE == Assembler::int16) {
    return !(UseZacas && UseZabha);
  }
  return false;
}

template <typename TESTSIZE, Assembler::operand_size ASMSIZE>
class CmpxchgTester {
  // The functions expect arguments to be type represented, not C-ABI argument representation.
  // Hence an unsigned should be zero-extended, and the same goes for the return value.
  typedef int64_t (*cmpxchg_func)(intptr_t addr, int64_t expected, int64_t new_value, int64_t result);

  typedef int64_t (*cmpxchg_narrow_func)(intptr_t addr, int64_t expected, int64_t new_value, int64_t result,
                                          int64_t scratch0, int64_t scratch1, int64_t scratch2);

  BufferBlob*  _bb;
  cmpxchg_func _func;
  cmpxchg_narrow_func _narrow;

 public:
  CmpxchgTester(int variant, bool boolean_result) {
    _bb = BufferBlob::create("riscvTest", 128);
    CodeBuffer code(_bb);
    MacroAssembler _masm(&code);
    address entry = _masm.pc();
    if (using_narrow<ASMSIZE>()) {
        address entry = _masm.pc();
       _masm.cmpxchg_narrow_value(/*addr*/ c_rarg0, /*expected*/ c_rarg1, /*new_value*/c_rarg2,
                        ASMSIZE, Assembler::relaxed, Assembler::relaxed,
                        /*result*/ c_rarg3, boolean_result, c_rarg4, c_rarg5, c_rarg6); /* Uses also t0-t1, caller saved */
      _masm.mv(c_rarg0, c_rarg3);
      _masm.ret();
      _narrow = ((cmpxchg_narrow_func)entry);
    } else {
      switch(variant) {
        default:
          _masm.cmpxchg(/*addr*/ c_rarg0, /*expected*/ c_rarg1, /*new_value*/c_rarg2,
                        ASMSIZE, Assembler::aq, Assembler::rl,
                        /*result*/ c_rarg3, boolean_result);
          _masm.mv(c_rarg0, c_rarg3);
          break;
        case 1:
          // expected == result
          _masm.cmpxchg(/*addr*/ c_rarg0, /*expected*/ c_rarg1, /*new_value*/c_rarg2,
                        ASMSIZE, Assembler::aq, Assembler::rl,
                        /*result*/ c_rarg1, boolean_result);
          _masm.mv(c_rarg0, c_rarg1);
          break;
        case 2:
          // new_value == result
          _masm.cmpxchg(/*addr*/ c_rarg0, /*expected*/ c_rarg1, /*new_value*/c_rarg2,
                        ASMSIZE, Assembler::aq, Assembler::rl,
                        /*result*/ c_rarg2, boolean_result);
          _masm.mv(c_rarg0, c_rarg2);
          break;
        case 3:
          // expected == new_value
          _masm.cmpxchg(/*addr*/ c_rarg0, /*expected*/ c_rarg1, /*new_value*/ c_rarg1,
                        ASMSIZE, Assembler::aq, Assembler::rl,
                        /*result*/ c_rarg2, boolean_result);
          _masm.mv(c_rarg0, c_rarg2);
          break;

      }
      _masm.ret();
      _func = ((cmpxchg_func)entry);
    }
    _masm.flush(); // icache invalidate
  }

  ~CmpxchgTester() {
    BufferBlob::free(_bb);
  }

  TESTSIZE cmpxchg(intptr_t addr, TESTSIZE expected, TESTSIZE new_value) {
    if (using_narrow<ASMSIZE>()) {
      return _narrow(addr, expected, new_value, /* dummy result */ 67, -1, -1, -1);
    } else {
      return _func(addr, expected, new_value, /* dummy result */ 67);
    }
  }
};

template <typename TESTSIZE, Assembler::operand_size ASMSIZE>
static void plain_cmpxchg_test(int variant, TESTSIZE dv, TESTSIZE ex, TESTSIZE nv, TESTSIZE eret, TESTSIZE edata, bool bv) {
  CmpxchgTester<TESTSIZE, ASMSIZE> cmpxchg(variant, bv);
  TESTSIZE data = dv;
  TESTSIZE ret = cmpxchg.cmpxchg((intptr_t)&data, ex, nv);
  ASSERT_EQ(ret,  eret);
  ASSERT_EQ(data, edata);
}

template <typename TESTSIZE, Assembler::operand_size ASMSIZE>
static void run_plain_cmpxchg_tests() {
  TESTSIZE max = std::numeric_limits<TESTSIZE>::max();
  TESTSIZE min = std::numeric_limits<TESTSIZE>::min();
  TESTSIZE val[] = {37, min, max};
  for (int i = 0; i < 3; i++) {
    // Normal
    plain_cmpxchg_test<TESTSIZE, ASMSIZE>(     0 /* variant */ , val[i] /* start value */,
                                          val[i] /* expected */,     42 /* new value */,
                                          val[i] /* return */  ,     42 /* end value*/, false /* boolean ret*/);

    plain_cmpxchg_test<TESTSIZE, ASMSIZE>(     0 /* variant */ , val[i] /* start value */,
                                              36 /* expected */,     42 /* new value */,
                                          val[i] /* return */  , val[i] /* end value */, false /* boolean ret*/);

    plain_cmpxchg_test<TESTSIZE, ASMSIZE>(     0 /* variant */ , val[i] /* start value */,
                                          val[i] /* expected */,     42 /* new value */,
                                               1 /* return */  ,     42 /* end value*/, true /* boolean ret*/);

    plain_cmpxchg_test<TESTSIZE, ASMSIZE>(     0 /* variant */ , val[i] /* start value */,
                                              36 /* expected */,     42 /* new value */,
                                               0 /* return */  , val[i] /* end value */, true /* boolean ret*/);

    // result == expected register
    plain_cmpxchg_test<TESTSIZE, ASMSIZE>(     1 /* variant */ ,  val[i] /* start value */,
                                          val[i] /* expected */,      42 /* new value */,
                                          val[i] /* return */  ,      42 /* end value*/, false /* boolean ret*/);

    plain_cmpxchg_test<TESTSIZE, ASMSIZE>(     1 /* variant */ ,  val[i] /* start value */,
                                              36 /* expected */,      42 /* new value */,
                                          val[i] /* return */  ,  val[i] /* end value */, false /* boolean ret*/);

    plain_cmpxchg_test<TESTSIZE, ASMSIZE>(     1 /* variant */ , val[i] /* start value */,
                                          val[i] /* expected */,     42 /* new value */,
                                               1 /* return */  ,     42 /* end value*/, true /* boolean ret*/);

    plain_cmpxchg_test<TESTSIZE, ASMSIZE>(     1 /* variant */ , val[i] /* start value */,
                                              36 /* expected */,     42 /* new value */,
                                               0 /* return */  , val[i] /* end value */, true /* boolean ret*/);

    // new_value == result register
    plain_cmpxchg_test<TESTSIZE, ASMSIZE>(     2 /* variant */ , val[i] /* start value */,
                                          val[i] /* expected */,     42 /* new value */,
                                          val[i] /* return */  ,     42 /* end value*/, false /* boolean ret*/);

    plain_cmpxchg_test<TESTSIZE, ASMSIZE>(     2 /* variant */ , val[i] /* start value */,
                                              36 /* expected */,     42 /* new value */,
                                          val[i] /* return */  , val[i] /* end value */, false /* boolean ret*/);

    plain_cmpxchg_test<TESTSIZE, ASMSIZE>(     2 /* variant */ , val[i] /* start value */,
                                          val[i] /* expected */,     42 /* new value */,
                                               1 /* return */  ,     42 /* end value*/, true /* boolean ret*/);

    plain_cmpxchg_test<TESTSIZE, ASMSIZE>(    2 /* variant */ , val[i] /* start value */,
                                             36 /* expected */,     42 /* new value */,
                                              0 /* return */  , val[i] /* end value */, true /* boolean ret*/);

    // expected == new_value register
    plain_cmpxchg_test<TESTSIZE, ASMSIZE>(     3 /* variant */ , val[i] /* start value */,
                                          val[i] /* expected */,    42 /* new value */,
                                          val[i] /* return */  , val[i] /* end value */, false /* boolean ret*/);

    plain_cmpxchg_test<TESTSIZE, ASMSIZE>(     3 /* variant */ , val[i] /* start value */,
                                              36 /* expected */,     42 /* new value */,
                                          val[i] /* return */  , val[i] /* end value */, false /* boolean ret*/);

    plain_cmpxchg_test<TESTSIZE, ASMSIZE>(     3 /* variant */ , val[i] /* start value */,
                                          val[i] /* expected */,     42 /* new value */,
                                               1 /* return */  , val[i] /* end value */, true /* boolean ret*/);

    plain_cmpxchg_test<TESTSIZE, ASMSIZE>(    3 /* variant */ , val[i] /* start value */,
                                             36 /* expected */,     42 /* new value */,
                                              0 /* return */  , val[i] /* end value */, true /* boolean ret*/);
  }
}

TEST_VM(RiscV, cmpxchg_int64_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_plain_cmpxchg_tests<int64_t, Assembler::int64>();
  UseZacas = zacas;
}

TEST_VM(RiscV, cmpxchg_int64_maybe_zacas) {
  if (UseZacas) {
    run_plain_cmpxchg_tests<int64_t, Assembler::int64>();
  }
}

TEST_VM(RiscV, cmpxchg_int32_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_plain_cmpxchg_tests<int32_t, Assembler::int32>();
  UseZacas = zacas;
}

TEST_VM(RiscV, cmpxchg_int32_maybe_zacas) {
  if (UseZacas) {
    run_plain_cmpxchg_tests<int32_t, Assembler::int32>();
  }
}

TEST_VM(RiscV, cmpxchg_uint32_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_plain_cmpxchg_tests<uint32_t, Assembler::uint32>();
  UseZacas = zacas;
}

TEST_VM(RiscV, cmpxchg_uint32_maybe_zacas) {
  if (UseZacas) {
    run_plain_cmpxchg_tests<uint32_t, Assembler::uint32>();
  }
}

TEST_VM(RiscV, cmpxchg_int16_maybe_zacas_zabha) {
  if (UseZacas && UseZabha) {
    run_plain_cmpxchg_tests<int16_t, Assembler::int16>();
  }
}

TEST_VM(RiscV, cmpxchg_int8_maybe_zacas_zabha) {
  if (UseZacas && UseZabha) {
    run_plain_cmpxchg_tests<int8_t, Assembler::int8>();
  }
}

template <typename TESTSIZE, Assembler::operand_size ASMSIZE>
static void run_narrow_cmpxchg_tests() {
  CmpxchgTester<TESTSIZE, ASMSIZE> cmpxchg(0, false);
  CmpxchgTester<TESTSIZE, ASMSIZE> cmpxchg_bool(0, true);
  // Assume natural aligned
  TESTSIZE data[8];
  TESTSIZE ret;
  TESTSIZE max = std::numeric_limits<TESTSIZE>::max();
  TESTSIZE min = std::numeric_limits<TESTSIZE>::min();
  TESTSIZE val[] = {121, min, max};
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 7; j++) {
      memset(data, -1, sizeof(data));
      data[i] = val[i];
      ret = cmpxchg.cmpxchg((intptr_t)&data[i], val[i], 42);
      ASSERT_EQ(ret, val[i]);
      ASSERT_EQ(data[i], 42);

      data[i] = val[i];
      ret = cmpxchg.cmpxchg((intptr_t)&data[i], 120, 42);
      ASSERT_EQ(ret, val[i]);
      ASSERT_EQ(data[i], val[i]);

      data[i] = val[i];
      ret = cmpxchg_bool.cmpxchg((intptr_t)&data[i], val[i], 42);
      ASSERT_EQ(ret, 1);
      ASSERT_EQ(data[i], 42);

      data[i] = val[i];
      ret = cmpxchg_bool.cmpxchg((intptr_t)&data[i], 120, 42);
      ASSERT_EQ(ret, 0);
      ASSERT_EQ(data[i], val[i]);
    }
  }
}

TEST_VM(RiscV, cmpxchg_narrow_int16_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_narrow_cmpxchg_tests<int16_t, Assembler::int16>();
  UseZacas = zacas;
}

TEST_VM(RiscV, cmpxchg_narrow_int16_maybe_zacas) {
  if (UseZacas) {
    bool zabha = UseZabha;
    UseZabha = false;
    run_narrow_cmpxchg_tests<int16_t, Assembler::int16>();
    UseZabha = zabha;
  }
}

TEST_VM(RiscV, cmpxchg_narrow_int8_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_narrow_cmpxchg_tests<int8_t, Assembler::int8>();
  UseZacas = zacas;
}

TEST_VM(RiscV, cmpxchg_narrow_int8_maybe_zacas) {
  if (UseZacas) {
    bool zabha = UseZabha;
    UseZabha = false;
    run_narrow_cmpxchg_tests<int8_t, Assembler::int8>();
    UseZabha = zabha;
  }
}

template <typename TESTSIZE>
TESTSIZE next_count(TESTSIZE now, TESTSIZE add) {
  if ((std::numeric_limits<TESTSIZE>::max() - add) >= now) {
    return now + add;
  }
  TESTSIZE diff = std::numeric_limits<TESTSIZE>::max() - now;
  add -= diff + 1; // add one to the diff for the wrap around.
  return std::numeric_limits<TESTSIZE>::min() + add;
}

constexpr int64_t PAR_IT_END       = 10000;
constexpr int64_t NUMBER_THREADS   = 4;
constexpr int64_t TOTAL_ITERATIONS = NUMBER_THREADS * PAR_IT_END;

template <typename TESTSIZE, ENABLE_IF(std::numeric_limits<TESTSIZE>::max() <= (std::numeric_limits<TESTSIZE>::min() + TOTAL_ITERATIONS))>
constexpr TESTSIZE result_count() {
  int64_t range = std::numeric_limits<TESTSIZE>::max() - std::numeric_limits<TESTSIZE>::min() + 1;
  int64_t rest = TOTAL_ITERATIONS % range;
  return std::numeric_limits<TESTSIZE>::min() + rest;
}

template <typename TESTSIZE, ENABLE_IF(std::numeric_limits<TESTSIZE>::max() > (std::numeric_limits<TESTSIZE>::min() + TOTAL_ITERATIONS))>
constexpr TESTSIZE result_count() {
  return std::numeric_limits<TESTSIZE>::min() + TOTAL_ITERATIONS;
}

template <typename TESTSIZE, Assembler::operand_size ASMSIZE>
static void run_concurrent_cmpxchg_tests() {
  volatile TESTSIZE data = std::numeric_limits<TESTSIZE>::min();
  int num_threads = NUMBER_THREADS;
  CmpxchgTester<TESTSIZE, ASMSIZE> cmpxchg(0, false); // variant 0, not bool ret
  auto incThread = [&](Thread* _current, int _id) {   // _id starts from 0..(CTHREAD-1)
    TESTSIZE my_oldvalue = std::numeric_limits<TESTSIZE>::min() + _id;
    for (int64_t i = 0; i < PAR_IT_END ; i++) {
      TESTSIZE newvalue = next_count<TESTSIZE>(my_oldvalue,  1);
      TESTSIZE ret;
      do {
        ret = cmpxchg.cmpxchg((intptr_t)&data, my_oldvalue, newvalue);
      } while (ret != my_oldvalue);
      my_oldvalue = next_count<TESTSIZE>(my_oldvalue, num_threads);
    }
  };
  TestThreadGroup<decltype(incThread)> ttg(incThread, num_threads);
  ttg.doit();
  ttg.join();
  ASSERT_EQ(data, result_count<TESTSIZE>());
}

template <typename TESTSIZE, Assembler::operand_size ASMSIZE>
static void run_concurrent_alt_cmpxchg_tests() {
  volatile TESTSIZE data = std::numeric_limits<TESTSIZE>::min();
  int num_threads = NUMBER_THREADS;
  CmpxchgTester<TESTSIZE, ASMSIZE> cmpxchg(0, false); // variant 0, not bool ret
  auto incThread = [&](Thread* _current, int _id) {   // _id starts from 0..(CTHREAD-1)
    for (int i = 0; i < PAR_IT_END; i++) {
      TESTSIZE oldvalue;
      TESTSIZE ret = 0;
      do {
        oldvalue = ret;
        TESTSIZE newvalue = next_count<TESTSIZE>(oldvalue, 1);
        ret = cmpxchg.cmpxchg((intptr_t)&data, oldvalue, newvalue);
      } while (ret != oldvalue);
    }
  };
  TestThreadGroup<decltype(incThread)> ttg(incThread, num_threads);
  ttg.doit();
  ttg.join();
  ASSERT_EQ(data, result_count<TESTSIZE>());
}

TEST_VM(RiscV, cmpxchg_int64_concurrent_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_concurrent_cmpxchg_tests<int64_t, Assembler::int64>();
  run_concurrent_alt_cmpxchg_tests<int64_t, Assembler::int64>();
  UseZacas = zacas;
}

TEST_VM(RiscV, cmpxchg_int64_concurrent_maybe_zacas) {
  if (UseZacas) {
    run_concurrent_cmpxchg_tests<int64_t, Assembler::int64>();
    run_concurrent_alt_cmpxchg_tests<int64_t, Assembler::int64>();
  }
}

TEST_VM(RiscV, cmpxchg_int32_concurrent_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_concurrent_cmpxchg_tests<int32_t, Assembler::int32>();
  run_concurrent_alt_cmpxchg_tests<int32_t, Assembler::int32>();
  UseZacas = zacas;
}

TEST_VM(RiscV, cmpxchg_int32_concurrent_maybe_zacas) {
  if (UseZacas) {
    run_concurrent_cmpxchg_tests<int32_t, Assembler::int32>();
    run_concurrent_alt_cmpxchg_tests<int32_t, Assembler::int32>();
  }
}

TEST_VM(RiscV, cmpxchg_uint32_concurrent_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_concurrent_cmpxchg_tests<uint32_t, Assembler::uint32>();
  run_concurrent_alt_cmpxchg_tests<uint32_t, Assembler::uint32>();
  UseZacas = zacas;
}

TEST_VM(RiscV, cmpxchg_uint32_concurrent_maybe_zacas) {
  if (UseZacas) {
    run_concurrent_cmpxchg_tests<uint32_t, Assembler::uint32>();
    run_concurrent_alt_cmpxchg_tests<uint32_t, Assembler::uint32>();
  }
}

TEST_VM(RiscV, cmpxchg_narrow_int16_concurrent_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_concurrent_cmpxchg_tests<int16_t, Assembler::int16>();
  run_concurrent_alt_cmpxchg_tests<int16_t, Assembler::int16>();
  UseZacas = zacas;
}

TEST_VM(RiscV, cmpxchg_narrow_int16_concurrent_maybe_zacas) {
  if (UseZacas) {
    bool zabha = UseZabha;
    UseZabha = false;
    run_concurrent_cmpxchg_tests<int16_t, Assembler::int16>();
    run_concurrent_alt_cmpxchg_tests<int16_t, Assembler::int16>();
    UseZabha = zabha;
  }
}

TEST_VM(RiscV, cmpxchg_narrow_int8_concurrent_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_concurrent_cmpxchg_tests<int8_t, Assembler::int8>();
  run_concurrent_alt_cmpxchg_tests<int8_t, Assembler::int8>();
  UseZacas = zacas;
}

TEST_VM(RiscV, cmpxchg_narrow_int8_concurrent_maybe_zacas) {
  if (UseZacas) {
    bool zabha = UseZabha;
    UseZabha = false;
    run_concurrent_cmpxchg_tests<int8_t, Assembler::int8>();
    run_concurrent_alt_cmpxchg_tests<int8_t, Assembler::int8>();
    UseZabha = zabha;
  }
}

TEST_VM(RiscV, cmpxchg_int16_concurrent_maybe_zacas_zabha) {
  if (UseZacas && UseZabha) {
    run_concurrent_cmpxchg_tests<int16_t, Assembler::int16>();
    run_concurrent_alt_cmpxchg_tests<int16_t, Assembler::int16>();
  }
}

TEST_VM(RiscV, cmpxchg_int8_concurrent_maybe_zacas_zabha) {
  if (UseZacas && UseZabha) {
    run_concurrent_cmpxchg_tests<int8_t, Assembler::int8>();
    run_concurrent_alt_cmpxchg_tests<int8_t, Assembler::int8>();
  }
}

template <typename TESTSIZE, Assembler::operand_size ASMSIZE>
class WeakCmpxchgTester {
  // The functions expect arguments to be type represented, not C-ABI argument representation.
  // Hence an unsigned should be zero-extended, and the same goes for the return value.
  typedef int64_t (*weak_cmpxchg_narrow_func)(intptr_t addr, int64_t expected, int64_t new_value, int64_t result,
                                   int64_t scratch0, int64_t scratch1, int64_t scratch2);

  typedef int64_t (*weak_cmpxchg_func)(intptr_t addr, int64_t expected, int64_t new_value, int64_t result);

  BufferBlob*  _bb;
  weak_cmpxchg_narrow_func _narrow_weak;
  weak_cmpxchg_func _weak;

 public:
  WeakCmpxchgTester() : _bb(nullptr), _narrow_weak(nullptr), _weak(nullptr) {
    _bb = BufferBlob::create("riscvTest", 128);
    CodeBuffer code(_bb);
    MacroAssembler _masm(&code);
    if (using_narrow<ASMSIZE>()) {
        address entry = _masm.pc();
       _masm.weak_cmpxchg_narrow_value(/*addr*/ c_rarg0, /*expected*/ c_rarg1, /*new_value*/ c_rarg2,
                                      ASMSIZE, Assembler::relaxed, Assembler::relaxed,
                                      /*result*/ c_rarg3, c_rarg4, c_rarg5, c_rarg6); /* Uses also t0-t1, caller saved */
      _masm.mv(c_rarg0, c_rarg3);
      _masm.ret();
      _narrow_weak = ((weak_cmpxchg_narrow_func)entry);
    } else {
        address entry = _masm.pc();
       _masm.weak_cmpxchg(/*addr*/ c_rarg0, /*expected*/ c_rarg1, /*new_value*/ c_rarg2,
                          ASMSIZE, Assembler::relaxed, Assembler::relaxed, /*result*/ c_rarg3);
      _masm.mv(c_rarg0, c_rarg3);
      _masm.ret();
      _weak = ((weak_cmpxchg_func)entry);
    }
    _masm.flush(); // icache invalidate
  }

  TESTSIZE weak_cmpxchg(intptr_t addr, TESTSIZE expected, TESTSIZE new_value) {
    if (using_narrow<ASMSIZE>()) {
      return _narrow_weak(addr, expected, new_value, /* dummy result */ 67, -1, -1, -1);
    } else {
      return _weak(addr, expected, new_value, /* dummy result */ 67);
    }
  }

  ~WeakCmpxchgTester() {
    BufferBlob::free(_bb);
  }
};

template <typename TESTSIZE, Assembler::operand_size ASMSIZE>
void run_weak_cmpxchg_tests() {
  TESTSIZE max = std::numeric_limits<TESTSIZE>::max();
  TESTSIZE min = std::numeric_limits<TESTSIZE>::min();
  TESTSIZE val[] = {121, min, max};
  for (int i = 0; i < 3; i++) {
    WeakCmpxchgTester<TESTSIZE, ASMSIZE> cmpxchg;
    TESTSIZE data = val[i];
    TESTSIZE ret = cmpxchg.weak_cmpxchg((intptr_t)&data, val[i], 42);
    ASSERT_EQ(ret, (TESTSIZE)1);
    ASSERT_EQ(data, (TESTSIZE)42);

    data = val[i];
    ret = cmpxchg.weak_cmpxchg((intptr_t)&data, 120, 42);
    ASSERT_EQ(ret, (TESTSIZE)0);
    ASSERT_EQ(data, (TESTSIZE)val[i]);
  }
}

TEST_VM(RiscV, weak_cmpxchg_int64_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_weak_cmpxchg_tests<int64_t, Assembler::int64>();
  UseZacas = zacas;
}

TEST_VM(RiscV, weak_cmpxchg_int64_maybe_zacas) {
  if (UseZacas) {
    run_weak_cmpxchg_tests<int64_t, Assembler::int64>();
  }
}

TEST_VM(RiscV, weak_cmpxchg_int32_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_weak_cmpxchg_tests<int32_t, Assembler::int32>();
  UseZacas = zacas;
}

TEST_VM(RiscV, weak_cmpxchg_int32_maybe_zacas) {
  if (UseZacas) {
    run_weak_cmpxchg_tests<int32_t, Assembler::int32>();
  }
}

TEST_VM(RiscV, weak_cmpxchg_uint32_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_weak_cmpxchg_tests<uint32_t, Assembler::uint32>();
  UseZacas = zacas;
}

TEST_VM(RiscV, weak_cmpxchg_uint32_maybe_zacas) {
  if (UseZacas) {
    run_weak_cmpxchg_tests<uint32_t, Assembler::uint32>();
  }
}

TEST_VM(RiscV, weak_cmpxchg_narrow_int16_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_weak_cmpxchg_tests<int16_t, Assembler::int16>();
  UseZacas = zacas;
}

TEST_VM(RiscV, weak_cmpxchg_narrow_int8_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_weak_cmpxchg_tests<int8_t, Assembler::int8>();
  UseZacas = zacas;
}

TEST_VM(RiscV, weak_cmpxchg_narrow_int16_maybe_zacas) {
  if (UseZacas) {
    bool zabha = UseZabha;
    UseZabha = false;
    run_weak_cmpxchg_tests<int16_t, Assembler::int16>();
    UseZabha = zabha;
  }
}

TEST_VM(RiscV, weak_cmpxchg_narrow_int8_maybe_zacas) {
  if (UseZacas) {
    bool zabha = UseZabha;
    UseZabha = false;
    run_weak_cmpxchg_tests<int8_t, Assembler::int8>();
    UseZabha = zabha;
  }
}

TEST_VM(RiscV, weak_cmpxchg_int16_maybe_zacas_zabha) {
  if (UseZacas && UseZabha) {
    run_weak_cmpxchg_tests<int16_t, Assembler::int16>();
  }
}

TEST_VM(RiscV, weak_cmpxchg_int8_maybe_zacas_zabha) {
  if (UseZacas && UseZabha) {
    run_weak_cmpxchg_tests<int8_t, Assembler::int8>();
  }
}

template <typename TESTSIZE, Assembler::operand_size ASMSIZE>
static void run_concurrent_weak_cmpxchg_tests() {
  volatile TESTSIZE data = std::numeric_limits<TESTSIZE>::min();
  int num_threads = NUMBER_THREADS;
  WeakCmpxchgTester<TESTSIZE, ASMSIZE> cmpxchg; // not bool ret
  auto incThread = [&](Thread* _current, int _id) { // _id starts from 0..(CTHREAD-1)
    TESTSIZE my_oldvalue = std::numeric_limits<TESTSIZE>::min() + _id;
    for (int64_t i = 0; i < PAR_IT_END; i++) {
      TESTSIZE newvalue = next_count<TESTSIZE>(my_oldvalue, 1);
      TESTSIZE ret;
      do {
        ret = cmpxchg.weak_cmpxchg((intptr_t)&data, my_oldvalue, newvalue);
      } while (ret != 1);
      my_oldvalue = next_count<TESTSIZE>(my_oldvalue, num_threads);
    }
  };
  TestThreadGroup<decltype(incThread)> ttg(incThread, num_threads);
  ttg.doit();
  ttg.join();
  ASSERT_EQ(data, result_count<TESTSIZE>());
}

template <typename TESTSIZE, Assembler::operand_size ASMSIZE>
static void run_concurrent_alt_weak_cmpxchg_tests() {
  volatile TESTSIZE data = std::numeric_limits<TESTSIZE>::min();
  int num_threads = NUMBER_THREADS;
  WeakCmpxchgTester<TESTSIZE, ASMSIZE> cmpxchg; // not bool ret
  auto incThread = [&](Thread* _current, int _id) { // _id starts from 0..(CTHREAD-1)
    for (int i = 0; i < PAR_IT_END; i++) {
      TESTSIZE oldvalue;
      TESTSIZE ret = 0;
      do {
        oldvalue = data;
        TESTSIZE newvalue = next_count<TESTSIZE>(oldvalue, 1);
        ret = cmpxchg.weak_cmpxchg((intptr_t)&data, oldvalue, newvalue);
      } while (ret != 1);
    }
  };
  TestThreadGroup<decltype(incThread)> ttg(incThread, num_threads);
  ttg.doit();
  ttg.join();
  ASSERT_EQ(data, result_count<TESTSIZE>());
}

TEST_VM(RiscV, weak_cmpxchg_int64_concurrent_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_concurrent_weak_cmpxchg_tests<int64_t, Assembler::int64>();
  run_concurrent_alt_weak_cmpxchg_tests<int64_t, Assembler::int64>();
  UseZacas = zacas;
}

TEST_VM(RiscV, weak_cmpxchg_int64_concurrent_maybe_zacas) {
  if (UseZacas) {
    run_concurrent_weak_cmpxchg_tests<int64_t, Assembler::int64>();
    run_concurrent_alt_weak_cmpxchg_tests<int64_t, Assembler::int64>();
  }
}

TEST_VM(RiscV, weak_cmpxchg_int32_concurrent_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_concurrent_weak_cmpxchg_tests<int32_t, Assembler::int32>();
  run_concurrent_alt_weak_cmpxchg_tests<int32_t, Assembler::int32>();
  UseZacas = zacas;
}

TEST_VM(RiscV, weak_cmpxchg_int32_concurrent_maybe_zacas) {
  if (UseZacas) {
    run_concurrent_weak_cmpxchg_tests<int32_t, Assembler::int32>();
    run_concurrent_alt_weak_cmpxchg_tests<int32_t, Assembler::int32>();
  }
}

TEST_VM(RiscV, weak_cmpxchg_narrow_int16_concurrent_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_concurrent_weak_cmpxchg_tests<int16_t, Assembler::int16>();
  run_concurrent_alt_weak_cmpxchg_tests<int16_t, Assembler::int16>();
  UseZacas = zacas;
}

TEST_VM(RiscV, weak_cmpxchg_narrow_int16_concurrent_maybe_zacas) {
  if (UseZacas) {
    bool zabha = UseZabha;
    UseZabha = false;
    run_concurrent_weak_cmpxchg_tests<int16_t, Assembler::int16>();
    run_concurrent_alt_weak_cmpxchg_tests<int16_t, Assembler::int16>();
    UseZabha = zabha;
  }
}

TEST_VM(RiscV, weak_cmpxchg_narrow_int8_concurrent_lr_sc) {
  bool zacas = UseZacas;
  UseZacas = false;
  run_concurrent_weak_cmpxchg_tests<int8_t, Assembler::int8>();
  run_concurrent_alt_weak_cmpxchg_tests<int8_t, Assembler::int8>();
  UseZacas = zacas;
}

TEST_VM(RiscV, weak_cmpxchg_narrow_int8_concurrent_maybe_zacas) {
  if (UseZacas) {
    bool zabha = UseZabha;
    UseZabha = false;
    run_concurrent_weak_cmpxchg_tests<int8_t, Assembler::int8>();
    run_concurrent_alt_weak_cmpxchg_tests<int8_t, Assembler::int8>();
    UseZabha = zabha;
  }
}

TEST_VM(RiscV, weak_cmpxchg_int16_concurrent_maybe_zacas_zabha) {
  if (UseZacas && UseZabha) {
    run_concurrent_weak_cmpxchg_tests<int16_t, Assembler::int16>();
    run_concurrent_alt_weak_cmpxchg_tests<int16_t, Assembler::int16>();
  }
}

TEST_VM(RiscV, weak_cmpxchg_int8_concurrent_maybe_zacas_zabha) {
  if (UseZacas && UseZabha) {
    run_concurrent_weak_cmpxchg_tests<int8_t, Assembler::int8>();
    run_concurrent_alt_weak_cmpxchg_tests<int8_t, Assembler::int8>();
  }
}

#endif  // RISCV
