/* * 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.greenthreads; import static org.jikesrvm.runtime.VM_SysCall.sysCall; import org.jikesrvm.SubordinateArchitecture.VM_ProcessorLocalState; import org.jikesrvm.annotations.NoSubArchCompile; import org.jikesrvm.VM; import org.jikesrvm.VM_Constants; import org.jikesrvm.objectmodel.VM_ThinLockConstants; import org.jikesrvm.runtime.VM_Magic; import org.jikesrvm.scheduler.VM_Processor; import org.jikesrvm.scheduler.VM_ProcessorLock; import org.jikesrvm.scheduler.VM_Scheduler; import org.jikesrvm.scheduler.VM_Thread; import org.vmmagic.pragma.Inline; import org.vmmagic.pragma.LogicallyUninterruptible; import org.vmmagic.pragma.Uninterruptible; import org.vmmagic.unboxed.Offset; /** * Multiplex execution of large number of VM_Threads on small * number of o/s kernel threads. */ @Uninterruptible public final class VM_GreenSubArchProcessor extends VM_Processor { /** * thread previously running on this processor */ public VM_GreenThread previousThread; /** * Should this processor dispatch a new VM_Thread when * "threadSwitch" is called? * Also used to decide if it's safe to call yield() when * contending for a lock. * A value of: * 1 means "yes" (switching enabled) * <= 0 means "no" (switching disabled) */ private int threadSwitchingEnabledCount; /** * Was "threadSwitch" called while this processor had * thread switching disabled? */ int threadSwitchPending; /** * non-null --> a processor that has no work to do */ static VM_GreenSubArchProcessor idleProcessor; /** * The reason given for disabling thread switching */ private final String[] threadSwitchDisabledReason = VM.VerifyAssertions ? new String[10] : null; /** * threads to be added to ready queue */ public VM_GlobalGreenThreadQueue transferQueue; /** guard for transferQueue */ public final VM_ProcessorLock transferMutex; /** guard for collectorThread */ public final VM_ProcessorLock collectorThreadMutex; /** the collector thread to run */ public VM_GreenThread collectorThread; /** * threads waiting for a timeslice in which to run */ VM_GreenThreadQueue readyQueue; /** * thread to run when nothing else to do */ VM_GreenThreadQueue idleQueue; /** * Create data object to be associated with an subarch processor * @param id id that will be returned by getCurrentProcessorId() for * this processor. */ @NoSubArchCompile public VM_GreenSubArchProcessor(int id) { super(id); this.transferMutex = new VM_ProcessorLock(); this.collectorThreadMutex = new VM_ProcessorLock(); this.transferQueue = new VM_GlobalGreenThreadQueue(this.transferMutex); this.readyQueue = new VM_GreenThreadQueue(); this.idleQueue = new VM_GreenThreadQueue(); // TODO - Remove hack this.threadId = id << VM_ThinLockConstants.TL_THREAD_ID_SHIFT; sysCall.sysVirtualSubArchProcessorBind(VM_Magic.objectAsAddress(this), id - 1); // TODO - Remove when initializeProcessor is being called by subarch isInitialized = true; } /** * Code executed to initialize a subarch processor and * prepare it to execute Java threads. */ public void initializeProcessor() { // subarch processor is now ready to run java threads isInitialized = true; // enable multiprocessing // enableThreadSwitching(); } /** * Is it ok to switch to a new VM_Thread in this processor? */ @Inline @Override public boolean threadSwitchingEnabled() { return threadSwitchingEnabledCount == 1; } /** * Enable thread switching in this processor. */ @Override public void enableThreadSwitching() { ++threadSwitchingEnabledCount; if (VM.VerifyAssertions) { VM._assert(threadSwitchingEnabledCount <= 1); // if (MM_Interface.gcInProgress()) { // VM._assert(threadSwitchingEnabledCount < 1 || getCurrentProcessorId() == 0); // } } if (threadSwitchingEnabled() && threadSwitchPending != 0) { takeYieldpoint = threadSwitchPending; threadSwitchPending = 0; } } /** * Disable thread switching in this processor. * @param reason for disabling thread switching */ @Inline @Override public void disableThreadSwitching(String reason) { --threadSwitchingEnabledCount; if (VM.VerifyAssertions && (-threadSwitchingEnabledCount < threadSwitchDisabledReason.length)) { VM_Magic.setObjectAtOffset(threadSwitchDisabledReason, Offset.fromIntZeroExtend(-threadSwitchingEnabledCount << VM_Constants.BYTES_IN_ADDRESS), reason); // threadSwitchDisabledReason[-threadSwitchingEnabledCount] = reason; } } /** * Request the thread executing on the processor to take the next executed yieldpoint * and issue memory synchronization instructions */ @Override public void requestPostCodePatchSync() { if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED); } /** * Get processor that's being used to run the current java thread. */ @Inline public static VM_GreenSubArchProcessor getCurrentProcessor() { return (VM_GreenSubArchProcessor)VM_ProcessorLocalState.getCurrentProcessor(); } /** * Get id of processor that's being used to run the current java thread. */ @Inline public static int getCurrentProcessorId() { return getCurrentProcessor().id; } /** * Become next "ready" thread. * Note: This method is ONLY intended for use by VM_Thread. * @param timerTick timer interrupted if true */ @Override public void dispatch(boolean timerTick) { // no processor locks should be held across a thread switch if (VM.VerifyAssertions) checkLockCount(0); VM_GreenThread newThread = getRunnableThread(); while (newThread.suspendIfPending()) { newThread = getRunnableThread(); } previousThread = (VM_GreenThread)activeThread; activeThread = (VM_GreenThread)newThread; // TODO - Implement some load balancing if runqueue is not empty and other processors are idle // TODO - Deal with time intervals threadId = newThread.getLockingId(); activeThreadStackLimit = newThread.stackLimit; // Delay this to last possible moment so we can sysWrite // TODO - VM_Magic.threadSwitch(previousThread, newThread.contextRegisters); } /** * Find a thread that can be run by this processor and remove it * from its queue. */ @Inline private VM_GreenThread getRunnableThread() { // Is there a GC thread waiting to be scheduled? if (collectorThread != null) { // Schedule GC threads first. This avoids a deadlock when GC is trigerred // during scheduling (usually due to a write barrier) collectorThreadMutex.lock("getting runnable gc thread"); if (VM.VerifyAssertions) VM._assert(collectorThread != null); VM_GreenThread ct = collectorThread; if (VM.TraceThreadScheduling > 1) { VM_Scheduler.trace("VM_Processor", "getRunnableThread: collector thread", ct.getIndex()); } collectorThread = null; collectorThreadMutex.unlock(); return ct; } for (int i = transferQueue.length(); 0 < i; i--) { transferMutex.lock("transfer queue mutex for dequeue"); VM_GreenThread t = transferQueue.dequeue(); transferMutex.unlock(); if (VM.VerifyAssertions) VM._assert(!t.isGCThread()); if (t.beingDispatched && t != VM_Scheduler.getCurrentThread()) { // thread's stack in use by some OTHER dispatcher if (VM.TraceThreadScheduling > 1) { VM_Scheduler.trace("VM_Processor", "getRunnableThread: stack in use", t.getIndex()); } transferMutex.lock("transfer queue mutex for an enqueue due to dispatch"); transferQueue.enqueue(t); transferMutex.unlock(); } else { if (VM.TraceThreadScheduling > 1) { VM_Scheduler.trace("VM_Processor", "getRunnableThread: transfer to readyQueue", t.getIndex()); } readyQueue.enqueue(t); } } if (!readyQueue.isEmpty()) { VM_GreenThread t = readyQueue.dequeue(); if (VM.TraceThreadScheduling > 1) { VM_Scheduler.trace("VM_Processor", "getRunnableThread: readyQueue", t.getIndex()); } if (VM.VerifyAssertions) { // local queue: no other dispatcher should be running on thread's stack VM._assert(!t.beingDispatched || t == VM_Scheduler.getCurrentThread()); } return t; } if (!idleQueue.isEmpty()) { VM_GreenThread t = idleQueue.dequeue(); if (VM.TraceThreadScheduling > 1) { VM_Scheduler.trace("VM_Processor", "getRunnableThread: idleQueue", t.getIndex()); } if (VM.VerifyAssertions) { // local queue: no other dispatcher should be running on thread's stack VM._assert(!t.beingDispatched || t == VM_Scheduler.getCurrentThread()); } return t; } // should only get here if the idle thread contended on a lock (due to debug) if (VM.VerifyAssertions) VM._assert(VM_Scheduler.getCurrentThread() instanceof VM_IdleThread); return VM_GreenScheduler.getCurrentThread(); } //-----------------// // Load Balancing // //-----------------// /** * Add a thread to this processor's transfer queue. */ public void transferThread(VM_Thread thread) { if (VM.VerifyAssertions) VM._assert(thread instanceof VM_GreenThread); VM_GreenThread t = (VM_GreenThread) thread; if (t.isGCThread()) { collectorThreadMutex.lock("gc thread transfer"); collectorThread = t; /* Implied by transferring a gc thread */ requestYieldToGC(); collectorThreadMutex.unlock(); } else if (this != getCurrentProcessor() || (t.beingDispatched && t != VM_Scheduler.getCurrentThread())) { transferMutex.lock("thread transfer"); transferQueue.enqueue(t); transferMutex.unlock(); } else if (t.isIdleThread()) { idleQueue.enqueue(t); } else { readyQueue.enqueue(t); } } /** * Put thread onto most lightly loaded virtual processor. */ public void scheduleThread(VM_GreenThread t) { // TODO - Deal with processor affinity // if t is the last runnable thread on this processor, don't move it if (t == VM_Scheduler.getCurrentThread() && readyQueue.isEmpty() && transferQueue.isEmpty()) { if (VM.TraceThreadScheduling > 0) { VM_Scheduler.trace("VM_Processor.scheduleThread", "staying on same processor:", t.getIndex()); } getCurrentProcessor().transferThread(t); return; } // if a processor is idle, transfer t to it VM_GreenSubArchProcessor idle = idleProcessor; if (idle != null) { idleProcessor = null; if (VM.TraceThreadScheduling > 0) { VM_Scheduler.trace("VM_Processor.scheduleThread", "outgoing to idle processor:", t.getIndex()); } idle.transferThread(t); return; } // otherwise distribute threads round robin if (VM.TraceThreadScheduling > 0) { VM_Scheduler.trace("VM_Processor.scheduleThread", "outgoing to round-robin processor:", t.getIndex()); } chooseNextProcessor(t).transferThread(t); } /** * Cycle (round robin) through the available subarch processors. */ private VM_GreenSubArchProcessor chooseNextProcessor(VM_GreenThread t) { t.chosenProcessorId = (t.chosenProcessorId % VM_GreenScheduler.numSubArchProcessors) + 1; return VM_GreenScheduler.subArchProcessors[t.chosenProcessorId]; } //---------------------// // Garbage Collection // //---------------------// @LogicallyUninterruptible /* GACK --dave */ public void dumpProcessorState() { VM.sysWrite("Processor "); VM.sysWriteInt(id); if (this == VM_GreenSubArchProcessor.getCurrentProcessor()) VM.sysWrite(" (me)"); VM.sysWrite(" running thread"); if (activeThread != null) { activeThread.dump(); } else { VM.sysWrite(" NULL Active Thread"); } VM.sysWrite("\n"); VM.sysWrite(" system thread id "); VM.sysWriteInt(pthread_id); VM.sysWrite("\n"); VM.sysWrite(" transferQueue:"); if (transferQueue != null) transferQueue.dump(); VM.sysWrite(" readyQueue:"); if (readyQueue != null) readyQueue.dump(); VM.sysWrite(" idleQueue:"); if (idleQueue != null) idleQueue.dump(); VM.sysWrite(" status: "); int status = vpStatus; if (status == IN_NATIVE) VM.sysWrite("IN_NATIVE\n"); if (status == IN_JAVA) VM.sysWrite("IN_JAVA\n"); if (status == BLOCKED_IN_NATIVE) VM.sysWrite("BLOCKED_IN_NATIVE\n"); VM.sysWrite(" timeSliceExpired: "); VM.sysWriteInt(timeSliceExpired); VM.sysWrite("\n"); } /** * Fail if thread switching is disabled on this processor */ @Override public void failIfThreadSwitchingDisabled() { if (!threadSwitchingEnabled()) { VM.sysWrite("No threadswitching on proc ", id); VM.sysWrite(" with addr ", VM_Magic.objectAsAddress(VM_GreenSubArchProcessor.getCurrentProcessor())); if (VM.VerifyAssertions) { for (int i=0; i <= -threadSwitchingEnabledCount; i++) { VM.sysWrite(" because: ", threadSwitchDisabledReason[i]); } } VM.sysWriteln(); VM._assert(false); } } }