/*
 * Copyright (c) 2015, 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.
 */

#ifndef SHARE_GC_Z_ZFORWARDING_INLINE_HPP
#define SHARE_GC_Z_ZFORWARDING_INLINE_HPP

#include "gc/z/zForwarding.hpp"

#include "gc/z/zAddress.inline.hpp"
#include "gc/z/zAttachedArray.inline.hpp"
#include "gc/z/zForwardingAllocator.inline.hpp"
#include "gc/z/zHash.inline.hpp"
#include "gc/z/zHeap.hpp"
#include "gc/z/zIterator.inline.hpp"
#include "gc/z/zLock.inline.hpp"
#include "gc/z/zPage.inline.hpp"
#include "gc/z/zUtils.inline.hpp"
#include "gc/z/zVirtualMemory.inline.hpp"
#include "runtime/atomic.hpp"
#include "utilities/debug.hpp"
#include "utilities/powerOfTwo.hpp"

inline uint32_t ZForwarding::nentries(const ZPage* page) {
  // The number returned by the function is used to size the hash table of
  // forwarding entries for this page. This hash table uses linear probing.
  // The size of the table must be a power of two to allow for quick and
  // inexpensive indexing/masking. The table is also sized to have a load
  // factor of 50%, i.e. sized to have double the number of entries actually
  // inserted, to allow for good lookup/insert performance.
  return round_up_power_of_2(page->live_objects() * 2);
}

inline ZForwarding* ZForwarding::alloc(ZForwardingAllocator* allocator, ZPage* page, ZPageAge to_age) {
  const size_t nentries = ZForwarding::nentries(page);
  void* const addr = AttachedArray::alloc(allocator, nentries);
  return ::new (addr) ZForwarding(page, to_age, nentries);
}

inline ZForwarding::ZForwarding(ZPage* page, ZPageAge to_age, size_t nentries)
  : _virtual(page->virtual_memory()),
    _object_alignment_shift(page->object_alignment_shift()),
    _entries(nentries),
    _page(page),
    _from_age(page->age()),
    _to_age(to_age),
    _claimed(false),
    _ref_lock(),
    _ref_count(1),
    _done(false),
    _relocated_remembered_fields_state(ZPublishState::none),
    _relocated_remembered_fields_array(),
    _relocated_remembered_fields_publish_young_seqnum(0),
    _in_place(false),
    _in_place_top_at_start(),
    _in_place_thread(nullptr) {}

inline ZPageType ZForwarding::type() const {
  return _page->type();
}

inline ZPageAge ZForwarding::from_age() const {
  return _from_age;
}

inline ZPageAge ZForwarding::to_age() const {
  return _to_age;
}

inline zoffset ZForwarding::start() const {
  return _virtual.start();
}

inline zoffset_end ZForwarding::end() const {
  return _virtual.end();
}

inline size_t ZForwarding::size() const {
  return _virtual.size();
}

inline size_t ZForwarding::object_alignment_shift() const {
  return _object_alignment_shift;
}

inline bool ZForwarding::is_promotion() const {
  return _from_age != ZPageAge::old &&
         _to_age == ZPageAge::old;
}

template <typename Function>
inline void ZForwarding::object_iterate(Function function) {
  ZObjectClosure<Function> cl(function);
  _page->object_iterate(function);
}

template <typename Function>
inline void ZForwarding::address_unsafe_iterate_via_table(Function function) {
  for (ZForwardingCursor i = 0; i < _entries.length(); i++) {
    const ZForwardingEntry entry = at(&i);
    if (!entry.populated()) {
      // Skip empty entries
      continue;
    }

    // Find to-object

    const zoffset from_offset = start() + (entry.from_index() << object_alignment_shift());
    const zaddress_unsafe from_addr = ZOffset::address_unsafe(from_offset);

    // Apply function
    function(from_addr);
  }
}

template <typename Function>
inline void ZForwarding::object_iterate_forwarded_via_livemap(Function function) {
  assert(!in_place_relocation(), "Not allowed to use livemap iteration");

  object_iterate([&](oop obj) {
    // Find to-object
    const zaddress_unsafe from_addr = to_zaddress_unsafe(obj);
    const zaddress to_addr = this->find(from_addr);
    const oop to_obj = to_oop(to_addr);

    // Apply function
    function(to_obj);
  });
}

