package org.jruby.runtime;
import java.io.ByteArrayOutputStream;
import org.jruby.EvalType;
import org.jruby.Ruby;
import org.jruby.RubyModule;
import org.jruby.compiler.Compilable;
import org.jruby.ir.IRClosure;
import org.jruby.ir.IRScope;
import org.jruby.ir.interpreter.FullInterpreterContext;
import org.jruby.ir.interpreter.Interpreter;
import org.jruby.ir.interpreter.InterpreterContext;
import org.jruby.ir.persistence.IRDumper;
import org.jruby.ir.runtime.IRRuntimeHelpers;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.cli.Options;
import org.jruby.util.log.Logger;
import org.jruby.util.log.LoggerFactory;
public class MixedModeIRBlockBody extends IRBlockBody implements Compilable<CompiledIRBlockBody> {
private static final Logger LOG = LoggerFactory.getLogger(MixedModeIRBlockBody.class);
protected boolean pushScope;
protected boolean reuseParentScope;
private boolean displayedCFG = false;
private InterpreterContext interpreterContext;
private int callCount = 0;
private volatile CompiledIRBlockBody jittedBody;
public MixedModeIRBlockBody(IRClosure closure, Signature signature) {
super(closure, signature);
this.pushScope = true;
this.reuseParentScope = false;
if (!closure.getManager().getInstanceConfig().isJitEnabled()) setCallCount(-1);
}
@Override
public boolean canCallDirect() {
return jittedBody != null || (interpreterContext != null && interpreterContext.hasExplicitCallProtocol());
}
@Override
public void setCallCount(int callCount) {
synchronized (this) {
this.callCount = callCount;
}
}
@Override
public void completeBuild(CompiledIRBlockBody blockBody) {
setCallCount(-1);
this.jittedBody = blockBody;
}
@Override
public IRScope getIRScope() {
return closure;
}
public BlockBody getJittedBody() {
return jittedBody;
}
@Override
public ArgumentDescriptor[] getArgumentDescriptors() {
return closure.getArgumentDescriptors();
}
public InterpreterContext ensureInstrsReady() {
if (IRRuntimeHelpers.isDebug() && !displayedCFG) {
LOG.info("Executing '" + closure + "' (pushScope=" + pushScope + ", reuseParentScope=" + reuseParentScope);
LOG.info(closure.debugOutput());
displayedCFG = true;
}
if (interpreterContext == null) {
if (IRRuntimeHelpers.shouldPrintIR(closure.getStaticScope().getModule().getRuntime())) {
ByteArrayOutputStream baos = IRDumper.printIR(closure, false);
LOG.info("Printing simple IR for " + closure.getId() + ":\n" + new String(baos.toByteArray()));
}
interpreterContext = closure.getInterpreterContext();
}
return interpreterContext;
}
@Override
public String getName() {
return closure.getId();
}
@Override
protected IRubyObject callDirect(ThreadContext context, Block block, IRubyObject[] args, Block blockArg) {
assert jittedBody != null : "direct call in MixedModeIRBlockBody without jitted body";
return jittedBody.callDirect(context, block, args, blockArg);
}
@Override
protected IRubyObject yieldDirect(ThreadContext context, Block block, IRubyObject[] args, IRubyObject self) {
assert jittedBody != null : "direct yield in MixedModeIRBlockBody without jitted body";
return jittedBody.yieldDirect(context, block, args, self);
}
@Override
protected IRubyObject commonYieldPath(ThreadContext context, Block block, Block.Type type, IRubyObject[] args, IRubyObject self, Block blockArg) {
if (callCount >= 0) promoteToFullBuild(context);
InterpreterContext ic = ensureInstrsReady();
Binding binding = block.getBinding();
Visibility oldVis = binding.getFrame().getVisibility();
Frame prevFrame = context.preYieldNoScope(binding);
DynamicScope actualScope = binding.getDynamicScope();
if (ic.pushNewDynScope()) {
context.pushScope(block.allocScope(actualScope));
} else if (ic.reuseParentDynScope()) {
context.pushScope(actualScope);
}
self = IRRuntimeHelpers.updateBlockState(block, self);
try {
return Interpreter.INTERPRET_BLOCK(context, block, self, ic, args, binding.getMethod(), blockArg);
}
finally {
postYield(context, ic, binding, oldVis, prevFrame);
}
}
private void promoteToFullBuild(ThreadContext context) {
final Ruby runtime = context.runtime;
if (runtime.isBooting() && !Options.JIT_KERNEL.load()) return;
if (this.callCount < 0) return;
if (this.callCount++ >= runtime.getInstanceConfig().getJitThreshold()) {
synchronized (this) {
if (this.callCount >= 0) {
this.callCount = Integer.MIN_VALUE;
ensureInstrsReady();
closure.getNearestTopLocalVariableScope().prepareForCompilation();
FullInterpreterContext fic = closure.getFullInterpreterContext();
if (fic == null || !fic.hasExplicitCallProtocol()) {
if (Options.JIT_LOGGING.load()) {
LOG.info("JIT failed; no full IR or no call protocol found in block: " + closure);
}
return;
}
runtime.getJITCompiler().buildThresholdReached(context, this);
}
}
}
}
public RubyModule getImplementationClass() {
return closure.getStaticScope().getModule();
}
}