This document is talking about deprecated feature.

The tracing JIT in SpiderMonkey consists of a generic, low level component called nanojit which is co-maintained between Adobe and Mozilla, and a SpiderMonkey-specific high level component called jstracer. The nanojit component is language agnostic, and contains no knowledge about SpiderMonkey or any other part of the Mozilla codebase. The jstracer component consists of a monitor and a recorder. The monitor watches the executing SpiderMonkey interpreter. When the monitor determines that the interpreter has entered a region of code that would benefit from native compilation, the monitor activates the recorder. The recorder records the activity of the interpreter, using nanojit to build an efficient, native representation of the execution called a fragment. The monitor then calls into the native code stored in the fragment.

A schematic diagram of the components of the tracing JIT follows:

 TraceMonkey.png

Generic low level component: nanojit/*

The files in the nanojit directory define the nanojit component.

nanojit/LIR.*

The nanojit/LIR.cpp and nanojit/LIR.h files define the intermediate representation LIR, which is used as input to nanojit. LIR is a conventional three-address, linear SSA code. A single instruction of LIR is called a LIns, short for "LIR instruction". LIns values are depicted in blue in the schematic diagram. In contrast, a single native instruction is called a NIns, and is depicted in red in the schematic diagram.

The recorder in jstracer inserts LIns values into a LIR buffer held in a page, itself contained within a logical fragment, and the nanojit compilation pipeline and Assembler transform the LIns values into NIns values.

nanojit/Fragmento.*

The nanojit/Fragment.cpp and nanojit/Fragmento.cpp files define the Fragment and Fragmento classes. A Fragmento is a resource-management object that allocates and stores a set of Fragments and Pages, and manages their lifecycle. A Fragmento owns its associated Fragments.

A Fragment represents a single linear code sequence, typically terminating in a jump to another Fragment or back to the beginning of the Fragment. A Fragment's code is stored in a set of associated Pages and GuardRecords allocated from the Fragment's owning Fragmento. A Fragment initially holds no pages. As the compilation pipeline inserts LIR instructions (LIns values) into the Fragment, it allocates Pages to store the LIR code. Later, when the Fragment is assembled, it will allocate Pages for the native code (NIns values) produced by the Assembler. When the Fragment is destroyed, it returns its Pages to the Fragmento for reuse.

nanojit/Assembler.*

The nanojit/Assembler.cpp and nanojit/Assembler.h files define the class Assembler, which transforms LIns values into NIns values. In other words, an Assembler transforms LIR code into native code. An Assembler is also able to modify existing fragments of native code, by rewriting native jump instructions to jump to new locations. In this way the Assembler can "patch together" multiple fragments, so that program control can flow from one fragment into another, or back out of generated code and into the interpreter.

An Assembler is the only component of the tracing JIT that reads or writes native code. Therefore an Assembler contains several machine-specific methods which are implemented in the accompanying nanojit/Native*.* files.

An Assembler runs in a single pass over its input, transforming one LIns value to zero or more NIns values. It is important to keep in mind that this pass runs backwards from the last LIns in the input LIR code to the first, generating native code in reverse. Running backwards sometimes makes the logic difficult to follow, but it is an essential factor in maintaining the Assembler's high speed and small size.

In the SpiderMonkey tracing JIT there is only ever one Assembler active at a time, associated with the currently active Fragmento.

nanojit/RegAlloc.*

Nanojit's register allocator. This is a local register allocator, meaning that it does not allocate registers across basic blocks. It is correspondingly very simple: register allocation is done immediately, step-by-step as code is being generated. A running tally is kept of assignments between registers and LIR operands, and any time a new LIR operand is required in a register a new one is assigned from the list of free registers. When all registers are in use, the least-often-used register currently in use is spilled.

nanojit/Native*.*

The files Nativei386.h, Nativei386.cpp, NativeARM.h, NativeARM.cpp, etc. each define architecture-specific methods within the Assembler class. Only one architecture-specific variant is included into any given build of the Assembler; the architecture is selected and fixed when the build is configured.

The architecture-specific methods found in these files are the only functions within nanojit or TraceMonkey that emit raw bytes of machine-code into memory.

SpiderMonkey-specific high level component: jstracer.*

The files jstracer.cpp and jstracer.h contain a the mechanisms of monitoring and recording the activity of the interpreter.

Each SpiderMonkey JSContext holds a trace monitor of type JSTraceMonitor.

The trace monitor maintains some book-keeping information, as well as the collection of recorded Fragments, held in a hashtable keyed by the interpreter's program counter and global object shape at the time of recording.

At any moment, the trace monitor is in one of three major states: monitoring, recording, or executing. The transitions between these states are somewhat complex and delicate, but the overall picture is simple:

TraceStates.png

Monitoring

The trace monitor's initial state is monitoring. This means that SpiderMonkey is interpreting bytecode. Every timeSpiderMonkey interprets a backward-jump bytecode, the monitor makes note of the number of times the jump-target program-counter (PC) value has been jumped-to. This number is called the hit count for the PC. If the hit count of a particular PC reaches a threshold value, the target is considered hot.

When the monitor decides a target PC is hot, it looks in a hashtable of fragments to see if there is a fragment holding native code for that target PC. If it finds such a fragment, it transitions to executing mode. Otherwise it transitions to recording mode.

Recording

When the trace monitor enters recording mode, it constructs a new trace recorder, of type TraceRecorder. It then passes control back to the SpiderMonkey interpreter. The interpreter continues interpreting bytecode, but with an important difference: each bytecode it interprets is also sent to the trace monitor and recorded in the monitor's trace recorder.

Recording mode is temporary -- ideally it only lasts as long as it takes to record a single iteration of a loop in the interpreter -- and can end in a number of ways. All transitions out of recording mode eventually involve returning to monitoring mode:

Executing

When the monitor interprets a backward jump to a PC that it has a compiled trace of machine code for, the monitor enters executing mode. This mode is relatively simple compared to recording mode.

Some other terminology may be helpful when reading the code: