Skip to content

Commit

Permalink
Added jruby.jit.codeCache property to dump/load jitted classes to/fro…
Browse files Browse the repository at this point in the history
…m disk. Also modified ClassCache to look in normal classloader first (allowing pre-deploy jit dumping and bundling in a jar file) plus jruby.jit.debug property to output what the codeCache stuff is doing.
  • Loading branch information
headius committed Dec 11, 2009
1 parent 82c089a commit 7fee4a2
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 13 deletions.
3 changes: 3 additions & 0 deletions src/org/jruby/RubyInstanceConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,9 @@ public boolean shouldPrecompileAll() {
public static final boolean JIT_CACHE_ENABLED
= SafePropertyAccessor.getBoolean("jruby.jit.cache", true);

public static final String JIT_CODE_CACHE
= SafePropertyAccessor.getProperty("jruby.jit.codeCache", null);

public static interface LoadServiceCreator {
LoadService create(Ruby runtime);

Expand Down
75 changes: 63 additions & 12 deletions src/org/jruby/compiler/JITCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
package org.jruby.compiler;


import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import org.jruby.Ruby;
Expand All @@ -49,9 +53,12 @@
import org.jruby.util.ClassCache;
import org.jruby.util.CodegenUtils;
import org.jruby.util.JavaNameMangler;
import org.jruby.util.SafePropertyAccessor;
import org.objectweb.asm.ClassReader;

public class JITCompiler implements JITCompilerMBean {
public static final boolean USE_CACHE = true;
public static final boolean DEBUG = SafePropertyAccessor.getBoolean("jruby.jit.debug", false);

private AtomicLong compiledCount = new AtomicLong(0);
private AtomicLong successCount = new AtomicLong(0);
Expand Down Expand Up @@ -125,10 +132,8 @@ private DynamicMethod jitThresholdReached(final DefaultMethod method, RubyInstan
}



JITClassGenerator generator = new JITClassGenerator(name, context.getRuntime(), method, context);

String key = SexpMaker.create(name, method.getArgsNode(), method.getBodyNode());
JITClassGenerator generator = new JITClassGenerator(name, key, context.getRuntime(), method);

Class<Script> sourceClass = (Class<Script>)instanceConfig.getClassCache().cacheClassByKey(key, generator);

Expand Down Expand Up @@ -181,27 +186,50 @@ public class JITClassGenerator implements ClassCache.ClassGenerator {
private byte[] bytecode;
private String name;
private Ruby ruby;
private String packageName;
private String className;
private String filename;

public JITClassGenerator(String name, Ruby ruby, DefaultMethod method, ThreadContext context) {
public JITClassGenerator(String name, String key, Ruby ruby, DefaultMethod method) {
this.method = method;
String packageName = "ruby/jit/" + JavaNameMangler.mangleFilenameForClasspath(method.getPosition().getFile());
String cleanName = packageName + "/" + JavaNameMangler.mangleStringForCleanJavaIdentifier(name);
packageName = "ruby/jit/" + JavaNameMangler.mangleFilenameForClasspath(method.getPosition().getFile());
className = packageName + "/" + JavaNameMangler.mangleStringForCleanJavaIdentifier(name) + "_" + key.hashCode();
this.name = className.replaceAll("/", ".");
this.bodyNode = method.getBodyNode();
this.argsNode = method.getArgsNode();
final String filename = calculateFilename(argsNode, bodyNode);
filename = calculateFilename(argsNode, bodyNode);
staticScope = method.getStaticScope();
asmCompiler = new StandardASMCompiler(cleanName +
method.hashCode() + "_" + context.hashCode(), filename);
this.ruby = ruby;
}

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

// check if we have a cached compiled version on disk
String codeCache = RubyInstanceConfig.JIT_CODE_CACHE;
File cachedClassFile = new File(codeCache + "/" + className + ".class");
if (codeCache != null &&
cachedClassFile.exists()) {
FileInputStream fis = null;
try {
if (DEBUG) System.err.println("loading cached code from: " + cachedClassFile);
fis = new FileInputStream(cachedClassFile);
bytecode = new byte[(int)fis.getChannel().size()];
fis.read(bytecode);
name = new ClassReader(bytecode).getClassName();
return;
} catch (Exception e) {
// ignore and proceed to compile
} finally {
try {fis.close();} catch (Exception e) {}
}
}

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


asmCompiler = new StandardASMCompiler(className, filename);
asmCompiler.startScript(staticScope);
final ASTCompiler compiler = ruby.getInstanceConfig().newCompiler();

Expand Down Expand Up @@ -252,6 +280,27 @@ public void call(BodyCompiler context) {
"JITed method size exceeds configured max of " +
ruby.getInstanceConfig().getJitMaxSize());
}

if (codeCache != null && new File(codeCache).isDirectory()) {
if (!new File(codeCache, packageName).isDirectory()) {
boolean createdDirs = new File(codeCache, packageName).mkdirs();
if (!createdDirs) {
ruby.getWarnings().warn("could not create JIT cache dir: " + new File(codeCache, packageName));
}
}
// write to code cache
FileOutputStream fos = null;
try {
if (DEBUG) System.err.println("writing jitted code to to " + cachedClassFile);
fos = new FileOutputStream(cachedClassFile);
fos.write(bytecode);
} catch (Exception e) {
e.printStackTrace();
// ignore
} finally {
try {fos.close();} catch (Exception e) {}
}
}

compiledCount.incrementAndGet();
compileTime.addAndGet(System.nanoTime() - start);
Expand All @@ -264,14 +313,16 @@ public void call(BodyCompiler context) {
}
}
}

public void generate() {
compile();
}

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

public String name() {
compile();
return name;
}

Expand Down
19 changes: 18 additions & 1 deletion src/org/jruby/util/ClassCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.jruby.RubyInstanceConfig;
import org.jruby.compiler.JITCompiler;

/**
* A Simple cache which maintains a collection of classes that can potentially be shared among
Expand Down Expand Up @@ -37,6 +38,7 @@ public ClassCache(ClassLoader classLoader) {
}

public interface ClassGenerator {
void generate();
byte[] bytecode();
String name();
}
Expand Down Expand Up @@ -103,8 +105,23 @@ public Class<T> cacheClassByKey(Object key, ClassGenerator classGenerator)
}

protected Class<T> defineClass(ClassGenerator classGenerator) {
// attempt to load from classloaders
String className = classGenerator.name();
Class contents = null;
try {
contents = getClassLoader().loadClass(className);
if (JITCompiler.DEBUG) {
System.err.println("found jitted code in classloader: " + className);
}
} catch (ClassNotFoundException cnfe) {
if (JITCompiler.DEBUG) {
System.err.println("no jitted code in classloader: " + className);
}
// proceed to define in-memory
}
OneShotClassLoader oneShotCL = new OneShotClassLoader(getClassLoader());
Class<T> contents = (Class<T>)oneShotCL.defineClass(classGenerator.name(), classGenerator.bytecode());
classGenerator.generate();
contents = oneShotCL.defineClass(classGenerator.name(), classGenerator.bytecode());
classLoadCount.incrementAndGet();

return contents;
Expand Down

0 comments on commit 7fee4a2

Please sign in to comment.