2 JPC-RR: A x86 PC Hardware Emulator
5 Copyright (C) 2007-2009 Isis Innovation Limited
6 Copyright (C) 2009-2011 H. Ilari Liusvaara
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License version 2 as published by
10 the Free Software Foundation.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License along
18 with this program; if not, write to the Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 Based on JPC x86 PC Hardware emulator,
22 A project from the Physics Dept, The University of Oxford
24 Details about original JPC can be found at:
26 www-jpc.physics.ox.ac.uk
30 package org
.jpc
.emulator
;
32 import org
.jpc
.jrsr
.UTFInputLineStream
;
33 import org
.jpc
.jrsr
.UTFOutputLineStream
;
34 import static org
.jpc
.Misc
.nextParseLine
;
37 import java
.math
.BigInteger
;
38 import static org
.jpc
.Misc
.errorDialog
;
40 public class EventRecorder
implements TimerResponsive
42 private static final int EVENT_MAGIC_CLASS
= 0;
43 private static final int EVENT_MAGIC_SAVESTATE
= 1;
44 private static final int EVENT_MAGIC_NONE
= -1;
46 public static final int EVENT_TIMED
= 0;
47 public static final int EVENT_STATE_EFFECT_FUTURE
= 1;
48 public static final int EVENT_STATE_EFFECT
= 2;
49 public static final int EVENT_EXECUTE
= 3;
51 public class ReturnEvent
53 public long timestamp
;
54 public String
[] eventData
;
59 public long timestamp
; //Event timestamp (low bound)
60 public long sequenceNumber
; //Sequence number.
61 public int magic
; //Magic type.
62 public Class
<?
extends HardwareComponent
> clazz
; //Dispatch to where.
63 public String
[] args
; //Arguments to dispatch.
64 public Event prev
; //Previous event.
65 public Event next
; //Next event.
67 public void dispatch(PC target
, int level
) throws IOException
69 if(magic
!= EVENT_MAGIC_CLASS
)
70 return; //We really don't want to dispatch these.
72 HardwareComponent hwc
= target
.getComponent(clazz
);
74 throw new IOException("Invalid event target \"" + clazz
.getName() + "\": no component of such type");
75 EventDispatchTarget component
= (EventDispatchTarget
)hwc
;
76 component
.doEvent(timestamp
, args
, level
);
81 private Event current
;
84 private Event firstUndispatched
;
85 private Event lastUndispatched
;
87 private boolean directMode
;
88 private Clock sysClock
;
89 private Timer sysTimer
;
90 private long timerInvokeTime
;
91 private BigInteger savestateRerecordCount
;
92 private boolean dirtyFlag
;
93 private long cleanTime
;
94 private long attachTime
;
95 private String
[][] headers
;
96 private BigInteger movieRerecordCount
;
97 private String projectID
;
99 public String
getProjectID()
104 public void setProjectID(String proj
)
109 private boolean isDirty()
111 //sysClock == null should not happen, but include it just for sure.
112 return (dirtyFlag
|| sysClock
== null || cleanTime
!= sysClock
.getTime());
115 private void setClean()
118 cleanTime
= (sysClock
!= null) ? sysClock
.getTime() : -1;
121 public long getAttachTime()
126 public BigInteger
getRerecordCount()
128 return movieRerecordCount
.subtract(savestateRerecordCount
);
131 public void setRerecordCount(BigInteger newCount
)
133 movieRerecordCount
= newCount
;
136 public String
[][] getHeaders()
141 public void setHeaders(String
[][] newHeaders
)
143 if(newHeaders
== null) {
148 String
[][] tmp
= new String
[newHeaders
.length
][];
149 for(int i
= 0; i
< tmp
.length
; i
++)
150 if(newHeaders
[i
] != null)
151 tmp
[i
] = Arrays
.copyOf(newHeaders
[i
], newHeaders
[i
].length
);
156 public void setTimer(long time
)
159 sysTimer
.disable(); //No need for timers in direct mode.
162 if((sysTimer
.enabled() && timerInvokeTime
<= time
))
163 return; //No need for timer.
164 sysTimer
.setExpiry(timerInvokeTime
= time
);
167 public void addEvent(long timeLowBound
, Class
<?
extends HardwareComponent
> clazz
,
168 String
[] args
) throws IOException
170 /* Compute the final time for event. */
171 long timeNow
= sysClock
.getTime();
173 long time
= timeLowBound
;
174 if(last
!= null && time
< last
.timestamp
)
175 time
= last
.timestamp
;
179 HardwareComponent hwc
= pc
.getComponent(clazz
);
180 EventDispatchTarget component
= (EventDispatchTarget
)hwc
;
181 long freeLowBound
= -1;
183 freeLowBound
= component
.getEventTimeLowBound(time
, args
);
184 } catch(Exception e
) {}; //Shouldn't throw.
185 if(time
< freeLowBound
)
188 Event ev
= new Event();
190 ev
.magic
= EVENT_MAGIC_CLASS
;
194 if(firstUndispatched
== null)
195 firstUndispatched
= ev
;
196 if(lastUndispatched
!= null)
197 lastUndispatched
.next
= ev
;
198 ev
.prev
= lastUndispatched
;
199 lastUndispatched
= ev
;
203 handleUndispatchedEvents();
205 setTimer(timeNow
); //Fire it as soon as possible.
210 private void handleUndispatchedEvents()
212 //This has toi be outside or we can have ABBA deadlock.
213 long timeNow
= sysClock
.getTime();
215 //Synchronize to prevent racing with addEvent()
217 //First move undispatched events to main queue.
218 Event scan
= firstUndispatched
;
219 while(scan
!= null) {
221 //Compute time for event.
222 Event scanNext
= scan
.next
;
223 if(scan
.timestamp
< timeNow
)
224 scan
.timestamp
= timeNow
;
225 if(last
!= null && scan
.timestamp
< last
.timestamp
)
226 scan
.timestamp
= last
.timestamp
;
228 HardwareComponent hwc
= pc
.getComponent(scan
.clazz
);
229 EventDispatchTarget component
= (EventDispatchTarget
)hwc
;
230 long freeLowBound
= -1;
232 freeLowBound
= component
.getEventTimeLowBound(scan
.timestamp
, scan
.args
);
233 } catch(Exception e
) {}; //Shouldn't throw.
234 if(scan
.timestamp
< freeLowBound
)
235 scan
.timestamp
= freeLowBound
;
238 scan
.dispatch(pc
, EVENT_TIMED
);
239 } catch(Exception e
) {
240 System
.err
.println("Error: Event dispatch failed.");
241 errorDialog(e
, "Failed to dispatch event", null, "Dismiss");
245 //Because of constraints to time, the event must go last.
247 //Sequence number for first event is 0 and then increments by 1 for each event.
248 scan
.sequenceNumber
= ((last
!= null) ?
(last
.sequenceNumber
+ 1) : 0);
261 firstUndispatched
= null;
262 lastUndispatched
= null;
265 //Then fire apporiate events from main queue.
266 while(current
!= null && current
.timestamp
<= timeNow
) {
268 current
.dispatch(pc
, EVENT_EXECUTE
);
269 } catch(Exception e
) {
270 System
.err
.println("Error: Event dispatch failed.");
271 errorDialog(e
, "Failed to dispatch event", null, "Dismiss");
273 current
= current
.next
;
276 setTimer(current
.timestamp
);
279 public synchronized void setPCRunStatus(boolean running
)
281 directMode
= !running
;
283 handleUndispatchedEvents();
285 setTimer(current
.timestamp
);
288 public void truncateEventStream()
290 if(current
!= null) {
292 if(cache
!= null && current
.sequenceNumber
<= cache
.sequenceNumber
)
293 cache
= null; //Flush cache as event got truncated out.
302 while(scan
!= null) {
304 scan
.dispatch(pc
, EVENT_STATE_EFFECT
);
305 } catch(Exception e
) {}
310 } catch(Exception e
) {}
312 firstUndispatched
= null;
313 lastUndispatched
= null;
316 private void dispatchStart(PC target
)
318 Set
<HardwareComponent
> pcParts
= target
.allComponents();
319 for(HardwareComponent hwc
: pcParts
) {
320 if(!EventDispatchTarget
.class.isAssignableFrom(hwc
.getClass()))
322 EventDispatchTarget t
= (EventDispatchTarget
)hwc
;
323 t
.setEventRecorder(this);
328 private void dispatchEnd(PC target
) throws IOException
330 Set
<HardwareComponent
> pcParts
= target
.allComponents();
331 for(HardwareComponent hwc
: pcParts
) {
332 if(!EventDispatchTarget
.class.isAssignableFrom(hwc
.getClass()))
334 EventDispatchTarget t
= (EventDispatchTarget
)hwc
;
339 public EventRecorder()
345 firstUndispatched
= null;
346 lastUndispatched
= null;
350 savestateRerecordCount
= new BigInteger("0");
351 movieRerecordCount
= new BigInteger("0");
354 private static boolean isReservedName(String name
)
356 for(int i
= 0; i
< name
.length(); i
++) {
357 char j
= name
.charAt(i
);
358 if(j
>= '0' && j
<= '9')
360 if(j
>= 'A' && j
<= 'Z')
367 public EventRecorder(UTFInputLineStream lines
) throws IOException
369 boolean relativeTime
= false;
370 long lastTimestamp
= 0;
373 String
[] components
= nextParseLine(lines
);
374 while(components
!= null) {
375 Event ev
= new Event();
376 if(components
.length
< 2)
377 throw new IOException("Malformed event line");
381 ev
.timestamp
= timeStamp
= lastTimestamp
= Long
.parseLong(components
[0]) + lastTimestamp
;
383 ev
.timestamp
= timeStamp
= lastTimestamp
= Long
.parseLong(components
[0]);
385 throw new IOException("Negative timestamp value " + timeStamp
+ " not allowed");
386 } catch(NumberFormatException e
) {
387 throw new IOException("Invalid timestamp value \"" + components
[0] + "\"");
389 if(last
!= null && timeStamp
< last
.timestamp
)
390 throw new IOException("Timestamp order violation: " + timeStamp
+ "<" + last
.timestamp
);
391 String clazzName
= components
[1];
392 if(clazzName
.equals("SAVESTATE")) {
393 if(components
.length
< 3 || components
.length
> 4)
394 throw new IOException("Malformed SAVESTATE line");
395 ev
.magic
= EVENT_MAGIC_SAVESTATE
;
397 if(components
.length
== 3)
398 ev
.args
= new String
[]{components
[2], "0"};
400 ev
.args
= new String
[]{components
[2], components
[3]};
401 } else if(clazzName
.equals("OPTION")) {
402 if(components
.length
!= 3)
403 throw new IOException("Malformed OPTION line");
404 if("RELATIVE".equals(components
[2]))
406 else if("ABSOLUTE".equals(components
[2]))
407 relativeTime
= false;
409 throw new IOException("Unknown OPTION: '" + components
[2] + "'");
410 ev
.magic
= EVENT_MAGIC_NONE
;
411 } else if(isReservedName(clazzName
)) {
412 throw new IOException("Unknown special event type: '" + clazzName
+ "'");
414 //Something dispatchable.
415 ev
.magic
= EVENT_MAGIC_CLASS
;
418 clazz
= Class
.forName(clazzName
);
419 if(!EventDispatchTarget
.class.isAssignableFrom(clazz
))
420 throw new Exception("bad class");
421 if(!HardwareComponent
.class.isAssignableFrom(clazz
))
422 throw new Exception("bad class");
423 } catch(Exception e
) {
424 throw new IOException("\"" + clazzName
+ "\" is not valid event target");
426 ev
.clazz
= clazz
.asSubclass(HardwareComponent
.class);
427 if(components
.length
== 2)
430 ev
.args
= new String
[components
.length
- 2];
431 System
.arraycopy(components
, 2, ev
.args
, 0, ev
.args
.length
);
435 if(ev
.magic
!= EVENT_MAGIC_NONE
) {
437 //Sequence number for first event is 0 and then increments by 1 for each event.
438 ev
.sequenceNumber
= ((last
!= null) ?
(last
.sequenceNumber
+ 1) : 0);
445 components
= nextParseLine(lines
);
448 firstUndispatched
= null;
449 lastUndispatched
= null;
453 private void iterateIncrementSequence(Event from
)
455 while(from
!= null) {
456 from
.sequenceNumber
++;
461 public void markSave(String id
, BigInteger rerecords
) throws IOException
464 rerecords
= savestateRerecordCount
;
466 savestateRerecordCount
= rerecords
;
467 /* Current is next event to dispatch. So add it before it. Null means add to
469 Event ev
= new Event();
470 ev
.timestamp
= sysClock
.getTime();
471 ev
.magic
= EVENT_MAGIC_SAVESTATE
;
473 ev
.args
= new String
[]{id
, rerecords
.toString()};
475 if(current
!= null) {
476 //Give it current's sequence number and increment the rest of sequence numbers.
477 ev
.sequenceNumber
= current
.sequenceNumber
;
478 iterateIncrementSequence(current
);
479 ev
.prev
= current
.prev
;
485 //Sequence number for first event is 0 and then increments by 1 for each event.
486 ev
.sequenceNumber
= ((last
!= null) ?
(last
.sequenceNumber
+ 1) : 0);
492 if(ev
.prev
== null) {
498 public void attach(PC aPC
, String id
) throws IOException
500 Event oldCurrent
= current
;
502 Clock newSysClock
= (Clock
)aPC
.getComponent(Clock
.class);
503 long expectedTime
= newSysClock
.getTime();
504 BigInteger rerecordCount
= new BigInteger("0");
509 while(scan
!= null) {
510 if(scan
.magic
== EVENT_MAGIC_SAVESTATE
&& scan
.args
[0].equals(id
)) {
512 if(scan
.args
.length
> 1) {
513 rerecordCount
= new BigInteger(scan
.args
[1]);
514 if(rerecordCount
.signum() < 0)
515 throw new NumberFormatException("Negative rerecord count not allowed");
517 } catch(NumberFormatException e
) {
518 throw new IOException("Savestate rerecord count invalid");
525 throw new IOException("Savestate not compatible with event stream");
527 if(scan
.timestamp
!= expectedTime
)
528 throw new IOException("Incorrect savestate event timestamp");
536 boolean future
= false;
537 while(scan
!= null) {
541 scan
.dispatch(aPC
, EVENT_STATE_EFFECT_FUTURE
);
543 scan
.dispatch(aPC
, EVENT_STATE_EFFECT
);
547 } catch(IOException e
) {
548 //Back off the changes.
549 current
= oldCurrent
;
554 sysClock
= newSysClock
;
555 sysTimer
= sysClock
.newTimer(this);
556 directMode
= true; //Assume direct mode on attach.
557 timerInvokeTime
= -1; //No wait in progress.
558 savestateRerecordCount
= rerecordCount
;
559 attachTime
= expectedTime
;
562 handleUndispatchedEvents(); //Do the events that occur after and simultaneously with savestate.
565 public void saveEvents(UTFOutputLineStream lines
) throws IOException
568 long lastTimestamp
= 0;
569 lines
.encodeLine("0", "OPTION", "RELATIVE");
570 while(scan
!= null) {
571 if(scan
.magic
== EVENT_MAGIC_SAVESTATE
) {
572 lines
.encodeLine(scan
.timestamp
- lastTimestamp
, "SAVESTATE", scan
.args
[0], scan
.args
[1]);
574 int extra
= (scan
.args
!= null) ? scan
.args
.length
: 0;
575 Object
[] arr
= new Object
[2 + extra
];
576 arr
[0] = new Long(scan
.timestamp
- lastTimestamp
);
577 arr
[1] = scan
.clazz
.getName();
579 System
.arraycopy(scan
.args
, 0, arr
, 2, extra
);
580 lines
.encodeLine(arr
);
582 lastTimestamp
= scan
.timestamp
;
587 public long getLastEventTime()
590 long lastTimestamp
= 0;
591 while(scan
!= null) {
592 if(scan
.magic
== EVENT_MAGIC_CLASS
)
593 lastTimestamp
= scan
.timestamp
;
596 return lastTimestamp
;
599 public boolean isAtMovieEnd()
601 return (current
== null);
604 public synchronized long getEventCount()
606 return (last
!= null) ?
(last
.sequenceNumber
+ 1) : 0;
609 public synchronized long getEventCurrentSequence()
611 return (current
!= null) ? current
.sequenceNumber
: -1;
614 private String
getReturnClass(Event ev
)
616 if(ev
.magic
== EVENT_MAGIC_CLASS
)
617 return ev
.clazz
.getName();
618 else if(ev
.magic
== EVENT_MAGIC_SAVESTATE
)
621 return "<BAD EVENT TYPE>";
624 private ReturnEvent
convertToReturn(Event ev
)
626 ReturnEvent evr
= new ReturnEvent();
627 evr
.timestamp
= ev
.timestamp
;
629 evr
.eventData
= new String
[1];
631 evr
.eventData
= new String
[1 + ev
.args
.length
];
632 System
.arraycopy(ev
.args
, 0, evr
.eventData
, 1, ev
.args
.length
);
634 evr
.eventData
[0] = getReturnClass(ev
);
638 private int sMinFour(long a
, long b
, long c
, long d
)
640 //Compute maximum, ignoring -'s.
642 max
= (b
> max
) ? b
: max
;
643 max
= (c
> max
) ? c
: max
;
644 max
= (d
> max
) ? d
: max
;
648 //Now use max to get rid of -'s.
649 a
= (a
>= 0) ? a
: (max
+ 1);
650 b
= (b
>= 0) ? b
: (max
+ 1);
651 c
= (c
>= 0) ? c
: (max
+ 1);
652 d
= (d
>= 0) ? d
: (max
+ 1);
671 public synchronized ReturnEvent
getEventBySequence(long sequence
)
673 if(sequence
< 0 || last
== null || sequence
> last
.sequenceNumber
)
678 long distCurrent
= -1;
682 distFirst
= Math
.abs(first
.sequenceNumber
- sequence
);
684 distLast
= Math
.abs(last
.sequenceNumber
- sequence
);
686 distCurrent
= Math
.abs(current
.sequenceNumber
- sequence
);
688 distCache
= Math
.abs(cache
.sequenceNumber
- sequence
);
690 int minF
= sMinFour(distFirst
, distLast
, distCurrent
, distCache
);
692 //Find the nearest entrypoint.
707 return null; //Can't happen.
710 while(cache
!= null && sequence
< cache
.sequenceNumber
)
712 while(cache
!= null && sequence
> cache
.sequenceNumber
)
716 return convertToReturn(cache
);
719 public void callback()
721 handleUndispatchedEvents();
724 public int getTimerType()
729 public void dumpStatus(StatusDumper output
)
733 public void dumpSRPartial(SRDumper output
)