/*
 * Copyright (c) 2024, 2025, 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 "gc/shared/partialArrayState.hpp"
#include "memory/allocation.inline.hpp"
#include "memory/arena.hpp"
#include "nmt/memTag.hpp"
#include "oops/oopsHierarchy.hpp"
#include "runtime/atomic.hpp"
#include "runtime/orderAccess.hpp"
#include "utilities/debug.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/macros.hpp"

#include <new>

PartialArrayState::PartialArrayState(oop src, oop dst,
                                     size_t index, size_t length,
                                     size_t initial_refcount)
  : _source(src),
    _destination(dst),
    _length(length),
    _index(index),
    _refcount(initial_refcount)
{
  assert(index <= length, "precondition");
}

void PartialArrayState::add_references(size_t count) {
  size_t new_count = Atomic::add(&_refcount, count, memory_order_relaxed);
  assert(new_count >= count, "reference count overflow");
}

class PartialArrayStateAllocator::FreeListEntry {
public:
  FreeListEntry* _next;

  FreeListEntry(FreeListEntry* next) : _next(next) {}
  ~FreeListEntry() = default;

  NONCOPYABLE(FreeListEntry);
};

PartialArrayStateAllocator::PartialArrayStateAllocator(PartialArrayStateManager* manager)
  : _manager(manager),
    _free_list(),
    _arena(manager->register_allocator())
{}

PartialArrayStateAllocator::~PartialArrayStateAllocator() {
  // We don't need to clean up the free list.  Deallocating the entries
  // does nothing, since we're using arena allocation.  Instead, leave it
  // to the manager to release the memory.
  // Inform the manager that an allocator is no longer in use.
  _manager->release_allocator();
}

PartialArrayState* PartialArrayStateAllocator::allocate(oop src, oop dst,
                                                        size_t index,
                                                        size_t length,
                                                        size_t initial_refcount) {
  void* p;
  FreeListEntry* head = _free_list;
  if (head == nullptr) {
    p = NEW_ARENA_OBJ(_arena, PartialArrayState);
  } else {
    _free_list = head->_next;
    head->~FreeListEntry();
    p = head;
  }
  return ::new (p) PartialArrayState(src, dst, index, length, initial_refcount);
}

void PartialArrayStateAllocator::release(PartialArrayState* state) {
  size_t refcount = Atomic::sub(&state->_refcount, size_t(1), memory_order_release);
  if (refcount != 0) {
    assert(refcount + 1 != 0, "refcount underflow");
  } else {
    OrderAccess::acquire();
    // Don't need to call destructor; can't if not destructible.
    static_assert(!std::is_destructible<PartialArrayState>::value, "expected");
    _free_list = ::new (state) FreeListEntry(_free_list);
  }
}

PartialArrayStateManager::PartialArrayStateManager(uint max_allocators)
  : _arenas(NEW_C_HEAP_ARRAY(Arena, max_allocators, mtGC)),
    _max_allocators(max_allocators),
    _registered_allocators(0)
    DEBUG_ONLY(COMMA _released_allocators(0))
{}

PartialArrayStateManager::~PartialArrayStateManager() {
  reset();
  FREE_C_HEAP_ARRAY(Arena, _arenas);
}

Arena* PartialArrayStateManager::register_allocator() {
  uint idx = Atomic::fetch_then_add(&_registered_allocators, 1u, memory_order_relaxed);
  assert(idx < _max_allocators, "exceeded configured max number of allocators");
  return ::new (&_arenas[idx]) Arena(mtGC);
}

#ifdef ASSERT
void PartialArrayStateManager::release_allocator() {
  uint old = Atomic::fetch_then_add(&_released_allocators, 1u, memory_order_relaxed);
  assert(old < Atomic::load(&_registered_allocators), "too many releases");
}
#endif // ASSERT

void PartialArrayStateManager::reset() {
  uint count = Atomic::load(&_registered_allocators);
  assert(count == Atomic::load(&_released_allocators),
         "some allocators still active");
  for (uint i = 0; i < count; ++i) {
    _arenas[i].~Arena();
  }
  Atomic::store(&_registered_allocators, 0u);
  DEBUG_ONLY(Atomic::store(&_released_allocators, 0u);)
}
