/*
 * Copyright (c) 2017, 2019, Red Hat, Inc. All rights reserved.
 *
 * 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 "precompiled.hpp"

#include "gc_implementation/shenandoah/shenandoahHeap.inline.hpp"
#include "gc_implementation/shenandoah/shenandoahStrDedupQueue.inline.hpp"
#include "gc_implementation/shenandoah/shenandoahStringDedup.hpp"
#include "memory/allocation.inline.hpp"
#include "runtime/atomic.hpp"

ShenandoahStrDedupQueue::ShenandoahStrDedupQueue(ShenandoahStrDedupQueueSet* queue_set, uint num) :
  _queue_set(queue_set), _current_list(NULL), _queue_num(num) {
  assert(num < _queue_set->num_queues(), "Not valid queue number");
}

ShenandoahStrDedupQueue::~ShenandoahStrDedupQueue() {
  if (_current_list != NULL) {
    delete _current_list;
  }
}

void ShenandoahStrDedupQueue::oops_do(OopClosure* cl) {
  if (_current_list != NULL) {
    _current_list->oops_do(cl);
  }
}

ShenandoahStrDedupQueueSet::ShenandoahStrDedupQueueSet(uint n) :
  _num_queues(n), _free_list(NULL), _num_free_queues(0), _terminated(false), _claimed(0) {
  _lock = new Monitor(Mutex::leaf, "ShenandoahStrDedupQueueLock", false);

  _local_queues = NEW_C_HEAP_ARRAY(ShenandoahStrDedupQueue*, num_queues(), mtGC);
  _outgoing_work_list = NEW_C_HEAP_ARRAY(QueueChunkedList*, num_queues(), mtGC);

  for (uint index = 0; index < num_queues(); index ++) {
    _local_queues[index] = new ShenandoahStrDedupQueue(this, index);
    _outgoing_work_list[index] = NULL;
  }
}

ShenandoahStrDedupQueueSet::~ShenandoahStrDedupQueueSet() {
  QueueChunkedList* q;
  QueueChunkedList* tmp;

  for (uint index = 0; index < num_queues(); index ++) {
    if (_local_queues[index] != NULL) {
      delete _local_queues[index];
    }

    q = _outgoing_work_list[index];
    while (q != NULL) {
      tmp = q;
      q = q->next();
      delete tmp;
    }
  }

  q = _free_list;
  while (q != NULL) {
    tmp = q;
    q = tmp->next();
    delete tmp;
  }

  FREE_C_HEAP_ARRAY(ShenandoahStrDedupQueue*, _local_queues, mtGC);
  FREE_C_HEAP_ARRAY(QueueChunkedList*, _outgoing_work_list, mtGC);

  delete _lock;
}

size_t ShenandoahStrDedupQueueSet::claim() {
  size_t index = (size_t)Atomic::add(1, (volatile jint*)&_claimed) - 1;
  return index;
}

void ShenandoahStrDedupQueueSet::parallel_oops_do(OopClosure* cl) {
  assert(cl != NULL, "No closure");
  assert(SafepointSynchronize::is_at_safepoint(), "Must be at a safepoint");
  size_t claimed_index;
  while ((claimed_index = claim()) < num_queues()) {
    queue_at(claimed_index)->oops_do(cl);
    QueueChunkedList* head = _outgoing_work_list[claimed_index];
    while (head != NULL) {
      head->oops_do(cl);
      head = head->next();
    }
  }
}

void ShenandoahStrDedupQueueSet::oops_do_slow(OopClosure* cl) {
  assert(cl != NULL, "No closure");
  assert(SafepointSynchronize::is_at_safepoint(), "Must be at a safepoint");
  for (size_t index = 0; index < num_queues(); index ++) {
    queue_at(index)->oops_do(cl);
    QueueChunkedList* head = _outgoing_work_list[index];
    while (head != NULL) {
      head->oops_do(cl);
      head = head->next();
    }
  }
}

void ShenandoahStrDedupQueueSet::terminate() {
  MonitorLockerEx locker(_lock, Mutex::_no_safepoint_check_flag);
  _terminated = true;
  locker.notify_all();
}

void ShenandoahStrDedupQueueSet::release_chunked_list(QueueChunkedList* q) {
  assert(q != NULL, "null queue");
  MutexLockerEx locker(lock(), Mutex::_no_safepoint_check_flag);
  if (_num_free_queues >= 2 * num_queues()) {
    delete q;
  } else {
    q->set_next(_free_list);
    _free_list = q;
    _num_free_queues ++;
  }
}

QueueChunkedList* ShenandoahStrDedupQueueSet::allocate_no_lock() {
  assert_lock_strong(lock());

  if (_free_list != NULL) {
    QueueChunkedList* q = _free_list;
    _free_list = _free_list->next();
    _num_free_queues --;
    q->reset();
    return q;
  } else {
    return new QueueChunkedList();
  }
}

QueueChunkedList* ShenandoahStrDedupQueueSet::allocate_chunked_list() {
  MutexLockerEx locker(_lock, Mutex::_no_safepoint_check_flag);
  return allocate_no_lock();
}

QueueChunkedList* ShenandoahStrDedupQueueSet::push_and_get_atomic(QueueChunkedList* q, uint queue_num) {
  QueueChunkedList* head = _outgoing_work_list[queue_num];
  QueueChunkedList* result;
  q->set_next(head);
  while ((result = (QueueChunkedList*)Atomic::cmpxchg_ptr(q, &_outgoing_work_list[queue_num], head)) != head) {
    head = result;
    q->set_next(head);
  }

  {
    MutexLockerEx locker(lock(), Mutex::_no_safepoint_check_flag);
    q = allocate_no_lock();
    lock()->notify();
  }
  return q;
}

QueueChunkedList* ShenandoahStrDedupQueueSet::remove_work_list_atomic(uint queue_num) {
  assert(queue_num < num_queues(), "Invalid queue number");

  QueueChunkedList* list = _outgoing_work_list[queue_num];
  QueueChunkedList* result;
  while ((result = (QueueChunkedList*)Atomic::cmpxchg_ptr((QueueChunkedList*)NULL, &_outgoing_work_list[queue_num], list)) != list) {
    list = result;
  }

  return list;
}

void ShenandoahStrDedupQueueSet::parallel_cleanup() {
  ShenandoahStrDedupQueueCleanupClosure cl;
  parallel_oops_do(&cl);
}
