LIR after the NanoJIT merge
Sunday, October 4, 2009 When TraceMonkey was born, the team forked the NanoJIT backend from Tamarin. However, over the summer, the TraceMonkey and Tamarin teams wanted to merge their changes back into a shared repository. The intermediate representation (LIR) changed a bit. What's it look like now?
Here is what a basic LIR instruction looks like:
class LIns
{
union {
Reservation lastWord;
// force sizeof(LIns)==8 and 8-byte alignment on 64-bit machines.
// this is necessary because sizeof(Reservation)==4 and we want all
// instances of LIns to be pointer-aligned.
void* dummy;
};
}
What about Reservation?
// The opcode is not logically part of the Reservation, but we include it
// in this struct to ensure that opcode plus the Reservation fits in a
// single word.
struct Reservation
{
uint32_t arIndex:16; // index into stack frame. displ is -4*arIndex
Register reg:7; // register UnknownReg implies not in register
uint32_t used:1; // when set, the reservation is active
LOpcode opcode:8;
}
A LIR instruction is a padding around a 32 bit Reservation which contains the opcode. But where are the operands to an instruction?
This is the biggest difference after the merge, at least from Tamarin's perspective. Prior to the nanoJIT merge, LIR instructions were inserted into a contiguous chunk of memory. Each LIR instruction was one 32 bit word. The top 8 bits were reserved for the opcode while the lower 24 bits were used as operands. Each operand was represented as an 8 bit offset from the point in memory. The actual LIR instruction structure had no notion of pointers.
Now LIR instructions directly point to their operands. But where are the operands?. There are multiple LIR instruction types depending on the number of operands an instruction requires. For example, return instructions only need to point to the value they are returning. NanoJIT has a LInspOp1 class for instructions that have only one operand:
// 1-operand form. Used for LIR_ret, unary arithmetic/logic ops,
class LInsOp1
{
friend class LIns;
LIns* oprnd_1;
LIns ins;
}
"Ins" here points to "this" instruction. NanoJIT also has LInsOp2 for instructions with two operands:
// 2-operand form. Used for loads, guards, branches, comparisons, binary
class LInsOp2
{
LIns* oprnd_2;
LIns* oprnd_1;
LIns ins;
};
And one last one for instructions that have three operands. This means LIR instructions are variable length and can be up to 4 words in length. Now what about constant values such as the number 6? NanoJIT has a few other specialized LIR instructions such as LInsI:
// Used for LIR_int and LIR_ialloc.
class LInsI
{
int32_t imm32;
LIns ins;
};
If you want to get into the gritty details, checkout the NanoJIT merge MDC article. If you want to see how it is all implemented, checkout LIR.h on the mercurial repository on line 210 for a nice comment.

Reader Comments (4)
Mason, any idea on how the v8,nitro and tm engines compare re performance currently?
Hi Ian, I haven't done any benchmarks recently. Sunspider runs too fast that the differences were so small and within a margin of error that its pointless to benchmark. What we really need is a new JS test suite.
You should see this: http://morepypy.blogspot.com/2009/10/pypys-jit-now-supports-floats.html
Hi Michael, cool blog! Hope to see more stuff and thanks for the link.
Yea, I'm actually looking at AOTs and wondering if they'll work for JS.