/*
 * Copyright (c) 2017, 2021, Red Hat, Inc. All rights reserved.
 * Copyright Amazon.com Inc. 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_SHENANDOAH_SHENANDOAHUTILS_HPP
#define SHARE_GC_SHENANDOAH_SHENANDOAHUTILS_HPP

#include "gc/shared/gcCause.hpp"
#include "gc/shared/gcTraceTime.inline.hpp"
#include "gc/shared/gcVMOperations.hpp"
#include "gc/shared/isGCActiveMark.hpp"
#include "gc/shared/suspendibleThreadSet.hpp"
#include "gc/shared/workerThread.hpp"
#include "gc/shenandoah/shenandoahPhaseTimings.hpp"
#include "gc/shenandoah/shenandoahThreadLocalData.hpp"
#include "jfr/jfrEvents.hpp"
#include "memory/allocation.hpp"
#include "runtime/safepoint.hpp"
#include "runtime/vmOperations.hpp"
#include "runtime/vmThread.hpp"
#include "services/memoryService.hpp"

#include <cmath>
#include <limits>

class GCTimer;
class ShenandoahGeneration;

#define SHENANDOAH_RETURN_EVENT_MESSAGE(generation_type, prefix, postfix) \
  switch (generation_type) {                                              \
    case NON_GEN:                                                         \
      return prefix postfix;                                              \
    case GLOBAL:                                                          \
      return prefix " (Global)" postfix;                                  \
    case YOUNG:                                                           \
      return prefix " (Young)" postfix;                                   \
    case OLD:                                                             \
      return prefix " (Old)" postfix;                                     \
    default:                                                              \
      ShouldNotReachHere();                                               \
      return prefix " (Unknown)" postfix;                                 \
  }                                                                       \

class ShenandoahGCSession : public StackObj {
private:
  ShenandoahHeap* const _heap;
  ShenandoahGeneration* const _generation;
  GCTimer*  const _timer;
  GCTracer* const _tracer;

  TraceMemoryManagerStats _trace_cycle;

  static const char* cycle_end_message(ShenandoahGenerationType type);
public:
  ShenandoahGCSession(GCCause::Cause cause, ShenandoahGeneration* generation,
                      bool is_degenerated = false, bool is_out_of_cycle = false);
  ~ShenandoahGCSession();
};

/*
 * ShenandoahGCPhaseTiming tracks Shenandoah specific timing information
 * of a GC phase
 */
class ShenandoahTimingsTracker : public StackObj {
private:
  static ShenandoahPhaseTimings::Phase  _current_phase;

  ShenandoahPhaseTimings* const         _timings;
  const ShenandoahPhaseTimings::Phase   _phase;
  const bool                            _should_aggregate;
  ShenandoahPhaseTimings::Phase         _parent_phase;
  double _start;

public:
  ShenandoahTimingsTracker(ShenandoahPhaseTimings::Phase phase, bool should_aggregate = false);
  ~ShenandoahTimingsTracker();

  static ShenandoahPhaseTimings::Phase current_phase() { return _current_phase; }

  static bool is_current_phase_valid();
};

/*
 * ShenandoahPausePhase tracks a STW pause and emits Shenandoah timing and
 * a corresponding JFR event
 */
class ShenandoahPausePhase : public ShenandoahTimingsTracker {
private:
  GCTraceTimeWrapper<LogLevel::Info, LOG_TAGS(gc)> _tracer;
  ConcurrentGCTimer* const _timer;

public:
  ShenandoahPausePhase(const char* title, ShenandoahPhaseTimings::Phase phase, bool log_heap_usage = false);
  ~ShenandoahPausePhase();
};

/*
 * ShenandoahConcurrentPhase tracks a concurrent GC phase and emits Shenandoah timing and
 * a corresponding JFR event
 */
class ShenandoahConcurrentPhase : public ShenandoahTimingsTracker {
private:
  GCTraceTimeWrapper<LogLevel::Info, LOG_TAGS(gc)> _tracer;
  ConcurrentGCTimer* const _timer;

public:
  ShenandoahConcurrentPhase(const char* title, ShenandoahPhaseTimings::Phase phase, bool log_heap_usage = false);
  ~ShenandoahConcurrentPhase();
};

/*
 * ShenandoahGCPhase tracks Shenandoah specific timing information
 * and emits a corresponding JFR event of a GC phase
 */
class ShenandoahGCPhase : public ShenandoahTimingsTracker {
private:
  ConcurrentGCTimer* const _timer;

public:
  ShenandoahGCPhase(ShenandoahPhaseTimings::Phase phase);
  ~ShenandoahGCPhase();
};

