/*
 * 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.
 */

#include "gc/shared/gc_globals.hpp"
#include "gc/shared/gcCause.hpp"
#include "gc/shared/gcId.hpp"
#include "gc/z/zAbort.inline.hpp"
#include "gc/z/zBreakpoint.hpp"
#include "gc/z/zCollectedHeap.hpp"
#include "gc/z/zDirector.hpp"
#include "gc/z/zDriver.hpp"
#include "gc/z/zGCIdPrinter.hpp"
#include "gc/z/zGeneration.inline.hpp"
#include "gc/z/zHeap.inline.hpp"
#include "gc/z/zLock.inline.hpp"
#include "gc/z/zServiceability.hpp"
#include "gc/z/zStat.hpp"

static const ZStatPhaseCollection ZPhaseCollectionMinor("Minor Collection", true /* minor */);
static const ZStatPhaseCollection ZPhaseCollectionMajor("Major Collection", false /* minor */);

template <typename DriverT>
class ZGCCauseSetter : public GCCauseSetter {
private:
  DriverT* _driver;

public:
  ZGCCauseSetter(DriverT* driver, GCCause::Cause cause)
    : GCCauseSetter(ZCollectedHeap::heap(), cause),
      _driver(driver) {
    _driver->set_gc_cause(cause);
  }

  ~ZGCCauseSetter() {
    _driver->set_gc_cause(GCCause::_no_gc);
  }
};

ZLock*        ZDriver::_lock;
ZDriverMinor* ZDriver::_minor;
ZDriverMajor* ZDriver::_major;

void ZDriver::initialize() {
  _lock = new ZLock();
}

void ZDriver::lock() {
  _lock->lock();
}

void ZDriver::unlock() {
  _lock->unlock();
}

void ZDriver::set_minor(ZDriverMinor* minor) {
  _minor = minor;
}

void ZDriver::set_major(ZDriverMajor* major) {
  _major = major;
}

ZDriverMinor* ZDriver::minor() {
  return _minor;
}

ZDriverMajor* ZDriver::major() {
  return _major;
}

ZDriverLocker::ZDriverLocker() {
  ZDriver::lock();
}

ZDriverLocker::~ZDriverLocker() {
  ZDriver::unlock();
}

ZDriverUnlocker::ZDriverUnlocker() {
  ZDriver::unlock();
}

ZDriverUnlocker::~ZDriverUnlocker() {
  ZDriver::lock();
}

ZDriver::ZDriver()
  : _gc_cause(GCCause::_no_gc) {}

void ZDriver::set_gc_cause(GCCause::Cause cause) {
  _gc_cause = cause;
}

GCCause::Cause ZDriver::gc_cause() {
  return _gc_cause;
}

ZDriverMinor::ZDriverMinor()
  : ZDriver(),
    _port(),
    _gc_timer(),
    _jfr_tracer(),
    _used_at_start() {
  ZDriver::set_minor(this);
  set_name("ZDriverMinor");
  create_and_start();
}

bool ZDriverMinor::is_busy() const {
  return _port.is_busy();
}

void ZDriverMinor::collect(const ZDriverRequest& request) {
  switch (request.cause()) {
  case GCCause::_wb_young_gc:
    // Start synchronous GC
    _port.send_sync(request);
    break;

  case GCCause::_scavenge_alot:
  case GCCause::_z_timer:
  case GCCause::_z_allocation_rate:
  case GCCause::_z_allocation_stall:
  case GCCause::_z_high_usage:
    // Start asynchronous GC
    _port.send_async(request);
    break;

  default:
    fatal("Unsupported GC cause (%s)", GCCause::to_string(request.cause()));
    break;
  }
};

GCTracer* ZDriverMinor::jfr_tracer() {
  return &_jfr_tracer;
}

void ZDriverMinor::set_used_at_start(size_t used) {
  _used_at_start = used;
}

size_t ZDriverMinor::used_at_start() const {
  return _used_at_start;
}

class ZDriverScopeMinor : public StackObj {
private:
  GCIdMark                     _gc_id;
  GCCause::Cause               _gc_cause;
  ZGCCauseSetter<ZDriverMinor> _gc_cause_setter;
  ZStatTimer                   _stat_timer;
  ZServiceabilityCycleTracer   _tracer;

public:
  ZDriverScopeMinor(const ZDriverRequest& request, ConcurrentGCTimer* gc_timer)
    : _gc_id(),
      _gc_cause(request.cause()),
      _gc_cause_setter(ZDriver::minor(), _gc_cause),
      _stat_timer(ZPhaseCollectionMinor, gc_timer),
      _tracer(true /* minor */) {
    // Select number of worker threads to use
    ZGeneration::young()->set_active_workers(request.young_nworkers());
  }
};

