/*
 * Copyright (c) 2023, 2024, Oracle and/or its affiliates. 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 "precompiled.hpp"
#include "runtime/interfaceSupport.inline.hpp"
#include "runtime/lockStack.inline.hpp"
#include "runtime/os.hpp"
#include "unittest.hpp"
#include "utilities/globalDefinitions.hpp"

class LockStackTest : public ::testing::Test {
public:
  static void push_raw(LockStack& ls, oop obj) {
    ls._base[ls.to_index(ls._top)] = obj;
    ls._top += oopSize;
  }

  static void pop_raw(LockStack& ls) {
    ls._top -= oopSize;
#ifdef ASSERT
    ls._base[ls.to_index(ls._top)] = nullptr;
#endif
  }

  static oop at(LockStack& ls, int index) {
    return ls._base[index];
  }

  static size_t size(LockStack& ls) {
    return ls.to_index(ls._top);
  }
};

#define recursive_enter(ls, obj)             \
  do {                                       \
    bool ret = ls.try_recursive_enter(obj);  \
    EXPECT_TRUE(ret);                        \
  } while (false)

#define recursive_exit(ls, obj)             \
  do {                                      \
    bool ret = ls.try_recursive_exit(obj);  \
    EXPECT_TRUE(ret);                       \
  } while (false)

TEST_VM_F(LockStackTest, is_recursive) {
  if (LockingMode != LM_LIGHTWEIGHT || !VM_Version::supports_recursive_lightweight_locking()) {
    return;
  }

  JavaThread* THREAD = JavaThread::current();
  // the thread should be in vm to use locks
  ThreadInVMfromNative ThreadInVMfromNative(THREAD);

  LockStack& ls = THREAD->lock_stack();

  EXPECT_TRUE(ls.is_empty());

  oop obj0 = Universe::int_mirror();
  oop obj1 = Universe::float_mirror();

  push_raw(ls, obj0);

  // 0
  EXPECT_FALSE(ls.is_recursive(obj0));

  push_raw(ls, obj1);

  // 0, 1
  EXPECT_FALSE(ls.is_recursive(obj0));
  EXPECT_FALSE(ls.is_recursive(obj1));

  push_raw(ls, obj1);

  // 0, 1, 1
  EXPECT_FALSE(ls.is_recursive(obj0));
  EXPECT_TRUE(ls.is_recursive(obj1));

  pop_raw(ls);
  pop_raw(ls);
  push_raw(ls, obj0);

  // 0, 0
  EXPECT_TRUE(ls.is_recursive(obj0));

  push_raw(ls, obj0);

  // 0, 0, 0
  EXPECT_TRUE(ls.is_recursive(obj0));

  pop_raw(ls);
  push_raw(ls, obj1);

  // 0, 0, 1
  EXPECT_TRUE(ls.is_recursive(obj0));
  EXPECT_FALSE(ls.is_recursive(obj1));

  push_raw(ls, obj1);

  // 0, 0, 1, 1
  EXPECT_TRUE(ls.is_recursive(obj0));
  EXPECT_TRUE(ls.is_recursive(obj1));

  // Clear stack
  pop_raw(ls);
  pop_raw(ls);
  pop_raw(ls);
  pop_raw(ls);

  EXPECT_TRUE(ls.is_empty());
}

TEST_VM_F(LockStackTest, try_recursive_enter) {
  if (LockingMode != LM_LIGHTWEIGHT || !VM_Version::supports_recursive_lightweight_locking()) {
    return;
  }

  JavaThread* THREAD = JavaThread::current();
  // the thread should be in vm to use locks
  ThreadInVMfromNative ThreadInVMfromNative(THREAD);

  LockStack& ls = THREAD->lock_stack();

  EXPECT_TRUE(ls.is_empty());

  oop obj0 = Universe::int_mirror();
  oop obj1 = Universe::float_mirror();

  ls.push(obj0);

  // 0
  EXPECT_FALSE(ls.is_recursive(obj0));

  ls.push(obj1);

  // 0, 1
  EXPECT_FALSE(ls.is_recursive(obj0));
  EXPECT_FALSE(ls.is_recursive(obj1));

  recursive_enter(ls, obj1);

  // 0, 1, 1
  EXPECT_FALSE(ls.is_recursive(obj0));
  EXPECT_TRUE(ls.is_recursive(obj1));

  recursive_exit(ls, obj1);
  pop_raw(ls);
  recursive_enter(ls, obj0);

  // 0, 0
  EXPECT_TRUE(ls.is_recursive(obj0));

  recursive_enter(ls, obj0);

  // 0, 0, 0
  EXPECT_TRUE(ls.is_recursive(obj0));

  recursive_exit(ls, obj0);
  push_raw(ls, obj1);

  // 0, 0, 1
  EXPECT_TRUE(ls.is_recursive(obj0));
  EXPECT_FALSE(ls.is_recursive(obj1));

  recursive_enter(ls, obj1);

  // 0, 0, 1, 1
  EXPECT_TRUE(ls.is_recursive(obj0));
  EXPECT_TRUE(ls.is_recursive(obj1));

  // Clear stack
  pop_raw(ls);
  pop_raw(ls);
  pop_raw(ls);
  pop_raw(ls);

  EXPECT_TRUE(ls.is_empty());
}

TEST_VM_F(LockStackTest, contains) {
  if (LockingMode != LM_LIGHTWEIGHT) {
    return;
  }

  const bool test_recursive = VM_Version::supports_recursive_lightweight_locking();

  JavaThread* THREAD = JavaThread::current();
  // the thread should be in vm to use locks
  ThreadInVMfromNative ThreadInVMfromNative(THREAD);

  LockStack& ls = THREAD->lock_stack();

  EXPECT_TRUE(ls.is_empty());

  oop obj0 = Universe::int_mirror();
  oop obj1 = Universe::float_mirror();

  EXPECT_FALSE(ls.contains(obj0));

  ls.push(obj0);

  // 0
  EXPECT_TRUE(ls.contains(obj0));
  EXPECT_FALSE(ls.contains(obj1));

  if (test_recursive) {
    push_raw(ls, obj0);

    // 0, 0
    EXPECT_TRUE(ls.contains(obj0));
    EXPECT_FALSE(ls.contains(obj1));
  }

  push_raw(ls, obj1);

  // 0, 0, 1
  EXPECT_TRUE(ls.contains(obj0));
  EXPECT_TRUE(ls.contains(obj1));

  if (test_recursive) {
    push_raw(ls, obj1);

    // 0, 0, 1, 1
    EXPECT_TRUE(ls.contains(obj0));
    EXPECT_TRUE(ls.contains(obj1));
  }

  pop_raw(ls);
  if (test_recursive) {
    pop_raw(ls);
    pop_raw(ls);
  }
  push_raw(ls, obj1);

  // 0, 1
  EXPECT_TRUE(ls.contains(obj0));
  EXPECT_TRUE(ls.contains(obj1));

  // Clear stack
  pop_raw(ls);
  pop_raw(ls);

  EXPECT_TRUE(ls.is_empty());
}

TEST_VM_F(LockStackTest, remove) {
  if (LockingMode != LM_LIGHTWEIGHT) {
    return;
  }

  const bool test_recursive = VM_Version::supports_recursive_lightweight_locking();

  JavaThread* THREAD = JavaThread::current();
  // the thread should be in vm to use locks
  ThreadInVMfromNative ThreadInVMfromNative(THREAD);

  LockStack& ls = THREAD->lock_stack();

  EXPECT_TRUE(ls.is_empty());

  oop obj0 = Universe::int_mirror();
  oop obj1 = Universe::float_mirror();
  oop obj2 = Universe::short_mirror();
  oop obj3 = Universe::long_mirror();

  push_raw(ls, obj0);

  // 0
  {
    size_t removed = ls.remove(obj0);
    EXPECT_EQ(removed, 1u);
    EXPECT_FALSE(ls.contains(obj0));
  }

  if (test_recursive) {
    push_raw(ls, obj0);
    push_raw(ls, obj0);

    // 0, 0
    {
      size_t removed = ls.remove(obj0);
      EXPECT_EQ(removed, 2u);
      EXPECT_FALSE(ls.contains(obj0));
    }
  }

  push_raw(ls, obj0);
  push_raw(ls, obj1);

  // 0, 1
  {
    size_t removed = ls.remove(obj0);
    EXPECT_EQ(removed, 1u);
    EXPECT_FALSE(ls.contains(obj0));
    EXPECT_TRUE(ls.contains(obj1));

    ls.remove(obj1);
    EXPECT_TRUE(ls.is_empty());
  }

  push_raw(ls, obj0);
  push_raw(ls, obj1);

  // 0, 1
  {
    size_t removed = ls.remove(obj1);
    EXPECT_EQ(removed, 1u);
    EXPECT_FALSE(ls.contains(obj1));
    EXPECT_TRUE(ls.contains(obj0));

    ls.remove(obj0);
    EXPECT_TRUE(ls.is_empty());
  }

  if (test_recursive) {
    push_raw(ls, obj0);
    push_raw(ls, obj0);
    push_raw(ls, obj1);

    // 0, 0, 1
    {
      size_t removed = ls.remove(obj0);
      EXPECT_EQ(removed, 2u);
      EXPECT_FALSE(ls.contains(obj0));
      EXPECT_TRUE(ls.contains(obj1));

      ls.remove(obj1);
      EXPECT_TRUE(ls.is_empty());
    }

    push_raw(ls, obj0);
    push_raw(ls, obj1);
    push_raw(ls, obj1);

    // 0, 1, 1
    {
      size_t removed = ls.remove(obj1);
      EXPECT_EQ(removed, 2u);
      EXPECT_FALSE(ls.contains(obj1));
      EXPECT_TRUE(ls.contains(obj0));

      ls.remove(obj0);
      EXPECT_TRUE(ls.is_empty());
    }

    push_raw(ls, obj0);
    push_raw(ls, obj1);
    push_raw(ls, obj1);
    push_raw(ls, obj2);
    push_raw(ls, obj2);
    push_raw(ls, obj2);
    push_raw(ls, obj2);
    push_raw(ls, obj3);

    // 0, 1, 1, 2, 2, 2, 2, 3
    {
      EXPECT_EQ(size(ls), 8u);

      size_t removed = ls.remove(obj1);
      EXPECT_EQ(removed, 2u);

      EXPECT_TRUE(ls.contains(obj0));
      EXPECT_FALSE(ls.contains(obj1));
      EXPECT_TRUE(ls.contains(obj2));
      EXPECT_TRUE(ls.contains(obj3));

      EXPECT_EQ(at(ls, 0), obj0);
      EXPECT_EQ(at(ls, 1), obj2);
      EXPECT_EQ(at(ls, 2), obj2);
      EXPECT_EQ(at(ls, 3), obj2);
      EXPECT_EQ(at(ls, 4), obj2);
      EXPECT_EQ(at(ls, 5), obj3);
      EXPECT_EQ(size(ls), 6u);

      removed = ls.remove(obj2);
      EXPECT_EQ(removed, 4u);

      EXPECT_TRUE(ls.contains(obj0));
      EXPECT_FALSE(ls.contains(obj1));
      EXPECT_FALSE(ls.contains(obj2));
      EXPECT_TRUE(ls.contains(obj3));

      EXPECT_EQ(at(ls, 0), obj0);
      EXPECT_EQ(at(ls, 1), obj3);
      EXPECT_EQ(size(ls), 2u);

      removed = ls.remove(obj0);
      EXPECT_EQ(removed, 1u);

      EXPECT_FALSE(ls.contains(obj0));
      EXPECT_FALSE(ls.contains(obj1));
      EXPECT_FALSE(ls.contains(obj2));
      EXPECT_TRUE(ls.contains(obj3));

      EXPECT_EQ(at(ls, 0), obj3);
      EXPECT_EQ(size(ls), 1u);

      removed = ls.remove(obj3);
      EXPECT_EQ(removed, 1u);

      EXPECT_TRUE(ls.is_empty());
      EXPECT_EQ(size(ls), 0u);
    }
  }

  EXPECT_TRUE(ls.is_empty());
}