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

#ifndef SHARE_GC_Z_ZPAGE_INLINE_HPP
#define SHARE_GC_Z_ZPAGE_INLINE_HPP

#include "gc/z/zPage.hpp"

#include "gc/z/zAddress.inline.hpp"
#include "gc/z/zGeneration.inline.hpp"
#include "gc/z/zGlobals.hpp"
#include "gc/z/zLiveMap.inline.hpp"
#include "gc/z/zRememberedSet.inline.hpp"
#include "gc/z/zVirtualMemory.inline.hpp"
#include "logging/logStream.hpp"
#include "runtime/atomicAccess.hpp"
#include "runtime/os.hpp"
#include "utilities/align.hpp"
#include "utilities/debug.hpp"

inline const char* ZPage::type_to_string() const {
  switch (type()) {
  case ZPageType::small:
    return "Small";

  case ZPageType::medium:
    return "Medium";

  case ZPageType::large:
    return "Large";

  default:
    fatal("Unexpected page type");
  }
}

inline uint32_t ZPage::object_max_count() const {
  switch (type()) {
  case ZPageType::large:
    // A large page can only contain a single
    // object aligned to the start of the page.
    return 1;

  default:
    return checked_cast<uint32_t>(size() >> object_alignment_shift());
  }
}

inline size_t ZPage::object_alignment_shift() const {
  switch (type()) {
  case ZPageType::small:
    return (size_t)ZObjectAlignmentSmallShift;

  case ZPageType::medium:
    return (size_t)ZObjectAlignmentMediumShift;

  case ZPageType::large:
    return (size_t)ZObjectAlignmentLargeShift;

  default:
    fatal("Unexpected page type");
    return 0;
  }
}

inline size_t ZPage::object_alignment() const {
  switch (type()) {
  case ZPageType::small:
    return (size_t)ZObjectAlignmentSmall;

  case ZPageType::medium:
    return (size_t)ZObjectAlignmentMedium;

  case ZPageType::large:
    return (size_t)ZObjectAlignmentLarge;

  default:
    fatal("Unexpected page type");
    return 0;
  }
}

inline ZPageType ZPage::type() const {
  return _type;
}

inline bool ZPage::is_small() const {
  return _type == ZPageType::small;
}

inline bool ZPage::is_medium() const {
  return _type == ZPageType::medium;
}

inline bool ZPage::is_large() const {
  return _type == ZPageType::large;
}

inline ZGenerationId ZPage::generation_id() const {
  return _generation_id;
}

inline bool ZPage::is_young() const {
  return _generation_id == ZGenerationId::young;
}

inline bool ZPage::is_old() const {
  return _generation_id == ZGenerationId::old;
}

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

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

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

inline zoffset_end ZPage::top() const {
  return _top;
}

inline size_t ZPage::remaining() const {
  return end() - top();
}

inline size_t ZPage::used() const {
  return top() - start();
}

inline const ZVirtualMemory& ZPage::virtual_memory() const {
  return _virtual;
}

inline uint32_t ZPage::single_partition_id() const {
  assert(!is_multi_partition(), "Don't fetch single partition id if page is multi-partition");
  return _single_partition_id;
}

inline bool ZPage::is_multi_partition() const {
  return _multi_partition_tracker != nullptr;
}

inline ZMultiPartitionTracker* ZPage::multi_partition_tracker() const {
  return _multi_partition_tracker;
}

inline ZPageAge ZPage::age() const {
  return _age;
}

inline uint32_t ZPage::seqnum() const {
  return _seqnum;
}

inline bool ZPage::is_allocating() const {
  return _seqnum == generation()->seqnum();
}

inline bool ZPage::is_relocatable() const {
  return _seqnum < generation()->seqnum();
}

inline bool ZPage::is_in(zoffset offset) const {
  return offset >= start() && offset < top();
}

inline bool ZPage::is_in(zaddress addr) const {
  const zoffset offset = ZAddress::offset(addr);
  return is_in(offset);
}

inline uintptr_t ZPage::local_offset(zoffset offset) const {
  assert(ZHeap::heap()->is_in_page_relaxed(this, ZOffset::address(offset)),
         "Invalid offset " PTR_FORMAT " page [" PTR_FORMAT ", " PTR_FORMAT ", " PTR_FORMAT ")",
         untype(offset), untype(start()), untype(top()), untype(end()));
  return offset - start();
}

inline uintptr_t ZPage::local_offset(zoffset_end offset) const {
  assert(offset <= end(), "Wrong offset");
  return offset - start();
}

inline uintptr_t ZPage::local_offset(zaddress addr) const {
  const zoffset offset = ZAddress::offset(addr);
  return local_offset(offset);
}

inline uintptr_t ZPage::local_offset(zaddress_unsafe addr) const {
  const zoffset offset = ZAddress::offset(addr);
  return local_offset(offset);
}

inline zoffset ZPage::global_offset(uintptr_t local_offset) const {
  return start() + local_offset;
}

inline bool ZPage::is_marked() const {
  assert(is_relocatable(), "Invalid page state");
  return _livemap.is_marked(_generation_id);
}

