/*
 * Copyright (c) 2021, 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/z/zAddress.inline.hpp"
#include "gc/z/zForwarding.inline.hpp"
#include "gc/z/zGeneration.inline.hpp"
#include "gc/z/zHeap.inline.hpp"
#include "gc/z/zIterator.inline.hpp"
#include "gc/z/zMark.hpp"
#include "gc/z/zPage.inline.hpp"
#include "gc/z/zPageTable.hpp"
#include "gc/z/zRemembered.inline.hpp"
#include "gc/z/zRememberedSet.hpp"
#include "gc/z/zTask.hpp"
#include "gc/z/zVerify.hpp"
#include "memory/iterator.hpp"
#include "oops/oop.inline.hpp"
#include "utilities/bitMap.inline.hpp"
#include "utilities/debug.hpp"

ZRemembered::ZRemembered(ZPageTable* page_table,
                         const ZForwardingTable* old_forwarding_table,
                         ZPageAllocator* page_allocator)
  : _page_table(page_table),
    _old_forwarding_table(old_forwarding_table),
    _page_allocator(page_allocator),
    _found_old() {}

template <typename Function>
void ZRemembered::oops_do_forwarded_via_containing(GrowableArrayView<ZRememberedSetContaining>* array, Function function) const {
  // The array contains duplicated from_addr values. Cache expensive operations.
  zaddress_unsafe from_addr = zaddress_unsafe::null;
  zaddress to_addr = zaddress::null;
  size_t object_size = 0;

  for (const ZRememberedSetContaining containing: *array) {
    if (from_addr != containing._addr) {
      from_addr = containing._addr;

      // Relocate object to new location
      to_addr = ZGeneration::old()->relocate_or_remap_object(from_addr);

      // Figure out size
      object_size = ZUtils::object_size(to_addr);
    }

    // Calculate how far into the from-object the remset entry is
    const uintptr_t field_offset = containing._field_addr - from_addr;

    // The 'containing' could contain mismatched (addr, addr_field).
    // Need to check if the field was within the reported object.
    if (field_offset < object_size) {
      // Calculate the corresponding address in the to-object
      const zaddress to_addr_field = to_addr + field_offset;

      function((volatile zpointer*)untype(to_addr_field));
    }
  }
}

bool ZRemembered::should_scan_page(ZPage* page) const {
  if (!ZGeneration::old()->is_phase_relocate()) {
    // If the old generation collection is not in the relocation phase, then it
    // will not need any synchronization on its forwardings.
    return true;
  }

  ZForwarding* const forwarding = ZGeneration::old()->forwarding(ZOffset::address_unsafe(page->start()));

  if (forwarding == nullptr) {
    // This page was provably not part of the old relocation set
    return true;
  }

  if (!forwarding->relocated_remembered_fields_is_concurrently_scanned()) {
    // Safe to scan
    return true;
  }

  // If we get here, we know that the old collection is concurrently relocating
  // objects. We need to be extremely careful not to scan a page that is
  // concurrently being in-place relocated because it's objects and previous
  // bits could be concurrently be moving around.
  //
  // Before calling this function ZRemembered::scan_forwarding ensures
  // that all forwardings that have not already been fully relocated,
  // will have had their "previous" remembered set bits scanned.
  //
  // The current page we're currently scanning could either be the same page
  // that was found during scan_forwarding, or it could have been replaced
  // by a new "allocating" page. There are two situations we have to consider:
  //
  // 1) If it is a proper new allocating page, then all objects where copied
  // after scan_forwarding ran, and we are guaranteed that no "previous"
  // remembered set bits are set. So, there's no need to scan this page.
  //
  // 2) If this is an in-place relocated page, then the entire page could
  // be concurrently relocated. Meaning that both objects and previous
  // remembered set bits could be moving around. However, if the in-place
  // relocation is ongoing, we've already scanned all relevant "previous"
  // bits when calling scan_forwarding. So, this page *must* not be scanned.
  //
  // Don't scan the page.
  return false;
}

bool ZRemembered::scan_page_and_clear_remset(ZPage* page) const {
  const bool can_trust_live_bits =
      page->is_relocatable() && !ZGeneration::old()->is_phase_mark();

  bool result = false;

  if (!can_trust_live_bits) {
    // We don't have full liveness info - scan all remset entries
    page->log_msg(" (scan_page_remembered)");
    int count = 0;
    page->oops_do_remembered([&](volatile zpointer* p) {
      result |= scan_field(p);
      count++;
    });
    page->log_msg(" (scan_page_remembered done: %d ignoring: " PTR_FORMAT " )", count, p2i(page->remset_current()));
  } else if (page->is_marked()) {
    // We have full liveness info - Only scan remset entries in live objects
    page->log_msg(" (scan_page_remembered_in_live)");
    page->oops_do_remembered_in_live([&](volatile zpointer* p) {
      result |= scan_field(p);
    });
  } else {
    page->log_msg(" (scan_page_remembered_dead)");
    // All objects are dead - do nothing
  }

  if (ZVerifyRemembered) {
    // Make sure self healing of pointers is ordered before clearing of
    // the previous bits so that ZVerify::after_scan can detect missing
    // remset entries accurately.
    OrderAccess::storestore();
  }

  // If we have consumed the remset entries above we also clear them.
  // The exception is if the page is completely empty/garbage, where we don't
  // want to race with an old collection modifying the remset as well.
  if (!can_trust_live_bits || page->is_marked()) {
    page->clear_remset_previous();
  }

  return result;
}

static void fill_containing(GrowableArrayCHeap<ZRememberedSetContaining, mtGC>* array, ZPage* page) {
  page->log_msg(" (fill_remembered_containing)");

  ZRememberedSetContainingIterator iter(page);

  for (ZRememberedSetContaining containing; iter.next(&containing);) {
    array->push(containing);
  }
}

struct ZRememberedScanForwardingContext {
  GrowableArrayCHeap<ZRememberedSetContaining, mtGC> _containing_array;

  struct Where {
    static const int NumRecords = 10;

    Tickspan _duration;
    int      _count;
    Tickspan _max_durations[NumRecords];
    int      _max_count;

    Where()
      : _duration(),
        _count(),
        _max_durations(),
        _max_count() {}

    void report(const Tickspan& duration) {
      _duration += duration;
      _count++;

      // Install into max array
      for (int i = 0; i < NumRecords; i++) {
        if (duration > _max_durations[i]) {
          // Slid to the side
          for (int j = _max_count - 1; i < j; j--) {
            _max_durations[j] = _max_durations[j - 1];
          }

          // Install
          _max_durations[i] = duration;
          if (_max_count < NumRecords) {
            _max_count++;
          }
          break;
        }
      }
    }

    void print(const char* name) {
      log_debug(gc, remset)("Remset forwarding %s: %.3fms count: %d %s",
          name, TimeHelper::counter_to_millis(_duration.value()), _count, Thread::current()->name());
      for (int i = 0; i < _max_count; i++) {
        log_debug(gc, remset)("  %.3fms", TimeHelper::counter_to_millis(_max_durations[i].value()));
      }
    }
  };

  Where _where[2];

  ZRememberedScanForwardingContext()
    : _containing_array(),
      _where() {}

  ~ZRememberedScanForwardingContext() {
    print();
  }

  void report_retained(const Tickspan& duration) {
    _where[0].report(duration);
  }

  void report_released(const Tickspan& duration) {
    _where[1].report(duration);
  }

  void print() {
    _where[0].print("retained");
    _where[1].print("released");
  }
};

struct ZRememberedScanForwardingMeasureRetained {
  ZRememberedScanForwardingContext* _context;
  Ticks                             _start;

  ZRememberedScanForwardingMeasureRetained(ZRememberedScanForwardingContext* context)
    : _context(context),
      _start(Ticks::now()) {
  }

  ~ZRememberedScanForwardingMeasureRetained() {
    const Ticks end = Ticks::now();
    const Tickspan duration = end - _start;
    _context->report_retained(duration);
  }
};

struct ZRememberedScanForwardingMeasureReleased {
  ZRememberedScanForwardingContext* _context;
  Ticks                             _start;

  ZRememberedScanForwardingMeasureReleased(ZRememberedScanForwardingContext* context)
    : _context(context),
      _start(Ticks::now()) {
  }

  ~ZRememberedScanForwardingMeasureReleased() {
    const Ticks end = Ticks::now();
    const Tickspan duration = end - _start;
    _context->report_released(duration);
  }
};

bool ZRemembered::scan_forwarding(ZForwarding* forwarding, void* context_void) const {
  ZRememberedScanForwardingContext* const context = (ZRememberedScanForwardingContext*)context_void;
  bool result = false;

  if (forwarding->retain_page(ZGeneration::old()->relocate_queue())) {
    ZRememberedScanForwardingMeasureRetained measure(context);
    forwarding->page()->log_msg(" (scan_forwarding)");

    // We don't want to wait for the old relocation to finish and publish all
    // relocated remembered fields. Reject its fields and collect enough data
    // up-front.
    forwarding->relocated_remembered_fields_notify_concurrent_scan_of();

    // Collect all remset info while the page is retained
    GrowableArrayCHeap<ZRememberedSetContaining, mtGC>* array = &context->_containing_array;
    array->clear();
    fill_containing(array, forwarding->page());
    forwarding->release_page();

    // Relocate (and mark) while page is released, to prevent
    // retain deadlock when relocation threads in-place relocate.
    oops_do_forwarded_via_containing(array, [&](volatile zpointer* p) {
      result |= scan_field(p);
    });

  } else {
    ZRememberedScanForwardingMeasureReleased measure(context);

    // The page has been released. If the page was relocated while this young
    // generation collection was running, the old generation relocation will
    // have published all addresses of fields that had a remembered set entry.
    forwarding->relocated_remembered_fields_apply_to_published([&](volatile zpointer* p) {
      result |= scan_field(p);
    });
  }

  return result;
}

// When scanning the remembered set during the young generation marking, we
// want to visit all old pages. And we want that to be done in parallel and
// fast.
//
// Walking over the entire page table and letting the workers claim indices
// have been shown to have scalability issues.
//
// So, we have the "found old" optimization, which allows us to perform much
// fewer claims (order of old pages, instead of order of slots in the page
// table), and it allows us to read fewer pages.
//
// The set of "found old pages" isn't precise, and can contain stale entries
// referring to slots of freed pages, or even slots where young pages have
// been installed. However, it will not lack any of the old pages.
//
// The data is maintained very similar to when and how we maintain the
// remembered set bits: We keep two separates sets, one for read-only access
// by the young marking, and a currently active set where we register new
// pages. When pages get relocated, or die, the page table slot for that page
// must be cleared. This clearing is done just like we do with the remset
// scanning: The old entries are not copied to the current active set, only
// slots that were found to actually contain old pages are registered in the
// active set.

ZRemembered::FoundOld::FoundOld()
    // Array initialization requires copy constructors, which CHeapBitMap
    // doesn't provide. Instantiate two instances, and populate an array
    // with pointers to the two instances.
  : _allocated_bitmap_0{ZAddressOffsetMax >> ZGranuleSizeShift, mtGC, true /* clear */},
    _allocated_bitmap_1{ZAddressOffsetMax >> ZGranuleSizeShift, mtGC, true /* clear */},
    _bitmaps{&_allocated_bitmap_0, &_allocated_bitmap_1},
    _current{0} {}

