Skip to content

Commit

Permalink
Showing 214 changed files with 2,938 additions and 2,022 deletions.
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/Ruby.java
Original file line number Diff line number Diff line change
@@ -1254,7 +1254,7 @@ private void init() {
initThreadStatuses();

// Create an IR manager and a top-level IR scope and bind it to the top-level static-scope object
irManager = new IRManager();
irManager = new IRManager(getInstanceConfig());
// FIXME: This registers itself into static scope as a side-effect. Let's make this
// relationship handled either more directly or through a descriptice method
// FIXME: We need a failing test case for this since removing it did not regress tests
4 changes: 4 additions & 0 deletions core/src/main/java/org/jruby/RubyBasicObject.java
Original file line number Diff line number Diff line change
@@ -163,6 +163,10 @@ public class RubyBasicObject implements Cloneable, IRubyObject, Serializable, Co
public static final int USER6_F = (1<<(FL_USHIFT+6));
public static final int USER7_F = (1<<(FL_USHIFT+7));
public static final int USER8_F = (1<<(FL_USHIFT+8));
public static final int USER9_F = (1<<(FL_USHIFT+9));
public static final int USERA_F = (1<<(FL_USHIFT+10));
public static final int REFINED_MODULE_F = USER9_F;
public static final int IS_OVERLAID_F = USERA_F;

public static final int COMPARE_BY_IDENTITY_F = USER8_F;

187 changes: 186 additions & 1 deletion core/src/main/java/org/jruby/RubyModule.java
Original file line number Diff line number Diff line change
@@ -62,6 +62,7 @@
import org.jruby.internal.runtime.methods.SynchronizedDynamicMethod;
import org.jruby.internal.runtime.methods.UndefinedMethod;
import org.jruby.internal.runtime.methods.WrapperMethod;
import org.jruby.ir.IRMethod;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
@@ -545,6 +546,170 @@ private String calculateAnonymousName() {
return anonymousName;
}


@JRubyMethod(name = "refine", required = 1)
public IRubyObject refine(ThreadContext context, IRubyObject classArg, Block block) {
if (!block.isGiven()) throw context.runtime.newArgumentError("no block given");
if (block.isEscaped()) throw context.runtime.newArgumentError("can't pass a Proc as a block to Module#refine");
if (!(classArg instanceof RubyClass)) throw context.runtime.newTypeError(classArg, context.runtime.getClassClass());
if (refinements == null) refinements = new HashMap<>();
if (activatedRefinements == null) activatedRefinements = new HashMap<>();

RubyClass classWeAreRefining = (RubyClass) classArg;
RubyModule refinement = refinements.get(classWeAreRefining);
if (refinement == null) {
refinement = createNewRefinedModule(context, classWeAreRefining);

// Add it to the activated chain of other refinements already added to this class we are refining
addActivatedRefinement(context, classWeAreRefining, refinement);
}

// Executes the block supplied with the defined method definitions using the refinment as it's module.
yieldRefineBlock(context, refinement, block);

return refinement;
}

private RubyModule createNewRefinedModule(ThreadContext context, RubyClass classWeAreRefining) {
RubyModule newRefinement = new RubyModule(context.runtime, classWeAreRefining);
newRefinement.setFlag(REFINED_MODULE_F, true);
newRefinement.refinedClass = classWeAreRefining;
newRefinement.definedAt = this;
refinements.put(classWeAreRefining, newRefinement);

return newRefinement;
}

private void yieldRefineBlock(ThreadContext context, RubyModule refinement, Block block) {
block.setEvalType(EvalType.MODULE_EVAL);
block.getBinding().setSelf(refinement);
block.yieldSpecific(context);
}

// This has three cases:
// 1. class being refined has never had any refines happen to it yet: return itself
// 2. class has been refined: return already existing refinementwrapper (chain of modules to call against)
// 3. refinement is already in the refinementwrapper so we do not need to add it to the wrapper again: return null
private RubyClass getAlreadyActivatedRefinementWrapper(RubyClass classWeAreRefining, RubyModule refinement) {
// We have already encountered at least one refine on this class. Return that wrapper.
RubyClass moduleWrapperForRefinment = activatedRefinements.get(classWeAreRefining);
if (moduleWrapperForRefinment == null) return classWeAreRefining;

for (RubyModule c = moduleWrapperForRefinment; c != null && c.isIncluded(); c = c.getSuperClass()) {
if (c.getNonIncludedClass() == refinement) return null;
}

return moduleWrapperForRefinment;
}

/*
* We will find whether we have already refined once and get that set of includedmodules or we will start to create
* one. The new refinement will be added as a new included module on the front. It will also add all superclasses
* of the refinement into this call chain.
*/
// MRI: add_activated_refinement
private void addActivatedRefinement(ThreadContext context, RubyClass classWeAreRefining, RubyModule refinement) {
RubyClass superClass = getAlreadyActivatedRefinementWrapper(classWeAreRefining, refinement);
if (superClass == null) return; // already been refined and added to refinementwrapper

refinement.setFlag(IS_OVERLAID_F, true);
IncludedModuleWrapper iclass = new IncludedModuleWrapper(context.runtime, superClass, refinement);
RubyClass c = iclass;
c.refinedClass = classWeAreRefining;
for (refinement = refinement.getSuperClass(); refinement != null; refinement = refinement.getSuperClass()) {
refinement.setFlag(IS_OVERLAID_F, true);
RubyClass superClazz = c.getSuperClass();
c.setModuleSuperClass(new IncludedModuleWrapper(context.runtime, c.getSuperClass(), refinement));
c.refinedClass = classWeAreRefining;
c = superClazz;
}
activatedRefinements.put(classWeAreRefining, iclass);
}

@JRubyMethod(name = "using", required = 1, frame = true)
public IRubyObject using(ThreadContext context, IRubyObject refinedModule) {
if (context.getFrameSelf() != this) throw context.runtime.newRuntimeError("Module#using is not called on self");
// FIXME: This is a lame test and I am unsure it works with JIT'd bodies...
if (context.getCurrentScope().getStaticScope().getIRScope() instanceof IRMethod) {
throw context.runtime.newRuntimeError("Module#using is not permitted in methods");
}

// I pass the cref even though I don't need to so that the concept is simpler to read
usingModule(context, this, refinedModule);

return this;
}

// mri: rb_using_module
public void usingModule(ThreadContext context, RubyModule cref, IRubyObject refinedModule) {
if (!(refinedModule instanceof RubyModule))throw context.runtime.newTypeError(refinedModule, context.runtime.getModule());

usingModuleRecursive(cref, (RubyModule) refinedModule);
}

// mri: using_module_recursive
private void usingModuleRecursive(RubyModule cref, RubyModule refinedModule) {
RubyClass superClass = refinedModule.getSuperClass();

// For each superClass of the refined module also use their refinements for the given cref
if (superClass != null) usingModuleRecursive(cref, superClass);

//RubyModule realRefinedModule = refinedModule instanceof IncludedModule ?
// ((IncludedModule) refinedModule).getRealClass() : refinedModule;

Map<RubyClass, RubyModule> refinements = refinedModule.refinements;
if (refinements == null) return; // No refinements registered for this module

for (Map.Entry<RubyClass, RubyModule> entry: refinements.entrySet()) {
usingRefinement(cref, entry.getKey(), entry.getValue());
}
}

// This is nearly identical to getAlreadyActivatedRefinementWrapper but thw maps they work against are different.
// This has three cases:
// 1. class being refined has never had any refines happen to it yet: return itself
// 2. class has been refined: return already existing refinementwrapper (chain of modules to call against)
// 3. refinement is already in the refinementwrapper so we do not need to add it to the wrapper again: return null
private RubyModule getAlreadyRefinementWrapper(RubyModule cref, RubyClass classWeAreRefining, RubyModule refinement) {
// We have already encountered at least one refine on this class. Return that wrapper.
RubyModule moduleWrapperForRefinment = cref.refinements.get(classWeAreRefining);
if (moduleWrapperForRefinment == null) return classWeAreRefining;

for (RubyModule c = moduleWrapperForRefinment; c != null && c.isIncluded(); c = c.getSuperClass()) {
if (c.getNonIncludedClass() == refinement) return null;
}

return moduleWrapperForRefinment;
}

/*
* Within the context of this cref any references to the class we are refining will try and find
* that definition from the refinement instead. At one point I was confused how this would not
* conflict if the same module was used in two places but the cref must be a lexically containing
* module so it cannot live in two files.
*/
private void usingRefinement(RubyModule cref, RubyClass classWeAreRefining, RubyModule refinement) {
// Our storage cubby in cref for all known refinements
if (cref.refinements == null) cref.refinements = new HashMap<>();

RubyModule superClass = getAlreadyRefinementWrapper(cref, classWeAreRefining, refinement);
if (superClass == null) return; // already been refined and added to refinementwrapper

refinement.setFlag(IS_OVERLAID_F, true);
RubyModule lookup = new IncludedModuleWrapper(getRuntime(), (RubyClass) superClass, refinement);
RubyModule iclass = lookup;
lookup.refinedClass = classWeAreRefining;

for (refinement = refinement.getSuperClass(); refinement != null && refinement != classWeAreRefining; refinement = refinement.getSuperClass()) {
refinement.setFlag(IS_OVERLAID_F, true);
RubyClass newInclude = new IncludedModuleWrapper(getRuntime(), lookup.getSuperClass(), refinement);
lookup.setSuperClass(newInclude);
lookup = newInclude;
lookup.refinedClass = classWeAreRefining;
}
cref.refinements.put(classWeAreRefining, iclass);
}

/**
* Create a wrapper to use for including the specified module into this one.
*
@@ -4155,7 +4320,15 @@ public boolean isMethodBuiltin(String methodName) {

return method != null && method.isBuiltin();
}


public Map<RubyClass, RubyModule> getRefinements() {
return refinements;
}

public void setRefinements(Map<RubyClass, RubyModule> refinements) {
this.refinements = refinements;
}

private volatile Map<String, Autoload> autoloads = Collections.EMPTY_MAP;
protected volatile Map<String, DynamicMethod> methods = Collections.EMPTY_MAP;
protected Map<String, CacheEntry> cachedMethods = Collections.EMPTY_MAP;
@@ -4199,6 +4372,18 @@ public boolean isMethodBuiltin(String methodName) {

private volatile Map<String, IRubyObject> classVariables = Collections.EMPTY_MAP;

/** Refinements added to this module are stored here **/
private volatile Map<RubyClass, RubyModule> refinements = null;

/** A list of refinement hosts for this refinement */
private volatile Map<RubyClass, IncludedModuleWrapper> activatedRefinements = null;

/** The class this refinement refines */
volatile RubyClass refinedClass = null;

/** The moduel where this refinement was defined */
private volatile RubyModule definedAt = null;

private static final AtomicReferenceFieldUpdater CLASSVARS_UPDATER;

static {
19 changes: 19 additions & 0 deletions core/src/main/java/org/jruby/compiler/FullBuildSource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.jruby.compiler;

import org.jruby.RubyModule;
import org.jruby.ir.IRScope;
import org.jruby.ir.interpreter.InterpreterContext;

/**
* Blocks and methods both share same full build mechanism so they implement this to be buildable.
*/
public interface FullBuildSource {
public void setCallCount(int count);
public void switchToFullBuild(InterpreterContext interpreterContext);
public IRScope getIRScope();
public InterpreterContext ensureInstrsReady();
public String getName();
public String getFile();
public int getLine();
public RubyModule getImplementationClass();
}
87 changes: 68 additions & 19 deletions core/src/main/java/org/jruby/compiler/JITCompiler.java
Original file line number Diff line number Diff line change
@@ -36,12 +36,9 @@
import org.jruby.RubyModule;
import org.jruby.ast.util.SexpMaker;
import org.jruby.internal.runtime.methods.CompiledIRMethod;
import org.jruby.internal.runtime.methods.InterpretedIRMethod;
import org.jruby.internal.runtime.methods.MixedModeIRMethod;
import org.jruby.ir.IRMethod;
import org.jruby.ir.targets.JVMVisitor;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.Block;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.threading.DaemonThreadFactory;
@@ -52,10 +49,8 @@
import org.jruby.util.log.LoggerFactory;
import org.objectweb.asm.Opcodes;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;
@@ -150,8 +145,31 @@ public void tearDown() {
}
}
}

