Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 2cdb4af7a2f5
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 771fe3f2a8e9
Choose a head ref
  • 3 commits
  • 8 files changed
  • 1 contributor

Commits on Oct 1, 2015

  1. Revert csv.rb to stock.

    headius committed Oct 1, 2015
    Copy the full SHA
    b228d42 View commit details
  2. Modify blocks to allow jitting like methods.

    Lots of duplicated code here between jitting methods and jitting
    blocks but the logic is largely the same. MixedModeIRBlockBody,
    like the method equivalent, increments a counter until it reaches
    JIT threshold. It then forces the block to JIT and binds it into
    a CompiledIRBlockBody, which it uses from then on to execute the
    block.
    
    Things to do:
    
    * Clean up duplicated code paths and unify more JIT stuff for both
      blocks and methods.
    * Reduce indirection through MixedModeIRBlockBody.
    * More testing of various forms of blocks.
    headius committed Oct 1, 2015
    Copy the full SHA
    318c853 View commit details
  3. Synchronize submission of code to JIT.

    The old logic could trigger a method or block to JIT twice if two
    or more threads all got to that point at the same time. For
    methods, this just resulted in wasted work. For blocks, this
    appeared to cause some classloading and/or method handle lookup
    problems that manifested as NoSuchMethod errors and stack
    overflows in the compiled code, likely due to looking up the
    wrong method name in the wrong class.
    
    The synchronization probably slows down interpretation, since it
    fires for every call when JIT is enabled, so we will want to
    change to a lock-free mechanism. This is ok for now, though,
    since with JIT enabled both blocks and methods will eventually
    stop counting calls and go straight to the jitted body.
    headius committed Oct 1, 2015
    Copy the full SHA
    771fe3f View commit details
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/ast/DVarNode.java
Original file line number Diff line number Diff line change
@@ -40,7 +40,7 @@
/**
* Access a dynamic variable (e.g. block scope local variable).
*/
public class DVarNode extends Node implements INameNode, IScopedNode {
public class DVarNode extends Node implements INameNode, IScopedNode, SideEffectFree {
// The name of the variable
private String name;

167 changes: 161 additions & 6 deletions core/src/main/java/org/jruby/compiler/JITCompiler.java
Original file line number Diff line number Diff line change
@@ -37,9 +37,12 @@
import org.jruby.ast.util.SexpMaker;
import org.jruby.internal.runtime.methods.CompiledIRMethod;
import org.jruby.internal.runtime.methods.MixedModeIRMethod;
import org.jruby.ir.IRClosure;
import org.jruby.ir.interpreter.InterpreterContext;
import org.jruby.ir.targets.JVMVisitor;
import org.jruby.ir.targets.JVMVisitorMethodContext;
import org.jruby.runtime.CompiledIRBlockBody;
import org.jruby.runtime.MixedModeIRBlockBody;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.threading.DaemonThreadFactory;
@@ -54,6 +57,7 @@
import java.lang.invoke.MethodType;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutorService;
@@ -147,7 +151,9 @@ public void tearDown() {

public Runnable getTaskFor(ThreadContext context, Compilable method) {
if (method instanceof MixedModeIRMethod) {
return new JITTask((MixedModeIRMethod) method, method.getClassName(context));
return new MethodJITTask((MixedModeIRMethod) method, method.getClassName(context));
} else if (method instanceof MixedModeIRBlockBody) {
return new BlockJITTask((MixedModeIRBlockBody) method, method.getClassName(context));
}

return new FullBuildTask(method);
@@ -204,12 +210,12 @@ public void run() {
}
}

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

public JITTask(MixedModeIRMethod method, String className) {
public MethodJITTask(MixedModeIRMethod method, String className) {
this.method = method;
this.className = className;
this.methodName = method.getName();
@@ -241,7 +247,7 @@ public void run() {

String key = SexpMaker.sha1(method.getIRScope());
JVMVisitor visitor = new JVMVisitor();
JITClassGenerator generator = new JITClassGenerator(className, methodName, key, runtime, method, visitor);
MethodJITClassGenerator generator = new MethodJITClassGenerator(className, methodName, key, runtime, method, visitor);

JVMVisitorMethodContext context = new JVMVisitorMethodContext();
generator.compile(context);
@@ -317,6 +323,68 @@ public void run() {
}
}

private class BlockJITTask implements Runnable {
private final String className;
private final MixedModeIRBlockBody body;
private final String methodName;

public BlockJITTask(MixedModeIRBlockBody body, String className) {
this.body = body;
this.className = className;
this.methodName = body.getName();
}

public void run() {
try {
String key = SexpMaker.sha1(body.getIRScope());
JVMVisitor visitor = new JVMVisitor();
BlockJITClassGenerator generator = new BlockJITClassGenerator(className, methodName, key, runtime, body, visitor);

JVMVisitorMethodContext context = new JVMVisitorMethodContext();
generator.compile(context);

// FIXME: reinstate active bytecode size check
// At this point we still need to reinstate the bytecode size check, to ensure we're not loading code
// that's so big that JVMs won't even try to compile it. Removed the check because with the new IR JIT
// bytecode counts often include all nested scopes, even if they'd be different methods. We need a new
// mechanism of getting all body sizes.
Class sourceClass = visitor.defineFromBytecode(body.getIRScope(), generator.bytecode(), new OneShotClassLoader(runtime.getJRubyClassLoader()));

if (sourceClass == null) {
// class could not be found nor generated; give up on JIT and bail out
counts.failCount.incrementAndGet();
return;
} else {
generator.updateCounters(counts);
}

// successfully got back a jitted body

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

String jittedName = context.getJittedName();

// blocks only have variable-arity
body.completeBuild(
new CompiledIRBlockBody(
PUBLIC_LOOKUP.findStatic(sourceClass, jittedName, JVMVisitor.CLOSURE_SIGNATURE.type()),
body.getIRScope(),
((IRClosure) body.getIRScope()).getSignature().encode()));
} catch (Throwable t) {
if (config.isJitLogging()) {
log(body.getImplementationClass(), body.getFile(), body.getLine(), className + "." + methodName, "Could not compile; passes run: " + body.getIRScope().getExecutedPasses(), t.getMessage());
if (config.isJitLoggingVerbose()) {
t.printStackTrace();
}
}

counts.failCount.incrementAndGet();
}
}
}

public static String getHashForString(String str) {
return getHashForBytes(RubyEncoding.encodeUTF8(str));
}
@@ -335,8 +403,8 @@ public static String getHashForBytes(byte[] bytes) {
}
}

public static class JITClassGenerator {
public JITClassGenerator(String className, String methodName, String key, Ruby ruby, MixedModeIRMethod method, JVMVisitor visitor) {
public static class MethodJITClassGenerator {
public MethodJITClassGenerator(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
@@ -422,6 +490,93 @@ public String toString() {
private String name;
}

public static class BlockJITClassGenerator {
public BlockJITClassGenerator(String className, String methodName, String key, Ruby ruby, MixedModeIRBlockBody body, 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
// so we force the class name to be unique to this runtime.

// Also, invokedynamic forces us to make jitted bytecode unique to each runtime, since the call sites cache
// at class level rather than at our runtime level. This makes it impossible to share jitted bytecode
// across runtimes.

digestString = key + Math.abs(ruby.hashCode());
} else {
digestString = key;
}
this.className = packageName + "/" + className.replace('.', '/') + CLASS_METHOD_DELIMITER + JavaNameMangler.mangleMethodName(methodName) + "_" + digestString;
this.name = this.className.replaceAll("/", ".");
this.methodName = methodName;
this.body = body;
this.visitor = visitor;
}

@SuppressWarnings("unchecked")
protected void compile(JVMVisitorMethodContext context) {
if (bytecode != null) return;

// Time the compilation
long start = System.nanoTime();

InterpreterContext ic = body.ensureInstrsReady();

int insnCount = ic.getInstructions().length;
if (insnCount > Options.JIT_MAXSIZE.load()) {
// methods with more than our limit of basic blocks are likely too large to JIT, so bail out
throw new NotCompilableException("Could not compile " + body + "; instruction count " + insnCount + " exceeds threshold of " + Options.JIT_MAXSIZE.load());
}

// This may not be ok since we'll end up running passes specific to JIT
// CON FIXME: Really should clone scope before passes in any case
bytecode = visitor.compileToBytecode(body.getIRScope(), context);

compileTime = System.nanoTime() - start;
}

void updateCounters(JITCounts counts) {
counts.compiledCount.incrementAndGet();
counts.compileTime.addAndGet(compileTime);
counts.codeSize.addAndGet(bytecode.length);
counts.averageCompileTime.set(counts.compileTime.get() / counts.compiledCount.get());
counts.averageCodeSize.set(counts.codeSize.get() / counts.compiledCount.get());
synchronized (counts) {
if (counts.largestCodeSize.get() < bytecode.length) {
counts.largestCodeSize.set(bytecode.length);
}
}
}

// FIXME: Does anything call this? If so we should document it.
public void generate() {
compile(new JVMVisitorMethodContext());
}

public byte[] bytecode() {
return bytecode;
}

public String name() {
return name;
}

@Override
public String toString() {
return "{} at " + body.getFile() + ":" + body.getLine();
}

private final String packageName;
private final String className;
private final String methodName;
private final String digestString;
private final MixedModeIRBlockBody body;
private final JVMVisitor visitor;

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

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();
Original file line number Diff line number Diff line change
@@ -30,8 +30,8 @@ public class MixedModeIRMethod extends DynamicMethod implements IRMethodArgs, Po
protected final IRScope method;

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

protected DynamicMethodBox box = new DynamicMethodBox();
@@ -308,7 +308,14 @@ public void completeBuild(DynamicMethod newMethod) {
protected void tryJit(ThreadContext context, DynamicMethodBox box) {
if (context.runtime.isBooting()) return; // don't JIT during runtime boot

if (box.callCount++ >= Options.JIT_THRESHOLD.load()) context.runtime.getJITCompiler().buildThresholdReached(context, this);
synchronized (this) {
if (box.callCount >= 0) {
if (box.callCount++ >= Options.JIT_THRESHOLD.load()) {
box.callCount = -1;
context.runtime.getJITCompiler().buildThresholdReached(context, this);
}
}
}
}

public String getClassName(ThreadContext context) {
8 changes: 6 additions & 2 deletions core/src/main/java/org/jruby/ir/IRClosure.java
Original file line number Diff line number Diff line change
@@ -2,17 +2,21 @@

import java.util.ArrayList;
import java.util.List;

import org.jruby.RubyInstanceConfig;
import org.jruby.ir.instructions.*;
import org.jruby.ir.interpreter.ClosureInterpreterContext;
import org.jruby.ir.interpreter.InterpreterContext;
import org.jruby.ir.operands.*;
import org.jruby.ir.representations.BasicBlock;
import org.jruby.ir.transformations.inlining.CloneInfo;
import org.jruby.ir.transformations.inlining.SimpleCloneInfo;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.ArgumentDescriptor;
import org.jruby.runtime.BlockBody;
import org.jruby.runtime.IRBlockBody;
import org.jruby.runtime.InterpretedIRBlockBody;
import org.jruby.runtime.MixedModeIRBlockBody;
import org.jruby.runtime.Signature;
import org.objectweb.asm.Handle;

@@ -60,7 +64,7 @@ protected IRClosure(IRClosure c, IRScope lexicalParent, int closureId, String fu
if (getManager().isDryRun()) {
this.body = null;
} else {
this.body = new InterpretedIRBlockBody(this, c.body.getSignature());
this.body = new MixedModeIRBlockBody(c, c.getSignature());
}

this.signature = c.signature;
@@ -82,7 +86,7 @@ public IRClosure(IRManager manager, IRScope lexicalParent, int lineNumber, Stati
if (getManager().isDryRun()) {
this.body = null;
} else {
this.body = new InterpretedIRBlockBody(this, signature);
this.body = new MixedModeIRBlockBody(this, signature);
if (staticScope != null && !isBeginEndBlock) {
staticScope.setIRScope(this);
staticScope.setScopeType(this.getScopeType());
19 changes: 17 additions & 2 deletions core/src/main/java/org/jruby/ir/targets/JVMVisitor.java
Original file line number Diff line number Diff line change
@@ -107,6 +107,8 @@ public void codegenScope(IRScope scope, JVMVisitorMethodContext context) {
codegenScriptBody((IRScriptBody)scope);
} else if (scope instanceof IRMethod) {
emitMethodJIT((IRMethod)scope, context);
} else if (scope instanceof IRClosure) {
emitBlockJIT((IRClosure) scope, context);
} else if (scope instanceof IRModuleBody) {
emitModuleBodyJIT((IRModuleBody)scope);
} else {
@@ -222,7 +224,7 @@ public static final Signature signatureFor(IRScope method, boolean aritySplit) {
return METHOD_SIGNATURE_BASE.insertArgs(3, new String[]{"args"}, IRubyObject[].class);
}

private static final Signature CLOSURE_SIGNATURE = Signature
public static final Signature CLOSURE_SIGNATURE = Signature
.returning(IRubyObject.class)
.appendArgs(new String[]{"context", "scope", "self", "args", "block", "superName", "type"}, ThreadContext.class, StaticScope.class, IRubyObject.class, IRubyObject[].class, Block.class, String.class, Block.Type.class);

@@ -244,7 +246,7 @@ public void emitMethod(IRMethod method, JVMVisitorMethodContext context) {
emitWithSignatures(method, context, name);
}

public void emitMethodJIT(IRMethod method, JVMVisitorMethodContext context) {
public void emitMethodJIT(IRMethod method, JVMVisitorMethodContext context) {
String clsName = jvm.scriptToClass(method.getFileName());
String name = JavaNameMangler.encodeScopeForBacktrace(method) + "$" + methodIndex++;
jvm.pushscript(clsName, method.getFileName());
@@ -255,6 +257,19 @@ public void emitMethodJIT(IRMethod method, JVMVisitorMethodContext context) {
jvm.popclass();
}

public void emitBlockJIT(IRClosure closure, JVMVisitorMethodContext context) {
String clsName = jvm.scriptToClass(closure.getFileName());
String name = JavaNameMangler.encodeScopeForBacktrace(closure) + "$" + methodIndex++;
jvm.pushscript(clsName, closure.getFileName());

emitScope(closure, name, CLOSURE_SIGNATURE, false);

context.setJittedName(name);

jvm.cls().visitEnd();
jvm.popclass();
}

private void emitWithSignatures(IRMethod method, JVMVisitorMethodContext context, String name) {
context.setJittedName(name);

2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/runtime/IRBlockBody.java
Original file line number Diff line number Diff line change
@@ -82,7 +82,7 @@ public IRubyObject yieldSpecific(ThreadContext context, IRubyObject arg0, Bindin
}
}

private IRubyObject yieldSpecificMultiArgsCommon(ThreadContext context, IRubyObject[] args, Binding binding, Type type) {
IRubyObject yieldSpecificMultiArgsCommon(ThreadContext context, IRubyObject[] args, Binding binding, Type type) {
int blockArity = getSignature().arityValue();
if (blockArity == 0) {
args = IRubyObject.NULL_ARRAY; // discard args
Loading