BitMap* ZRemembered::FoundOld::current_bitmap() {
  return _bitmaps[_current];
}

BitMap* ZRemembered::FoundOld::previous_bitmap() {
  return _bitmaps[_current ^ 1];
}

void ZRemembered::FoundOld::flip() {
  _current ^= 1;
}

void ZRemembered::FoundOld::clear_previous() {
  previous_bitmap()->clear_large();
}

void ZRemembered::FoundOld::register_page(ZPage* page) {
  assert(page->is_old(), "Only register old pages");
  current_bitmap()->par_set_bit(untype(page->start()) >> ZGranuleSizeShift, memory_order_relaxed);
}

void ZRemembered::flip_found_old_sets() {
  _found_old.flip();
}

void ZRemembered::clear_found_old_previous_set() {
  _found_old.clear_previous();
}

void ZRemembered::register_found_old(ZPage* page) {
  assert(page->is_old(), "Should only register old pages");
  _found_old.register_page(page);
}

struct ZRemsetTableEntry {
  ZPage* _page;
  ZForwarding* _forwarding;
};

ZRemsetTableIterator::ZRemsetTableIterator(ZRemembered* remembered, bool previous)
  : _remembered(remembered),
    _bm(previous
        ? _remembered->_found_old.previous_bitmap()
        : _remembered->_found_old.current_bitmap()),
    _page_table(remembered->_page_table),
    _old_forwarding_table(remembered->_old_forwarding_table),
    _claimed(0) {}

  // This iterator uses the "found old" optimization.
