I'd appreciate a few more eyes on this. It's not super clean, but it
should allow the JIT to generate methods indefinitely without blowing
the permgen. I've run the simple test in the bug and it looks clean.
Have a look at the patch. Try brutalizing an app that continually
generates methods (like Rails in development mode), and let me know how
it goes. It will be in, in some form, for 1.0.
I've also attached it to this mail.
- Charlie
Index: test/testCompiler.rb
===================================================================
--- test/testCompiler.rb (revision 3593)
+++ test/testCompiler.rb (working copy)
@@ -12,7 +12,7 @@
context = StandardASMCompiler.new(node)
NodeCompilerFactory.getCompiler(node).compile(node, context)
- context.loadClass(JRuby.runtime)
+ context.loadClass(JRuby.runtime.getJRubyClassLoader)
end
def compile_and_run(src)
Index: src/org/jruby/runtime/CallbackFactory.java
===================================================================
--- src/org/jruby/runtime/CallbackFactory.java (revision 3592)
+++ src/org/jruby/runtime/CallbackFactory.java (working copy)
@@ -36,6 +36,7 @@
import org.jruby.runtime.callback.ReflectionCallbackFactory;
import org.jruby.runtime.callback.InvocationCallbackFactory;
import org.jruby.runtime.callback.DumpingInvocationCallbackFactory;
+import org.jruby.util.JRubyClassLoader;
/**
* Helper class to build Callback method.
@@ -160,7 +161,18 @@
} else if(dumping) {
return new DumpingInvocationCallbackFactory(runtime, type,
dumpingPath);
} else {
- return new InvocationCallbackFactory(runtime, type);
+ return new InvocationCallbackFactory(runtime, type,
runtime.getJRubyClassLoader());
}
}
+
+ public static CallbackFactory createFactory(Ruby runtime, Class type,
ClassLoader classLoader) {
+ if(reflection) {
+ return new ReflectionCallbackFactory(type);
+ } else if(dumping) {
+ return new DumpingInvocationCallbackFactory(runtime, type,
dumpingPath);
+ } else {
+ // FIXME: No, I don't like it.
+ return new InvocationCallbackFactory(runtime, type,
(JRubyClassLoader)classLoader);
+ }
+ }
}
Index: src/org/jruby/runtime/MethodFactory.java
===================================================================
--- src/org/jruby/runtime/MethodFactory.java (revision 3592)
+++ src/org/jruby/runtime/MethodFactory.java (working copy)
@@ -34,6 +34,7 @@
import org.jruby.internal.runtime.methods.InvocationMethodFactory;
import org.jruby.internal.runtime.methods.DumpingInvocationMethodFactory;
import org.jruby.parser.StaticScope;
+import org.jruby.util.JRubyClassLoader;
import org.jruby.util.collections.SinglyLinkedList;
/**
@@ -71,4 +72,14 @@
return new InvocationMethodFactory();
}
}
+
+ public static MethodFactory createFactory(ClassLoader classLoader) {
+ if(reflection) {
+ return new ReflectionMethodFactory();
+ } else if(dumping) {
+ return new DumpingInvocationMethodFactory(dumpingPath);
+ } else {
+ return new InvocationMethodFactory((JRubyClassLoader)classLoader);
+ }
+ }
}// MethodFactory
Index: src/org/jruby/runtime/callback/InvocationCallbackFactory.java
===================================================================
--- src/org/jruby/runtime/callback/InvocationCallbackFactory.java
(revision 3592)
+++ src/org/jruby/runtime/callback/InvocationCallbackFactory.java
(working copy)
@@ -39,11 +39,13 @@
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.CodegenUtils;
+import org.jruby.util.JRubyClassLoader;
public class InvocationCallbackFactory extends CallbackFactory implements
Opcodes {
private final static CodegenUtils cg = CodegenUtils.instance;
private final Class type;
+ private final JRubyClassLoader classLoader;
private final String typePath;
private final Ruby runtime;
@@ -59,8 +61,9 @@
private final static String IRUB = cg.p(RubyKernel.IRUBY_OBJECT);
private final static String IRUB_ID = cg.ci(RubyKernel.IRUBY_OBJECT);
- public InvocationCallbackFactory(Ruby runtime, Class type) {
+ public InvocationCallbackFactory(Ruby runtime, Class type,
JRubyClassLoader classLoader) {
this.type = type;
+ this.classLoader = classLoader;
this.typePath = cg.p(type);
this.runtime = runtime;
}
@@ -116,7 +119,7 @@
private Class tryClass(String name) {
try {
- return Class.forName(name, true, runtime.getJRubyClassLoader());
+ return classLoader.loadClass(name);
} catch (Exception e) {
return null;
}
@@ -169,7 +172,7 @@
mv.visitEnd();
cw.visitEnd();
byte[] code = cw.toByteArray();
- return runtime.getJRubyClassLoader().defineClass(name, code);
+ return classLoader.defineClass(name, code);
}
public Callback getMethod(String method) {
@@ -458,7 +461,7 @@
public CompiledBlockCallback getBlockCallback(String method) {
String mname = type.getName() + "Block" + method + "xx1";
String mnamePath = typePath + "Block" + method + "xx1";
- synchronized (runtime.getJRubyClassLoader()) {
+ synchronized (classLoader) {
Class c = tryClass(mname);
try {
if (c == null) {
Index: src/org/jruby/internal/runtime/methods/InvocationMethodFactory.java
===================================================================
--- src/org/jruby/internal/runtime/methods/InvocationMethodFactory.java
(revision 3592)
+++ src/org/jruby/internal/runtime/methods/InvocationMethodFactory.java
(working copy)
@@ -39,6 +39,7 @@
import org.jruby.runtime.MethodFactory;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
+import org.jruby.util.JRubyClassLoader;
import org.jruby.util.collections.SinglyLinkedList;
/**
@@ -57,6 +58,15 @@
private final static String SUPER_SIG = "(" + ci(RubyModule.class) +
ci(Arity.class) + ci(Visibility.class) + ")V";
private final static String COMPILED_SUPER_SIG = "(" +
ci(RubyModule.class) + ci(Arity.class) + ci(Visibility.class) +
ci(SinglyLinkedList.class) + ci(StaticScope.class) + ")V";
+ private JRubyClassLoader classLoader;
+
+ public InvocationMethodFactory() {
+ }
+
+ public InvocationMethodFactory(JRubyClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
/**
* Creates a class path name, from a Class.
*/
@@ -107,7 +117,11 @@
private Class tryClass(Ruby runtime, String name) {
try {
- return Class.forName(name,true,runtime.getJRubyClassLoader());
+ if (classLoader == null) {
+ return Class.forName(name, true,
runtime.getJRubyClassLoader());
+ } else {
+ return classLoader.loadClass(name);
+ }
} catch(Exception e) {
return null;
}
@@ -118,7 +132,11 @@
mv.visitEnd();
cw.visitEnd();
byte[] code = cw.toByteArray();
- return runtime.getJRubyClassLoader().defineClass(name, code);
+ if (classLoader == null) {
+ return runtime.getJRubyClassLoader().defineClass(name, code);
+ } else {
+ return classLoader.defineClass(name, code);
+ }
}
private String getReturnName(Class type, String method, Class[] args)
throws Exception {
Index: src/org/jruby/internal/runtime/methods/DefaultMethod.java
===================================================================
--- src/org/jruby/internal/runtime/methods/DefaultMethod.java (revision 3593)
+++ src/org/jruby/internal/runtime/methods/DefaultMethod.java (working copy)
@@ -58,6 +58,7 @@
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.CodegenUtils;
+import org.jruby.util.JRubyClassLoader;
import org.jruby.util.collections.SinglyLinkedList;
/**
@@ -87,7 +88,7 @@
String enabledValue = System.getProperty("jruby.jit.enabled");
String threshold = System.getProperty("jruby.jit.threshold");
- JIT_ENABLED = enabledValue == null ? true :
Boolean.getBoolean(enabledValue);
+ JIT_ENABLED = enabledValue == null ? true :
Boolean.getBoolean("jruby.jit.enabled");
JIT_LOGGING = Boolean.getBoolean("jruby.jit.logging");
JIT_LOGGING_VERBOSE =
Boolean.getBoolean("jruby.jit.logging.verbose");
JIT_THRESHOLD = threshold == null ? 0 :
Integer.parseInt(threshold);
@@ -260,13 +261,14 @@
};
String cleanName = CodegenUtils.cleanJavaIdentifier(name);
+ // FIXME: not handling empty bodies correctly...
StandardASMCompiler compiler = new
StandardASMCompiler(cleanName + hashCode() + "_" + context.hashCode(),
body.getPosition().getFile());
compiler.startScript();
Object methodToken = compiler.beginMethod("__file__",
args);
NodeCompilerFactory.getCompiler(body).compile(body,
compiler);
compiler.endMethod(methodToken);
compiler.endScript();
- Class sourceClass = compiler.loadClass(runtime);
+ Class sourceClass = compiler.loadClass(new
JRubyClassLoader(runtime.getJRubyClassLoader()));
jitCompiledScript = (Script)sourceClass.newInstance();
if (JIT_LOGGING) System.err.println("compiled: " +
className + "." + name);
Index: src/org/jruby/javasupport/util/CompilerHelpers.java
===================================================================
--- src/org/jruby/javasupport/util/CompilerHelpers.java (revision 3593)
+++ src/org/jruby/javasupport/util/CompilerHelpers.java (working copy)
@@ -62,7 +62,7 @@
SinglyLinkedList cref = context.peekCRef();
StaticScope scope = new LocalStaticScope(null, scopeNames);
- MethodFactory factory = MethodFactory.createFactory();
+ MethodFactory factory =
MethodFactory.createFactory(compiledClass.getClassLoader());
DynamicMethod method;
if (name == "initialize" || visibility.isModuleFunction() ||
context.isTopLevel()) {
Index: src/org/jruby/Ruby.java
===================================================================
--- src/org/jruby/Ruby.java (revision 3592)
+++ src/org/jruby/Ruby.java (working copy)
@@ -290,7 +290,7 @@
StandardASMCompiler compiler = new StandardASMCompiler(node);
NodeCompilerFactory.getCompiler(node).compile(node, compiler);
- Class scriptClass = compiler.loadClass(this);
+ Class scriptClass = compiler.loadClass(this.getJRubyClassLoader());
Script script = (Script)scriptClass.newInstance();
// FIXME: Pass something better for args and block here?
Index: src/org/jruby/compiler/impl/StandardASMCompiler.java
===================================================================
--- src/org/jruby/compiler/impl/StandardASMCompiler.java (revision 3593)
+++ src/org/jruby/compiler/impl/StandardASMCompiler.java (working copy)
@@ -144,12 +144,10 @@
sourcename = "EVAL" + hashCode();
}
- public Class loadClass(Ruby runtime) throws ClassNotFoundException {
- JRubyClassLoader jcl = runtime.getJRubyClassLoader();
+ public Class loadClass(JRubyClassLoader classLoader) throws
ClassNotFoundException {
+ classLoader.defineClass(cg.c(classname), classWriter.toByteArray());
- jcl.defineClass(cg.c(classname), classWriter.toByteArray());
-
- return jcl.loadClass(cg.c(classname));
+ return classLoader.loadClass(cg.c(classname));
}
public void writeClass(File destination) throws IOException {
@@ -227,6 +225,7 @@
classWriter.visit(V1_4, ACC_PUBLIC + ACC_SUPER, classname, null,
cg.p(Object.class), new String[] {cg.p(Script.class)});
classWriter.visitSource(sourcename, null);
+ createClassInit();
createConstructor();
}
@@ -285,6 +284,31 @@
mv.end();
}
+ private void createClassInit() {
+ ClassVisitor cv = getClassVisitor();
+
+ cv.visitField(ACC_STATIC | ACC_PRIVATE | ACC_FINAL, "$isClassLoaded",
cg.ci(Boolean.TYPE), null, Boolean.FALSE);
+ cv.visitField(ACC_STATIC | ACC_PRIVATE | ACC_FINAL, "$class",
cg.ci(Class.class), null, null);
+
+ SkinnyMethodAdapter mv = new
SkinnyMethodAdapter(cv.visitMethod(ACC_PUBLIC, "<clinit>", cg.sig(Void.TYPE),
null, null));
+ mv.start();
+
+ // This is a little hacky...since clinit recurses, set a boolean so we
don't continue trying to load class
+ mv.getstatic(classname, "$isClassLoaded", cg.ci(Boolean.TYPE));
+ Label doNotLoadClass = new Label();
+ mv.ifne(doNotLoadClass);
+
+ mv.ldc(Boolean.TRUE);
+ mv.putstatic(classname, "$isClassLoaded", cg.ci(Boolean.TYPE));
+ mv.ldc(cg.c(classname));
+ mv.invokestatic(cg.p(Class.class), "forName", cg.sig(Class.class,
cg.params(String.class)));
+ mv.putstatic(classname, "$class", cg.ci(Class.class));
+
+ mv.label(doNotLoadClass);
+ mv.voidreturn();
+ mv.end();
+ }
+
public Object beginMethod(String friendlyName, ClosureCallback args) {
SkinnyMethodAdapter newMethod = new
SkinnyMethodAdapter(getClassVisitor().visitMethod(ACC_PUBLIC | ACC_STATIC,
friendlyName, METHOD_SIGNATURE, null, null));
pushMethodAdapter(newMethod);
@@ -1182,11 +1206,17 @@
}
private void getCallbackFactory() {
+ SkinnyMethodAdapter mv = getMethodAdapter();
loadRuntime();
+ getCompiledClass();
+ mv.dup();
+ mv.invokevirtual(cg.p(Class.class), "getClassLoader",
cg.sig(ClassLoader.class));
+ mv.invokestatic(cg.p(CallbackFactory.class), "createFactory",
cg.sig(CallbackFactory.class, cg.params(Ruby.class, Class.class,
ClassLoader.class)));
+ }
+
+ private void getCompiledClass() {
SkinnyMethodAdapter mv = getMethodAdapter();
- mv.ldc(classname);
- mv.invokestatic(cg.p(Class.class), "forName", cg.sig(Class.class,
cg.params(String.class)));
- invokeIRuby("callbackFactory", cg.sig(CallbackFactory.class,
cg.params(Class.class)));
+ mv.getstatic(classname, "$class", cg.ci(Class.class));
}
private void getRubyClass() {
@@ -1260,8 +1290,7 @@
loadSelf();
// load the class we're creating, for binding purposes
- mv.ldc(classname.replace('/', '.'));
- mv.invokestatic(cg.p(Class.class), "forName", cg.sig(Class.class,
cg.params(String.class)));
+ getCompiledClass();
mv.ldc(name);
Index: build.xml
===================================================================
--- build.xml (revision 3592)
+++ build.xml (working copy)
@@ -320,7 +320,8 @@
</emma>
</target>
- <target name="test"
depends="copy-test-files,instrument,run-junit,coverage-report"/>
+ <!-- This now has enabled the compiler by default; we may want to run both
with and without compilation later -->
+ <target name="test"
depends="copy-test-files,instrument,enable-compiler,run-junit,coverage-report"/>
<target name="test-compiler"
depends="copy-test-files,instrument,enable-compiler,run-junit,coverage-report"/>
---------------------------------------------------------------------
To unsubscribe from this list please visit:
http://xircles.codehaus.org/manage_email