/*
* 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.scheduler;
import org.jikesrvm.VM;
import org.jikesrvm.VM_Constants;
import org.jikesrvm.annotations.NoSubArchCompile;
import org.jikesrvm.runtime.VM_Entrypoints;
import org.jikesrvm.runtime.VM_Magic;
import org.vmmagic.pragma.Entrypoint;
import org.vmmagic.pragma.NoInline;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.Offset;
/**
*
* <p> Alternative (to Java monitors) light-weight synchronization
* mechanism to implement thread scheduling {@link VM_Processor} and
* Java monitors {@link VM_Lock}. These locks should not be used
* where Java monitors would suffice. They are intended to be held
* only briefly!
*
* <p> Normally, contending <code>VM_Processor</code>s will spin on
* this processor lock's <code>latestContender</code> field. If
* <code>MCS_Locking</code> is set, the processors spin on processor
* local data. This is loosely based on an idea in Mellor-Crummey and
* Scott's paper in ASPLOS-IV (1991).
* 1. Possible project: determine those conditions under which MCS
* locking performs better than spinning on a global address.
*
* <p> Acquiring or releasing a lock involves atomically reading and
* setting the lock's <code>latestContender</code> field. If this
* field is null, the lock is unowned. Otherwise, the field points to
* the virtual processor that owns the lock, or, if MCS locking is
* being used, to the last vp on a circular queue of virtual
* processors spinning until they get the lock, or, if MCS locking is
* being used and the circular spin queue is being updated, to
* <code>IN_FLUX</code>.
*
* <p> Contention is best handled by doing something else. To support
* this, <code>tryLock</code> obtains the lock (and returns true) if
* the lock is unowned (and there is no spurious microcontention).
* Otherwise, it returns false.
*
* <p> Only when "doing something else" is not an attractive option
* (locking global thread queues, unlocking a thick lock with threads
* waiting, avoiding starvation on a thread that has issued several
* tryLocks, etc.) should lock() be called. Here, any remaining
* contention is handled by spinning on a local flag.
*
* <p> To add itself to the circular waiting queue, a processor must
* succeed in setting the latestContender field to IN_FLUX. A backoff
* strategy is used to reduce contention for this field. This
* strategy has both a pseudo-random (to prevent two or more virtual
* processors from backing off in lock step) and an exponential
* component (to deal with really high contention).
*
* <p> Releasing a lock entails either atomically setting the
* latestContender field to null (if this processor is the
* latestContender), or releasing the first virtual processor on the
* circular spin queue. In the latter case, the latestContender field
* must be set to IN_FLUX. To give unlock() priority over lock(), the
* backoff strategy is not used for unlocking: if a vp fails to set
* set the field to IN_FLUX, it tries again immediately.
*
* <p> Usage: system locks should only be used when synchronized
* methods cannot. Do not do anything, (such as trigger a type cast,
* allocate an object, or call any method of a class that does not
* implement Uninterruptible) that might allow a thread switch or
* trigger a garbage collection between lock and unlock.
*
* @see VM_Processor
* @see VM_Lock */
@Uninterruptible
public final class VM_ProcessorLock implements VM_Constants {
/**
* Should contending <code>VM_Processor</code>s spin on processor local addresses (true)
* or on a globally shared address (false).
*/
private static final boolean MCS_Locking = false;
/**
* The state of the processor lock.
* <ul>
* <li> <code>null</code>, if the lock is not owned;
* <li> the processor that owns the lock, if no processors are waithing;
* <li> the last in a circular chain of processors waiting to own the lock; or
* <li> <code>IN_FLUX</code>, if the circular chain is being edited.
* </ul>
* Only the first two states are possible unless MCS locking is implemented.
*/
@Entrypoint
VM_Processor latestContender;
/**
* Acquire a processor lock.
*/
public void lock(String reason) {
if (VM_Scheduler.getNumberOfProcessors() == 1) return;
VM_Processor i = VM_Processor.getCurrentProcessor();
if (VM.VerifyAssertions) {
i.registerLock(reason);
}
VM_Processor p;
int attempts = 0;
Offset latestContenderOffset = VM_Entrypoints.latestContenderField.getOffset();
do {
p = VM_Magic.objectAsProcessor(VM_Magic.addressAsObject(VM_Magic.prepareAddress(this, latestContenderOffset)));
if (p == null) { // nobody owns the lock
if (VM_Magic.attemptAddress(this, latestContenderOffset, Address.zero(), VM_Magic.objectAsAddress(i))) {
VM_Magic.isync(); // so subsequent instructions wont see stale values
return;
} else {
continue; // don't handle contention
}
} else if (MCS_Locking && VM_Magic.objectAsAddress(p).NE(IN_FLUX)) { // lock is owned, but not being changed
if (VM_Magic.attemptAddress(this, latestContenderOffset, VM_Magic.objectAsAddress(p), IN_FLUX)) {
VM_Magic.isync(); // so subsequent instructions wont see stale values
break;
}
}
handleMicrocontention(attempts++);
} while (true);
// i owns the lock
if (VM.VerifyAssertions && !MCS_Locking) VM._assert(VM.NOT_REACHED);
i.awaitingProcessorLock = this;
if (p.awaitingProcessorLock != this) { // make i first (and only) waiter on the contender chain
i.contenderLink = i;
} else { // make i last waiter on the contender chain
i.contenderLink = p.contenderLink;
p.contenderLink = i;
}
VM_Magic.sync(); // so other contender will see updated contender chain
VM_Magic.setObjectAtOffset(this, latestContenderOffset, i); // other processors can get at the lock
do { // spin, waiting for the lock
VM_Magic.isync(); // to make new value visible as soon as possible
} while (i.awaitingProcessorLock == this);
}
/**
* Conditionally acquire a processor lock.
* @return whether acquisition succeeded
*/
public boolean tryLock() {
if (VM_Scheduler.getNumberOfProcessors() == 1) return true;
Offset latestContenderOffset = VM_Entrypoints.latestContenderField.getOffset();
if (VM_Magic.prepareAddress(this, latestContenderOffset).isZero()) {
Address cp = VM_Magic.objectAsAddress(VM_Processor.getCurrentProcessor());
if (VM_Magic.attemptAddress(this, latestContenderOffset, Address.zero(), cp)) {
VM_Magic.isync(); // so subsequent instructions wont see stale values
return true;
}
}
return false;
}
/**
* Release a processor lock.
*/
public void unlock() {
if (VM_Scheduler.getNumberOfProcessors() == 1) return;
VM_Magic.sync(); // commit changes while lock was held so they are visiable to the next processor that acquires the lock
Offset latestContenderOffset = VM_Entrypoints.latestContenderField.getOffset();
VM_Processor i = VM_Processor.getCurrentProcessor();
if (!MCS_Locking) {
if (VM.VerifyAssertions) i.registerUnlock();
VM_Magic.setObjectAtOffset(this, latestContenderOffset, null); // latestContender = null;
return;
}
VM_Processor p;
do {
p = VM_Magic.objectAsProcessor(VM_Magic.addressAsObject(VM_Magic.prepareAddress(this, latestContenderOffset)));
if (p == i) { // nobody is waiting for the lock
if (VM_Magic.attemptAddress(this, latestContenderOffset, VM_Magic.objectAsAddress(p), Address.zero())) {
break;
}
} else
if (VM_Magic.objectAsAddress(p).NE(IN_FLUX)) { // there are waiters, but the contention chain is not being chainged
if (VM_Magic.attemptAddress(this, latestContenderOffset, VM_Magic.objectAsAddress(p), IN_FLUX)) {
break;
}
} else { // in flux
handleMicrocontention(-1); // wait a little before trying again
}
} while (true);
if (p != i) { // p is the last processor on the chain of processors contending for the lock
VM_Processor q = p.contenderLink; // q is first processor on the chain
if (p == q) { // only one processor waiting for the lock
q.awaitingProcessorLock = null; // q now owns the lock
VM_Magic.sync(); // make sure the chain of waiting processors gets updated before another processor accesses the chain
// other contenders can get at the lock:
VM_Magic.setObjectAtOffset(this, latestContenderOffset, q); // latestContender = q;
} else { // more than one processor waiting for the lock
p.contenderLink = q.contenderLink; // remove q from the chain
q.awaitingProcessorLock = null; // q now owns the lock
VM_Magic.sync(); // make sure the chain of waiting processors gets updated before another processor accesses the chain
VM_Magic.setObjectAtOffset(this, latestContenderOffset, p); // other contenders can get at the lock
}
}
if (VM.VerifyAssertions) i.registerUnlock();
}
/**
* An attempt to lock or unlock a processor lock has failed,
* presumably due to contention with another processor. Backoff a
* little to increase the likelihood that a subsequent retry will
* succeed.
*/
@NoInline
private static void handleMicrocontention(int n) {
VM_Magic.pause(); // reduce overhead of spin wait on IA
if (n <= 0) return; // method call overhead is delay enough
if (n > 100) {
VM.sysWriteln("Unexpectedly large processor lock contention");
VM_Scheduler.dumpStack();
VM.sysFail("Unexpectedly large processor lock contention");
}
int pid = VM_Processor.getCurrentProcessorId();
if (pid < 0) pid = -pid; // native processors have negative ids
delayIndex = (delayIndex + pid) % delayCount.length;
int delay = delayCount[delayIndex] * delayMultiplier; // pseudorandom backoff component
delay += delayBase << (n - 1); // exponential backoff component
for (int i = delay; i > 0; i--) ; // delay a different amount of time on each processor
}
private static final int delayMultiplier = 10;
private static final int delayBase = 64;
private static int delayIndex;
private static final int[] delayCount = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
/**
* For MCS locking, indicates that another processor is changing the
* state of the circular waiting queue.
*/
private static final Address IN_FLUX = Address.max();
}