public void fullBuildThresholdReached(final FullBuildSource method, final RubyInstanceConfig config) {
// Disable any other jit tasks from entering queue
method.setCallCount(-1);

Runnable jitTask = new FullBuildTask(method);

if (config.getJitThreshold() > 0) {
if (config.getJitBackground() && executor != null) {
try {
executor.submit(jitTask);
} catch (RejectedExecutionException ree) {
// failed to submit, just run it directly
jitTask.run();
}
} else {
// Because are non-asynchonously build if the JIT threshold happens to be 0 we will have no ic yet.
method.ensureInstrsReady();
// just run directly
jitTask.run();
}
}
}

public void jitThresholdReached(final InterpretedIRMethod method, final RubyInstanceConfig config, ThreadContext context, final String className, final String methodName) {
public void jitThresholdReached(final MixedModeIRMethod method, final RubyInstanceConfig config, ThreadContext context, final String className, final String methodName) {
// Disable any other jit tasks from entering queue
method.setCallCount(-1);

@@ -169,19 +187,48 @@ public void jitThresholdReached(final InterpretedIRMethod method, final RubyInst
jitTask.run();
}
} else {
// Because are non-asynchonously build if the JIT threshold happens to be 0 we will have no ic yet.
method.ensureInstrsReady();

// just run directly
jitTask.run();
}
}

private static final MethodHandles.Lookup PUBLIC_LOOKUP = MethodHandles.publicLookup().in(Ruby.class);

private class FullBuildTask implements Runnable {
private final FullBuildSource method;

public FullBuildTask(FullBuildSource method) {
this.method = method;
}

public void run() {
try {
method.switchToFullBuild(method.getIRScope().prepareFullBuild());

if (config.isJitLogging()) {
log(method.getImplementationClass(), method.getFile(), method.getLine(), method.getName(), "done building");
}
} catch (Throwable t) {
if (config.isJitLogging()) {
log(method.getImplementationClass(), method.getFile(), method.getLine(), method.getName(),
"Could not build; passes run: " + method.getIRScope().getExecutedPasses(), t.getMessage());
if (config.isJitLoggingVerbose()) {
t.printStackTrace();
}
}
}
}
}

private class JITTask implements Runnable {
private final String className;
private final InterpretedIRMethod method;
private final MixedModeIRMethod method;
private final String methodName;

public JITTask(String className, InterpretedIRMethod method, String methodName) {
public JITTask(String className, MixedModeIRMethod method, String methodName) {
this.className = className;
this.method = method;
this.methodName = methodName;
@@ -203,7 +250,7 @@ public void run() {
|| config.getExcludedMethods().contains(excludeModuleName + "#" + methodName)
|| config.getExcludedMethods().contains(methodName))) {
method.setCallCount(-1);
log(method, methodName, "skipping method: " + excludeModuleName + "#" + methodName);
log(method.getImplementationClass(), method.getFile(), method.getLine(), methodName, "skipping method: " + excludeModuleName + "#" + methodName);
return;
}
}
@@ -235,12 +282,12 @@ public void run() {
// logEvery n methods based on configuration
if (config.getJitLogEvery() > 0) {
if (methodCount % config.getJitLogEvery() == 0) {
log(method, methodName, "live compiled methods: " + methodCount);
log(method.getImplementationClass(), method.getFile(), method.getLine(), methodName, "live compiled methods: " + methodCount);
}
}

if (config.isJitLogging()) {
log(method, className + "." + methodName, "done jitting");
log(method.getImplementationClass(), method.getFile(), method.getLine(), className + "." + methodName, "done jitting");
}

Map<Integer, MethodType> signatures = ((IRMethod)method.getIRMethod()).getNativeSignatures();
@@ -273,7 +320,7 @@ public void run() {
return;
} catch (Throwable t) {
if (config.isJitLogging()) {
log(method, className + "." + methodName, "Could not compile; passes run: " + method.getIRMethod().getExecutedPasses(), t.getMessage());
log(method.getImplementationClass(), method.getFile(), method.getLine(), className + "." + methodName, "Could not compile; passes run: " + method.getIRMethod().getExecutedPasses(), t.getMessage());
if (config.isJitLoggingVerbose()) {
t.printStackTrace();
}
@@ -305,7 +352,7 @@ public static String getHashForBytes(byte[] bytes) {
}

public static class JITClassGenerator {
public JITClassGenerator(String className, String methodName, String key, Ruby ruby, InterpretedIRMethod method, JVMVisitor visitor) {
public JITClassGenerator(String className, String methodName, String key, Ruby ruby, MixedModeIRMethod method, JVMVisitor visitor) {
this.packageName = JITCompiler.RUBY_JIT_PREFIX;
if (RubyInstanceConfig.JAVA_VERSION == Opcodes.V1_7 || Options.COMPILE_INVOKEDYNAMIC.load() == true) {
// Some versions of Java 7 seems to have a bug that leaks definitions across cousin classloaders
@@ -378,20 +425,22 @@ public String toString() {
private final String className;
private final String methodName;
private final String digestString;
private final InterpretedIRMethod method;
private final MixedModeIRMethod method;
private final JVMVisitor visitor;

private byte[] bytecode;
private long compileTime;
private String name;
}

static void log(InterpretedIRMethod method, String name, String message, String... reason) {
String className = method.getImplementationClass().getBaseName();

static void log(RubyModule implementationClass, String file, int line, String name, String message, String... reason) {
boolean isBlock = implementationClass == null;
String className = isBlock ? "<block>" : implementationClass.getBaseName();
if (className == null) className = "<anon class>";

StringBuilder builder = new StringBuilder(message + ":" + className + "." + name + " at " + method.getIRMethod().getFileName() + ":" + method.getIRMethod().getLineNumber());
name = isBlock ? "" : "." + name;

StringBuilder builder = new StringBuilder(message + ":" + className + name + " at " + file + ":" + line);

if (reason.length > 0) {
builder.append(" because of: \"");
30 changes: 22 additions & 8 deletions core/src/main/java/org/jruby/ext/jruby/JRubyLibrary.java
Original file line number Diff line number Diff line change
@@ -222,10 +222,17 @@ public static IRubyObject methodArgs(IRubyObject recv) {
argsArray.append(RubyArray.newArray(runtime, block, getNameFrom(runtime, args.getBlock())));
}
} else if (method instanceof IRMethodArgs) {
for (String[] argParam: ((IRMethodArgs)method).getParameterList()) {
RubySymbol argType = runtime.newSymbol(argParam[0]);
if (argParam[1] == "") argsArray.append(RubyArray.newArray(runtime, argType));
else argsArray.append(RubyArray.newArray(runtime, argType, runtime.newSymbol(argParam[1])));
String[] argsDesc = ((IRMethodArgs) method).getParameterList();

for (int i = 0; i < argsDesc.length; i++) {
RubySymbol argType = runtime.newSymbol(argsDesc[i]);
i++;
String argName = argsDesc[i];
if (argName.isEmpty()) {
argsArray.append(RubyArray.newArray(runtime, argType));
} else {
argsArray.append(RubyArray.newArray(runtime, argType, runtime.newSymbol(argName)));
}
}
} else {
if (method.getArity() == Arity.OPTIONAL) {
@@ -287,10 +294,17 @@ public static String[] methodParameters(Ruby runtime, DynamicMethod method) {
argsArray.add("b" + getNameFrom(runtime, args.getBlock()));
}
} else if (method instanceof IRMethodArgs) {
for (String[] argParam: ((IRMethodArgs)method).getParameterList()) {
RubySymbol argType = runtime.newSymbol(argParam[0]);
if (argParam[1] == "") argsArray.add(argParam[0]);
else argsArray.add(argParam[0] + argParam[1]);
String[] argsDesc = ((IRMethodArgs) method).getParameterList();

for (int i = 0; i < argsDesc.length; i++) {
String argType = argsDesc[i];
i++;
String argName = argsDesc[i];
if (argName.isEmpty()) {
argsArray.add(argType);
} else {
argsArray.add(argType + argName);
}
}
} else {
if (method.getArity() == Arity.OPTIONAL) {
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
import java.util.List;

public interface IRMethodArgs {
public List<String[]> getParameterList();
public String[] getParameterList();

public enum ArgType {
key, keyreq, keyrest, block, opt, rest, req
Original file line number Diff line number Diff line change
@@ -18,12 +18,12 @@ public class InterpretedIRBodyMethod extends InterpretedIRMethod {
public InterpretedIRBodyMethod(IRScope method, RubyModule implementationClass) {
super(method, Visibility.PUBLIC, implementationClass);

this.box.callCount = -1;
callCount = -1;
}

@Override
public List<String[]> getParameterList() {
return new ArrayList<String[]>();
public String[] getParameterList() {
return new String[0];
}

@Override
@@ -40,9 +40,9 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz

protected IRubyObject callInternal(ThreadContext context, IRubyObject self, RubyModule clazz, String name, Block block) {
InterpreterContext ic = ensureInstrsReady();
if (!ic.hasExplicitCallProtocol()) {
this.pre(ic, context, self, name, block, getImplementationClass());
}

if (!ic.hasExplicitCallProtocol()) this.pre(ic, context, self, name, block, getImplementationClass());

try {
switch (method.getScopeType()) {
case MODULE_BODY: return INTERPRET_MODULE(ic, context, self, clazz, method.getName(), block);
@@ -51,9 +51,7 @@ protected IRubyObject callInternal(ThreadContext context, IRubyObject self, Ruby
default: throw new RuntimeException("invalid body method type: " + method);
}
} finally {
if (!ic.hasExplicitCallProtocol()) {
this.post(ic, context);
}
if (!ic.hasExplicitCallProtocol()) this.post(ic, context);
}
}

Original file line number Diff line number Diff line change
@@ -38,21 +38,8 @@ protected void pre(InterpreterContext ic, ThreadContext context, IRubyObject sel

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, Block block) {
DynamicMethodBox box = this.box;
if (box.callCount >= 0) tryJit(context, box);
DynamicMethod actualMethod = box.actualMethod;
if (actualMethod != null) return actualMethod.call(context, self, clazz, name, block);

if (IRRuntimeHelpers.isDebug()) doDebug();

return callInternal(context, self, clazz, name, block);
}

@Override
public DynamicMethod dup() {
InterpretedIRMetaClassBody x = new InterpretedIRMetaClassBody(method, implementationClass);
x.dupBox(this);

return x;
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,369 @@
package org.jruby.internal.runtime.methods;

import java.util.List;

import org.jruby.MetaClass;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
import org.jruby.ir.*;
import org.jruby.ir.interpreter.InterpreterContext;
import org.jruby.ir.runtime.IRRuntimeHelpers;
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;
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 MixedModeIRMethod extends DynamicMethod implements IRMethodArgs, PositionAware {
private static final Logger LOG = LoggerFactory.getLogger("InterpretedIRMethod");

private Arity arity;
private boolean displayedCFG = false; // FIXME: Remove when we find nicer way of logging CFG

protected final IRScope method;

protected static class DynamicMethodBox {
public DynamicMethod actualMethod;
public int callCount = 0;
}

protected DynamicMethodBox box = new DynamicMethodBox();

public MixedModeIRMethod(IRScope method, Visibility visibility, RubyModule implementationClass) {
super(implementationClass, visibility, CallConfiguration.FrameNoneScopeNone, method.getName());
this.method = method;
this.method.getStaticScope().determineModule();
this.arity = calculateArity();

// disable JIT if JIT is disabled
// FIXME: kinda hacky, but I use IRMethod data in JITCompiler.
if (!implementationClass.getRuntime().getInstanceConfig().getCompileMode().shouldJIT()) {
this.box.callCount = -1;
}
}

public IRScope getIRMethod() {
return method;
}

public DynamicMethod getActualMethod() {
return box.actualMethod;
}

public void setCallCount(int callCount) {
box.callCount = callCount;
}

public StaticScope getStaticScope() {
return method.getStaticScope();
}

public String[] getParameterList() {
ensureInstrsReady(); // Make sure method is minimally built before returning this info
return ((IRMethod) method).getArgDesc();
}

private Arity calculateArity() {
StaticScope s = method.getStaticScope();
if (s.getOptionalArgs() > 0 || s.getRestArg() >= 0) return Arity.required(s.getRequiredArgs());

return Arity.createArity(s.getRequiredArgs());
}

@Override
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());
}

// FIXME: for subclasses we should override this method since it can be simple get
// FIXME: to avoid cost of synch call in lazilyacquire we can save the ic here
public InterpreterContext ensureInstrsReady() {
if (method instanceof IRMethod) {
return ((IRMethod) method).lazilyAcquireInterpreterContext();
}
return method.getInterpreterContext();
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) {
if (IRRuntimeHelpers.isDebug()) doDebug();

DynamicMethodBox box = this.box;
if (box.callCount >= 0) tryJit(context, box);
DynamicMethod jittedMethod = box.actualMethod;

if (jittedMethod != null) {
return jittedMethod.call(context, self, clazz, name, args, block);
} else {
return INTERPRET_METHOD(context, ensureInstrsReady(), getImplementationClass().getMethodLocation(), self, name, args, block);
}
}

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());

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);
}
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, Block block) {
if (IRRuntimeHelpers.isDebug()) doDebug();

DynamicMethodBox box = this.box;
if (box.callCount >= 0) tryJit(context, box);
DynamicMethod jittedMethod = box.actualMethod;

if (jittedMethod != null) {
return jittedMethod.call(context, self, clazz, name, block);
} else {
return INTERPRET_METHOD(context, ensureInstrsReady(), getImplementationClass().getMethodLocation(), self, name, block);
}
}

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());

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);
}
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, Block block) {
if (IRRuntimeHelpers.isDebug()) doDebug();

DynamicMethodBox box = this.box;
if (box.callCount >= 0) tryJit(context, box);
DynamicMethod jittedMethod = box.actualMethod;

if (jittedMethod != null) {
return jittedMethod.call(context, self, clazz, name, arg0, block);
} else {
return INTERPRET_METHOD(context, ensureInstrsReady(), getImplementationClass().getMethodLocation(), self, name, arg0, block);
}
}

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());

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);
}
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1, Block block) {
if (IRRuntimeHelpers.isDebug()) doDebug();

DynamicMethodBox box = this.box;
if (box.callCount >= 0) tryJit(context, box);
DynamicMethod jittedMethod = box.actualMethod;

if (jittedMethod != null) {
return jittedMethod.call(context, self, clazz, name, arg0, arg1, block);
} else {
return INTERPRET_METHOD(context, ensureInstrsReady(), getImplementationClass().getMethodLocation(), self, name, arg0, arg1, block);
}
}

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());

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);
}
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) {
if (IRRuntimeHelpers.isDebug()) doDebug();

DynamicMethodBox box = this.box;
if (box.callCount >= 0) tryJit(context, box);
DynamicMethod jittedMethod = box.actualMethod;

if (jittedMethod != null) {
return jittedMethod.call(context, self, clazz, name, arg0, arg1, arg2, block);
} else {
return INTERPRET_METHOD(context, ensureInstrsReady(), getImplementationClass().getMethodLocation(), self, name, arg0, arg1, arg2, block);
}
}

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());

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);
}

}