inline BitMap::idx_t ZPage::bit_index(zaddress addr) const {
  return (local_offset(addr) >> object_alignment_shift()) * 2;
}

inline zoffset ZPage::offset_from_bit_index(BitMap::idx_t index) const {
  const uintptr_t l_offset = ((index / 2) << object_alignment_shift());
  return start() + l_offset;
}

inline oop ZPage::object_from_bit_index(BitMap::idx_t index) const {
  const zoffset offset = offset_from_bit_index(index);
  return to_oop(ZOffset::address(offset));
}

inline bool ZPage::is_live_bit_set(zaddress addr) const {
  assert(is_relocatable(), "Invalid page state");
  const BitMap::idx_t index = bit_index(addr);
  return _livemap.get(_generation_id, index);
}

inline bool ZPage::is_strong_bit_set(zaddress addr) const {
  assert(is_relocatable(), "Invalid page state");
  const BitMap::idx_t index = bit_index(addr);
  return _livemap.get(_generation_id, index + 1);
}

inline bool ZPage::is_object_live(zaddress addr) const {
  return is_allocating() || is_live_bit_set(addr);
}

inline bool ZPage::is_object_strongly_live(zaddress addr) const {
  return is_allocating() || is_strong_bit_set(addr);
}

inline bool ZPage::is_object_marked_live(zaddress addr) const {
  // This function is only used by the marking code and therefore has stronger
  // asserts that are not always valid to ask when checking for liveness.
  assert(!is_old() || ZGeneration::old()->is_phase_mark(), "Location should match phase");
  assert(!is_young() || ZGeneration::young()->is_phase_mark(), "Location should match phase");

  return is_object_live(addr);
}

inline bool ZPage::is_object_marked_strong(zaddress addr) const {
  // This function is only used by the marking code and therefore has stronger
  // asserts that are not always valid to ask when checking for liveness.
  assert(!is_old() || ZGeneration::old()->is_phase_mark(), "Location should match phase");
  assert(!is_young() || ZGeneration::young()->is_phase_mark(), "Location should match phase");

  return is_object_strongly_live(addr);
}

inline bool ZPage::is_object_marked(zaddress addr, bool finalizable) const {
  return finalizable ? is_object_marked_live(addr) : is_object_marked_strong(addr);
}

inline bool ZPage::mark_object(zaddress addr, bool finalizable, bool& inc_live) {
  assert(is_relocatable(), "Invalid page state");
  assert(is_in(addr), "Invalid address");

  // Verify oop
  assert_is_oop(addr);

  // Set mark bit
  const BitMap::idx_t index = bit_index(addr);
  return _livemap.set(_generation_id, index, finalizable, inc_live);
}

inline void ZPage::inc_live(uint32_t objects, size_t bytes) {
  _livemap.inc_live(objects, bytes);
}

#define assert_zpage_mark_state()                                                  \
  do {                                                                             \
    assert(is_marked(), "Should be marked");                                       \
    assert(!is_young() || !ZGeneration::young()->is_phase_mark(), "Wrong phase");  \
    assert(!is_old() || !ZGeneration::old()->is_phase_mark(), "Wrong phase");      \
  } while (0)

inline uint32_t ZPage::live_objects() const {
  assert_zpage_mark_state();

  return _livemap.live_objects();
}

inline size_t ZPage::live_bytes() const {
  assert_zpage_mark_state();

  return _livemap.live_bytes();
}

template <typename Function>
inline void ZPage::object_iterate(Function function) {
  auto do_bit = [&](BitMap::idx_t index) -> bool {
    const oop obj = object_from_bit_index(index);

    // Apply function
    function(obj);

    return true;
  };

  _livemap.iterate(_generation_id, do_bit);
}

inline void ZPage::remember(volatile zpointer* p) {
  const zaddress addr = to_zaddress((uintptr_t)p);
  const uintptr_t l_offset = local_offset(addr);
  _remembered_set.set_current(l_offset);
}

inline void ZPage::clear_remset_bit_non_par_current(uintptr_t l_offset) {
  _remembered_set.unset_non_par_current(l_offset);
}

inline void ZPage::clear_remset_range_non_par_current(uintptr_t l_offset, size_t size) {
  _remembered_set.unset_range_non_par_current(l_offset, size);
}

inline ZBitMap::ReverseIterator ZPage::remset_reverse_iterator_previous() {
  return _remembered_set.iterator_reverse_previous();
}

inline BitMap::Iterator ZPage::remset_iterator_limited_current(uintptr_t l_offset, size_t size) {
  return _remembered_set.iterator_limited_current(l_offset, size);
}

inline BitMap::Iterator ZPage::remset_iterator_limited_previous(uintptr_t l_offset, size_t size) {
  return _remembered_set.iterator_limited_previous(l_offset, size);
}

inline bool ZPage::is_remembered(volatile zpointer* p) {
  const zaddress addr = to_zaddress((uintptr_t)p);
  const uintptr_t l_offset = local_offset(addr);
  return _remembered_set.at_current(l_offset);
}