template <typename Function>
inline void ZForwarding::object_iterate_forwarded_via_table(Function function) {
  for (ZForwardingCursor i = 0; i < _entries.length(); i++) {
    const ZForwardingEntry entry = at(&i);
    if (!entry.populated()) {
      // Skip empty entries
      continue;
    }

    // Find to-object
    const zoffset to_offset = to_zoffset(entry.to_offset());
    const zaddress to_addr = ZOffset::address(to_offset);
    const oop to_obj = to_oop(to_addr);

    // Apply function
    function(to_obj);
  }
}

template <typename Function>
inline void ZForwarding::object_iterate_forwarded(Function function) {
  if (in_place_relocation()) {
    // The original objects are not available anymore, can't use the livemap
    object_iterate_forwarded_via_table(function);
  } else {
    object_iterate_forwarded_via_livemap(function);
  }
}

template <typename Function>
void ZForwarding::oops_do_in_forwarded(Function function) {
  object_iterate_forwarded([&](oop to_obj) {
    ZIterator::basic_oop_iterate_safe(to_obj, function);
  });
}

template <typename Function>
void ZForwarding::oops_do_in_forwarded_via_table(Function function) {
  object_iterate_forwarded_via_table([&](oop to_obj) {
    ZIterator::basic_oop_iterate_safe(to_obj, function);
  });
}

inline bool ZForwarding::in_place_relocation() const {
  assert(Atomic::load(&_ref_count) != 0, "The page has been released/detached");
  return _in_place;
}

inline ZForwardingEntry* ZForwarding::entries() const {
  return _entries(this);
}

inline ZForwardingEntry ZForwarding::at(ZForwardingCursor* cursor) const {
  // Load acquire for correctness with regards to
  // accesses to the contents of the forwarded object.
  return Atomic::load_acquire(entries() + *cursor);
}

inline ZForwardingEntry ZForwarding::first(uintptr_t from_index, ZForwardingCursor* cursor) const {
  const size_t mask = _entries.length() - 1;
  const size_t hash = ZHash::uint32_to_uint32((uint32_t)from_index);
  *cursor = hash & mask;
  return at(cursor);
}

inline ZForwardingEntry ZForwarding::next(ZForwardingCursor* cursor) const {
  const size_t mask = _entries.length() - 1;
  *cursor = (*cursor + 1) & mask;
  return at(cursor);
}

inline uintptr_t ZForwarding::index(zoffset from_offset) {
  return (from_offset - start()) >> object_alignment_shift();
}

inline ZForwardingEntry ZForwarding::find(uintptr_t from_index, ZForwardingCursor* cursor) const {
  // Reading entries in the table races with the atomic CAS done for
  // insertion into the table. This is safe because each entry is at
  // most updated once (from zero to something else).
  ZForwardingEntry entry = first(from_index, cursor);
  while (entry.populated()) {
    if (entry.from_index() == from_index) {
      // Match found, return matching entry
      return entry;
    }

    entry = next(cursor);
  }

  // Match not found, return empty entry
  return entry;
}

inline zaddress ZForwarding::find(zoffset from_offset, ZForwardingCursor* cursor) {
  const uintptr_t from_index = index(from_offset);
  const ZForwardingEntry entry = find(from_index, cursor);
  return entry.populated() ? ZOffset::address(to_zoffset(entry.to_offset())) : zaddress::null;
}

inline zaddress ZForwarding::find(zaddress from_addr, ZForwardingCursor* cursor) {
  return find(ZAddress::offset(from_addr), cursor);
}

inline zaddress ZForwarding::find(zaddress_unsafe from_addr, ZForwardingCursor* cursor) {
  return find(ZAddress::offset(from_addr), cursor);
}

inline zaddress ZForwarding::find(zaddress_unsafe from_addr) {
  ZForwardingCursor cursor;
  return find(from_addr, &cursor);
}

inline zoffset ZForwarding::insert(uintptr_t from_index, zoffset to_offset, ZForwardingCursor* cursor) {
  const ZForwardingEntry new_entry(from_index, untype(to_offset));
  const ZForwardingEntry old_entry; // Empty

  // Make sure that object copy is finished
  // before forwarding table installation
  OrderAccess::release();

  for (;;) {
    const ZForwardingEntry prev_entry = Atomic::cmpxchg(entries() + *cursor, old_entry, new_entry, memory_order_relaxed);
    if (!prev_entry.populated()) {
      // Success
      return to_offset;
    }

    // Find next empty or matching entry
    ZForwardingEntry entry = at(cursor);
    while (entry.populated()) {
      if (entry.from_index() == from_index) {
        // Match found, return already inserted address
        return to_zoffset(entry.to_offset());
      }

      entry = next(cursor);
    }
  }
}

