/*
 * Copyright (c) 2017, 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/zArray.inline.hpp"
#include "gc/z/zCollectedHeap.hpp"
#include "gc/z/zForwarding.inline.hpp"
#include "gc/z/zForwardingAllocator.inline.hpp"
#include "gc/z/zGeneration.inline.hpp"
#include "gc/z/zPage.inline.hpp"
#include "gc/z/zPageAllocator.hpp"
#include "gc/z/zRelocationSet.inline.hpp"
#include "gc/z/zRelocationSetSelector.inline.hpp"
#include "gc/z/zStat.hpp"
#include "gc/z/zTask.hpp"
#include "gc/z/zWorkers.hpp"
#include "runtime/atomic.hpp"
#include "utilities/debug.hpp"

class ZRelocationSetInstallTask : public ZTask {
private:
  ZForwardingAllocator* const    _allocator;
  ZForwarding**                  _forwardings;
  const size_t                   _nforwardings;
  const ZArray<ZPage*>*          _small;
  const ZArray<ZPage*>*          _medium;
  ZArrayParallelIterator<ZPage*> _small_iter;
  ZArrayParallelIterator<ZPage*> _medium_iter;

  void install(ZForwarding* forwarding, size_t index) {
    assert(index < _nforwardings, "Invalid index");

    ZPage* const page = forwarding->page();

    page->log_msg(" (relocation selected)");

    _forwardings[index] = forwarding;

    if (forwarding->is_promotion()) {
      // Before promoting an object (and before relocate start), we must ensure that all
      // contained zpointers are store good. The marking code ensures that for non-null
      // pointers, but null pointers are ignored. This code ensures that even null pointers
      // are made store good, for the promoted objects.
      page->object_iterate([&](oop obj) {
        ZIterator::basic_oop_iterate_safe(obj, ZBarrier::promote_barrier_on_young_oop_field);
      });
    }
  }

  void install_small(ZForwarding* forwarding, size_t index) {
    install(forwarding, index);
  }

  void install_medium(ZForwarding* forwarding, size_t index) {
    install(forwarding, index);
  }

  ZPageAge to_age(ZPage* page) {
    return ZRelocate::compute_to_age(page->age());
  }

public:
  ZRelocationSetInstallTask(ZForwardingAllocator* allocator, const ZRelocationSetSelector* selector)
    : ZTask("ZRelocationSetInstallTask"),
      _allocator(allocator),
      _forwardings(nullptr),
      _nforwardings((size_t)selector->selected_small()->length() + (size_t)selector->selected_medium()->length()),
      _small(selector->selected_small()),
      _medium(selector->selected_medium()),
      _small_iter(selector->selected_small()),
      _medium_iter(selector->selected_medium()) {

    // Reset the allocator to have room for the relocation
    // set, all forwardings, and all forwarding entries.
    const size_t relocation_set_size = _nforwardings * sizeof(ZForwarding*);
    const size_t forwardings_size = _nforwardings * sizeof(ZForwarding);
    const size_t forwarding_entries_size = selector->forwarding_entries() * sizeof(ZForwardingEntry);
    _allocator->reset(relocation_set_size + forwardings_size + forwarding_entries_size);

    // Allocate relocation set
    _forwardings = new (_allocator->alloc(relocation_set_size)) ZForwarding*[_nforwardings];
  }

  ~ZRelocationSetInstallTask() {
    assert(_allocator->is_full(), "Should be full");
  }

  virtual void work() {
    // Join the STS to block out VMThreads while running promote_barrier_on_young_oop_field
    SuspendibleThreadSetJoiner sts_joiner;

    // Allocate and install forwardings for small pages
    for (size_t page_index; _small_iter.next_index(&page_index);) {
      ZPage* page = _small->at(int(page_index));
      ZForwarding* const forwarding = ZForwarding::alloc(_allocator, page, to_age(page));
      install_small(forwarding, (size_t)_medium->length() + page_index);

      SuspendibleThreadSet::yield();
    }

    // Allocate and install forwardings for medium pages
    for (size_t page_index; _medium_iter.next_index(&page_index);) {
      ZPage* page = _medium->at(int(page_index));
      ZForwarding* const forwarding = ZForwarding::alloc(_allocator, page, to_age(page));
      install_medium(forwarding, page_index);

      SuspendibleThreadSet::yield();
    }
  }

  ZForwarding** forwardings() const {
    return _forwardings;
  }

  size_t nforwardings() const {
    return _nforwardings;
  }
};

ZRelocationSet::ZRelocationSet(ZGeneration* generation)
  : _generation(generation),
    _allocator(),
    _forwardings(nullptr),
    _nforwardings(0),
    _promotion_lock(),
    _flip_promoted_pages(),
    _in_place_relocate_promoted_pages() {}

ZWorkers* ZRelocationSet::workers() const {
  return _generation->workers();
}

ZGeneration* ZRelocationSet::generation() const {
  return _generation;
}

ZArray<ZPage*>* ZRelocationSet::flip_promoted_pages() {
  return &_flip_promoted_pages;
}

void ZRelocationSet::install(const ZRelocationSetSelector* selector) {
  // Install relocation set
  ZRelocationSetInstallTask task(&_allocator, selector);
  workers()->run(&task);

  _forwardings = task.forwardings();
  _nforwardings = task.nforwardings();

  // Update statistics
  _generation->stat_relocation()->at_install_relocation_set(_allocator.size());
}

static void destroy_and_clear(ZPageAllocator* page_allocator, ZArray<ZPage*>* array) {
  for (int i = 0; i < array->length(); i++) {
    // Delete non-relocating promoted pages from last cycle
    ZPage* const page = array->at(i);
    page_allocator->safe_destroy_page(page);
  }

  array->clear();
}
void ZRelocationSet::reset(ZPageAllocator* page_allocator) {
  // Destroy forwardings
  ZRelocationSetIterator iter(this);
  for (ZForwarding* forwarding; iter.next(&forwarding);) {
    forwarding->~ZForwarding();
  }

  _nforwardings = 0;

  destroy_and_clear(page_allocator, &_in_place_relocate_promoted_pages);
  destroy_and_clear(page_allocator, &_flip_promoted_pages);
}

void ZRelocationSet::register_flip_promoted(const ZArray<ZPage*>& pages) {
  ZLocker<ZLock> locker(&_promotion_lock);
  for (ZPage* const page : pages) {
    assert(!_flip_promoted_pages.contains(page), "no duplicates allowed");
    _flip_promoted_pages.append(page);
  }
}

void ZRelocationSet::register_in_place_relocate_promoted(ZPage* page) {
  ZLocker<ZLock> locker(&_promotion_lock);
  assert(!_in_place_relocate_promoted_pages.contains(page), "no duplicates allowed");
  _in_place_relocate_promoted_pages.append(page);
}
