Skip to content

Commit

Permalink
Merge branch 'opt_startup'
Browse files Browse the repository at this point in the history
* I think we've determined this is a good direction to take
  and tests pass and we can do additional cleanup and fixes
  in the master branch itself.

* This should not only improve startup time quite a bit
  but also reduce peak memory use by eliminating CFG and other
  dataflow analysis memory use for infrequently executed scopes.
  • Loading branch information
subbuss committed Feb 22, 2015
2 parents 29d2e25 + dd12f21 commit 10fafff
Show file tree
Hide file tree
Showing 13 changed files with 266 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,16 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
if (IRRuntimeHelpers.isDebug()) doDebug();

InterpreterContext ic = ensureInstrsReady();
return ic.engine.interpret(context, self, ic, getImplementationClass().getMethodLocation(), name, block, null);
if (ic.hasExplicitCallProtocol()) {
return ic.engine.interpret(context, self, ic, getImplementationClass().getMethodLocation(), name, block, null);
} else {
try {
this.pre(ic, context, self, name, block, getImplementationClass());
return ic.engine.interpret(context, self, ic, getImplementationClass().getMethodLocation(), name, block, null);
} finally {
this.post(ic, context);
}
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.jruby.parser.StaticScope;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.DynamicScope;
import org.jruby.runtime.PositionAware;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
Expand Down Expand Up @@ -83,11 +84,35 @@ public Arity getArity() {
return this.arity;
}

protected void post(InterpreterContext ic, ThreadContext context) {
// update call stacks (pop: ..)
context.popFrame();
if (ic.popDynScope()) {
context.popScope();
}
}

protected void pre(InterpreterContext ic, ThreadContext context, IRubyObject self, String name, Block block, RubyModule implClass) {
// update call stacks (push: frame, class, scope, etc.)
context.preMethodFrameOnly(implClass, name, self, block);
if (ic.pushNewDynScope()) {
context.pushScope(DynamicScope.newDynamicScope(ic.getStaticScope()));
}
context.setCurrentVisibility(getVisibility());
}

public InterpreterContext ensureInstrsReady() {
// Try unsync access first before calling more expensive method for getting IC
InterpreterContext ic = method.getInterpreterContext();

return ic == null ? method.prepareForInterpretation() : ic;
// Build/rebuild if necessary
if (ic == null) {
ic = method.prepareForInterpretation();
} else if (ic.needsRebuilding()) {
ic = method.prepareForInterpretation(true);
}

return ic;
}

@Override
Expand All @@ -105,12 +130,21 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
}
}

private static IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
IRubyObject self, String name, IRubyObject[] args, Block block) {
try {
ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());

return ic.engine.interpret(context, self, ic, implClass, name, args, block, null);
if (ic.hasExplicitCallProtocol()) {
return ic.engine.interpret(context, self, ic, implClass, name, args, block, null);
} else {
try {
this.pre(ic, context, self, name, block, implClass);
return ic.engine.interpret(context, self, ic, implClass, name, args, block, null);
} finally {
this.post(ic, context);
}
}
} finally {
ThreadContext.popBacktrace(context);
}
Expand All @@ -131,12 +165,21 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
}
}

private static IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
IRubyObject self, String name, Block block) {
try {
ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());

return ic.engine.interpret(context, self, ic, implClass, name, block, null);
if (ic.hasExplicitCallProtocol()) {
return ic.engine.interpret(context, self, ic, implClass, name, block, null);
} else {
try {
this.pre(ic, context, self, name, block, implClass);
return ic.engine.interpret(context, self, ic, implClass, name, block, null);
} finally {
this.post(ic, context);
}
}
} finally {
ThreadContext.popBacktrace(context);
}
Expand All @@ -157,12 +200,21 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
}
}

private static IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
IRubyObject self, String name, IRubyObject arg1, Block block) {
try {
ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());

return ic.engine.interpret(context, self, ic, implClass, name, arg1, block, null);
if (ic.hasExplicitCallProtocol()) {
return ic.engine.interpret(context, self, ic, implClass, name, arg1, block, null);
} else {
try {
this.pre(ic, context, self, name, block, implClass);
return ic.engine.interpret(context, self, ic, implClass, name, arg1, block, null);
} finally {
this.post(ic, context);
}
}
} finally {
ThreadContext.popBacktrace(context);
}
Expand All @@ -183,12 +235,21 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
}
}