inline bool ZPage::was_remembered(volatile zpointer* p) {
  const zaddress addr = to_zaddress((uintptr_t)p);
  const uintptr_t l_offset = local_offset(addr);
  return _remembered_set.at_previous(l_offset);
}

inline zaddress_unsafe ZPage::find_base_unsafe(volatile zpointer* p) {
  if (is_large()) {
    return ZOffset::address_unsafe(start());
  }

  // Note: when thinking about excluding looking at the index corresponding to
  // the field address p, it's important to note that for medium pages both p
  // and it's associated base could map to the same index.
  const BitMap::idx_t index = bit_index(zaddress(uintptr_t(p)));
  const BitMap::idx_t base_index = _livemap.find_base_bit(index);
  if (base_index == BitMap::idx_t(-1)) {
    return zaddress_unsafe::null;
  } else {
    return ZOffset::address_unsafe(offset_from_bit_index(base_index));
  }
}

inline zaddress_unsafe ZPage::find_base(volatile zpointer* p) {
  assert_zpage_mark_state();

  return find_base_unsafe(p);
}

template <typename Function>
inline void ZPage::oops_do_remembered(Function function) {
  _remembered_set.iterate_previous([&](uintptr_t local_offset) {
    const zoffset offset = start() + local_offset;
    const zaddress addr = ZOffset::address(offset);

    function((volatile zpointer*)addr);
  });
}

template <typename Function>
inline void ZPage::oops_do_remembered_in_live(Function function) {
  assert(!is_allocating(), "Must have liveness information");
  assert(!ZGeneration::old()->is_phase_mark(), "Must have liveness information");
  assert(is_marked(), "Must have liveness information");

  ZRememberedSetContainingInLiveIterator iter(this);
  for (ZRememberedSetContaining containing; iter.next(&containing);) {
    function((volatile zpointer*)containing._field_addr);
  }

  iter.print_statistics();
}

template <typename Function>
inline void ZPage::oops_do_current_remembered(Function function) {
  _remembered_set.iterate_current([&](uintptr_t local_offset) {
    const zoffset offset = start() + local_offset;
    const zaddress addr = ZOffset::address(offset);

    function((volatile zpointer*)addr);
  });
}

inline zaddress ZPage::alloc_object(size_t size) {
  assert(is_allocating(), "Invalid state");

  const size_t aligned_size = align_up(size, object_alignment());
  const zoffset_end addr = top();

  zoffset_end new_top;

  if (!to_zoffset_end(&new_top, addr, aligned_size)) {
    // Next top would be outside of the heap - bail
    return zaddress::null;
  }

  if (new_top > end()) {
    // Not enough space left in the page
    return zaddress::null;
  }

  _top = new_top;

  return ZOffset::address(to_zoffset(addr));
}

inline zaddress ZPage::alloc_object_atomic(size_t size) {
  assert(is_allocating(), "Invalid state");

  const size_t aligned_size = align_up(size, object_alignment());
  zoffset_end addr = top();

  for (;;) {
    zoffset_end new_top;

    if (!to_zoffset_end(&new_top, addr, aligned_size)) {
      // Next top would be outside of the heap - bail
      return zaddress::null;
    }

    if (new_top > end()) {
      // Not enough space left
      return zaddress::null;
    }

    const zoffset_end prev_top = AtomicAccess::cmpxchg(&_top, addr, new_top);
    if (prev_top == addr) {
      // Success
      return ZOffset::address(to_zoffset(addr));
    }

    // Retry
    addr = prev_top;
  }
}

inline bool ZPage::undo_alloc_object(zaddress addr, size_t size) {
  assert(is_allocating(), "Invalid state");

  const zoffset offset = ZAddress::offset(addr);
  const size_t aligned_size = align_up(size, object_alignment());
  const zoffset_end old_top = top();
  const zoffset_end new_top = old_top - aligned_size;

  if (new_top != offset) {
    // Failed to undo allocation, not the last allocated object
    return false;
  }

  _top = new_top;

  // Success
  return true;
}

inline bool ZPage::undo_alloc_object_atomic(zaddress addr, size_t size) {
  assert(is_allocating(), "Invalid state");

  const zoffset offset = ZAddress::offset(addr);
  const size_t aligned_size = align_up(size, object_alignment());
  zoffset_end old_top = top();

  for (;;) {
    const zoffset_end new_top = old_top - aligned_size;
    if (new_top != offset) {
      // Failed to undo allocation, not the last allocated object
      return false;
    }

    const zoffset_end prev_top = AtomicAccess::cmpxchg(&_top, old_top, new_top);
    if (prev_top == old_top) {
      // Success
      return true;
    }

    // Retry
    old_top = prev_top;
  }
}

inline void ZPage::log_msg(const char* msg_format, ...) const {
  LogTarget(Trace, gc, page) target;
  if (target.is_enabled()) {
    va_list argp;
    va_start(argp, msg_format);
    LogStream stream(target);
    print_on_msg(&stream, err_msg(FormatBufferDummy(), msg_format, argp));
    va_end(argp);
  }
}

#endif // SHARE_GC_Z_ZPAGE_INLINE_HPP
