2 JPC-RR: A x86 PC Hardware Emulator
5 Copyright (C) 2007-2009 Isis Innovation Limited
6 Copyright (C) 2009-2010 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 static org
.jpc
.Misc
.errorDialog
;
39 public class EventRecorder
implements TimerResponsive
41 private static final int EVENT_MAGIC_CLASS
= 0;
42 private static final int EVENT_MAGIC_SAVESTATE
= 1;
43 private static final int EVENT_MAGIC_NONE
= -1;
45 public static final int EVENT_TIMED
= 0;
46 public static final int EVENT_STATE_EFFECT_FUTURE
= 1;
47 public static final int EVENT_STATE_EFFECT
= 2;
48 public static final int EVENT_EXECUTE
= 3;
50 public class ReturnEvent
52 public long timestamp
;
53 public String
[] eventData
;
58 public long timestamp
; //Event timestamp (low bound)
59 public long sequenceNumber
; //Sequence number.
60 public int magic
; //Magic type.
61 public Class
<?
extends HardwareComponent
> clazz
; //Dispatch to where.
62 public String
[] args
; //Arguments to dispatch.
63 public Event prev
; //Previous event.
64 public Event next
; //Next event.
66 public void dispatch(PC target
, int level
) throws IOException
68 if(magic
!= EVENT_MAGIC_CLASS
)
69 return; //We really don't want to dispatch these.
71 HardwareComponent hwc
= target
.getComponent(clazz
);
73 throw new IOException("Invalid event target \"" + clazz
.getName() + "\": no component of such type");
74 EventDispatchTarget component
= (EventDispatchTarget
)hwc
;
75 component
.doEvent(timestamp
, args
, level
);
80 private Event current
;
83 private Event firstUndispatched
;
84 private Event lastUndispatched
;
86 private boolean directMode
;
87 private Clock sysClock
;
88 private Timer sysTimer
;
89 private long timerInvokeTime
;
90 private long savestateRerecordCount
;
91 private boolean dirtyFlag
;
92 private long cleanTime
;
93 private long attachTime
;
94 private String
[][] headers
;
95 private long movieRerecordCount
;
97 private boolean isDirty()
99 //sysClock == null should not happen, but include it just for sure.
100 return (dirtyFlag
|| sysClock
== null || cleanTime
!= sysClock
.getTime());
103 private void setClean()
106 cleanTime
= (sysClock
!= null) ? sysClock
.getTime() : -1;
109 public long getAttachTime()
114 public long getRerecordCount()
116 return movieRerecordCount
- savestateRerecordCount
;
119 public void setRerecordCount(long newCount
)
121 movieRerecordCount
= newCount
;
124 public String
[][] getHeaders()
129 public void setHeaders(String
[][] newHeaders
)
131 if(newHeaders
== null) {
136 String
[][] tmp
= new String
[newHeaders
.length
][];
137 for(int i
= 0; i
< tmp
.length
; i
++)
138 if(newHeaders
[i
] != null)
139 tmp
[i
] = Arrays
.copyOf(newHeaders
[i
], newHeaders
[i
].length
);
144 public void setTimer(long time
)
147 sysTimer
.disable(); //No need for timers in direct mode.
150 if((sysTimer
.enabled() && timerInvokeTime
<= time
))
151 return; //No need for timer.
152 sysTimer
.setExpiry(timerInvokeTime
= time
);
155 public void addEvent(long timeLowBound
, Class
<?
extends HardwareComponent
> clazz
,
156 String
[] args
) throws IOException
158 /* Compute the final time for event. */
159 long timeNow
= sysClock
.getTime();
161 long time
= timeLowBound
;
162 if(last
!= null && time
< last
.timestamp
)
163 time
= last
.timestamp
;
167 HardwareComponent hwc
= pc
.getComponent(clazz
);
168 EventDispatchTarget component
= (EventDispatchTarget
)hwc
;
169 long freeLowBound
= -1;
171 freeLowBound
= component
.getEventTimeLowBound(time
, args
);
172 } catch(Exception e
) {}; //Shouldn't throw.
173 if(time
< freeLowBound
)
176 Event ev
= new Event();
178 ev
.magic
= EVENT_MAGIC_CLASS
;
182 if(firstUndispatched
== null)
183 firstUndispatched
= ev
;
184 if(lastUndispatched
!= null)
185 lastUndispatched
.next
= ev
;
186 ev
.prev
= lastUndispatched
;
187 lastUndispatched
= ev
;
191 handleUndispatchedEvents();
193 setTimer(timeNow
); //Fire it as soon as possible.
198 private void handleUndispatchedEvents()
200 //This has toi be outside or we can have ABBA deadlock.
201 long timeNow
= sysClock
.getTime();
203 //Synchronize to prevent racing with addEvent()
205 //First move undispatched events to main queue.
206 Event scan
= firstUndispatched
;
207 while(scan
!= null) {
209 //Compute time for event.
210 Event scanNext
= scan
.next
;
211 if(scan
.timestamp
< timeNow
)
212 scan
.timestamp
= timeNow
;
213 if(last
!= null && scan
.timestamp
< last
.timestamp
)
214 scan
.timestamp
= last
.timestamp
;
216 HardwareComponent hwc
= pc
.getComponent(scan
.clazz
);
217 EventDispatchTarget component
= (EventDispatchTarget
)hwc
;
218 long freeLowBound
= -1;
220 freeLowBound
= component
.getEventTimeLowBound(scan
.timestamp
, scan
.args
);
221 } catch(Exception e
) {}; //Shouldn't throw.
222 if(scan
.timestamp
< freeLowBound
)
223 scan
.timestamp
= freeLowBound
;
226 scan
.dispatch(pc
, EVENT_TIMED
);
227 } catch(Exception e
) {
228 System
.err
.println("Error: Event dispatch failed.");
229 errorDialog(e
, "Failed to dispatch event", null, "Dismiss");
233 //Because of constraints to time, the event must go last.
235 //Sequence number for first event is 0 and then increments by 1 for each event.
236 scan
.sequenceNumber
= ((last
!= null) ?
(last
.sequenceNumber
+ 1) : 0);
249 firstUndispatched
= null;
250 lastUndispatched
= null;
253 //Then fire apporiate events from main queue.
254 while(current
!= null && current
.timestamp
<= timeNow
) {
256 current
.dispatch(pc
, EVENT_EXECUTE
);
257 } catch(Exception e
) {
258 System
.err
.println("Error: Event dispatch failed.");
259 errorDialog(e
, "Failed to dispatch event", null, "Dismiss");
261 current
= current
.next
;
264 setTimer(current
.timestamp
);
267 public synchronized void setPCRunStatus(boolean running
)
269 directMode
= !running
;
271 handleUndispatchedEvents();
273 setTimer(current
.timestamp
);
276 public void truncateEventStream()
278 if(current
!= null) {
280 if(current
.sequenceNumber
<= cache
.sequenceNumber
)
281 cache
= null; //Flush cache as event got truncated out.
290 while(scan
!= null) {
292 scan
.dispatch(pc
, EVENT_STATE_EFFECT
);
293 } catch(Exception e
) {}
298 } catch(Exception e
) {}
300 firstUndispatched
= null;
301 lastUndispatched
= null;
304 private void dispatchStart(PC target
)
306 Set
<HardwareComponent
> pcParts
= target
.allComponents();
307 for(HardwareComponent hwc
: pcParts
) {
308 if(!EventDispatchTarget
.class.isAssignableFrom(hwc
.getClass()))
310 EventDispatchTarget t
= (EventDispatchTarget
)hwc
;
311 t
.setEventRecorder(this);
316 private void dispatchEnd(PC target
) throws IOException
318 Set
<HardwareComponent
> pcParts
= target
.allComponents();
319 for(HardwareComponent hwc
: pcParts
) {
320 if(!EventDispatchTarget
.class.isAssignableFrom(hwc
.getClass()))
322 EventDispatchTarget t
= (EventDispatchTarget
)hwc
;
327 public EventRecorder()
333 firstUndispatched
= null;
334 lastUndispatched
= null;
340 private static boolean isReservedName(String name
)
342 for(int i
= 0; i
< name
.length(); i
++) {
343 char j
= name
.charAt(i
);
344 if(j
>= '0' && j
<= '9')
346 if(j
>= 'A' && j
<= 'Z')
353 public EventRecorder(UTFInputLineStream lines
) throws IOException
355 boolean relativeTime
= false;
356 long lastTimestamp
= 0;
359 String
[] components
= nextParseLine(lines
);
360 while(components
!= null) {
361 Event ev
= new Event();
362 if(components
.length
< 2)
363 throw new IOException("Malformed event line");
367 ev
.timestamp
= timeStamp
= lastTimestamp
= Long
.parseLong(components
[0]) + lastTimestamp
;
369 ev
.timestamp
= timeStamp
= lastTimestamp
= Long
.parseLong(components
[0]);
371 throw new IOException("Negative timestamp value " + timeStamp
+ " not allowed");
372 } catch(NumberFormatException e
) {
373 throw new IOException("Invalid timestamp value \"" + components
[0] + "\"");
375 if(last
!= null && timeStamp
< last
.timestamp
)
376 throw new IOException("Timestamp order violation: " + timeStamp
+ "<" + last
.timestamp
);
377 String clazzName
= components
[1];
378 if(clazzName
.equals("SAVESTATE")) {
379 if(components
.length
< 3 || components
.length
> 4)
380 throw new IOException("Malformed SAVESTATE line");
381 ev
.magic
= EVENT_MAGIC_SAVESTATE
;
383 if(components
.length
== 3)
384 ev
.args
= new String
[]{components
[2], "0"};
386 ev
.args
= new String
[]{components
[2], components
[3]};
387 } else if(clazzName
.equals("OPTION")) {
388 if(components
.length
!= 3)
389 throw new IOException("Malformed OPTION line");
390 if("RELATIVE".equals(components
[2]))
392 else if("ABSOLUTE".equals(components
[2]))
393 relativeTime
= false;
395 throw new IOException("Unknown OPTION: '" + components
[2] + "'");
396 ev
.magic
= EVENT_MAGIC_NONE
;
397 } else if(isReservedName(clazzName
)) {
398 throw new IOException("Unknown special event type: '" + clazzName
+ "'");
400 //Something dispatchable.
401 ev
.magic
= EVENT_MAGIC_CLASS
;
404 clazz
= Class
.forName(clazzName
);
405 if(!EventDispatchTarget
.class.isAssignableFrom(clazz
))
406 throw new Exception("bad class");
407 if(!HardwareComponent
.class.isAssignableFrom(clazz
))
408 throw new Exception("bad class");
409 } catch(Exception e
) {
410 throw new IOException("\"" + clazzName
+ "\" is not valid event target");
412 ev
.clazz
= clazz
.asSubclass(HardwareComponent
.class);
413 if(components
.length
== 2)
416 ev
.args
= new String
[components
.length
- 2];
417 System
.arraycopy(components
, 2, ev
.args
, 0, ev
.args
.length
);
421 if(ev
.magic
!= EVENT_MAGIC_NONE
) {
423 //Sequence number for first event is 0 and then increments by 1 for each event.
424 ev
.sequenceNumber
= ((last
!= null) ?
(last
.sequenceNumber
+ 1) : 0);
431 components
= nextParseLine(lines
);
434 firstUndispatched
= null;
435 lastUndispatched
= null;
439 private void iterateIncrementSequence(Event from
)
441 while(from
!= null) {
442 from
.sequenceNumber
++;
447 public void markSave(String id
, long rerecords
) throws IOException
450 rerecords
= savestateRerecordCount
;
452 savestateRerecordCount
= rerecords
;
453 /* Current is next event to dispatch. So add it before it. Null means add to
455 Event ev
= new Event();
456 ev
.timestamp
= sysClock
.getTime();
457 ev
.magic
= EVENT_MAGIC_SAVESTATE
;
459 ev
.args
= new String
[]{id
, (new Long(rerecords
)).toString()};
461 if(current
!= null) {
462 //Give it current's sequence number and increment the rest of sequence numbers.
463 ev
.sequenceNumber
= current
.sequenceNumber
;
464 iterateIncrementSequence(current
);
465 ev
.prev
= current
.prev
;
471 //Sequence number for first event is 0 and then increments by 1 for each event.
472 ev
.sequenceNumber
= ((last
!= null) ?
(last
.sequenceNumber
+ 1) : 0);
478 if(ev
.prev
== null) {
484 public void attach(PC aPC
, String id
) throws IOException
486 Event oldCurrent
= current
;
488 Clock newSysClock
= (Clock
)aPC
.getComponent(Clock
.class);
489 long expectedTime
= newSysClock
.getTime();
490 long rerecordCount
= 0;
495 while(scan
!= null) {
496 if(scan
.magic
== EVENT_MAGIC_SAVESTATE
&& scan
.args
[0].equals(id
)) {
498 if(scan
.args
.length
> 1)
499 rerecordCount
= Long
.parseLong(scan
.args
[1]);
500 if(rerecordCount
< 0)
501 throw new NumberFormatException("Negative rerecord count not allowed");
502 } catch(NumberFormatException e
) {
503 throw new IOException("Savestate rerecord count invalid");
510 throw new IOException("Savestate not compatible with event stream");
512 if(scan
.timestamp
!= expectedTime
)
513 throw new IOException("Incorrect savestate event timestamp");
521 boolean future
= false;
522 while(scan
!= null) {
526 scan
.dispatch(aPC
, EVENT_STATE_EFFECT_FUTURE
);
528 scan
.dispatch(aPC
, EVENT_STATE_EFFECT
);
532 } catch(IOException e
) {
533 //Back off the changes.
534 current
= oldCurrent
;
539 sysClock
= newSysClock
;
540 sysTimer
= sysClock
.newTimer(this);
541 directMode
= true; //Assume direct mode on attach.
542 timerInvokeTime
= -1; //No wait in progress.
543 savestateRerecordCount
= rerecordCount
;
544 attachTime
= expectedTime
;
547 handleUndispatchedEvents(); //Do the events that occur after and simultaneously with savestate.
550 public void saveEvents(UTFOutputLineStream lines
) throws IOException
553 long lastTimestamp
= 0;
554 lines
.encodeLine("0", "OPTION", "RELATIVE");
555 while(scan
!= null) {
556 if(scan
.magic
== EVENT_MAGIC_SAVESTATE
) {
557 lines
.encodeLine(scan
.timestamp
- lastTimestamp
, "SAVESTATE", scan
.args
[0], scan
.args
[1]);
559 int extra
= (scan
.args
!= null) ? scan
.args
.length
: 0;
560 Object
[] arr
= new Object
[2 + extra
];
561 arr
[0] = new Long(scan
.timestamp
- lastTimestamp
);
562 arr
[1] = scan
.clazz
.getName();
564 System
.arraycopy(scan
.args
, 0, arr
, 2, extra
);
565 lines
.encodeLine(arr
);
567 lastTimestamp
= scan
.timestamp
;
572 public long getLastEventTime()
575 long lastTimestamp
= 0;
576 while(scan
!= null) {
577 if(scan
.magic
== EVENT_MAGIC_CLASS
)
578 lastTimestamp
= scan
.timestamp
;
581 return lastTimestamp
;
584 public boolean isAtMovieEnd()
586 return (current
== null);
589 public synchronized long getEventCount()
591 return (last
!= null) ?
(last
.sequenceNumber
+ 1) : 0;
594 public synchronized long getEventCurrentSequence()
596 return (current
!= null) ? current
.sequenceNumber
: -1;
599 private String
getReturnClass(Event ev
)
601 if(ev
.magic
== EVENT_MAGIC_CLASS
)
602 return ev
.clazz
.getName();
603 else if(ev
.magic
== EVENT_MAGIC_SAVESTATE
)
606 return "<BAD EVENT TYPE>";
609 private ReturnEvent
convertToReturn(Event ev
)
611 ReturnEvent evr
= new ReturnEvent();
612 evr
.timestamp
= ev
.timestamp
;
614 evr
.eventData
= new String
[1];
616 evr
.eventData
= new String
[1 + ev
.args
.length
];
617 System
.arraycopy(ev
.args
, 0, evr
.eventData
, 1, ev
.args
.length
);
619 evr
.eventData
[0] = getReturnClass(ev
);
623 private int sMinFour(long a
, long b
, long c
, long d
)
625 //Compute maximum, ignoring -'s.
627 max
= (b
> max
) ? b
: max
;
628 max
= (c
> max
) ? c
: max
;
629 max
= (d
> max
) ? d
: max
;
633 //Now use max to get rid of -'s.
634 a
= (a
>= 0) ? a
: (max
+ 1);
635 b
= (b
>= 0) ? b
: (max
+ 1);
636 c
= (c
>= 0) ? c
: (max
+ 1);
637 d
= (d
>= 0) ? d
: (max
+ 1);
656 public synchronized ReturnEvent
getEventBySequence(long sequence
)
658 if(sequence
< 0 || last
== null || sequence
> last
.sequenceNumber
)
663 long distCurrent
= -1;
666 distFirst
= Math
.abs(first
.sequenceNumber
- sequence
);
667 distLast
= Math
.abs(first
.sequenceNumber
- sequence
);
669 distCurrent
= Math
.abs(current
.sequenceNumber
- sequence
);
671 distCache
= Math
.abs(cache
.sequenceNumber
- sequence
);
673 //Find the nearest entrypoint.
674 switch(sMinFour(distFirst
, distLast
, distCurrent
, distCache
)) {
688 return null; //Can't happen.
691 while(sequence
< cache
.sequenceNumber
)
693 while(sequence
> cache
.sequenceNumber
)
695 return convertToReturn(cache
);
698 public void callback()
700 handleUndispatchedEvents();
703 public int getTimerType()
708 public void dumpStatus(StatusDumper output
)
712 public void dumpSRPartial(SRDumper output
)