protected void doDebug() {
// FIXME: This is printing out IRScope CFG but JIT may be active and it might not reflect
// currently executing. Move into JIT and into interp since they will be getting CFG from
// different sources
// FIXME: This is only printing out CFG once. If we keep applying more passes then we
// will want to print out after those new passes.
ensureInstrsReady();
LOG.info("Executing '" + method.getName() + "'");
if (!displayedCFG) {
LOG.info(method.debugOutput());
displayedCFG = true;
}
}

public DynamicMethod getMethodForCaching() {
DynamicMethod method = box.actualMethod;
if (method instanceof CompiledIRMethod) {
return method;
}
return this;
}

public void switchToJitted(CompiledIRMethod newMethod) {
this.box.actualMethod = newMethod;
this.box.actualMethod.serialNumber = this.serialNumber;
this.box.callCount = -1;
getImplementationClass().invalidateCacheDescendants();
}


protected void tryJit(ThreadContext context, DynamicMethodBox box) {
Ruby runtime = context.runtime;

// don't JIT during runtime boot
if (runtime.isBooting()) return;

String className;
if (implementationClass.isSingleton()) {
MetaClass metaClass = (MetaClass)implementationClass;
RubyClass realClass = metaClass.getRealClass();
// if real class is Class
if (realClass == context.runtime.getClassClass()) {
// use the attached class's name
className = ((RubyClass)metaClass.getAttached()).getName();
} else {
// use the real class name
className = realClass.getName();
}
} else {
// use the class name
className = implementationClass.getName();
}


if (box.callCount++ >= Options.JIT_THRESHOLD.load()) {
context.runtime.getJITCompiler().jitThresholdReached(this, context.runtime.getInstanceConfig(), context, className, name);
}
}

public void setActualMethod(CompiledIRMethod method) {
this.box.actualMethod = method;
}

protected void dupBox(MixedModeIRMethod orig) {
this.box = orig.box;
}

@Override
public DynamicMethod dup() {
MixedModeIRMethod x = new MixedModeIRMethod(method, visibility, implementationClass);
x.box = box;

return x;
}

public String getFile() {
return method.getFileName();
}

