/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Common Public License (CPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/cpl1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.jikesrvm.mm.mminterface;

import org.jikesrvm.VM;
import org.jikesrvm.mm.mmtk.SynchronizedCounter;
import org.jikesrvm.runtime.Magic;
import org.jikesrvm.runtime.Time;
import org.vmmagic.pragma.Uninterruptible;

/**
 * This class implements barrier synchronization.
 * The mechanism handles proper resetting by usnig 3 underlying counters
 * and supports unconditional blocking until the number of participants
 * can be determined.
 */
@Uninterruptible
final class Barrier {

  public static final int VERBOSE = 0;

  // The value of target is one more than the number of threads we expect to arrive.
  // It is one greater to allow safely updating the currentCounter value.
  // If target is -1, no thread can proceed past the barrier.
  // 3 counters are needed to support proper resetting without race conditions.
  //
  private volatile int target = -1;
  private static final int NUM_COUNTERS = 3;
  final SynchronizedCounter[] counters;
  SynchronizedCounter currentCounter;

  // Debugging constants
  private static final long WARN_PERIOD =  Time.secsToNanos(20); // Print msg every WARN_PERIOD seconds
  private static final long TIME_OUT =  3 * WARN_PERIOD; // Die after TIME_OUT seconds;

  public Barrier() {
    counters = new SynchronizedCounter[NUM_COUNTERS];
    for (int i = 0; i < NUM_COUNTERS; i++) {
      counters[i] = new SynchronizedCounter();
    }
    currentCounter = new SynchronizedCounter();
  }

  // Set target to appropriate value
  //
  public void setTarget(int t) {
    Magic.isync();
    if (VM.VerifyAssertions) VM._assert(t >= 0);
    target = t + 1;
    Magic.sync();
  }

  public void clearTarget() {
    Magic.isync();
    target = -1;
    Magic.sync();
  }

  // Returns whether caller was first to arrive.
  // The coding to ensure resetting is delicate.
  //
  public int arrive(int where) {
    Magic.isync();
    int cur = currentCounter.peek();
    SynchronizedCounter c = counters[cur];
    int myValue = c.increment();
    // Do NOT use the currentCounter variables unless designated thread
    if (VERBOSE >= 1) {
      VM.sysWriteln(where,": myValue = ",myValue);
    }
    if (VM.VerifyAssertions) VM._assert(myValue >= 0 && (target == -1 || myValue <= target));
    if (myValue + 2 == target) {
      // last one to show up
      int next = (cur + 1) % NUM_COUNTERS;
      int prev = (cur - 1 + NUM_COUNTERS) % NUM_COUNTERS;
      counters[prev].reset();       // everyone has seen the value so safe to reset now
      if (next == 0) {
        currentCounter.reset(); // everyone has arrived but still waiting
      } else {
        currentCounter.increment();
      }
      c.increment(); // now safe to let others past barrier
      Magic.sync();
      return myValue;
    } else {
      // everyone else
      long startNano = 0;
      long lastElapsedNano = 0;
      while (true) {
        long startCycles = Time.cycles();
        long endCycles = startCycles + ((long) 1e9); // a few hundred milliseconds more or less.
        long nowCycles;
        do {
          if (target != -1 && c.peek() == target) {
            Magic.sync();
            return myValue;
          }
          nowCycles = Time.cycles();
        } while (startCycles < nowCycles && nowCycles < endCycles); /* check against both ends to guard against CPU migration */

        /*
         * According to the cycle counter, we've been spinning for a while.
         * Time to check nanoTime and see if we should print a warning and/or sysFail.
         */
        if (startNano == 0) {
          startNano = Time.nanoTime();
        } else {
          long nowNano = Time.nanoTime();
          long elapsedNano = nowNano - startNano;
          if (elapsedNano - lastElapsedNano > WARN_PERIOD) {
            VM.sysWrite("GC Warning: Barrier wait has reached ",Time.nanosToSecs(elapsedNano),
                        " seconds.  Called from ");
            VM.sysWrite(where,".  myOrder = ",myValue,"  count is ");
            VM.sysWriteln(c.peek()," waiting for ",target - 1);
            lastElapsedNano = elapsedNano;
          }
          if (elapsedNano > TIME_OUT) {
            VM.sysFail("GC Error: Barrier Timeout");
          }
        }
      }
    }
  }
}