class ShenandoahGCWorkerPhase : public StackObj {
private:
  ShenandoahPhaseTimings* const       _timings;
  const ShenandoahPhaseTimings::Phase _phase;
public:
  ShenandoahGCWorkerPhase(ShenandoahPhaseTimings::Phase phase);
  ~ShenandoahGCWorkerPhase();
};

// Aggregates all the things that should happen before/after the pause.
class ShenandoahGCPauseMark : public StackObj {
private:
  ShenandoahHeap* const _heap;
  const GCIdMark                _gc_id_mark;
  const SvcGCMarker             _svc_gc_mark;
  const IsSTWGCActiveMark       _is_gc_active_mark;
  TraceMemoryManagerStats       _trace_pause;

public:
  ShenandoahGCPauseMark(uint gc_id, const char* notification_action, SvcGCMarker::reason_type type);
};

class ShenandoahSafepoint : public AllStatic {
public:
  // Check if Shenandoah GC safepoint is in progress. This is nominally
  // equivalent to calling SafepointSynchronize::is_at_safepoint(), but
  // it also checks the Shenandoah specifics, when it can.
  static inline bool is_at_shenandoah_safepoint() {
    if (!SafepointSynchronize::is_at_safepoint()) return false;

    Thread* const thr = Thread::current();
    // Shenandoah GC specific safepoints are scheduled by control thread.
    // So if we are enter here from control thread, then we are definitely not
    // at Shenandoah safepoint, but at something else.
    if (thr == ShenandoahHeap::heap()->control_thread()) return false;

    // This is not VM thread, cannot see what VM thread is doing,
    // so pretend this is a proper Shenandoah safepoint
    if (!thr->is_VM_thread()) return true;

    // Otherwise check we are at proper operation type
    VM_Operation* vm_op = VMThread::vm_operation();
    if (vm_op == nullptr) return false;

    VM_Operation::VMOp_Type type = vm_op->type();
    return type == VM_Operation::VMOp_ShenandoahInitMark ||
           type == VM_Operation::VMOp_ShenandoahFinalMarkStartEvac ||
           type == VM_Operation::VMOp_ShenandoahInitUpdateRefs ||
           type == VM_Operation::VMOp_ShenandoahFinalUpdateRefs ||
           type == VM_Operation::VMOp_ShenandoahFinalRoots ||
           type == VM_Operation::VMOp_ShenandoahFullGC ||
           type == VM_Operation::VMOp_ShenandoahDegeneratedGC;
  }
};

class ShenandoahWorkerSession : public StackObj {
protected:
  ShenandoahWorkerSession(uint worker_id);
public:
  static inline uint worker_id() {
    return WorkerThread::worker_id();
  }
};

class ShenandoahConcurrentWorkerSession : public ShenandoahWorkerSession {
private:
  EventGCPhaseConcurrent _event;

public:
  ShenandoahConcurrentWorkerSession(uint worker_id) : ShenandoahWorkerSession(worker_id) { }
  ~ShenandoahConcurrentWorkerSession();
};

class ShenandoahParallelWorkerSession : public ShenandoahWorkerSession {
private:
  EventGCPhaseParallel _event;

public:
  ShenandoahParallelWorkerSession(uint worker_id) : ShenandoahWorkerSession(worker_id) { }
  ~ShenandoahParallelWorkerSession();
};

// Regions cannot be uncommitted when concurrent reset is zeroing out the bitmaps.
// This CADR class enforces this by forbidding region uncommits while it is in scope.
class ShenandoahNoUncommitMark : public StackObj {
  ShenandoahHeap* const _heap;
public:
  explicit ShenandoahNoUncommitMark(ShenandoahHeap* heap) : _heap(heap) {
    _heap->forbid_uncommit();
  }

  ~ShenandoahNoUncommitMark() {
    _heap->allow_uncommit();
  }
};

// Casting a double that cannot be represented as a size_t may result in undefined behavior.
// This small function checks if the given double is representable in a size_t and returns
// that representation if it is. Otherwise, if the double cannot be safely cast to a size_t
// it returns zero.
inline size_t shenandoah_safe_size_cast(const double d) {
  static constexpr double size_max_as_double = static_cast<double>(std::numeric_limits<size_t>::max());
  if (std::isnan(d) || d < 0 || d >= size_max_as_double) {
    // NaN is unordered, all comparisons will be false.
    // +Inf is always greater than, -Inf is always less than
    return 0;
  }
  return static_cast<size_t>(d);
}



#endif // SHARE_GC_SHENANDOAH_SHENANDOAHUTILS_HPP
