Skip to content

respect jit.max to stop compilation #5870

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Sep 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions core/src/main/java/org/jruby/RubyInstanceConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,13 @@ public void setJitMaxSize(int jitMaxSize) {
this.jitMaxSize = jitMaxSize;
}

/**
* @return true if JIT compilation is enabled
*/
public boolean isJitEnabled() {
return getJitThreshold() >= 0 && getCompileMode().shouldJIT();
}

/**
* @see Options#LAUNCH_INPROC
*/
Expand Down
107 changes: 52 additions & 55 deletions core/src/main/java/org/jruby/compiler/BlockJITTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,79 +26,76 @@

package org.jruby.compiler;

import org.jruby.Ruby;
import org.jruby.ast.util.SexpMaker;
import org.jruby.ir.IRClosure;
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.util.OneShotClassLoader;

class BlockJITTask implements Runnable {
private JITCompiler jitCompiler;
import static org.jruby.compiler.MethodJITTask.*;

class BlockJITTask extends JITCompiler.Task {

private final String className;
private final MixedModeIRBlockBody body;
private final String methodName;

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

public void run() {
// We synchronize against the JITCompiler object so at most one code body will jit at once in a given runtime.
// This works around unsolved concurrency issues within the process of preparing and jitting the IR.
// See #4739 for a reproduction script that produced various errors without this.
synchronized (jitCompiler) {
try {
String key = SexpMaker.sha1(body.getIRScope());
JVMVisitor visitor = new JVMVisitor(jitCompiler.runtime);
BlockJITClassGenerator generator = new BlockJITClassGenerator(className, methodName, key, jitCompiler.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(jitCompiler.runtime.getJRubyClassLoader()));

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

// successfully got back a jitted body

String jittedName = context.getVariableName();

// blocks only have variable-arity
body.completeBuild(
new CompiledIRBlockBody(
JITCompiler.PUBLIC_LOOKUP.findStatic(sourceClass, jittedName, JVMVisitor.CLOSURE_SIGNATURE.type()),
body.getIRScope(),
((IRClosure) body.getIRScope()).getSignature().encode()));

if (jitCompiler.config.isJitLogging()) {
JITCompiler.log(body.getImplementationClass(), body.getFile(), body.getLine(), className + "." + methodName, "done jitting");
}
} catch (Throwable t) {
if (jitCompiler.config.isJitLogging()) {
JITCompiler.log(body.getImplementationClass(), body.getFile(), body.getLine(), className + "." + methodName, "Could not compile; passes run: " + body.getIRScope().getExecutedPasses(), t.getMessage());
if (jitCompiler.config.isJitLoggingVerbose()) {
t.printStackTrace();
}
}

jitCompiler.counts.failCount.incrementAndGet();
@Override
public void exec() throws NoSuchMethodException, IllegalAccessException {
// Check if the method has been explicitly excluded
String excludeModuleName = checkExcludedMethod(jitCompiler.config, className, methodName, body.getImplementationClass());
if (excludeModuleName != null) {
body.setCallCount(-1);
if (jitCompiler.config.isJitLogging()) {
JITCompiler.log(body, methodName, "skipping block in " + excludeModuleName);
}
return;
}

final String key = SexpMaker.sha1(body.getIRScope());
final Ruby runtime = jitCompiler.runtime;
JVMVisitor visitor = new JVMVisitor(runtime);
BlockJITClassGenerator generator = new BlockJITClassGenerator(className, methodName, key, runtime, body, visitor);

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

Class<?> sourceClass = defineClass(generator, visitor, body.getIRScope(), body.ensureInstrsReady());
if (sourceClass == null) return; // class could not be found nor generated; give up on JIT and bail out

// successfully got back a jitted body
String jittedName = context.getVariableName();

// blocks only have variable-arity
body.completeBuild(
new CompiledIRBlockBody(
JITCompiler.PUBLIC_LOOKUP.findStatic(sourceClass, jittedName, JVMVisitor.CLOSURE_SIGNATURE.type()),
body.getIRScope(),
((IRClosure) body.getIRScope()).getSignature().encode()));
}

@Override
protected void logJitted() {
logImpl("block done jitting");
}

@Override
protected void logFailed(final Throwable ex) {
logImpl("could not compile block; passes run: " + body.getIRScope().getExecutedPasses(), ex);
}

@Override
protected void logImpl(final String message, Object... reason) {
JITCompiler.log(body, methodName, message, reason);
}

}
8 changes: 4 additions & 4 deletions core/src/main/java/org/jruby/compiler/FullBuildTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
* Created by headius on 12/8/16.
*/
class FullBuildTask implements Runnable {
private JITCompiler jitCompiler;

private final JITCompiler jitCompiler;
private final Compilable<InterpreterContext> method;

FullBuildTask(JITCompiler jitCompiler, Compilable<InterpreterContext> method) {
Expand All @@ -26,12 +27,11 @@ public void run() {
method.completeBuild(method.getIRScope().prepareFullBuild());

if (jitCompiler.config.isJitLogging()) {
JITCompiler.log(method.getImplementationClass(), method.getFile(), method.getLine(), method.getName(), "done building");
JITCompiler.log(method, method.getName(), "done building");
}
} catch (Throwable t) {
if (jitCompiler.config.isJitLogging()) {
JITCompiler.log(method.getImplementationClass(), method.getFile(), method.getLine(), method.getName(),
"Could not build; passes run: " + method.getIRScope().getExecutedPasses(), t.getMessage());
JITCompiler.log(method, method.getName(), "could not build; passes run: " + method.getIRScope().getExecutedPasses(), t);
if (jitCompiler.config.isJitLoggingVerbose()) {
t.printStackTrace();
}
Expand Down
120 changes: 106 additions & 14 deletions core/src/main/java/org/jruby/compiler/JITCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,16 @@
import org.jruby.RubyModule;
import org.jruby.internal.runtime.methods.CompiledIRMethod;
import org.jruby.internal.runtime.methods.MixedModeIRMethod;
import org.jruby.ir.IRScope;
import org.jruby.ir.interpreter.InterpreterContext;
import org.jruby.ir.targets.JVMVisitor;
import org.jruby.ir.targets.JVMVisitorMethodContext;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.MixedModeIRBlockBody;
import org.jruby.runtime.ThreadContext;
import org.jruby.threading.DaemonThreadFactory;
import org.jruby.util.ClassDefiningClassLoader;
import org.jruby.util.OneShotClassLoader;
import org.jruby.util.log.Logger;
import org.jruby.util.log.LoggerFactory;

Expand Down Expand Up @@ -177,24 +183,28 @@ public Runnable getTaskFor(ThreadContext context, Compilable method) {
public void buildThresholdReached(ThreadContext context, final Compilable method) {
final RubyInstanceConfig config = context.runtime.getInstanceConfig();

// Disable any other jit tasks from entering queue
method.setCallCount(-1);
final Runnable task = getTaskFor(context, method);

Runnable jitTask = getTaskFor(context, method);
if (task instanceof Task && config.getJitMax() >= 0 && config.getJitMax() < getSuccessCount()) {
if (config.isJitLogging()) {
JITCompiler.log(method, method.getName(), "skipping: jit.max threshold reached");
}
return;
}

try {
if (config.getJitBackground() && config.getJitThreshold() > 0) {
try {
executor.submit(jitTask);
executor.submit(task);
} catch (RejectedExecutionException ree) {
// failed to submit, just run it directly
jitTask.run();
task.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();
task.run();
}
} catch (Exception e) {
throw new NotCompilableException(e);
Expand All @@ -221,22 +231,104 @@ public static String getHashForBytes(byte[] bytes) {
}
}

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>";
static void log(Compilable<?> target, String name, String message, Object... reason) {
String className = target.getImplementationClass().getName();

StringBuilder builder = new StringBuilder(32);
builder.append(message).append(": ").append(className)
.append(' ').append(name == null ? "" : name)
.append(" at ").append(file).append(':').append(line);
builder.append(message).append(": ").append(className);
if (name != null) builder.append(' ').append(name);
builder.append(" at ").append(target.getFile()).append(':').append(target.getLine());

if (reason.length > 0) {
builder.append(" because of: \"");
for (String aReason : reason) builder.append(aReason);
for (Object aReason : reason) builder.append(aReason);
builder.append('"');
}

LOG.info(builder.toString());
}

static abstract class Task implements Runnable {

protected final JITCompiler jitCompiler;

public Task(JITCompiler jitCompiler) {
this.jitCompiler = jitCompiler;
}

public void run() {
// We synchronize against the JITCompiler object so at most one code body will jit at once in a given runtime.
// This works around unsolved concurrency issues within the process of preparing and jitting the IR.
// See #4739 for a reproduction script that produced various errors without this.
synchronized (jitCompiler) {
try {
exec();
} catch (Throwable ex) {
jitFailed(ex);
}
}
}

protected abstract void exec() throws Exception ;

// shared helper methods :

protected void jitFailed(final Throwable ex) {
if (jitCompiler.config.isJitLogging()) {
logFailed(ex);
if (jitCompiler.config.isJitLoggingVerbose()) {
ex.printStackTrace();
}
}

jitCompiler.counts.failCount.incrementAndGet();
}

protected Class<?> defineClass(final JITClassGenerator generator, final JVMVisitor visitor,
final IRScope scope, final InterpreterContext interpreterContext) {
// 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(scope, generator.bytecode(), getCodeLoader(jitCompiler.runtime));

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

// successfully got back a jitted method/block
long methodCount = jitCompiler.counts.successCount.incrementAndGet();

if (jitCompiler.config.isJitLogging()) logJitted();

// logEvery n methods based on configuration
if (jitCompiler.config.getJitLogEvery() > 0) {
if (methodCount % jitCompiler.config.getJitLogEvery() == 0) {
logImpl("live compiled count: " + methodCount);
}
}

return sourceClass;
}

ClassDefiningClassLoader getCodeLoader(final Ruby runtime) {
return new OneShotClassLoader(runtime.getJRubyClassLoader());
}

protected void logJitted() {
logImpl("done jitting");
}

protected void logFailed(Throwable ex) {
logImpl("could not compile", ex);
}

protected abstract void logImpl(String msg, Object... cause) ;

}

}
Loading