* Now talking on #baselinediscuss :O * djvj (kvijayan@moz-DB4A9C19.scl3.mozilla.com) has joined #baselinediscuss and we are complete :) heh, initially joined #baslinediscuss and was wondering why no-one was showing up k, let's get started I'd be interested in knowing more about the motivation and architecture of the baseline maximecb: I'll just provide a quick overview of the major design approach of baseline. We can talk motivation/justification after that. ok * jandem lets djvj talk first to avoid duplication ;) 1. IC every potentially-polymorphic optimizable op. jandem: thx :) 2. IC jitcode is aggressively shared. For every "kind" of operation (e.g. "add two integers", "optimized property access based on object shape"), we have a single piece of stubcode which performs that action Everything jumps to that stub? (every of that operation) yeah this means that on a per-site basis, we have a linked list of Stub objects, each of which store the relevant state for that stub (e.g. for a shape-optimized property access, the shape to check for, the offset to access the value at given a successful shape check). each stub object also keeps a pointer to the stubcode These stub chains are referred to by a table of "ICEntry" objects associated with a BaselineScript. Each ICEntry recording the bytecode PC, the native code PC associated with the IC chain, and a pointer to the first stub in the chain. so IC chain entry becomes: Load(ICentryPointer->firstStub, reg); Call(reg[offsetOfStubCodeField], reg) Are the ICs all allocated inline in the machine code, or do they jump all over the place? the stub objects are heap allocated. as needed. There's no reserved space for them. well, not in the machine code ok so, stubcode is generic, and is called (not jumped into, since the code is shared and doesn't know where to return to), and each stubcode is passed a pointer to the stubobject it was invoked from. the stubcode retreives any state it needs from the stub, performs its task. On success it simply "ret"s back to the main jitcode. On failure, it loads the next stub object pointer from the current stub, loads the stubcode pointer from that stub, and does a tail-call (jump, leaving return address in place) into the next stub. overall it's a pretty simple design all the chains end in some fallback stub that generally invokes some C++ function to handle the slowpath logic. Do you try to share IC entries that do the same thing (check for the same shape?) Maybe not, since they're in a chain eh yeah, it's hard to share those. Although bhackett had some ideas for sharing entire chains. but no, we don't do that. the other major thing is that we use the same kind of mechanism to implement type monitoring. every operation that needs its result monitored has two IC chains: the main IC chain, and a TypeMonitor IC chain. it executed both one after the other? the optimized stubs in the main IC chain don't return back to the jitcode - they load the first TypeMonitor stub and jump into that, passing the result. The TypeMonitor IC chains basically attach simple no-op stubs - as new types flow through, we attach an optimized stub that checks for that type, and returns immediately. Similarly, for operations which update heap objects (set-property, etc.), each optimized stub is associated with its own TypeUpdate IC chain. (so eventually the stubs attached for a particular operation will provide ionMonkey with information about how to inline/optimize this operation) jandem: plz continue :) my fingers are tired. I'm assuming these stubs also trip guards when needed? ok yes, for a type monitor stub the guard is like "does the value match this stub's type", and a GetProp stub has guards like "does the shape match" if a guard fails we try the next stub, until we reach the fallback stub (which just calls into the VM and attached new stubs if needed) so the plan is for IonMonkey to eventually use the IC stubs as main source of type information like it can see if a GetProp accesses the same shape and inline that directly without any IC's and if an Ion shape guard fails we bailout to the baseline compiler, and this will update the IC, etc the baseline compiler itself is very basic, most of the time it's just calling IC's and pushing/popping values on the stack I understand ion uses the C stack, is that the case for baseline too? yes And you do a fair bit of pushing/popping because your register allocator is very simple? yeah, exactly technically, we have no register allocator for baseline there are a few predefined registers which we use to store some well-known things yeah Do you expect the new baseline to be faster than JaegerMonkey? no it is / will be slower Slower but better integrated We want it to be the same speed as JM-without-TI (it's unlikely it'll be faster than JM-with-TI) because JM actually has a register allocator, and more inline paths instead of ICs however, JM doesn't collect type info it's either compiled with TI turned off. Or with TI turned on, but it needs the interpreter to run for a while and fill out the type sets before it does an optimized compile yeah, and JM is too complicated now that we have Ion Ion and baseline share the same calling convention, so cross-JIT calls etc are easier baseline is a lot more like Ion, shares code with Ion, etc so, there are a few other benefits we hope to gain currently when type info changes and invalidates jitcode, we fail hard - usually falling back all the way to the interpreter, and re-compiling after getting hot. with baseline, we can bail out from Ion into baseline jitcode, avoiding the interpreter. yeah, we hope we can greatly simplify the interpreter later on I heard you were considering replacing it by an AST walker That's a possibility. yes, that's an option. If we also compile from the AST we don't need bytecode it's what v8 does, basically The baseline JIT compiles machine code from the MIR, or from the LIR? from bytecode -- MIR and LIR are only used by Ion, the optimizing compiler it compiles bytecode to MIR, MIR to LIR, LIR to machine code How are you going to translate Ion stack frames to baseline frames? we want compilation times to be very fast, and the baseline compiler does not really benefit from the MIR representation maximecb: we already do that. Baseline stack frames are basically the same as interpreter stack frames, but growing down instead of up, and on the C stack instead of the interpreter stack. so the bailout mechanism that goes to interpreter just needed to be repurposed a bit to reconstruct a baseline stack frame. How do you deal with the issue of bailing out from inlined code? The current interpreter/Ion implementation already deals with that. Ion snapshots keep track of how to unpack multiple interpreter stack frames from an Ion stack frame + registers. How complete is the Baseline? What's missing? we are missing support for some bytecode ops, missing optimized IC stubs, and need a lot of testing (there are test failures still) and also OSR (on-stack replacement) from the interpreter right now we compile everything, but most scripts on the web run only once (70-80% or so, IIRC) so we want to interpret these a bit first, to save memory/compilation time oh, not 70-80% runs once, but runs < 10 times (or has < 10 loop iterations) oic, so you jaegermonkey everything because you can't jump from your interpreter to compiled? no we can also jump from the interpreter into JM code we need the same mechanism in baseline well, we can jump into baselien code currently only at function call boundaries OSR is for doing that at loop-edges Thanks for the very good overview :) np yeah np djvj: we're making a lot of progress now btw ;)