Tamarin Trace Recording - Actually Recording

This post will discuss how Tamarin deals with actually recording instructions. The general idea behind a trace recorder is to look at backwards branches, and once a loop is detected as "hot", that is, been jumped to enough, we can start recording the execution of the loop. Tamarin takes an interesting approach to this. Tamarin compiles EVERY loop whose loop condition evaluates to true more than once. I'll get into the details about this later on. However, we need to first look at a data structure called a Fragment. At the beginning of every LoopEdge call we see:

Interpreter.cpp
Fragment* frag = frago->obtain(state);
Let's look a bit closer at what a Fragment contains:
Fragmento.h
// Fragments are linear sequences of native code that have a single entry
// point at the start of the fragment and may have one or more exit points

class Fragment {
NIns* code() { return _code; }
void setCode(NIns* codee) { _code = codee; }
LInsp map() { return _map; }
void setMap(LInsp map) { _map = map; }
GuardRecord* links() { return _links; }
int32_t& hits() { return _hits; }

A subtle distinction between fragments and fragmento: Fragments control individual traces and the information linked to them. FragmentO creates and manages Fragments. My question is why not go the enterprise route and call it a fragment manager?
FragmentO.cpp 
Fragment* Fragmento::obtain(const avmplus::InterpState &is)
{
FragID id = Fragment::idFor(is.pc, is.ip);
Fragment* f = _frags->get(id);
if (!f) {
f = newFrag(is);
_frags->put(id, f);
f->anchor = f;
f->anchorid = _frags->size();
}
return f;
}
_frags is a map that lists IDs to their address given by the garbage collector. The ID is a 64 bit generated number in which bits 32-63 are the PC, and 0-31 is the instruction pointer. If a fragment is not found with this id, we create a new Fragment that is associated with the combination of the PC and IP. Anyway we now have a fragment for the current interpreter state. Once we have this, we can check to see if we need to start recording. Looking in the loopEdge() method:
Interpreter.cpp    
if (!frag->code())
{
if (++frag->hits() > HOTLOOP)
{
TIMING_START(t_sot)
sot(frag,state);
TIMING_END(t_sot)
}
}
The naming convention is kind of confusing at first. sot refers to Start Of Trace. Let's look at what defines a HOTLOOP:
Interpreter.cpp
const int32_t HOTLOOP = 2;
The fragment's count will be 1 upon first iteration of the loop. I'm assuming this is to account for random one time backwards branches. But if it happens again, we start tracing. Therefore, any loop in reality, is actually traced. Once we are in sot we do the following:
Interpreter.cpp
void Interpreter::sot(Fragment *tracefrag, InterpState &interp)
{
// Other setup stuff
set_tracing(true);
}
Once set tracing is true, the following macro becomes false and we fall out of the interpreter switch loop:
Interpreter.cpp
#define NEXTIP (*ip++ & m_nottracing)
Which falls through to:
Interpreter.cpp
handle_trace:
trace(f, ip-1, sp, rp);
Which for some reason calls a function called trace2()! which does the actual tracing of instructions. We finally stop tracing again when we jump backwards:
Interpreter.cpp
void Interpreter::loopedge(InterpState &state) {
if (tracing())
{
eot(state, frag);
}
}
Of course this is the simplified version, and there are a bunch of corner cases which I'll get into later. I'll also get into detail of what is being traced in another post!