void ZDriverMinor::gc(const ZDriverRequest& request) {
  ZDriverScopeMinor scope(request, &_gc_timer);
  ZGCIdMinor minor_id(gc_id());
  ZGeneration::young()->collect(ZYoungType::minor, &_gc_timer);
}

static void handle_alloc_stalling_for_young() {
  ZHeap::heap()->handle_alloc_stalling_for_young();
}

void ZDriverMinor::handle_alloc_stalls() const {
  handle_alloc_stalling_for_young();
}

void ZDriverMinor::run_thread() {
  // Main loop
  for (;;) {
    // Wait for GC request
    const ZDriverRequest request = _port.receive();

    ZDriverLocker locker;

    abortpoint();

    // Run GC
    gc(request);

    abortpoint();

    // Notify GC completed
    _port.ack();

    // Handle allocation stalls
    handle_alloc_stalls();

    // Good point to consider back-to-back GC
    ZDirector::evaluate_rules();
  }
}

void ZDriverMinor::terminate() {
  const ZDriverRequest request(GCCause::_no_gc, 0, 0);
  _port.send_async(request);
}

static bool should_clear_all_soft_references(GCCause::Cause cause) {
  // Clear all soft references if implied by the GC cause
  switch (cause) {
  case GCCause::_wb_full_gc:
  case GCCause::_metadata_GC_clear_soft_refs:
  case GCCause::_z_allocation_stall:
    return true;

  case GCCause::_heap_dump:
  case GCCause::_heap_inspection:
  case GCCause::_wb_breakpoint:
  case GCCause::_dcmd_gc_run:
  case GCCause::_java_lang_system_gc:
  case GCCause::_full_gc_alot:
  case GCCause::_jvmti_force_gc:
  case GCCause::_z_timer:
  case GCCause::_z_warmup:
  case GCCause::_z_allocation_rate:
  case GCCause::_z_proactive:
  case GCCause::_metadata_GC_threshold:
  case GCCause::_codecache_GC_threshold:
  case GCCause::_codecache_GC_aggressive:
    break;

  default:
    fatal("Unsupported GC cause (%s)", GCCause::to_string(cause));
    break;
  }

  // Clear all soft references if threads are stalled waiting for an old collection
  if (ZHeap::heap()->is_alloc_stalling_for_old()) {
    return true;
  }

  // Don't clear all soft references
  return false;
}

static bool should_preclean_young(GCCause::Cause cause) {
  // Preclean young if implied by the GC cause
  switch (cause) {
  case GCCause::_heap_dump:
  case GCCause::_heap_inspection:
  case GCCause::_wb_full_gc:
  case GCCause::_wb_breakpoint:
  case GCCause::_dcmd_gc_run:
  case GCCause::_java_lang_system_gc:
  case GCCause::_full_gc_alot:
  case GCCause::_jvmti_force_gc:
  case GCCause::_metadata_GC_clear_soft_refs:
  case GCCause::_z_allocation_stall:
    return true;

  case GCCause::_z_timer:
  case GCCause::_z_warmup:
  case GCCause::_z_allocation_rate:
  case GCCause::_z_proactive:
  case GCCause::_metadata_GC_threshold:
  case GCCause::_codecache_GC_threshold:
  case GCCause::_codecache_GC_aggressive:
    break;

  default:
    fatal("Unsupported GC cause (%s)", GCCause::to_string(cause));
    break;
  }

  // Preclean young if threads are stalled waiting for an old collection
  if (ZHeap::heap()->is_alloc_stalling_for_old()) {
    return true;
  }

  // We clear all soft references as a last-ditch effort to collect memory
  // before throwing an OOM. Therefore it is important that when the GC policy
  // is to clear all soft references, that we also pre-clean the young
  // generation, as we might otherwise throw premature OOM.
  //
  // Therefore, all causes that trigger all soft ref clearing must also trigger
  // pre-cleaning of young gen. If allocations stalled when checking for all
  // soft ref clearing, then since we hold the driver locker all the way until
  // we check for young gen pre-cleaning, we can be certain that we should
  // catch that above and perform young gen pre-cleaning.
  assert(!should_clear_all_soft_references(cause), "Clearing all soft references without pre-cleaning young gen");

  return false;
}

ZDriverMajor::ZDriverMajor()
  : ZDriver(),
    _port(),
    _gc_timer(),
    _jfr_tracer(),
    _used_at_start() {
  ZDriver::set_major(this);
  set_name("ZDriverMajor");
  create_and_start();
}

bool ZDriverMajor::is_busy() const {
  return _port.is_busy();
}

