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:
Generic low level component: nanojit/*
The files in the
nanojit directory define the nanojit component.
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
nanojit/Fragmento.cpp files define the
Fragmento classes. A
Fragmento is a resource-management object that allocates and stores a set of
Pages, and manages their lifecycle. A
Fragmento owns its associated
Fragment represents a single linear code sequence, typically terminating in a jump to another
Fragment or back to the beginning of the
Fragment's code is stored in a set of associated
GuardRecords allocated from the
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.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.
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
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
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.
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.*
jstracer.h contain a the mechanisms of monitoring and recording the activity of the interpreter.
Each spidermonkey JSContext holds a trace monitor of type
The trace monitor maintains some book-keeping information, as well as the collection of recorded
Fragments, held in a hashtable keyed by a the interpreter's program counter and global object shape at the time of recording.
When the trace monitor decides to begin recording the activity of the interpreter, it constructs a new trace recorder, of type