/*
 * 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/shared/stringdedup/stringDedup.hpp"
#include "gc/shared/stringdedup/stringDedupThread.hpp"
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
#include "gc/shenandoah/shenandoahStrDedupQueue.inline.hpp"
#include "gc/shenandoah/shenandoahStringDedup.inline.hpp"
#include "logging/log.hpp"
#include "runtime/mutex.hpp"
#include "runtime/mutexLocker.hpp"

ShenandoahStrDedupQueue::ShenandoahStrDedupQueue() :
  _consumer_queue(NULL),
  _num_producer_queue(ShenandoahHeap::heap()->max_workers()),
  _published_queues(NULL),
  _free_list(NULL),
  _num_free_buffer(0),
  _max_free_buffer(ShenandoahHeap::heap()->max_workers() * 2),
  _cancel(false),
  _total_buffers(0) {
  _producer_queues = NEW_C_HEAP_ARRAY(ShenandoahQueueBuffer*, _num_producer_queue, mtGC);
  for (size_t index = 0; index < _num_producer_queue; index ++) {
    _producer_queues[index] = NULL;
  }
}

ShenandoahStrDedupQueue::~ShenandoahStrDedupQueue() {
  MonitorLockerEx ml(StringDedupQueue_lock, Mutex::_no_safepoint_check_flag);
  for (size_t index = 0; index < num_queues(); index ++) {
    release_buffers(queue_at(index));
  }

  release_buffers(_free_list);
  FREE_C_HEAP_ARRAY(ShenandoahQueueBuffer*, _producer_queues);
}

void ShenandoahStrDedupQueue::wait_impl() {
  MonitorLockerEx ml(StringDedupQueue_lock, Mutex::_no_safepoint_check_flag);
  while (_consumer_queue == NULL && !_cancel) {
    ml.wait(Mutex::_no_safepoint_check_flag);
    assert(_consumer_queue == NULL, "Why wait?");
    _consumer_queue = _published_queues;
    _published_queues = NULL;
  }
}

void ShenandoahStrDedupQueue::cancel_wait_impl() {
  MonitorLockerEx ml(StringDedupQueue_lock, Mutex::_no_safepoint_check_flag);
  _cancel = true;
  ml.notify();
}

void ShenandoahStrDedupQueue::unlink_or_oops_do_impl(StringDedupUnlinkOrOopsDoClosure* cl, size_t queue) {
  ShenandoahQueueBuffer* q = queue_at(queue);
  while (q != NULL) {
    q->unlink_or_oops_do(cl);
    q = q->next();
  }
}

ShenandoahQueueBuffer* ShenandoahStrDedupQueue::queue_at(size_t queue_id) const {
  assert(queue_id <= num_queues(), "Invalid queue id");
  if (queue_id < _num_producer_queue) {
    return _producer_queues[queue_id];
  } else if (queue_id == _num_producer_queue) {
    return _consumer_queue;
  } else {
    assert(queue_id == _num_producer_queue + 1, "Must be");
    return _published_queues;
  }
}

void ShenandoahStrDedupQueue::set_producer_buffer(ShenandoahQueueBuffer* buf, size_t queue_id) {
  assert(queue_id < _num_producer_queue, "Not a producer queue id");
  _producer_queues[queue_id] = buf;
}

void ShenandoahStrDedupQueue::push_impl(uint worker_id, oop string_oop) {
  assert(worker_id < _num_producer_queue, "Invalid queue id. Can only push to producer queue");
  assert(ShenandoahStringDedup::is_candidate(string_oop), "Not a candidate");

  ShenandoahQueueBuffer* buf = queue_at((size_t)worker_id);

  if (buf == NULL) {
    MonitorLockerEx ml(StringDedupQueue_lock, Mutex::_no_safepoint_check_flag);
    buf = new_buffer();
    set_producer_buffer(buf, worker_id);
  } else if (buf->is_full()) {
    MonitorLockerEx ml(StringDedupQueue_lock, Mutex::_no_safepoint_check_flag);
    buf->set_next(_published_queues);
    _published_queues = buf;
    buf = new_buffer();
    set_producer_buffer(buf, worker_id);
    ml.notify();
  }

  assert(!buf->is_full(), "Sanity");
  buf->push(string_oop);
}

oop ShenandoahStrDedupQueue::pop_impl() {
  assert(Thread::current() == StringDedupThread::thread(), "Must be dedup thread");
  while (true) {
    if (_consumer_queue == NULL) {
      MonitorLockerEx ml(StringDedupQueue_lock, Mutex::_no_safepoint_check_flag);
      _consumer_queue = _published_queues;
      _published_queues = NULL;
    }

    // there is nothing
    if (_consumer_queue == NULL) {
      return NULL;
    }

    oop obj = NULL;
    if (pop_candidate(obj)) {
      assert(ShenandoahStringDedup::is_candidate(obj), "Must be a candidate");
      return obj;
    }
    assert(obj == NULL, "No more candidate");
  }
}

bool ShenandoahStrDedupQueue::pop_candidate(oop& obj) {
  ShenandoahQueueBuffer* to_release = NULL;
  bool suc = true;
  do {
    if (_consumer_queue->is_empty()) {
      ShenandoahQueueBuffer* buf = _consumer_queue;
      _consumer_queue = _consumer_queue->next();
      buf->set_next(to_release);
      to_release = buf;

      if (_consumer_queue == NULL) {
        suc = false;
        break;
      }
    }
    obj = _consumer_queue->pop();
  } while (obj == NULL);

  if (to_release != NULL) {
    MonitorLockerEx ml(StringDedupQueue_lock, Mutex::_no_safepoint_check_flag);
    release_buffers(to_release);
  }

  return suc;
}

ShenandoahQueueBuffer* ShenandoahStrDedupQueue::new_buffer() {
  assert_lock_strong(StringDedupQueue_lock);
  if (_free_list != NULL) {
    assert(_num_free_buffer > 0, "Sanity");
    ShenandoahQueueBuffer* buf = _free_list;
    _free_list = _free_list->next();
    _num_free_buffer --;
    buf->reset();
    return buf;
  } else {
    assert(_num_free_buffer == 0, "Sanity");
    _total_buffers ++;
    return new ShenandoahQueueBuffer;
  }
}

void ShenandoahStrDedupQueue::release_buffers(ShenandoahQueueBuffer* list) {
  assert_lock_strong(StringDedupQueue_lock);
  while (list != NULL) {
    ShenandoahQueueBuffer* tmp = list;
    list = list->next();
    if (_num_free_buffer < _max_free_buffer) {
      tmp->set_next(_free_list);
      _free_list = tmp;
      _num_free_buffer ++;
    } else {
      _total_buffers --;
      delete tmp;
    }
  }
}

void ShenandoahStrDedupQueue::print_statistics_impl() {
  Log(gc, stringdedup) log;
  log.debug("  Queue:");
  log.debug("    Total buffers: " SIZE_FORMAT " (" SIZE_FORMAT " %s). " SIZE_FORMAT " buffers are on free list",
    _total_buffers,
    byte_size_in_proper_unit(_total_buffers * sizeof(ShenandoahQueueBuffer)),
    proper_unit_for_byte_size(_total_buffers * sizeof(ShenandoahQueueBuffer)),
    _num_free_buffer);
}

class VerifyQueueClosure : public OopClosure {
private:
  ShenandoahHeap* _heap;
public:
  VerifyQueueClosure();

  void do_oop(oop* o);
  void do_oop(narrowOop* o) {
    ShouldNotCallThis();
  }
};

VerifyQueueClosure::VerifyQueueClosure() :
  _heap(ShenandoahHeap::heap()) {
}

void VerifyQueueClosure::do_oop(oop* o) {
  if (*o != NULL) {
    oop obj = *o;
    shenandoah_assert_correct(o, obj);
    assert(java_lang_String::is_instance(obj), "Object must be a String");
  }
}

void ShenandoahStrDedupQueue::verify_impl() {
  VerifyQueueClosure vcl;
  for (size_t index = 0; index < num_queues(); index ++) {
    ShenandoahQueueBuffer* buf = queue_at(index);
    while (buf != NULL) {
      buf->oops_do(&vcl);
      buf = buf->next();
    }
  }
}
