The simulator does not actually execute the program, nor translate code, but rather proceeds through the behavior trace file updating state information. Standard discrete, event-based simulation techniques are used.
Figure 4.4 describes the basic operation of the simulator in pseudocode.
Figure 4.4: Simulator Pseudocode
The Interpreter and Compiler modules are modeled as separate object, each with its own event-list. The event-list for the Interpreter object is derived from the behavior trace file. Each function entry and exit is an Interpreter event.
A Compiler event is the completion of compilation of a file. For two of the compilation strategies--most-frequently-executed-so-far and longest-so-far--the entire Compiler event-list is not known at any given time. However, the event at the head of the list is always known.
Similarly, the entire Interpreter event-list is not know at any given time. It is too dependent on when functions are compiled for it to be determined in advance. However, the event at the head of the list can again be calculated at any given time.
A shared data structure (the ``Monitor'' in Figure 3.1) contains the compilation status of each function, i.e., whether the function is available in native code or not. Performing a Compiler event (the Do event C line in Figure 4.4) simply consists of updating this data structure by changing the status of the appropriate functions to ``compiled.'' For the most-frequently-executed-so-far and longest-so-far compilation strategies, the next file to compile is chosen at this time and the event is placed in the Compiler event-list.
Performing an Interpreter event (the Do event I line Figure 4.4) consists of updating the statistics being kept on each function. These statistics consist of the number of calls made to each function, the amount of time spent interpreting each function, and the amount of time spent executing the native-code version of each function.
In order to charge each function properly, the Interpreter must keep a stack of function activation records. Whenever a function entry event occurs, the function and its current compilation status are pushed on the stack. When a function exit event occurs, the corresponding function activation record is popped from the stack.
The replace-preemptive strategy complicates our story. In order to keep a proper accounting of time spent in interpretation and time spent in execution of native code, the Interpreter object must be informed whenever a Compiler event occurs.