bool ZRemsetTableIterator::next(ZRemsetTableEntry* entry_addr) {
  BitMap::idx_t prev = AtomicAccess::load(&_claimed);

  for (;;) {
    if (prev == _bm->size()) {
      return false;
    }

    const BitMap::idx_t page_index = _bm->find_first_set_bit(_claimed);
    if (page_index == _bm->size()) {
      AtomicAccess::cmpxchg(&_claimed, prev, page_index, memory_order_relaxed);
      return false;
    }

    const BitMap::idx_t res = AtomicAccess::cmpxchg(&_claimed, prev, page_index + 1, memory_order_relaxed);
    if (res != prev) {
      // Someone else claimed
      prev = res;
      continue;
    }

    // Found bit - look around for page or forwarding to scan

    ZForwarding* forwarding = nullptr;
    if (ZGeneration::old()->is_phase_relocate()) {
      forwarding = _old_forwarding_table->at(page_index);
    }

    ZPage* page = _page_table->at(page_index);
    if (page != nullptr && !page->is_old()) {
      page = nullptr;
    }

    if (page == nullptr && forwarding == nullptr) {
      // Nothing to scan
      continue;
    }

    // Found old page or old forwarding
    entry_addr->_forwarding = forwarding;
    entry_addr->_page = page;

    return true;
  }
}

