/*
 * 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/g1/g1BatchedTask.hpp"
#include "gc/shared/workerThread.hpp"
#include "runtime/atomic.hpp"
#include "unittest.hpp"

class G1BatchedTaskWorkers : AllStatic {
  static WorkerThreads* _workers;
  static WorkerThreads* workers() {
    if (_workers == nullptr) {
      _workers = new WorkerThreads("G1 Small Workers", MaxWorkers);
      _workers->initialize_workers();
      _workers->set_active_workers(MaxWorkers);
    }
    return _workers;
  }

public:
  static const uint MaxWorkers = 4;
  static void run_task(WorkerTask* task) {
    workers()->run_task(task);
  }
};

WorkerThreads* G1BatchedTaskWorkers::_workers = nullptr;

class G1TestSubTask : public G1AbstractSubTask {
  mutable uint _phase;
  volatile uint _num_do_work; // Amount of do_work() has been called.

  void check_and_inc_phase(uint expected) const {
    ASSERT_EQ(_phase, expected);
    _phase++;
  }

  bool volatile* _do_work_called_by;

protected:
  uint _max_workers;

  void do_work_called(uint worker_id) {
    Atomic::inc(&_num_do_work);
    bool orig_value = Atomic::cmpxchg(&_do_work_called_by[worker_id], false, true);
    ASSERT_EQ(orig_value, false);
  }

  void verify_do_work_called_by(uint num_workers) {
    ASSERT_EQ(Atomic::load(&_num_do_work), num_workers);
    // Do not need to check the _do_work_called_by array. The count is already verified
    // by above statement, and we already check that a given flag is only set once.
  }

public:
  // Actual use of GCParPhasesSentinel will cause an assertion failure when trying
  // to add timing information - this should be disabled here.
  G1TestSubTask() : G1AbstractSubTask(G1GCPhaseTimes::GCParPhasesSentinel),
    _phase(0),
    _num_do_work(0),
    _do_work_called_by(nullptr),
    _max_workers(0) {
    check_and_inc_phase(0);
  }

  ~G1TestSubTask() {
    check_and_inc_phase(3);
    FREE_C_HEAP_ARRAY(bool, _do_work_called_by);
  }

  double worker_cost() const override {
    check_and_inc_phase(1);
    return 1.0;
  }

  // Called by G1BatchedTask to provide information about the maximum
  // number of workers for all subtasks after it has been determined.
  void set_max_workers(uint max_workers) override {
    assert(max_workers >= 1, "must be");
    check_and_inc_phase(2);

    _do_work_called_by = NEW_C_HEAP_ARRAY(bool, max_workers, mtInternal);
    for (uint i = 0; i < max_workers; i++) {
      _do_work_called_by[i] = false;
    }
    _max_workers = max_workers;
  }

  void do_work(uint worker_id) override {
    do_work_called(worker_id);
  }
};

class G1SerialTestSubTask : public G1TestSubTask {
public:
  G1SerialTestSubTask() : G1TestSubTask() { }
  ~G1SerialTestSubTask() {
    verify_do_work_called_by(1);
  }

  double worker_cost() const override {
    G1TestSubTask::worker_cost();
    return 1.0;
  }
};

class G1ParallelTestSubTask : public G1TestSubTask {
public:
  G1ParallelTestSubTask() : G1TestSubTask() { }
  ~G1ParallelTestSubTask() {
    verify_do_work_called_by(_max_workers);
  }

  double worker_cost() const override {
    G1TestSubTask::worker_cost();
    return 2.0;
  }
};

class G1TestBatchedTask : public G1BatchedTask {
public:
  G1TestBatchedTask() : G1BatchedTask("Batched Test Task", nullptr) {
    add_serial_task(new G1SerialTestSubTask());
    add_parallel_task(new G1ParallelTestSubTask());
  }
};

TEST_VM(G1BatchedTask, check) {
  G1TestBatchedTask task;
  uint tasks = task.num_workers_estimate();
  ASSERT_EQ(tasks, 3u);
  task.set_max_workers(G1BatchedTaskWorkers::MaxWorkers);
  G1BatchedTaskWorkers::run_task(&task);
}