private static IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
IRubyObject self, String name, IRubyObject arg1, IRubyObject arg2, Block block) {
try {
ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());

return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, block, null);
if (ic.hasExplicitCallProtocol()) {
return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, block, null);
} else {
try {
this.pre(ic, context, self, name, block, implClass);
return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, block, null);
} finally {
this.post(ic, context);
}
}
} finally {
ThreadContext.popBacktrace(context);
}
Expand All @@ -209,12 +270,21 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
}
}

private static IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass,
IRubyObject self, String name, IRubyObject arg1, IRubyObject arg2, IRubyObject arg3, Block block) {
try {
ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());

return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, arg3, block, null);
if (ic.hasExplicitCallProtocol()) {
return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, arg3, block, null);
} else {
try {
this.pre(ic, context, self, name, block, implClass);
return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, arg3, block, null);
} finally {
this.post(ic, context);
}
}
} finally {
ThreadContext.popBacktrace(context);
}
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/org/jruby/ir/IRClosure.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ public IRClosure(IRManager manager, IRScope lexicalParent, int lineNumber, Stati
}

@Override
public InterpreterContext allocateInterpreterContext(Instr[] instructionList) {
return new ClosureInterpreterContext(this, instructionList);
public InterpreterContext allocateInterpreterContext(Instr[] instructionList, boolean rebuild) {
return new ClosureInterpreterContext(this, instructionList, rebuild);
}

public void setBeginEndBlock() {
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/org/jruby/ir/IREvalScript.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ public IREvalScript(IRManager manager, IRScope lexicalParent, String fileName,
}

@Override
public InterpreterContext allocateInterpreterContext(Instr[] instructionList) {
return new BeginEndInterpreterContext(this, instructionList);
public InterpreterContext allocateInterpreterContext(Instr[] instructionList, boolean rebuild) {
return new BeginEndInterpreterContext(this, instructionList, rebuild);
}

@Override
Expand Down
98 changes: 84 additions & 14 deletions core/src/main/java/org/jruby/ir/IRScope.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
* and so on ...
*/
public abstract class IRScope implements ParseResult {

private static final Logger LOG = LoggerFactory.getLogger("IRScope");

private static AtomicInteger globalScopeCount = new AtomicInteger();
Expand Down Expand Up @@ -139,6 +140,13 @@ public abstract class IRScope implements ParseResult {

private TemporaryVariable yieldClosureVariable;

// What state is this scope in?
enum ScopeState {
INIT, INTERPED, INSTRS_CLONED, CFG_BUILT
};

private ScopeState state = ScopeState.INIT;

// Used by cloning code
protected IRScope(IRScope s, IRScope lexicalParent) {
this.lexicalParent = lexicalParent;
Expand All @@ -163,6 +171,7 @@ protected IRScope(IRScope s, IRScope lexicalParent) {

this.localVars = new HashMap<String, LocalVariable>(s.localVars);
this.scopeId = globalScopeCount.getAndIncrement();
this.state = ScopeState.INIT; // SSS FIXME: Is this correct?

this.executedPasses = new ArrayList<CompilerPass>();

Expand Down Expand Up @@ -208,6 +217,7 @@ public IRScope(IRManager manager, IRScope lexicalParent, String name,

this.localVars = new HashMap<String, LocalVariable>();
this.scopeId = globalScopeCount.getAndIncrement();
this.state = ScopeState.INIT;

this.executedPasses = new ArrayList<CompilerPass>();

Expand Down Expand Up @@ -448,9 +458,10 @@ public CFG buildCFG() {
CFG newCFG = new CFG(this);
newCFG.build(getInstrs());
// Clear out instruction list after CFG has been built.
this.instrList = null;
instrList = null;

setCFG(newCFG);
state = ScopeState.CFG_BUILT;

return newCFG;
}
Expand All @@ -465,6 +476,20 @@ public CFG getCFG() {

@Interp
protected Instr[] prepareInstructions() {
if (getCFG() == null) {
int n = instrList.size();
Instr[] linearizedInstrArray = instrList.toArray(new Instr[n]);
for (int ipc = 0; ipc < n; ipc++) {
Instr i = linearizedInstrArray[ipc];
i.setIPC(ipc);
if (i instanceof LabelInstr) {
((LabelInstr)i).getLabel().setTargetPC(ipc+1);
}
}

return linearizedInstrArray;
}

setupLinearization();

boolean simple_method = this instanceof IRMethod;
Expand Down Expand Up @@ -584,6 +609,19 @@ private void optimizeSimpleScopes() {
}

protected void initScope(boolean jitMode) {
// FIXME: This is messy and prepareForInterpretation and prepareForCompilation need to
// clean up the lifecycle aspects of creating CFG from instrList and running passes in
// a consistent and predictable way. This is a hack atm to unbreak the fact JIT
// may happen before IC.build count and thus not have cloned the instrs (which then
// modifies instrs IC is using causing weird blowups.
//
// If the scope has already been interpreted once,
// the scope can be on the call stack right now.
// So, clone instructions before modifying them!
if (state != ScopeState.INIT && getCFG() == null) {
cloneInstrs();
}

runCompilerPasses(getManager().getCompilerPasses(this));

if (!jitMode && RubyInstanceConfig.IR_COMPILER_PASSES == null) {
Expand All @@ -603,26 +641,58 @@ protected void initScope(boolean jitMode) {
}

/** Make version specific to scope which needs it (e.g. Closure vs non-closure). */
public InterpreterContext allocateInterpreterContext(Instr[] instructionList) {
return new InterpreterContext(this, instructionList);
public InterpreterContext allocateInterpreterContext(Instr[] instructionList, boolean rebuild) {
return new InterpreterContext(this, instructionList, rebuild);
}

/** Run any necessary passes to get the IR ready for interpretation */
public synchronized InterpreterContext prepareForInterpretation() {
if (interpreterContext != null) return interpreterContext; // Already prepared
public InterpreterContext prepareForInterpretation() {
return prepareForInterpretation(false);
}

protected void cloneInstrs() {
cloneInstrs(new SimpleCloneInfo(this, false));
}

initScope(false);
protected void cloneInstrs(SimpleCloneInfo cloneInfo) {
// FIXME: not cloning if we happen to have a CFG violates the spirit of this method name.
// We do this currently because in a scenario where a nested closure is called much more than
// an outer scope we will process that closure first independently. If at a later point we
// process the outer scope then the inner scope will have nuked instrList and explode if we
// try to clone the non-existent instrList.
if (getCFG() != null) return;

List<Instr> newInstrList = new ArrayList<>(instrList.size());

for (Instr instr: this.instrList) {
newInstrList.add(instr.clone(cloneInfo));
}

// System.out.println("-- passes run for: " + this + " = " + java.util.Arrays.toString(executedPasses.toArray()));
instrList = newInstrList;
state = ScopeState.INSTRS_CLONED;
for (IRClosure cl : getClosures()) {
cl.cloneInstrs(cloneInfo.cloneForCloningClosure(cl));
}
}

// Always add call protocol instructions now for both interpreter and JIT
// since we are removing support for implicit stuff in the interpreter.
// When JIT later runs this same pass, it will be a NOP there.
if (!isUnsafeScope()) {
(new AddCallProtocolInstructions()).run(this);
/** Run any necessary passes to get the IR ready for interpretation */
public synchronized InterpreterContext prepareForInterpretation(boolean rebuild) {
if (interpreterContext == null) {
this.state = ScopeState.INTERPED;
} else if (!rebuild || getCFG() != null) {
return interpreterContext; // Already prepared/rebuilt
} else {
// Build CFG, run passes, etc.
initScope(false);

// Always add call protocol instructions now for both interpreter and JIT
// since we are removing support for implicit stuff in the interpreter.
// When JIT later runs this same pass, it will be a NOP there.
if (!isUnsafeScope()) {
(new AddCallProtocolInstructions()).run(this);
}
}

interpreterContext = allocateInterpreterContext(prepareInstructions());
interpreterContext = allocateInterpreterContext(prepareInstructions(), rebuild);

return interpreterContext;
}
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/org/jruby/ir/IRScriptBody.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ public void setTopLevelBindingScope(DynamicScope tlbScope) {
}

@Override
public InterpreterContext allocateInterpreterContext(Instr[] instructionList) {
return new BeginEndInterpreterContext(this, instructionList);
public InterpreterContext allocateInterpreterContext(Instr[] instructionList, boolean rebuild) {
return new BeginEndInterpreterContext(this, instructionList, rebuild);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
public class BeginEndInterpreterContext extends InterpreterContext {
private List<IRClosure> beginBlocks;

public BeginEndInterpreterContext(IRScope scope, Instr[] instructions) {
super(scope, instructions);
public BeginEndInterpreterContext(IRScope scope, Instr[] instructions, boolean rebuild) {
super(scope, instructions, rebuild);

beginBlocks = scope.getBeginBlocks();
}
Expand Down
Loading

0 comments on commit 10fafff

Please sign in to comment.