void ZDriverMajor::collect(const ZDriverRequest& request) {
  switch (request.cause()) {
  case GCCause::_heap_dump:
  case GCCause::_heap_inspection:
  case GCCause::_wb_full_gc:
  case GCCause::_dcmd_gc_run:
  case GCCause::_java_lang_system_gc:
  case GCCause::_full_gc_alot:
  case GCCause::_jvmti_force_gc:
  case GCCause::_metadata_GC_clear_soft_refs:
  case GCCause::_codecache_GC_aggressive:
    // Start synchronous GC
    _port.send_sync(request);
    break;

  case GCCause::_z_timer:
  case GCCause::_z_warmup:
  case GCCause::_z_allocation_rate:
  case GCCause::_z_allocation_stall:
  case GCCause::_z_proactive:
  case GCCause::_codecache_GC_threshold:
  case GCCause::_metadata_GC_threshold:
    // Start asynchronous GC
    _port.send_async(request);
    break;

  case GCCause::_wb_breakpoint:
    ZBreakpoint::start_gc();
    _port.send_async(request);
    break;

  default:
    fatal("Unsupported GC cause (%s)", GCCause::to_string(request.cause()));
    break;
  }
}

GCTracer* ZDriverMajor::jfr_tracer() {
  return &_jfr_tracer;
}

void ZDriverMajor::set_used_at_start(size_t used) {
  _used_at_start = used;
}

size_t ZDriverMajor::used_at_start() const {
  return _used_at_start;
}

class ZDriverScopeMajor : public StackObj {
private:
  GCIdMark                     _gc_id;
  GCCause::Cause               _gc_cause;
  ZGCCauseSetter<ZDriverMajor> _gc_cause_setter;
  ZStatTimer                   _stat_timer;
  ZServiceabilityCycleTracer   _tracer;

public:
  ZDriverScopeMajor(const ZDriverRequest& request, ConcurrentGCTimer* gc_timer)
    : _gc_id(),
      _gc_cause(request.cause()),
      _gc_cause_setter(ZDriver::major(), _gc_cause),
      _stat_timer(ZPhaseCollectionMajor, gc_timer),
      _tracer(false /* minor */) {
    // Select number of worker threads to use
    ZGeneration::young()->set_active_workers(request.young_nworkers());
    ZGeneration::old()->set_active_workers(request.old_nworkers());

    // Set up soft reference policy
    const bool clear_all = should_clear_all_soft_references(request.cause());
    ZGeneration::old()->set_soft_reference_policy(clear_all);
  }

  ~ZDriverScopeMajor() {
    // Update data used by soft reference policy
    ZCollectedHeap::heap()->update_capacity_and_used_at_gc();

    // Signal that we have completed a visit to all live objects
    ZCollectedHeap::heap()->record_whole_heap_examined_timestamp();
  }
};

void ZDriverMajor::collect_young(const ZDriverRequest& request) {
  ZGCIdMajor major_id(gc_id(), 'Y');
  if (should_preclean_young(request.cause())) {
    // Collect young generation and promote everything to old generation
    ZGeneration::young()->collect(ZYoungType::major_full_preclean, &_gc_timer);

    abortpoint();

    // Collect young generation and gather roots pointing into old generation
    ZGeneration::young()->collect(ZYoungType::major_full_roots, &_gc_timer);
  } else {
    // Collect young generation and gather roots pointing into old generation
    ZGeneration::young()->collect(ZYoungType::major_partial_roots, &_gc_timer);
  }

  abortpoint();

  // Handle allocations waiting for a young collection
  handle_alloc_stalling_for_young();
}

void ZDriverMajor::collect_old() {
  ZGCIdMajor major_id(gc_id(), 'O');
  ZGeneration::old()->collect(&_gc_timer);
}

void ZDriverMajor::gc(const ZDriverRequest& request) {
  ZDriverScopeMajor scope(request, &_gc_timer);

  // Collect the young generation
  collect_young(request);

  abortpoint();

  // Collect the old generation
  collect_old();
}

static void handle_alloc_stalling_for_old() {
  const bool cleared_all = ZGeneration::old()->uses_clear_all_soft_reference_policy();
  ZHeap::heap()->handle_alloc_stalling_for_old(cleared_all);
}

void ZDriverMajor::handle_alloc_stalls() const {
  handle_alloc_stalling_for_old();
}

void ZDriverMajor::run_thread() {
  // Main loop
  for (;;) {
    // Wait for GC request
    const ZDriverRequest request = _port.receive();

    ZDriverLocker locker;

    ZBreakpoint::at_before_gc();

    abortpoint();

    // Run GC
    gc(request);

    abortpoint();

    // Notify GC completed
    _port.ack();

    // Handle allocation stalls
    handle_alloc_stalls();

    ZBreakpoint::at_after_gc();
  }
}

void ZDriverMajor::terminate() {
  const ZDriverRequest request(GCCause::_no_gc, 0, 0);
  _port.send_async(request);
}