void ZRemembered::remap_current(ZRemsetTableIterator* iter) {
  for (ZRemsetTableEntry entry; iter->next(&entry);) {
    assert(entry._forwarding == nullptr, "Shouldn't be looking for forwardings");
    assert(entry._page != nullptr, "Must have found a page");
    assert(entry._page->is_old(), "Should only have found old pages");

    entry._page->oops_do_current_remembered(ZBarrier::load_barrier_on_oop_field);
  }
}

// This task scans the remembered set and follows pointers when possible.
// Interleaving remembered set scanning with marking makes the marking times
// lower and more predictable.
class ZRememberedScanMarkFollowTask : public ZRestartableTask {
private:
  ZRemembered* const   _remembered;
  ZMark* const         _mark;
  ZRemsetTableIterator _remset_table_iterator;

public:
  ZRememberedScanMarkFollowTask(ZRemembered* remembered, ZMark* mark)
    : ZRestartableTask("ZRememberedScanMarkFollowTask"),
      _remembered(remembered),
      _mark(mark),
      _remset_table_iterator(remembered, true /* previous */) {
    _mark->prepare_work();
    _remembered->_page_allocator->enable_safe_destroy();
  }

  ~ZRememberedScanMarkFollowTask() {
    _remembered->_page_allocator->disable_safe_destroy();
    _mark->finish_work();
    // We are done scanning the set of old pages.
    // Clear the set for the next young collection.
    _remembered->clear_found_old_previous_set();
  }

  virtual void work_inner() {
    ZRememberedScanForwardingContext context;

    // Follow initial roots
    if (!_mark->follow_work_partial()) {
      // Bail
      return;
    }

    for (ZRemsetTableEntry entry; _remset_table_iterator.next(&entry);) {
      bool left_marking = false;
      ZForwarding* forwarding = entry._forwarding;
      ZPage* page = entry._page;

      // Scan forwarding
      if (forwarding != nullptr) {
        bool found_roots = _remembered->scan_forwarding(forwarding, &context);
        ZVerify::after_scan(forwarding);
        if (found_roots) {
          // Follow remembered set when possible
          left_marking = !_mark->follow_work_partial();
        }
      }

      // Scan page
      if (page != nullptr) {
        if (_remembered->should_scan_page(page)) {
          // Visit all entries pointing into young gen
          bool found_roots = _remembered->scan_page_and_clear_remset(page);

          if (found_roots && !left_marking) {
            // Follow remembered set when possible
            left_marking = !_mark->follow_work_partial();
          }
        }

        // The remset scanning maintains the "maybe old" pages optimization.
        //
        // We maintain two sets of old pages: The first is the currently active
        // set, where old pages are registered into. The second is the old
        // read-only copy. The two sets flip during young mark start. This
        // analogous to how we set and clean remembered set bits.
        //
        // The iterator reads from the read-only copy, and then here, we install
        // entries in the current active set.
        _remembered->register_found_old(page);
      }

      SuspendibleThreadSet::yield();
      if (left_marking) {
        // Bail
        return;
      }
    }

    _mark->follow_work_complete();
  }

  virtual void work() {
    SuspendibleThreadSetJoiner sts_joiner;
    work_inner();
    // We might have found pointers into the other generation, and then we want to
    // publish such marking stacks to prevent that generation from getting a mark continue.
    // We also flush in case of a resize where a new worker thread continues the marking
    // work, causing a mark continue for the collected generation.
    ZHeap::heap()->mark_flush(Thread::current());
  }

  virtual void resize_workers(uint nworkers) {
    _mark->resize_workers(nworkers);
  }
};

void ZRemembered::scan_and_follow(ZMark* mark) {
  {
    // Follow the object graph and lazily scan the remembered set
    ZRememberedScanMarkFollowTask task(this, mark);
    ZGeneration::young()->workers()->run(&task);

    // Try to terminate after following the graph
    if (ZAbort::should_abort() || !mark->try_terminate_flush()) {
      return;
    }
  }

  // If flushing failed, we have to restart marking again, but this time we don't need to
  // scan the remembered set.
  mark->mark_follow();
}

bool ZRemembered::scan_field(volatile zpointer* p) const {
  assert(ZGeneration::young()->is_phase_mark(), "Wrong phase");

  const zaddress addr = ZBarrier::remset_barrier_on_oop_field(p);

  if (!is_null(addr) && ZHeap::heap()->is_young(addr)) {
    remember(p);
    return true;
  }

  return false;
}

void ZRemembered::flip() {
  ZRememberedSet::flip();
  flip_found_old_sets();
}