public int getLine() {
return method.getLineNumber();
}
}
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/ir/IRBindingEvalScript.java
Original file line number Diff line number Diff line change
@@ -72,7 +72,7 @@ public LocalVariable getLocalVariable(String name, int scopeDepth) {
@Override
public LocalVariable getNewLocalVariable(String name, int depth) {
assert depth == nearestNonEvalScopeDepth: "Local variable depth in IREvalScript:getNewLocalVariable for " + name + " must be " + nearestNonEvalScopeDepth + ". Got " + depth;
LocalVariable lvar = new ClosureLocalVariable(this, name, 0, nearestNonEvalScope.evalScopeVars.size());
LocalVariable lvar = new ClosureLocalVariable(name, 0, nearestNonEvalScope.evalScopeVars.size());
nearestNonEvalScope.evalScopeVars.put(name, lvar);
// CON: unsure how to get static scope to reflect this name as in IRClosure and IRMethod
return lvar;
266 changes: 207 additions & 59 deletions core/src/main/java/org/jruby/ir/IRBuilder.java

Large diffs are not rendered by default.

73 changes: 24 additions & 49 deletions core/src/main/java/org/jruby/ir/IRClosure.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.jruby.ir;

import java.util.ArrayList;
import java.util.List;
import org.jruby.ir.instructions.*;
import org.jruby.ir.interpreter.ClosureInterpreterContext;
import org.jruby.ir.interpreter.InterpreterContext;
@@ -24,15 +26,12 @@ public class IRClosure extends IRScope {
public final Label endLabel; // Label for the end of the closure (used to implement retry)
public final int closureId; // Unique id for this closure within the nearest ancestor method.

private int nestingDepth; // How many nesting levels within a method is this closure nested in?

private boolean isBeginEndBlock;

/** The parameter names, for Proc#parameters */
private String[] parameterList;

private Signature signature;
private int argumentType;

/** Added for interp/JIT purposes */
private IRBlockBody body;
@@ -50,15 +49,6 @@ protected IRClosure(IRManager manager, IRScope lexicalParent, String fileName, i
setName(prefix + closureId);
this.body = null;
this.parameterList = new String[] {};

// set nesting depth
int n = 0;
IRScope s = this.getLexicalParent();
while (s instanceof IRClosure) {
n++;
s = s.getLexicalParent();
}
this.nestingDepth = n;
}

/** Used by cloning code */
@@ -78,17 +68,16 @@ protected IRClosure(IRClosure c, IRScope lexicalParent, int closureId, String fu
this.signature = c.signature;
}

public IRClosure(IRManager manager, IRScope lexicalParent, int lineNumber, StaticScope staticScope, Signature signature, int argumentType) {
this(manager, lexicalParent, lineNumber, staticScope, signature, argumentType, "_CLOSURE_");
public IRClosure(IRManager manager, IRScope lexicalParent, int lineNumber, StaticScope staticScope, Signature signature) {
this(manager, lexicalParent, lineNumber, staticScope, signature, "_CLOSURE_");
}

public IRClosure(IRManager manager, IRScope lexicalParent, int lineNumber, StaticScope staticScope, Signature signature, int argumentType, String prefix) {
this(manager, lexicalParent, lineNumber, staticScope, signature, argumentType, prefix, false);
public IRClosure(IRManager manager, IRScope lexicalParent, int lineNumber, StaticScope staticScope, Signature signature, String prefix) {
this(manager, lexicalParent, lineNumber, staticScope, signature, prefix, false);
}

public IRClosure(IRManager manager, IRScope lexicalParent, int lineNumber, StaticScope staticScope, Signature signature, int argumentType, String prefix, boolean isBeginEndBlock) {
public IRClosure(IRManager manager, IRScope lexicalParent, int lineNumber, StaticScope staticScope, Signature signature, String prefix, boolean isBeginEndBlock) {
this(manager, lexicalParent, lexicalParent.getFileName(), lineNumber, staticScope, prefix);
this.argumentType = argumentType;
this.signature = signature;
lexicalParent.addClosure(this);

@@ -101,13 +90,13 @@ public IRClosure(IRManager manager, IRScope lexicalParent, int lineNumber, Stati
staticScope.setScopeType(this.getScopeType());
}
}

this.nestingDepth++;
}

@Override
public InterpreterContext allocateInterpreterContext(Instr[] instructionList, boolean rebuild) {
return new ClosureInterpreterContext(this, instructionList, rebuild);
public InterpreterContext allocateInterpreterContext(List<Instr> instructions) {
interpreterContext = new ClosureInterpreterContext(this, instructions);

return interpreterContext;
}

public void setBeginEndBlock() {
@@ -174,17 +163,7 @@ public boolean isFlipScope() {
}

public String toStringBody() {
StringBuilder buf = new StringBuilder();
buf.append(getName()).append(" = { \n");

CFG c = getCFG();
if (c != null) {
buf.append("\nCFG:\n").append(c.toStringGraph()).append("\nInstructions:\n").append(c.toStringInstrs());
} else {
buf.append(toStringInstrs());
}
buf.append("\n}\n\n");
return buf.toString();
return new StringBuilder(getName()).append(" = {\n").append(toStringInstrs()).append("\n}\n\n").toString();
}

public BlockBody getBlockBody() {
@@ -203,7 +182,7 @@ protected LocalVariable findExistingLocalVariable(String name, int scopeDepth) {

public LocalVariable getNewLocalVariable(String name, int depth) {
if (depth == 0 && !(this instanceof IRFor)) {
LocalVariable lvar = new ClosureLocalVariable(this, name, 0, getStaticScope().addVariableThisScope(name));
LocalVariable lvar = new ClosureLocalVariable(name, 0, getStaticScope().addVariableThisScope(name));
localVars.put(name, lvar);
return lvar;
} else {
@@ -268,27 +247,27 @@ public LocalVariable getLocalVariable(String name, int depth) {
return lvar;
}

public int getNestingDepth() {
return nestingDepth;
}

protected IRClosure cloneForInlining(CloneInfo ii, IRClosure clone) {
clone.nestingDepth = this.nestingDepth;
// SSS FIXME: This is fragile. Untangle this state.
// Why is this being copied over to InterpretedIRBlockBody?
clone.setParameterList(this.parameterList);
clone.isBeginEndBlock = this.isBeginEndBlock;

SimpleCloneInfo clonedII = ii.cloneForCloningClosure(clone);

if (getCFG() != null) {
clone.setCFG(getCFG().clone(clonedII, clone));
} else {
for (Instr i: getInstrs()) {
clone.addInstr(i.clone(clonedII));
}
// if (getCFG() != null) {
// clone.setCFG(getCFG().clone(clonedII, clone));
// } else {
List<Instr> newInstrs = new ArrayList<>(interpreterContext.getInstructions().length);

for (Instr i: interpreterContext.getInstructions()) {
newInstrs.add(i.clone(clonedII));
}

clone.allocateInterpreterContext(newInstrs);

// }

return clone;
}

@@ -325,10 +304,6 @@ public Signature getSignature() {
return signature;
}

public int getArgumentType() {
return argumentType;
}

public void setHandle(Handle handle) {
this.handle = handle;
}
6 changes: 4 additions & 2 deletions core/src/main/java/org/jruby/ir/IREvalScript.java
Original file line number Diff line number Diff line change
@@ -35,8 +35,10 @@ public IREvalScript(IRManager manager, IRScope lexicalParent, String fileName,
}

@Override
public InterpreterContext allocateInterpreterContext(Instr[] instructionList, boolean rebuild) {
return new BeginEndInterpreterContext(this, instructionList, rebuild);
public InterpreterContext allocateInterpreterContext(List<Instr> instructions) {
interpreterContext = new BeginEndInterpreterContext(this, instructions);

return interpreterContext;
}

@Override
1 change: 1 addition & 0 deletions core/src/main/java/org/jruby/ir/IRFlags.java
Original file line number Diff line number Diff line change
@@ -44,6 +44,7 @@ public enum IRFlags {
HAS_EXPLICIT_CALL_PROTOCOL, // contains call protocol instrs => we don't need to manage bindings frame implicitly
HAS_LOOPS, // has a loop
HAS_NONLOCAL_RETURNS, // has a non-local return
MAYBE_USING_REFINEMENTS, // a call to 'using' discovered...is it "the" using...maybe?
RECEIVES_CLOSURE_ARG, // This scope (or parent receives a closure
RECEIVES_KEYWORD_ARGS, // receives keyword args
REQUIRES_DYNSCOPE, // does this scope require a dynamic scope?
8 changes: 4 additions & 4 deletions core/src/main/java/org/jruby/ir/IRFor.java
Original file line number Diff line number Diff line change
@@ -11,12 +11,12 @@
* Represents a 'for' loop
*/
public class IRFor extends IRClosure {
public IRFor(IRManager manager, IRScope lexicalParent, int lineNumber, StaticScope staticScope, Signature signature, int argumentType, String labelPrefix) {
super(manager, lexicalParent, lineNumber, StaticScopeFactory.newIRBlockScope(staticScope), signature, argumentType, labelPrefix, labelPrefix == "_BEGIN_");
public IRFor(IRManager manager, IRScope lexicalParent, int lineNumber, StaticScope staticScope, Signature signature, String labelPrefix) {
super(manager, lexicalParent, lineNumber, StaticScopeFactory.newIRBlockScope(staticScope), signature, labelPrefix, labelPrefix == "_BEGIN_");
}

public IRFor(IRManager manager, IRScope lexicalParent, int lineNumber, StaticScope staticScope, Signature signature, int argumentType) {
this(manager, lexicalParent, lineNumber, StaticScopeFactory.newIRBlockScope(staticScope), signature, argumentType, "_FOR_LOOP_");
public IRFor(IRManager manager, IRScope lexicalParent, int lineNumber, StaticScope staticScope, Signature signature) {
this(manager, lexicalParent, lineNumber, StaticScopeFactory.newIRBlockScope(staticScope), signature, "_FOR_LOOP_");
}

/** Used by cloning code */
49 changes: 45 additions & 4 deletions core/src/main/java/org/jruby/ir/IRManager.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.jruby.ir;

import java.util.EnumSet;
import org.jruby.RubyInstanceConfig;
import org.jruby.ir.instructions.Instr;
import org.jruby.ir.listeners.IRScopeListener;
import org.jruby.ir.listeners.InstructionsListener;
import org.jruby.ir.operands.*;
@@ -14,13 +16,24 @@
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.jruby.ir.passes.DeadCodeElimination;
import org.jruby.ir.passes.OptimizeDelegationPass;
import org.jruby.ir.passes.OptimizeDynScopesPass;
import org.jruby.ir.passes.OptimizeTempVarsPass;

import static org.jruby.ir.IRFlags.RECEIVES_CLOSURE_ARG;
import static org.jruby.ir.IRFlags.REQUIRES_DYNSCOPE;

public class IRManager {
public static final String SAFE_COMPILER_PASSES = "";
public static final String DEFAULT_COMPILER_PASSES = "OptimizeTempVarsPass,LocalOptimizationPass";
public static final String DEFAULT_JIT_PASSES = "OptimizeDelegationPass,DeadCodeElimination,AddLocalVarLoadStoreInstructions,OptimizeDynScopesPass,AddCallProtocolInstructions,EnsureTempsAssigned";
public static final String DEFAULT_BUILD_PASSES = "LocalOptimizationPass";
public static final String DEFAULT_JIT_PASSES = "LocalOptimizationPass,OptimizeDelegationPass,DeadCodeElimination,AddLocalVarLoadStoreInstructions,OptimizeDynScopesPass,AddCallProtocolInstructions,EnsureTempsAssigned";
public static final String DEFAULT_INLINING_COMPILER_PASSES = "LocalOptimizationPass";

private final CompilerPass deadCodeEliminationPass = new DeadCodeElimination();
private final CompilerPass optimizeDynScopesPass = new OptimizeDynScopesPass();
private final CompilerPass optimizeDelegationPass = new OptimizeDelegationPass();

private int dummyMetaClassCount = 0;
private final IRModuleBody object = new IRClassBody(this, null, "Object", "", 0, null);
private final Nil nil = new Nil();
@@ -40,12 +53,14 @@ public class IRManager {
private List<CompilerPass> inliningCompilerPasses;
private List<CompilerPass> jitPasses;
private List<CompilerPass> safePasses;
private final RubyInstanceConfig config;

// If true then code will not execute (see ir/ast tool)
private boolean dryRun = false;

public IRManager() {
compilerPasses = CompilerPass.getPassesFromString(RubyInstanceConfig.IR_COMPILER_PASSES, DEFAULT_COMPILER_PASSES);
public IRManager(RubyInstanceConfig config) {
this.config = config;
compilerPasses = CompilerPass.getPassesFromString(RubyInstanceConfig.IR_COMPILER_PASSES, DEFAULT_BUILD_PASSES);
inliningCompilerPasses = CompilerPass.getPassesFromString(RubyInstanceConfig.IR_COMPILER_PASSES, DEFAULT_INLINING_COMPILER_PASSES);
jitPasses = CompilerPass.getPassesFromString(RubyInstanceConfig.IR_JIT_PASSES, DEFAULT_JIT_PASSES);
safePasses = CompilerPass.getPassesFromString(null, SAFE_COMPILER_PASSES);
@@ -203,4 +218,30 @@ public TemporaryLocalVariable newTemporaryLocalVariable(int index) {

return tempVar;
}

public Instr[] optimizeTemporaryVariablesIfEnabled(IRScope scope, Instr[] instrs) {
// FIXME: Make this check ir.passes and not run if ir.passes is set and does not contain opttempvars.
return OptimizeTempVarsPass.optimizeTmpVars(scope, instrs);
}

/**
* For scopes that don't require a dynamic scope we can run DCE and some other passes which cannot
* be stymied by escaped bindings.
*/
protected void optimizeIfSimpleScope(IRScope scope) {
// We cannot pick the passes if we want an explicit set to run.
if (RubyInstanceConfig.IR_COMPILER_PASSES != null) return;

EnumSet<IRFlags> flags = scope.getFlags();

if (!scope.isUnsafeScope() && !flags.contains(REQUIRES_DYNSCOPE)) {
if (flags.contains(RECEIVES_CLOSURE_ARG)) optimizeDelegationPass.run(scope);
deadCodeEliminationPass.run(scope);
optimizeDynScopesPass.run(scope);
}
}

public RubyInstanceConfig getInstanceConfig() {
return config;
}
}
45 changes: 21 additions & 24 deletions core/src/main/java/org/jruby/ir/IRMethod.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
package org.jruby.ir;

import org.jruby.ast.MethodDefNode;
import org.jruby.internal.runtime.methods.IRMethodArgs;
import org.jruby.ir.interpreter.InterpreterContext;
import org.jruby.ir.operands.LocalVariable;
import org.jruby.ir.representations.BasicBlock;
import org.jruby.parser.StaticScope;

import java.lang.invoke.MethodType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jruby.ast.MethodDefNode;
import org.jruby.ir.interpreter.InterpreterContext;
import org.jruby.ir.operands.LocalVariable;
import org.jruby.ir.representations.BasicBlock;
import org.jruby.parser.StaticScope;

public class IRMethod extends IRScope {
public final boolean isInstanceMethod;

// Argument description of the form [:req, "a"], [:opt, "b"] ..
private List<String[]> argDesc;
private String[] argDesc;

// Signatures to the jitted versions of this method
private Map<Integer, MethodType> signatures;
@@ -34,43 +32,45 @@ public IRMethod(IRManager manager, IRScope lexicalParent, MethodDefNode defn, St

this.defn = defn;
this.isInstanceMethod = isInstanceMethod;
this.argDesc = new ArrayList<>();
this.signatures = new HashMap<>();
this.argDesc = null;
this.signatures = null;

if (!getManager().isDryRun() && staticScope != null) {
staticScope.setIRScope(this);
staticScope.setScopeType(this.getScopeType());
}
}

/** Run any necessary passes to get the IR ready for interpretation */
public synchronized InterpreterContext prepareForInterpretation() {
public synchronized InterpreterContext lazilyAcquireInterpreterContext() {
if (defn != null) {
IRBuilder.topIRBuilder(getManager(), this).defineMethodInner(defn, getLexicalParent());

defn = null;
}

return super.prepareForInterpretation();
return interpreterContext;
}

public synchronized List<BasicBlock> prepareForCompilation() {
if (defn != null) prepareForInterpretation();
public synchronized BasicBlock[] prepareForInitialCompilation() {
if (defn != null) lazilyAcquireInterpreterContext();

return super.prepareForCompilation();
return super.prepareForInitialCompilation();
}

@Override
public IRScopeType getScopeType() {
return isInstanceMethod ? IRScopeType.INSTANCE_METHOD : IRScopeType.CLASS_METHOD;
}

public void addArgDesc(IRMethodArgs.ArgType type, String argName) {
argDesc.add(new String[]{type.name(), argName});
public String[] getArgDesc() {
return argDesc;
}

public List<String[]> getArgDesc() {
return argDesc;
/**
* Set upon completion of IRBuild of this IRMethod.
*/
public void setArgDesc(String[] argDesc) {
this.argDesc = argDesc;
}

@Override
@@ -87,13 +87,10 @@ public LocalVariable getLocalVariable(String name, int scopeDepth) {
}

public void addNativeSignature(int arity, MethodType signature) {
if (signatures == null) signatures = new HashMap<>();
signatures.put(arity, signature);
}

public MethodType getNativeSignature(int arity) {
return signatures.get(arity);
}

public Map<Integer, MethodType> getNativeSignatures() {
return Collections.unmodifiableMap(signatures);
}
686 changes: 215 additions & 471 deletions core/src/main/java/org/jruby/ir/IRScope.java

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions core/src/main/java/org/jruby/ir/IRScriptBody.java
Original file line number Diff line number Diff line change
@@ -31,8 +31,10 @@ public void setTopLevelBindingScope(DynamicScope tlbScope) {
}

@Override
public InterpreterContext allocateInterpreterContext(Instr[] instructionList, boolean rebuild) {
return new BeginEndInterpreterContext(this, instructionList, rebuild);
public InterpreterContext allocateInterpreterContext(List<Instr> instructions) {
interpreterContext = new BeginEndInterpreterContext(this, instructions);

return interpreterContext;
}

@Override
@@ -53,7 +55,7 @@ public String toString() {
/* Record a begin block -- not all scope implementations can handle them */
@Override
public void recordBeginBlock(IRClosure beginBlockClosure) {
if (beginBlocks == null) beginBlocks = new ArrayList<IRClosure>();
if (beginBlocks == null) beginBlocks = new ArrayList<>();
beginBlockClosure.setBeginEndBlock();
beginBlocks.add(beginBlockClosure);
}
5 changes: 4 additions & 1 deletion core/src/main/java/org/jruby/ir/IRTranslator.java
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
import org.jruby.Ruby;
import org.jruby.RubyInstanceConfig;
import org.jruby.ast.RootNode;
import org.jruby.ir.interpreter.InterpreterContext;
import org.jruby.ir.persistence.IRWriter;
import org.jruby.ir.persistence.IRWriterFile;
import org.jruby.ir.persistence.util.IRFileExpert;
@@ -23,7 +24,9 @@ public R execute(Ruby runtime, ParseResult result, S specificObject) {
if (result instanceof IRScriptBody) { // Already have it (likely from read from persistent store).
scope = (IRScriptBody) result;
} else if (result instanceof RootNode) { // Need to perform create IR from AST
scope = IRBuilder.buildRoot(runtime.getIRManager(), (RootNode) result);
// FIXME: In terms of writing and reading we should emit enough to rebuild IC + minimal IRScope state
InterpreterContext ic = IRBuilder.buildRoot(runtime.getIRManager(), (RootNode) result);
scope = (IRScriptBody) ic.getScope();
scope.setTopLevelBindingScope(((RootNode) result).getScope());

if (RubyInstanceConfig.IR_WRITING) {
13 changes: 0 additions & 13 deletions core/src/main/java/org/jruby/ir/dataflow/DataFlowConstants.java

This file was deleted.

34 changes: 9 additions & 25 deletions core/src/main/java/org/jruby/ir/dataflow/DataFlowProblem.java
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

public abstract class DataFlowProblem<T extends DataFlowProblem<T, U>, U extends FlowGraphNode<T, U>> {
/* -------------- Public fields and methods below ---------------- */
public enum DF_Direction { FORWARD, BACKWARD, BIDIRECTIONAL };
public enum DF_Direction { FORWARD, BACKWARD, BIDIRECTIONAL }

public final DF_Direction direction;

@@ -47,7 +47,7 @@ public void compute_MOP_Solution() {
LinkedList<U> workList = generateWorkList();

// 2. Initialize a bitset with a flag set for all basic blocks
int numNodes = scope.cfg().getMaxNodeID();
int numNodes = scope.getCFG().getMaxNodeID();
BitSet bbSet = new BitSet(1+numNodes);
bbSet.flip(0, numNodes); // set all bits from default of 0 to 1 (enebo: could we invert this in algo?)

@@ -62,9 +62,9 @@ public void compute_MOP_Solution() {
* on direction.
*/
protected LinkedList<U> generateWorkList() {
LinkedList<U> wl = new LinkedList<U>();
LinkedList<U> wl = new LinkedList<>();
Iterator<BasicBlock> it = direction == DF_Direction.FORWARD ?
scope.cfg().getReversePostOrderTraverser() : scope.cfg().getPostOrderTraverser();
scope.getCFG().getReversePostOrderTraverser() : scope.getCFG().getPostOrderTraverser();

while (it.hasNext()) {
wl.add(getFlowGraphNode(it.next()));
@@ -77,14 +77,6 @@ public int getDFVarsCount() {
return nextVariableId + 1;
}

public Iterable<BasicBlock> getIncomingSourcesOf(BasicBlock bb) {
return scope.cfg().getIncomingSources(bb);
}

public Iterable<BasicBlock> getOutgoingDestinationsOf(BasicBlock bb) {
return scope.cfg().getOutgoingDestinations(bb);
}

/* Individual analyses should override this */
public String getDataFlowVarsForOutput() {
return "";
@@ -111,11 +103,11 @@ public U getFlowGraphNode(BasicBlock bb) {
}

public U getEntryNode() {
return getFlowGraphNode(scope.cfg().getEntryBB());
return getFlowGraphNode(scope.getCFG().getEntryBB());
}

public U getExitNode() {
return getFlowGraphNode(scope.cfg().getExitBB());
return getFlowGraphNode(scope.getCFG().getExitBB());
}

public int addDataFlowVar() {
@@ -134,18 +126,10 @@ public int addDataFlowVar() {
private Map<BasicBlock, U> basicBlockToFlowGraph;

private void buildFlowGraph() {
flowGraphNodes = new LinkedList<U>();
basicBlockToFlowGraph = new HashMap<BasicBlock, U>();

// SSS FIXME: We need to do more work on our dependency setup.
// Since LVA runs analyses on nested closures, and dependencies aren't
// checked on nested scopes, it can happen that for closures,
// the cfg hasn't been built yet.
if (scope.getCFG() == null) {
scope.buildCFG();
}
flowGraphNodes = new LinkedList<>();
basicBlockToFlowGraph = new HashMap<>();

for (BasicBlock bb: scope.cfg().getBasicBlocks()) {
for (BasicBlock bb: scope.getCFG().getBasicBlocks()) {
U fgNode = buildFlowGraphNode(bb);
fgNode.init();
fgNode.buildDataFlowVars();
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ public FlowGraphNode(T problem, BasicBlock basicBlock) {
this.basicBlock = basicBlock;

// Cache the rescuer node for easy access
rescuer = problem.getScope().cfg().getRescuerBBFor(basicBlock);
rescuer = problem.getScope().getCFG().getRescuerBBFor(basicBlock);
}

/**
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package org.jruby.ir.dataflow.analyses;

import org.jruby.dirgra.Edge;
import org.jruby.ir.IRClosure;
import org.jruby.ir.IRScope;
import org.jruby.ir.dataflow.DataFlowConstants;
import org.jruby.ir.dataflow.FlowGraphNode;
import org.jruby.ir.instructions.ClosureAcceptingInstr;
import org.jruby.ir.instructions.Instr;
Original file line number Diff line number Diff line change
@@ -87,7 +87,7 @@ public Set<LocalVariable> addLiveLocalVars(Set<LocalVariable> list, BitSet livin
*/
public List<Variable> getVarsLiveOnScopeEntry() {
List<Variable> liveVars = new ArrayList<Variable>();
BitSet liveIn = getFlowGraphNode(getScope().cfg().getEntryBB()).getLiveOutBitSet();
BitSet liveIn = getFlowGraphNode(getScope().getCFG().getEntryBB()).getLiveOutBitSet();

for (int i = 0; i < liveIn.size(); i++) {
if (!liveIn.get(i)) continue;
Original file line number Diff line number Diff line change
@@ -248,7 +248,7 @@ public void addLoads(Map<Operand, Operand> varRenameMap) {
// System.out.println("\t--> Reqd loads : " + java.util.Arrays.toString(reqdLoads.toArray()));
for (LocalVariable v : reqdLoads) {
if (scope.usesLocalVariable(v) || scope.definesLocalVariable(v)) {
if (isEvalScript || !(v instanceof ClosureLocalVariable) || (scope != ((ClosureLocalVariable)v).definingScope)) {
if (isEvalScript || !(v instanceof ClosureLocalVariable) || !((ClosureLocalVariable)v).isDefinedLocally()) {
it.add(new LoadLocalVarInstr(scope, getLocalVarReplacement(v, scope, varRenameMap), v));
}
}
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@
import org.jruby.ir.IRClosure;
import org.jruby.ir.IRScope;
import org.jruby.ir.Operation;
import org.jruby.ir.dataflow.DataFlowConstants;
import org.jruby.ir.dataflow.FlowGraphNode;
import org.jruby.ir.instructions.*;
import org.jruby.ir.operands.*;
@@ -92,7 +91,7 @@ public void applyTransferFunction(Instr i) {
// Allocate a new hash-set and modify it to get around ConcurrentModificationException on dirtyVars
Set<LocalVariable> newDirtyVars = new HashSet<LocalVariable>(dirtyVars);
for (LocalVariable v : dirtyVars) {
if ((v instanceof ClosureLocalVariable) && ((ClosureLocalVariable)v).definingScope != scope) {
if ((v instanceof ClosureLocalVariable) && !((ClosureLocalVariable)v).isDefinedLocally()) {
newDirtyVars.remove(v);
}
}
@@ -157,7 +156,7 @@ public boolean addStores(Map<Operand, Operand> varRenameMap, Set<LocalVariable>
// i,j are dirty inside the block, but not used outside

if (basicBlock.isExitBB()) {
LiveVariablesProblem lvp = (LiveVariablesProblem)scope.getDataFlowSolution(DataFlowConstants.LVP_NAME);
LiveVariablesProblem lvp = scope.getLiveVariablesProblem();
java.util.Collection<LocalVariable> liveVars = lvp.getVarsLiveOnScopeExit();
if (liveVars != null) {
dirtyVars.retainAll(liveVars); // Intersection with variables live on exit from the scope
@@ -218,7 +217,7 @@ public boolean addStores(Map<Operand, Operand> varRenameMap, Set<LocalVariable>
// instance from a different depth and that could mislead us. See if there is a way to fix this.
// If we introduced 'definingScope' in all local variables, we could simply check for scope match
// without the instanceof check here.
if ( (v instanceof ClosureLocalVariable && ((ClosureLocalVariable)v).definingScope != scope)
if ( (v instanceof ClosureLocalVariable && !((ClosureLocalVariable)v).isDefinedLocally())
|| (!(v instanceof ClosureLocalVariable) && scope.getScopeType().isClosureType()))
{
addedStores = true;
@@ -242,10 +241,7 @@ public boolean addStores(Map<Operand, Operand> varRenameMap, Set<LocalVariable>
// If this also happens to be exit BB, we would have intersected already earlier -- so no need to do it again!

if (!basicBlock.isExitBB()) {
LiveVariablesProblem lvp = (LiveVariablesProblem)scope.getDataFlowSolution(DataFlowConstants.LVP_NAME);
if (lvp == null) {
System.out.println("LVP missing for " + scope);
}
LiveVariablesProblem lvp = scope.getLiveVariablesProblem();
java.util.Collection<LocalVariable> liveVars = lvp.getVarsLiveOnScopeExit();
if (liveVars != null) {
dirtyVars.retainAll(liveVars); // Intersection with variables live on exit from the scope
Original file line number Diff line number Diff line change
@@ -67,7 +67,7 @@ boolean addClosureExitStoreLocalVars(ListIterator<Instr> instrs, Set<LocalVariab
boolean addedStores = false;
boolean isEvalScript = scope instanceof IREvalScript;
for (LocalVariable v : dirtyVars) {
if (isEvalScript || !(v instanceof ClosureLocalVariable) || (scope != ((ClosureLocalVariable)v).definingScope)) {
if (isEvalScript || !(v instanceof ClosureLocalVariable) || !((ClosureLocalVariable)v).isDefinedLocally()) {
addedStores = true;
instrs.add(new StoreLocalVarInstr(getLocalVarReplacement(v, varRenameMap), scope, v));
}
@@ -89,7 +89,7 @@ public void addStores(Map<Operand, Operand> varRenameMap) {

Set<LocalVariable> dirtyVars = null;
IRScope cfgScope = getScope();
CFG cfg = cfgScope.cfg();
CFG cfg = cfgScope.getCFG();

this.scopeHasLocalVarStores = false;
this.scopeHasUnrescuedExceptions = false;
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
import org.jruby.dirgra.Edge;
import org.jruby.ir.IRClosure;
import org.jruby.ir.Operation;
import org.jruby.ir.dataflow.DataFlowConstants;
import org.jruby.ir.dataflow.FlowGraphNode;
import org.jruby.ir.instructions.*;
import org.jruby.ir.instructions.boxing.*;
@@ -282,11 +281,11 @@ public void applyTransferFunction(Instr i) {
} else if (o instanceof WrappedIRClosure) {
// Fetch the nested unboxing-analysis problem, creating one if necessary
IRClosure cl = ((WrappedIRClosure)o).getClosure();
UnboxableOpsAnalysisProblem subProblem = (UnboxableOpsAnalysisProblem)cl.getDataFlowSolution(DataFlowConstants.UNBOXING);
UnboxableOpsAnalysisProblem subProblem = cl.getUnboxableOpsAnalysisProblem();
if (subProblem == null) {
subProblem = new UnboxableOpsAnalysisProblem();
subProblem.setup(cl);
cl.setDataFlowSolution(DataFlowConstants.UNBOXING, subProblem);
cl.putUnboxableOpsAnalysisProblem(subProblem);
}

UnboxableOpsAnalysisNode exitNode = subProblem.getExitNode();
@@ -565,7 +564,7 @@ public void unbox(Map<Variable, TemporaryLocalVariable> unboxMap) {
}

// Only worry about vars live on exit from the BB
LiveVariablesProblem lvp = (LiveVariablesProblem)problem.getScope().getDataFlowSolution(DataFlowConstants.LVP_NAME);
LiveVariablesProblem lvp = problem.getScope().getLiveVariablesProblem();
BitSet liveVarsSet = lvp.getFlowGraphNode(basicBlock).getLiveInBitSet();

List<Instr> newInstrs = new ArrayList<Instr>();
@@ -655,7 +654,7 @@ public void unbox(Map<Variable, TemporaryLocalVariable> unboxMap) {

// Fetch the nested unboxing-analysis problem, creating one if necessary
IRClosure cl = ((WrappedIRClosure)o).getClosure();
UnboxableOpsAnalysisProblem subProblem = (UnboxableOpsAnalysisProblem)cl.getDataFlowSolution(DataFlowConstants.UNBOXING);
UnboxableOpsAnalysisProblem subProblem = cl.getUnboxableOpsAnalysisProblem();
UnboxableOpsAnalysisNode exitNode = subProblem.getExitNode();

// Compute solution
11 changes: 8 additions & 3 deletions core/src/main/java/org/jruby/ir/instructions/CallInstr.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.jruby.ir.instructions;

import org.jruby.ir.IRScope;
import org.jruby.ir.IRVisitor;
import org.jruby.ir.Operation;
import org.jruby.ir.instructions.specialized.OneFixnumArgNoBlockCallInstr;
@@ -18,11 +19,15 @@
public class CallInstr extends CallBase implements ResultInstr {
protected Variable result;

public static CallInstr create(Variable result, String name, Operand receiver, Operand[] args, Operand closure) {
return create(CallType.NORMAL, result, name, receiver, args, closure);
public static CallInstr create(IRScope scope, Variable result, String name, Operand receiver, Operand[] args, Operand closure) {
return create(scope, CallType.NORMAL, result, name, receiver, args, closure);
}

public static CallInstr create(CallType callType, Variable result, String name, Operand receiver, Operand[] args, Operand closure) {
public static CallInstr create(IRScope scope, CallType callType, Variable result, String name, Operand receiver, Operand[] args, Operand closure) {
if (scope.maybeUsingRefinements()) {
// FIXME: Make same instr with refined callSite here or push though all path below
}

if (!containsArgSplat(args)) {
boolean hasClosure = closure != null;

Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@

import org.jruby.RubyModule;
import org.jruby.internal.runtime.methods.InterpretedIRBodyMethod;
import org.jruby.internal.runtime.methods.InterpretedIRMethod;
import org.jruby.ir.IRVisitor;
import org.jruby.ir.Operation;
import org.jruby.ir.operands.Operand;
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package org.jruby.ir.interpreter;

import java.util.ArrayList;
import java.util.List;
import org.jruby.ir.IRClosure;
import org.jruby.ir.IRScope;
import org.jruby.ir.instructions.Instr;
import org.jruby.ir.operands.WrappedIRClosure;

/**
* Script body and Evals both have begin/end bodies and need the same state
@@ -14,8 +12,8 @@
public class BeginEndInterpreterContext extends InterpreterContext {
private List<IRClosure> beginBlocks;

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

beginBlocks = scope.getBeginBlocks();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.jruby.ir.interpreter;

import java.util.List;
import org.jruby.ir.IRClosure;
import org.jruby.ir.instructions.Instr;
import org.jruby.runtime.DynamicScope;
@@ -9,8 +10,8 @@
* Interpreter knowledge needed to interpret a closure.
*/
public class ClosureInterpreterContext extends InterpreterContext {
public ClosureInterpreterContext(IRClosure scope, Instr[] instructions, boolean rebuild) {
super(scope, instructions, rebuild);
public ClosureInterpreterContext(IRClosure scope, List<Instr> instructions) {
super(scope, instructions);
}

/**
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package org.jruby.ir.interpreter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jruby.ir.IRFlags;
import org.jruby.ir.IRMethod;
import org.jruby.ir.IRScope;
import org.jruby.ir.dataflow.DataFlowProblem;
import org.jruby.ir.instructions.Instr;
import org.jruby.ir.instructions.ReceiveSelfInstr;
import org.jruby.ir.passes.CompilerPass;
import org.jruby.ir.representations.BasicBlock;
import org.jruby.ir.representations.CFG;
import org.jruby.ir.representations.CFGLinearizer;

/**
* Created by enebo on 2/27/15.
*/
public class FullInterpreterContext extends InterpreterContext {
private CFG cfg;

// Creation of this field will happen in generateInstructionsForInterpretation or during IRScope.prepareForInitialCompilation.
// FIXME: At some point when we relinearize after running another phase of passes we should document that here to know how this field is changed
private BasicBlock[] linearizedBBList = null;

/** Map of name -> dataflow problem */
private Map<String, DataFlowProblem> dataFlowProblems;

/** What passes have been run on this scope? */
private List<CompilerPass> executedPasses = new ArrayList<>();

// FIXME: Perhaps abstract IC into interface of base class so we do not have a null instructions field here
public FullInterpreterContext(IRScope scope, Instr[] instructions) {
super(scope, null);

cfg = buildCFG(instructions);
}

/**
* have this interpretercontext fully built? This is slightly more complicated than this simple check, but it
* should work. In -X-C full builds we linearize at the beginning of our generateInstructionsForIntepretation
* method. Last thing we do essentially is set instructions to be something. For JIT builds last thing we
* need to check is whether we have linearized the BB list.
*/
@Override
public boolean buildComplete() {
return linearizedBBList != null;
}

public BasicBlock[] linearizeBasicBlocks() {
linearizedBBList = CFGLinearizer.linearize(cfg);
return linearizedBBList;
}

private CFG buildCFG(Instr[] instructions) {
CFG newCFG = new CFG(getScope());

newCFG.build(instructions);

return newCFG;
}

@Override
public boolean hasExplicitCallProtocol() {
return getScope().getFlags().contains(IRFlags.HAS_EXPLICIT_CALL_PROTOCOL);
}

@Override
public boolean pushNewDynScope() {
return !getScope().getFlags().contains(IRFlags.DYNSCOPE_ELIMINATED) && !reuseParentDynScope();
}

@Override
public boolean popDynScope() {
return pushNewDynScope() || reuseParentDynScope();
}

@Override
public boolean reuseParentDynScope() {
return getScope().getFlags().contains(IRFlags.REUSE_PARENT_DYNSCOPE);
}

/** We plan on running this in full interpreted mode. This will fixup ipc, rpc, and generate instr list */
public void generateInstructionsForIntepretation() {
linearizeBasicBlocks();
boolean simple_method = getScope() instanceof IRMethod;

// Pass 1. Set up IPCs for labels and instructions and build linear instr list
List<Instr> newInstrs = new ArrayList<>();
int ipc = 0;
for (BasicBlock b: getLinearizedBBList()) {
// All same-named labels must be same Java instance for this to work or we would need
// to examine all Label operands and update this as well which would be expensive.
b.getLabel().setTargetPC(ipc);

List<Instr> bbInstrs = b.getInstrs();
int bbInstrsLength = bbInstrs.size();
// FIXME: Can be replaced with System.arrayCopy to avoid call newInstrs.add a zillion times
for (int i = 0; i < bbInstrsLength; i++) {
Instr instr = bbInstrs.get(i);
if (simple_method && SimpleMethodInterpreterEngine.OPERATIONS.get(instr.getOperation()) == null) simple_method = false;
if (!(instr instanceof ReceiveSelfInstr)) {
instr.setIPC(ipc);
newInstrs.add(instr);
ipc++;
}
}
}

if (simple_method) getScope().getFlags().add(IRFlags.SIMPLE_METHOD);

cfg.getExitBB().getLabel().setTargetPC(ipc + 1); // Exit BB ipc

Instr[] linearizedInstrArray = newInstrs.toArray(new Instr[newInstrs.size()]);

// Pass 2: Use ipc info from previous to mark all linearized instrs rpc
ipc = 0;
for (BasicBlock b : getLinearizedBBList()) {
BasicBlock rescuerBB = cfg.getRescuerBBFor(b);
int rescuerPC = rescuerBB == null ? -1 : rescuerBB.getLabel().getTargetPC();
for (Instr instr : b.getInstrs()) {
// FIXME: If we did not omit instrs from previous pass, we could end up just doing
// a size and for loop this n times instead of walking an examining each instr
if (!(instr instanceof ReceiveSelfInstr)) {
linearizedInstrArray[ipc].setRPC(rescuerPC);
ipc++;
}
}
}

instructions = linearizedInstrArray;
temporaryVariablecount = getScope().getTemporaryVariablesCount();

// System.out.println("SCOPE: " + getScope().getName());
// System.out.println("INSTRS: " + cfg.toStringInstrs());
}

@Override
public CFG getCFG() {
return cfg;
}

@Override
public void computeScopeFlagsFromInstructions() {
for (BasicBlock b: cfg.getBasicBlocks()) {
for (Instr i: b.getInstrs()) {
i.computeScopeFlags(getScope());
}
}
}

public Map<String, DataFlowProblem> getDataFlowProblems() {
if (dataFlowProblems == null) dataFlowProblems = new HashMap<>();
return dataFlowProblems;
}

public List<CompilerPass> getExecutedPasses() {
return executedPasses;
}

// FIXME: Potentially remove
public BasicBlock[] getLinearizedBBList() {
return linearizedBBList;
}

@Override
public String toStringInstrs() {
return "\nCFG:\n" + cfg.toStringGraph() + "\nInstructions:\n" + cfg.toStringInstrs();
}
}
12 changes: 3 additions & 9 deletions core/src/main/java/org/jruby/ir/interpreter/Interpreter.java
Original file line number Diff line number Diff line change
@@ -7,7 +7,6 @@
import org.jruby.RubyModule;
import org.jruby.RubyString;
import org.jruby.ast.RootNode;
import org.jruby.internal.runtime.methods.InterpretedIRMethod;
import org.jruby.ir.IRBindingEvalScript;
import org.jruby.ir.IRBuilder;
import org.jruby.ir.IRClosure;
@@ -17,7 +16,6 @@
import org.jruby.ir.IRTranslator;
import org.jruby.ir.operands.IRException;
import org.jruby.ir.operands.WrappedIRClosure;
import org.jruby.ir.representations.CFG;
import org.jruby.ir.runtime.IRBreakJump;
import org.jruby.ir.runtime.IRRuntimeHelpers;
import org.jruby.parser.StaticScope;
@@ -67,15 +65,14 @@ public static void runBeginBlocks(List<IRClosure> beBlocks, ThreadContext contex

for (IRClosure b: beBlocks) {
// SSS FIXME: Should I piggyback on WrappedIRClosure.retrieve or just copy that code here?
b.prepareForInterpretation();
Block blk = (Block)(new WrappedIRClosure(b.getSelf(), b)).retrieve(context, self, currScope, context.getCurrentScope(), temp);
blk.yield(context, null);
}
}

@Override
protected IRubyObject execute(Ruby runtime, IRScriptBody irScope, IRubyObject self) {
BeginEndInterpreterContext ic = (BeginEndInterpreterContext) irScope.prepareForInterpretation();
BeginEndInterpreterContext ic = (BeginEndInterpreterContext) irScope.getInterpreterContext();
ThreadContext context = runtime.getCurrentContext();
String name = ROOT;

@@ -233,12 +230,9 @@ private static BeginEndInterpreterContext prepareIC(ThreadContext context, Dynam
// we end up growing dynamicscope potentially based on any changes made.
staticScope.setIRScope(script);

IRBuilder.topIRBuilder(runtime.getIRManager(), script).buildEvalRoot(rootNode);
BeginEndInterpreterContext ic = (BeginEndInterpreterContext) script.prepareForInterpretation();
BeginEndInterpreterContext ic = (BeginEndInterpreterContext) IRBuilder.topIRBuilder(runtime.getIRManager(), script).buildEvalRoot(rootNode);

if (IRRuntimeHelpers.isDebug()) {
LOG.info(script.debugOutput());
}
if (IRRuntimeHelpers.isDebug()) LOG.info(script.debugOutput());

return ic;
}
171 changes: 88 additions & 83 deletions core/src/main/java/org/jruby/ir/interpreter/InterpreterContext.java
Original file line number Diff line number Diff line change
@@ -1,33 +1,29 @@
package org.jruby.ir.interpreter;

import org.jruby.ir.IRClassBody;
import org.jruby.ir.IREvalScript;
import java.util.Collection;
import java.util.List;
import org.jruby.ir.IRClosure;
import org.jruby.ir.IRFlags;
import org.jruby.ir.IRMetaClassBody;
import org.jruby.ir.IRMethod;
import org.jruby.ir.IRModuleBody;
import org.jruby.ir.IRScope;
import org.jruby.ir.instructions.Instr;
import org.jruby.ir.instructions.LabelInstr;
import org.jruby.ir.representations.CFG;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.DynamicScope;
import org.jruby.runtime.ThreadContext;

public class InterpreterContext {
private final int temporaryVariablecount;
private final int temporaryBooleanVariablecount;
private final int temporaryFixnumVariablecount;
private final int temporaryFloatVariablecount;
protected int temporaryVariablecount;

private final String name;
private final String fileName;
private final int lineNumber;
private final StaticScope staticScope;
private final Instr[] instructions;
// startup interp will mark this at construction and not change but full interpreter will write it
// much later after running compiler passes. JIT will not use this field at all.
protected Instr[] instructions;

// Cached computed fields
private final boolean hasExplicitCallProtocol;
private final boolean isDynscopeEliminated;
private final boolean pushNewDynScope;
private final boolean reuseParentDynScope;
private final boolean popDynScope;
@@ -36,116 +32,105 @@ public class InterpreterContext {

private final static InterpreterEngine BODY_INTERPRETER = new BodyInterpreterEngine();
private final static InterpreterEngine DEFAULT_INTERPRETER = new InterpreterEngine();
private final static InterpreterEngine STARTUP_INTERPRETER = new StartupInterpreterEngine();
private final static InterpreterEngine SIMPLE_METHOD_INTERPRETER = new InterpreterEngine();
public final InterpreterEngine engine;

// FIXME: Hack this should be a clone eventually since JIT might change this. Comment for it reflects what it should be.
// View of CFG at time of creating this context.
private CFG cfg = null;
private IRScope scope;

private int runCount = 0;
private boolean rebuilt = false;
public InterpreterContext(IRScope scope, List<Instr> instructions) {
this.scope = scope;

public InterpreterContext(IRScope scope, Instr[] instructions, boolean rebuild) {
//FIXME: Remove once we conditionally plug in CFG on debug-only
this.cfg = scope.getCFG();
this.rebuilt = rebuild;
if (this.rebuilt) {
this.runCount = 30;
}

/*
if (scope instanceof IRModuleBody || scope instanceof IRClassBody) {
engine = BODY_INTERPRETER;
// ENEBO: Playing with unboxable and subset instruction sets
//} else if (scope instanceof IRMethod && scope.getFlags().contains(IRFlags.SIMPLE_METHOD)) {
// engine = SIMPLE_METHOD_INTERPRETER;
} else {
engine = DEFAULT_INTERPRETER;
}
*/
engine = DEFAULT_INTERPRETER;
// FIXME: Hack null instructions means coming from FullInterpreterContext but this should be way cleaner
// For impl testing - engine = determineInterpreterEngine(scope);
engine = instructions == null ? DEFAULT_INTERPRETER : STARTUP_INTERPRETER;

this.name = scope.getName();
this.fileName = scope.getFileName();
this.lineNumber = scope.getLineNumber();
this.staticScope = scope.getStaticScope();
this.metaClassBodyScope = scope instanceof IRMetaClassBody;
this.temporaryVariablecount = scope.getTemporaryVariablesCount();
this.temporaryBooleanVariablecount = scope.getBooleanVariablesCount();
this.temporaryFixnumVariablecount = scope.getFixnumVariablesCount();
this.temporaryFloatVariablecount = scope.getFloatVariablesCount();
this.instructions = instructions;
this.instructions = instructions != null ? prepareBuildInstructions(instructions) : null;
this.hasExplicitCallProtocol = scope.getFlags().contains(IRFlags.HAS_EXPLICIT_CALL_PROTOCOL);
this.reuseParentDynScope = scope.getFlags().contains(IRFlags.REUSE_PARENT_DYNSCOPE);
this.isDynscopeEliminated = scope.getFlags().contains(IRFlags.DYNSCOPE_ELIMINATED);
this.pushNewDynScope = !isDynscopeEliminated && !reuseParentDynScope;
this.pushNewDynScope = !scope.getFlags().contains(IRFlags.DYNSCOPE_ELIMINATED) && !reuseParentDynScope;
this.popDynScope = this.pushNewDynScope || this.reuseParentDynScope;
this.receivesKeywordArguments = scope.getFlags().contains(IRFlags.RECEIVES_KEYWORD_ARGS);
}

public CFG getCFG() {
return this.cfg;
private InterpreterEngine determineInterpreterEngine(IRScope scope) {
if (scope instanceof IRModuleBody) {
return BODY_INTERPRETER;
} else if (scope instanceof IRMethod && scope.getFlags().contains(IRFlags.SIMPLE_METHOD)) {
return SIMPLE_METHOD_INTERPRETER; // ENEBO: Playing with unboxable and subset instruction sets
} else {
return DEFAULT_INTERPRETER;
}
}

private Instr[] prepareBuildInstructions(List<Instr> instructions) {
int length = instructions.size();
Instr[] linearizedInstrArray = instructions.toArray(new Instr[length]);
for (int ipc = 0; ipc < length; ipc++) {
Instr i = linearizedInstrArray[ipc];
i.setIPC(ipc);

if (i instanceof LabelInstr) ((LabelInstr) i).getLabel().setTargetPC(ipc + 1);
}

return linearizedInstrArray;
}

public boolean isRebuilt() {
return this.rebuilt;
public IRScope getScope() {
return scope;
}

public void incrementRunCount() {
this.runCount++;
/**
* Is the build complete? For startup builds, which this class represents, we finish build in the constructor
* so it is always complete. For FullInterpreterContext this is more complicated (see javadocs there for more
* info).
*/
public boolean buildComplete() {
return true;
}

public boolean needsRebuilding() {
return this.runCount == 30;
public CFG getCFG() {
return null;
}

public Object[] allocateTemporaryVariables() {
return temporaryVariablecount > 0 ? new Object[temporaryVariablecount] : null;
}

public boolean[] allocateTemporaryBooleanVariables() {
return temporaryBooleanVariablecount > 0 ? new boolean[temporaryBooleanVariablecount] : null;
return null;
}

public long[] allocateTemporaryFixnumVariables() {
return temporaryFixnumVariablecount > 0 ? new long[temporaryFixnumVariablecount] : null;
return null;
}

public double[] allocateTemporaryFloatVariables() {
return temporaryFloatVariablecount > 0 ? new double[temporaryFloatVariablecount] : null;
}

public String getFileName() {
return fileName;
return null;
}

public StaticScope getStaticScope() {
return staticScope;
}

public int getTemporaryVariablecount() {
return temporaryVariablecount;
return scope.getStaticScope();
}

public int getTemporaryBooleanVariablecount() {
return temporaryBooleanVariablecount;
}

public int getTemporaryFixnumVariablecount() {
return temporaryFixnumVariablecount;
public String getFileName() {
return scope.getFileName();
}

public int getTemporaryFloatVariablecount() {
return temporaryFloatVariablecount;
public String getName() {
return scope.getName();
}

public Instr[] getInstructions() {
return instructions;
}

public boolean isDynscopeEliminated() {
return isDynscopeEliminated;
public void computeScopeFlagsFromInstructions() {
for (Instr instr : getInstructions()) {
instr.computeScopeFlags(scope);
}
}

/**
@@ -154,9 +139,9 @@ public boolean isDynscopeEliminated() {
public DynamicScope newDynamicScope(ThreadContext context) {
// Add a parent-link to current dynscope to support non-local returns cheaply. This doesn't
// affect variable scoping since local variables will all have the right scope depth.
if (metaClassBodyScope) return DynamicScope.newDynamicScope(staticScope, context.getCurrentScope());
if (metaClassBodyScope) return DynamicScope.newDynamicScope(getStaticScope(), context.getCurrentScope());

return DynamicScope.newDynamicScope(staticScope);
return DynamicScope.newDynamicScope(getStaticScope());
}

public boolean hasExplicitCallProtocol() {
@@ -183,11 +168,11 @@ public boolean receivesKeywordArguments() {
public String toString() {
StringBuilder buf = new StringBuilder();

buf.append(fileName).append(':').append(lineNumber);
if (name != null) buf.append(' ').append(name);
buf.append(getFileName()).append(':').append(scope.getLineNumber());
if (getName() != null) buf.append(' ').append(getName());

if (cfg != null) {
buf.append("\nCFG:\n").append(cfg.toStringInstrs());
if (instructions == null) {
buf.append("No Instructions. Full Build before linearizeInstr?");
} else {
int i = 0;
for (Instr instr : instructions) {
@@ -199,4 +184,24 @@ public String toString() {

return buf.toString();
}
}

public String toStringInstrs() {
StringBuilder b = new StringBuilder();
int length = instructions.length;

for (int i = 0; i < length; i++) {
if (i > 0) b.append("\n");
b.append(" ").append(i).append('\t').append(instructions[i]);
}

Collection<IRClosure> nestedClosures = scope.getClosures();
if (nestedClosures != null && !nestedClosures.isEmpty()) {
b.append("\n\n------ Closures encountered in this scope ------\n");
for (IRClosure c: nestedClosures)
b.append(c.toStringBody());
b.append("------------------------------------------------\n");
}

return b.toString();
}
}
57 changes: 17 additions & 40 deletions core/src/main/java/org/jruby/ir/interpreter/InterpreterEngine.java
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@
import org.jruby.RubyFloat;
import org.jruby.RubyModule;
import org.jruby.common.IRubyWarnings;
import org.jruby.exceptions.RaiseException;
import org.jruby.exceptions.Unrescuable;
import org.jruby.ir.IRScope;
import org.jruby.ir.Operation;
@@ -53,8 +52,6 @@
import org.jruby.ir.operands.UnboxedFixnum;
import org.jruby.ir.operands.UnboxedFloat;
import org.jruby.ir.operands.Variable;
import org.jruby.ir.runtime.IRBreakJump;
import org.jruby.ir.runtime.IRReturnJump;
import org.jruby.ir.runtime.IRRuntimeHelpers;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.Block;
@@ -69,7 +66,8 @@
import java.util.Stack;

/**
* Base full interpreter. Subclasses can use utility methods here and override what they want.
* Base full interpreter. Subclasses can use utility methods here and override what they want. This method requires
* that it has fully built and has had a CFG made, etc...
*/
public class InterpreterEngine {

@@ -120,19 +118,11 @@ public IRubyObject interpret(ThreadContext context, IRubyObject self,
IRScope scope = currScope.getIRScope();
boolean acceptsKeywordArgument = interpreterContext.receivesKeywordArguments();

Stack<Integer> rescuePCs = null;
if (interpreterContext.getCFG() == null) {
rescuePCs = new Stack<Integer>();
}

// Init profiling this scope
boolean debug = IRRuntimeHelpers.isDebug();
boolean profile = IRRuntimeHelpers.inProfileMode();
Integer scopeVersion = profile ? Profiler.initProfiling(scope) : 0;

// Update profile
interpreterContext.incrementRunCount();

// Enter the looooop!
while (ipc < n) {
Instr instr = instrs[ipc];
@@ -177,32 +167,17 @@ public IRubyObject interpret(ThreadContext context, IRubyObject self,
currDynScope = interpreterContext.newDynamicScope(context);
context.pushScope(currDynScope);
} else {
processBookKeepingOp(context, instr, operation, name, args, self, block, implClass, rescuePCs);
processBookKeepingOp(context, instr, operation, name, args, self, block, implClass, null);
}
break;
case OTHER_OP:
processOtherOp(context, instr, operation, currDynScope, currScope, temp, self, blockType, floats, fixnums, booleans);
break;
}
} catch (Throwable t) {
if (debug) {
extractToMethodToAvoidC2Crash(context, instr, t);
}
if (debug) extractToMethodToAvoidC2Crash(instr, t);

if (rescuePCs == null) {
// When CFG is present
ipc = instr.getRPC();
} else {
// When CFG is absent
if (rescuePCs.empty()
|| (t instanceof IRBreakJump && (instr instanceof BreakInstr))
|| (t instanceof IRReturnJump && (instr instanceof NonlocalReturnInstr)))
{
ipc = -1;
} else {
ipc = rescuePCs.pop();
}
}
ipc = instr.getRPC();

if (debug) {
Interpreter.LOG.info("in : " + interpreterContext.getStaticScope().getIRScope() + ", caught Java throwable: " + t + "; excepting instr: " + instr);
@@ -221,7 +196,7 @@ public IRubyObject interpret(ThreadContext context, IRubyObject self,
throw context.runtime.newRuntimeError("BUG: interpreter fell through to end unexpectedly");
}

private static void interpretIntOp(AluInstr instr, Operation op, long[] fixnums, boolean[] booleans) {
protected static void interpretIntOp(AluInstr instr, Operation op, long[] fixnums, boolean[] booleans) {
TemporaryLocalVariable dst = (TemporaryLocalVariable)instr.getResult();
long i1 = getFixnumArg(fixnums, instr.getArg1());
long i2 = getFixnumArg(fixnums, instr.getArg2());
@@ -242,7 +217,7 @@ private static void interpretIntOp(AluInstr instr, Operation op, long[] fixnums,
}
}

private static void interpretFloatOp(AluInstr instr, Operation op, double[] floats, boolean[] booleans) {
protected static void interpretFloatOp(AluInstr instr, Operation op, double[] floats, boolean[] booleans) {
TemporaryLocalVariable dst = (TemporaryLocalVariable)instr.getResult();
double a1 = getFloatArg(floats, instr.getArg1());
double a2 = getFloatArg(floats, instr.getArg2());
@@ -258,7 +233,7 @@ private static void interpretFloatOp(AluInstr instr, Operation op, double[] floa
}
}

private static void receiveArg(ThreadContext context, Instr i, Operation operation, IRubyObject[] args, boolean acceptsKeywordArgument, DynamicScope currDynScope, Object[] temp, Object exception, Block block) {
protected static void receiveArg(ThreadContext context, Instr i, Operation operation, IRubyObject[] args, boolean acceptsKeywordArgument, DynamicScope currDynScope, Object[] temp, Object exception, Block block) {
Object result;
ResultInstr instr = (ResultInstr)i;

@@ -288,7 +263,7 @@ private static void receiveArg(ThreadContext context, Instr i, Operation operati
}
}

private static void processCall(ThreadContext context, Instr instr, Operation operation, DynamicScope currDynScope, StaticScope currScope, Object[] temp, IRubyObject self) {
protected static void processCall(ThreadContext context, Instr instr, Operation operation, DynamicScope currDynScope, StaticScope currScope, Object[] temp, IRubyObject self) {
Object result;

switch(operation) {
@@ -348,7 +323,7 @@ private static void processCall(ThreadContext context, Instr instr, Operation op
}
}

private static void processBookKeepingOp(ThreadContext context, Instr instr, Operation operation,
protected static void processBookKeepingOp(ThreadContext context, Instr instr, Operation operation,
String name, IRubyObject[] args, IRubyObject self, Block block,
RubyModule implClass, Stack<Integer> rescuePCs) {
switch(operation) {
@@ -397,8 +372,9 @@ private static void processBookKeepingOp(ThreadContext context, Instr instr, Ope
}
}

private static IRubyObject processReturnOp(ThreadContext context, Instr instr, Operation operation, DynamicScope currDynScope, Object[] temp, IRubyObject self, Block.Type blockType, StaticScope currScope)
{
protected static IRubyObject processReturnOp(ThreadContext context, Instr instr, Operation operation,
DynamicScope currDynScope, Object[] temp, IRubyObject self,
Block.Type blockType, StaticScope currScope) {
switch(operation) {
// --------- Return flavored instructions --------
case RETURN: {
@@ -423,8 +399,9 @@ private static IRubyObject processReturnOp(ThreadContext context, Instr instr, O
return null;
}

private static void processOtherOp(ThreadContext context, Instr instr, Operation operation, DynamicScope currDynScope, StaticScope currScope, Object[] temp, IRubyObject self, Block.Type blockType, double[] floats, long[] fixnums, boolean[] booleans)
{
protected static void processOtherOp(ThreadContext context, Instr instr, Operation operation, DynamicScope currDynScope,
StaticScope currScope, Object[] temp, IRubyObject self, Block.Type blockType,
double[] floats, long[] fixnums, boolean[] booleans) {
Object result;
switch(operation) {
case RECV_SELF:
@@ -537,7 +514,7 @@ private static void processOtherOp(ThreadContext context, Instr instr, Operation
* If you put this code into the method above it will hard crash some production builds of C2 in Java 8. We aren't
* sure exactly which builds, but it seems to appear more often in Linux builds than Mac. - Chris Seaton
*/
private static void extractToMethodToAvoidC2Crash(ThreadContext context, Instr instr, Throwable t) {
protected static void extractToMethodToAvoidC2Crash(Instr instr, Throwable t) {
if (!(t instanceof Unrescuable) && !instr.canRaiseException()) {
System.err.println("BUG: Got exception " + t + " but instr " + instr + " is not supposed to be raising exceptions!");
}
8 changes: 4 additions & 4 deletions core/src/main/java/org/jruby/ir/interpreter/Profiler.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.jruby.ir.interpreter;

import org.jruby.RubyModule;
import org.jruby.internal.runtime.methods.InterpretedIRMethod;
import org.jruby.internal.runtime.methods.MixedModeIRMethod;
import org.jruby.ir.*;
import org.jruby.ir.instructions.CallBase;
import org.jruby.ir.instructions.Instr;
@@ -19,7 +19,7 @@ private static class IRCallSite {
int v; // scope version
CallBase call;
long count;
InterpretedIRMethod tgtM;
MixedModeIRMethod tgtM;

public IRCallSite() {}

@@ -111,12 +111,12 @@ private static void analyzeProfile() {
CachingCallSite ccs = (CachingCallSite)runtimeCS;
CacheEntry ce = ccs.getCache();

if (!(ce.method instanceof InterpretedIRMethod)) {
if (!(ce.method instanceof MixedModeIRMethod)) {
// System.out.println("NOT IR-M!");
continue;
} else {
callSites.add(cs);
cs.tgtM = (InterpretedIRMethod)ce.method;
cs.tgtM = (MixedModeIRMethod)ce.method;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package org.jruby.ir.interpreter;

import java.util.Stack;
import org.jruby.RubyModule;
import org.jruby.common.IRubyWarnings;
import org.jruby.ir.IRScope;
import org.jruby.ir.Operation;
import org.jruby.ir.instructions.BreakInstr;
import org.jruby.ir.instructions.CheckForLJEInstr;
import org.jruby.ir.instructions.CopyInstr;
import org.jruby.ir.instructions.GetFieldInstr;
import org.jruby.ir.instructions.Instr;
import org.jruby.ir.instructions.JumpInstr;
import org.jruby.ir.instructions.NonlocalReturnInstr;
import org.jruby.ir.instructions.RuntimeHelperCall;
import org.jruby.ir.instructions.SearchConstInstr;
import org.jruby.ir.runtime.IRBreakJump;
import org.jruby.ir.runtime.IRReturnJump;
import org.jruby.ir.runtime.IRRuntimeHelpers;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.Block;
import org.jruby.runtime.DynamicScope;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.ivars.VariableAccessor;
import org.jruby.runtime.opto.ConstantCache;

/**
* This interpreter is meant to interpret the instructions generated directly from IRBuild.
*/
public class StartupInterpreterEngine extends InterpreterEngine {
public IRubyObject interpret(ThreadContext context, IRubyObject self,
InterpreterContext interpreterContext, RubyModule implClass,
String name, IRubyObject[] args, Block block, Block.Type blockType) {
Instr[] instrs = interpreterContext.getInstructions();
Object[] temp = interpreterContext.allocateTemporaryVariables();
int n = instrs.length;
int ipc = 0;
Object exception = null;

StaticScope currScope = interpreterContext.getStaticScope();
DynamicScope currDynScope = context.getCurrentScope();
IRScope scope = currScope.getIRScope();
boolean acceptsKeywordArgument = interpreterContext.receivesKeywordArguments();

Stack<Integer> rescuePCs = new Stack<>();

// Init profiling this scope
boolean debug = IRRuntimeHelpers.isDebug();
boolean profile = IRRuntimeHelpers.inProfileMode();
Integer scopeVersion = profile ? Profiler.initProfiling(scope) : 0;

// Enter the looooop!
while (ipc < n) {
Instr instr = instrs[ipc];
ipc++;
Operation operation = instr.getOperation();
if (debug) {
Interpreter.LOG.info("I: {}", instr);
Interpreter.interpInstrsCount++;
} else if (profile) {
Profiler.instrTick(operation);
Interpreter.interpInstrsCount++;
}

try {
switch (operation.opClass) {
case ARG_OP:
receiveArg(context, instr, operation, args, acceptsKeywordArgument, currDynScope, temp, exception, block);
break;
case CALL_OP:
if (profile) Profiler.updateCallSite(instr, scope, scopeVersion);
processCall(context, instr, operation, currDynScope, currScope, temp, self);
break;
case RET_OP:
return processReturnOp(context, instr, operation, currDynScope, temp, self, blockType, currScope);
case BRANCH_OP:
switch (operation) {
case JUMP: ipc = ((JumpInstr)instr).getJumpTarget().getTargetPC(); break;
default: ipc = instr.interpretAndGetNewIPC(context, currDynScope, currScope, self, temp, ipc); break;
}
break;
case BOOK_KEEPING_OP:
if (operation == Operation.PUSH_BINDING) {
// IMPORTANT: Preserve this update of currDynScope.
// This affects execution of all instructions in this scope
// which will now use the updated value of currDynScope.
currDynScope = interpreterContext.newDynamicScope(context);
context.pushScope(currDynScope);
} else {
processBookKeepingOp(context, instr, operation, name, args, self, block, implClass, rescuePCs);
}
break;
case OTHER_OP:
processOtherOp(context, instr, operation, currDynScope, currScope, temp, self, blockType);
break;
}
} catch (Throwable t) {
if (debug) extractToMethodToAvoidC2Crash(instr, t);

if (rescuePCs.empty() || (t instanceof IRBreakJump && instr instanceof BreakInstr) ||
(t instanceof IRReturnJump && instr instanceof NonlocalReturnInstr)) {
ipc = -1;
} else {
ipc = rescuePCs.pop();
}

if (debug) {
Interpreter.LOG.info("in : " + interpreterContext.getStaticScope().getIRScope() + ", caught Java throwable: " + t + "; excepting instr: " + instr);
Interpreter.LOG.info("ipc for rescuer: " + ipc);
}

if (ipc == -1) {
Helpers.throwException(t);
} else {
exception = t;
}
}
}

// Control should never get here!
throw context.runtime.newRuntimeError("BUG: interpreter fell through to end unexpectedly");
}

protected static void processOtherOp(ThreadContext context, Instr instr, Operation operation, DynamicScope currDynScope,
StaticScope currScope, Object[] temp, IRubyObject self, Block.Type blockType) {
switch(operation) {
case RECV_SELF:
break;
case COPY: {
CopyInstr c = (CopyInstr)instr;
setResult(temp, currDynScope, c.getResult(), retrieveOp(c.getSource(), context, self, currDynScope, currScope, temp));
break;
}
case GET_FIELD: {
GetFieldInstr gfi = (GetFieldInstr)instr;
IRubyObject object = (IRubyObject)gfi.getSource().retrieve(context, self, currScope, currDynScope, temp);
VariableAccessor a = gfi.getAccessor(object);
Object result = a == null ? null : (IRubyObject)a.get(object);
if (result == null) {
if (context.runtime.isVerbose()) {
context.runtime.getWarnings().warning(IRubyWarnings.ID.IVAR_NOT_INITIALIZED, "instance variable " + gfi.getRef() + " not initialized");
}
result = context.nil;
}
setResult(temp, currDynScope, gfi.getResult(), result);
break;
}
case SEARCH_CONST: {
SearchConstInstr sci = (SearchConstInstr)instr;
ConstantCache cache = sci.getConstantCache();
Object result = !ConstantCache.isCached(cache) ?
sci.cache(context, currScope, currDynScope, self, temp) : cache.value;
setResult(temp, currDynScope, sci.getResult(), result);
break;
}
case RUNTIME_HELPER: {
RuntimeHelperCall rhc = (RuntimeHelperCall)instr;
setResult(temp, currDynScope, rhc.getResult(),
rhc.callHelper(context, currScope, currDynScope, self, temp, blockType));
break;
}
case CHECK_FOR_LJE:
((CheckForLJEInstr) instr).check(context, currDynScope, blockType);
break;
case LOAD_FRAME_CLOSURE:
setResult(temp, currDynScope, instr, context.getFrameBlock());
return;
// ---------- All the rest ---------
default:
setResult(temp, currDynScope, instr, instr.interpret(context, currScope, currDynScope, self, temp));
break;
}
}

}
Loading

0 comments on commit 09b27ad

Please sign in to comment.