/* JPC: An x86 PC Hardware Emulator for a pure Java Virtual Machine Copyright (C) 2012-2013 Ian Preston This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program 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 for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Details (including contact information) can be found at: jpc.sourceforge.net or the developer website sourceforge.net/projects/jpc/ End of licence header */ package org.jpc.j2se; import org.jpc.emulator.*; import org.jpc.emulator.motherboard.*; import org.jpc.support.Clock; import java.io.*; import java.util.ArrayList; import java.util.List; import java.util.PriorityQueue; import java.util.logging.*; public class VirtualClock extends AbstractHardwareComponent implements Clock { public static long IPS = Option.ips.intValue(25000000); private static final boolean DEBUG = false; private static final Logger LOGGING = Logger.getLogger(VirtualClock.class.getName()); private PriorityQueue<Timer> timers; private volatile boolean ticksEnabled; private long ticksOffset; private long ticksStatic; private long totalTicks = 0; // emulated cycles, monotonically increasing private long totalEmulatedNanos = 0; // emulated nanos, monotonically increasing private static final boolean REAL_TIME = !Option.deterministic.isSet(); //sync clock with real clock by default //required for tracking real time private static final long MIN_SLEEP_NANOS = 10000000L; // 10 milli-seconds private long nanosToSleep = 0; private long nextRateCheckTicks = 0; private long lastRealNanos; private long lastTotalTicks; private static final long RATE_CHECK_INTERVAL = 2*1000000; public VirtualClock() { timers = new PriorityQueue<Timer>(20); ticksEnabled = false; ticksOffset = 0; ticksStatic = 0; } public void saveState(DataOutput output) throws IOException { output.writeBoolean(ticksEnabled); output.writeLong(ticksOffset); output.writeLong(getTime()); } public void loadState(DataInput input, PC pc) throws IOException { ticksEnabled = input.readBoolean(); ticksOffset = input.readLong(); ticksStatic = input.readLong(); } public synchronized Timer newTimer(TimerResponsive object) { Timer tempTimer = new Timer(object, this); return tempTimer; } private boolean process() { Timer tempTimer; tempTimer = timers.peek(); if ((tempTimer == null) || !tempTimer.check(getTime())) return false; else return true; } public synchronized void update(Timer object) { timers.remove(object); if (object.enabled()) { timers.offer(object); } } public long getTime() { if (ticksEnabled) { return this.getRealTime() + ticksOffset; } else { return ticksStatic; } } public long getIPS() { return IPS; } private long getRealTime() { return getEmulatedNanos(); } public long getRealMillis() { return getEmulatedNanos()/1000000; } public long getEmulatedMicros() { return getEmulatedNanos()/1000; } public long getEmulatedNanos() { if (REAL_TIME) return totalEmulatedNanos + convertTicksToNanos(totalTicks-lastTotalTicks); return (long)(((double)totalTicks)*1000000000/IPS); } public long getTickRate() { return 1000000000L; // nano seconds } public long getTicks() { return totalTicks; } public void pause() { if (ticksEnabled) { ticksStatic = getTime(); ticksEnabled = false; } } public void resume() { if (!ticksEnabled) { ticksOffset = ticksStatic - getRealTime(); ticksEnabled = true; lastRealNanos = System.nanoTime(); lastTotalTicks = getTicks(); nextRateCheckTicks = lastTotalTicks + RATE_CHECK_INTERVAL; } } public void reset() { this.pause(); ticksOffset = 0; ticksStatic = 0; } public String toString() { return "Virtual Clock"; } public void updateNowAndProcess(boolean sleep) { if (REAL_TIME) { Timer tempTimer; synchronized (this) { tempTimer = timers.peek(); } long expiry = tempTimer.getExpiry(); long now = getEmulatedNanos(); long nanoDelay = expiry-now; if (nanoDelay > 0) { nanosToSleep += nanoDelay; if (nanosToSleep > MIN_SLEEP_NANOS) // don't waste time with loads of tiny sleeps (eg. mixer) { try { if (DEBUG) System.out.printf("Halt: sleep for %d millis %d nanos...\n", (nanosToSleep)/1000000L, nanosToSleep % 1000000); if (nanosToSleep > 100000000) nanosToSleep = 100000000L; Thread.sleep(nanosToSleep/1000000L, (int)(nanosToSleep % 1000000)); } catch (InterruptedException ex) { Logger.getLogger(VirtualClock.class.getName()).log(Level.SEVERE, null, ex); } nanosToSleep = 0; } totalTicks += convertNanosToTicks(nanoDelay)+1; // only place where ticks gets out of sync with number of instructions } if (!tempTimer.check(getEmulatedNanos())) throw new IllegalStateException("Should have forced interrupt!"); } else { Timer tempTimer; synchronized (this) { tempTimer = timers.peek(); } long expiry = tempTimer.getExpiry(); if (sleep) try { long toSleep = Math.min((expiry - getTime()) / 1000000, 100); // System.out.printf("Sleeping for %x millis", toSleep); Thread.sleep(toSleep); } catch (InterruptedException ex) { Logger.getLogger(VirtualClock.class.getName()).log(Level.SEVERE, null, ex); } // cast time difference to microseconds, then convert to cycles totalTicks = (long)((double)expiry * IPS / getTickRate());//totalTicks += ((expiry - getEmulatedNanos())/1000)*1000 * IPS / getTickRate(); if (totalTicks < 0) { System.out.println(printTimerQueue()); throw new IllegalStateException("Time cannot be negative! expiry=" + expiry + ", tick rate=" + getTickRate() + ", IPS=" + IPS); } if ((expiry * IPS) % getTickRate() != 0) totalTicks++; if (!tempTimer.check(getTime())) throw new IllegalStateException("Should have forced interrupt!"); } } public void updateAndProcess(int instructions) { update(instructions); process(); } public long convertNanosToTicks(long nanos) { return (long)(((double)nanos)*IPS/1000000000); // return nanos * IPS / 1000000000L; } public long convertTicksToNanos(long ticks) { return (long)(((double)ticks)*1000000000/IPS); // return ticks * 1000000000L / IPS; } public void update(int instructions) { totalTicks += instructions; if ((REAL_TIME) && (totalTicks > nextRateCheckTicks)) { long realNanosDelta = System.nanoTime() - lastRealNanos; long emulatedNanosDelta = convertTicksToNanos(totalTicks-lastTotalTicks); nextRateCheckTicks += RATE_CHECK_INTERVAL; if (Math.abs(emulatedNanosDelta - realNanosDelta) > 100000) { totalEmulatedNanos += emulatedNanosDelta; lastRealNanos += realNanosDelta; lastTotalTicks = totalTicks; changeTimeRate(((double) realNanosDelta/emulatedNanosDelta)); } } } private void changeTimeRate(double factor) { if (DEBUG) System.out.printf("Changing speed from %.1fMHz to ", ((float)(IPS/100000))/10); if (factor > 1.02) factor = 1.02; else if (factor < 0.98) factor = 0.98; IPS /= factor; if (DEBUG) { System.out.printf("%.1fMHz.\n", ((float) (IPS / 100000)) / 10); System.out.printf("Clock: IPS:%d time:%d next Exp:%d\n", IPS, getEmulatedNanos(), nextExpiry()); System.out.println(printTimerQueue()); } } public long nextExpiry() { Timer tempTimer; tempTimer = timers.peek(); if (tempTimer == null) return Long.MAX_VALUE; return tempTimer.getExpiry(); } public long ticksToNanos(long ticks) { return (long)((double)ticks*1000000000/getIPS()); } public String printTimerQueue() { StringBuilder b = new StringBuilder(); int n = timers.size(); List<Timer> all = new ArrayList<Timer>(n); for (int i=0; i < n; i++) { Timer t = timers.poll(); all.add(t); b.append(String.format("Timer class: %70s expiry %020d\n", t.callback.getClass(), t.getExpiry())); } timers.addAll(all); return b.toString(); } // Only used to force interupts at certain times public void setNextPitExpiry(long ticks) { Timer pit = timers.poll(); PriorityQueue<Timer> tmp = new PriorityQueue<Timer>(timers.size()); while (!(pit.callback instanceof IntervalTimer.TimerChannel)) { tmp.add(pit); if (timers.isEmpty()) throw new IllegalStateException("PIT timer not set!"); pit = timers.poll(); } pit.setExpiry(ticksToNanos(ticks)); timers.addAll(tmp); } }