inline zaddress ZForwarding::insert(zoffset from_offset, zaddress to_addr, ZForwardingCursor* cursor) {
  const uintptr_t from_index = index(from_offset);
  const zoffset to_offset = ZAddress::offset(to_addr);
  const zoffset to_offset_final = insert(from_index, to_offset, cursor);
  return ZOffset::address(to_offset_final);
}

inline zaddress ZForwarding::insert(zaddress from_addr, zaddress to_addr, ZForwardingCursor* cursor) {
  return insert(ZAddress::offset(from_addr), to_addr, cursor);
}

inline void ZForwarding::relocated_remembered_fields_register(volatile zpointer* p) {
  // Invariant: Page is being retained
  assert(ZGeneration::young()->is_phase_mark(), "Only called when");

  const ZPublishState res = Atomic::load(&_relocated_remembered_fields_state);

  // none:      Gather remembered fields
  // published: Have already published fields - not possible since they haven't been
  //            collected yet
  // reject:    YC rejected fields collected by the OC
  // accept:    YC has marked that there's no more concurrent scanning of relocated
  //            fields - not possible since this code is still relocating objects

  if (res == ZPublishState::none) {
    _relocated_remembered_fields_array.push(p);
    return;
  }

  assert(res == ZPublishState::reject, "Unexpected value");
}

// Returns true iff the page is being (or about to be) relocated by the OC
// while the YC gathered the remembered fields of the "from" page.
inline bool ZForwarding::relocated_remembered_fields_is_concurrently_scanned() const {
  return Atomic::load(&_relocated_remembered_fields_state) == ZPublishState::reject;
}

template <typename Function>
inline void ZForwarding::relocated_remembered_fields_apply_to_published(Function function) {
  // Invariant: Page is not being retained
  assert(ZGeneration::young()->is_phase_mark(), "Only called when");

  const ZPublishState res = Atomic::load_acquire(&_relocated_remembered_fields_state);

  // none:      Nothing published - page had already been relocated before YC started
  // published: OC relocated and published relocated remembered fields
  // reject:    A previous YC concurrently scanned relocated remembered fields of the "from" page
  // accept:    A previous YC marked that it didn't do (reject)

  if (res == ZPublishState::published) {
    log_debug(gc, remset)("Forwarding remset accept          : " PTR_FORMAT " " PTR_FORMAT " (" PTR_FORMAT ", %s)",
        untype(start()), untype(end()), p2i(this), Thread::current()->name());

    // OC published relocated remembered fields
    ZArrayIterator<volatile zpointer*> iter(&_relocated_remembered_fields_array);
    for (volatile zpointer* to_field_addr; iter.next(&to_field_addr);) {
      function(to_field_addr);
    }

    // YC responsible for the array - eagerly deallocate
    _relocated_remembered_fields_array.clear_and_deallocate();
  }

  assert(_relocated_remembered_fields_publish_young_seqnum != 0, "Must have been set");
  if (_relocated_remembered_fields_publish_young_seqnum == ZGeneration::young()->seqnum()) {
    log_debug(gc, remset)("scan_forwarding failed retain unsafe " PTR_FORMAT, untype(start()));
    // The page was relocated concurrently with the current young generation
    // collection. Mark that it is unsafe (and unnecessary) to call scan_page
    // on the page in the page table.
    assert(res != ZPublishState::accept, "Unexpected");
    Atomic::store(&_relocated_remembered_fields_state, ZPublishState::reject);
  } else {
    log_debug(gc, remset)("scan_forwarding failed retain safe " PTR_FORMAT, untype(start()));
    // Guaranteed that the page was fully relocated and removed from page table.
    // Because of this we can signal to scan_page that any page found in page table
    // of the same slot as the current forwarding is a page that is safe to scan,
    // and in fact must be scanned.
    Atomic::store(&_relocated_remembered_fields_state, ZPublishState::accept);
  }
}

#endif // SHARE_GC_Z_ZFORWARDING_INLINE_HPP
