diff --git a/core/pom.rb b/core/pom.rb index 7ada71f0b37..9fd2625b6f4 100644 --- a/core/pom.rb +++ b/core/pom.rb @@ -166,6 +166,16 @@ '${project.build.outputDirectory}' ], 'executable' => 'java', 'classpathScope' => 'compile' ) + + execute_goals( 'exec', + :id => 'specialized-object-generator', + 'arguments' => [ '-Djruby.bytecode.version=${base.java.version}', + '-classpath', + xml( '' ), + 'org.jruby.specialized.RubyObjectSpecializer', + '${project.build.outputDirectory}' ], + 'executable' => 'java', + 'classpathScope' => 'compile' ) end end diff --git a/core/pom.xml b/core/pom.xml index c93c228a0e7..113027cc6bd 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -417,6 +417,24 @@ DO NOT MODIFY - GENERATED CODE compile + + specialized-object-generator + process-classes + + exec + + + + -Djruby.bytecode.version=${base.java.version} + -classpath + + org.jruby.specialized.RubyObjectSpecializer + ${project.build.outputDirectory} + + java + compile + + diff --git a/core/src/main/java/org/jruby/specialized/RubyObjectSpecializer.java b/core/src/main/java/org/jruby/specialized/RubyObjectSpecializer.java index 3599062c324..362ebafd57d 100644 --- a/core/src/main/java/org/jruby/specialized/RubyObjectSpecializer.java +++ b/core/src/main/java/org/jruby/specialized/RubyObjectSpecializer.java @@ -35,13 +35,14 @@ import org.jruby.runtime.Helpers; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.builtin.IRubyObject; -import org.jruby.util.ClassDefiningClassLoader; import org.jruby.util.cli.Options; import org.jruby.util.collections.NonBlockingHashMapLong; import org.objectweb.asm.Label; import org.objectweb.asm.tree.LabelNode; import java.lang.invoke.MethodHandles; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Set; import static org.jruby.util.CodegenUtils.ci; @@ -54,6 +55,7 @@ public class RubyObjectSpecializer { public static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + private static final String GENERATED_PACKAGE = "org/jruby/gen/"; private final Ruby runtime; @@ -78,56 +80,20 @@ static class ClassAndAllocator { } public ObjectAllocator specializeForVariables(RubyClass klass, Set foundVariables) { - int size = foundVariables.size(); + // clamp to max object width + int size = Math.min(foundVariables.size(), Options.REIFY_VARIABLES_MAX.load()); - // clamp to max object width (jruby/jruby# - size = Math.min(size, Options.REIFY_VARIABLES_MAX.load()); - - ClassAndAllocator cna = null; - String className = null; + ClassAndAllocator cna; if (Options.REIFY_VARIABLES_NAME.load()) { - className = klass.getName(); - - if (className.startsWith("#")) { - className = "Anonymous" + Integer.toHexString(System.identityHashCode(klass)); - } else { - className = className.replace("::", "/"); - } + // use Ruby class name for debugging, profiling + cna = generateSpecializedRubyObject(uniqueClassName(klass), size, false); } else { - // Generate class for specified size + // Generic class for specified size cna = getClassForSize(size); if (cna == null) { - className = "RubyObject" + size; - } - } - - // if we have a className, proceed to generate - if (className != null) { - final String clsPath = "org/jruby/gen/" + className; - - synchronized (this) { - Class specialized; - try { - // try loading class without generating - specialized = runtime.getJRubyClassLoader().loadClass(clsPath.replace('/', '.')); - } catch (ClassNotFoundException cnfe) { - // generate specialized class - specialized = generateInternal(size, clsPath); - } - - try { - ObjectAllocator allocator = (ObjectAllocator) specialized.getDeclaredClasses()[0].getConstructor().newInstance(); - - cna = new ClassAndAllocator(specialized, allocator); - - if (!Options.REIFY_VARIABLES_NAME.load()) { - specializedClasses.put(size, cna); - } - } catch (Throwable t) { - throw new RuntimeException(t); - } + cna = generateSpecializedRubyObject(genericClassName(size), size, true); } } @@ -154,7 +120,78 @@ public ObjectAllocator specializeForVariables(RubyClass klass, Set found return cna.allocator; } - private Class generateInternal(int size, final String clsPath) { + private ClassAndAllocator generateSpecializedRubyObject(String className, int size, boolean cache) { + ClassAndAllocator cna; + + synchronized (this) { + Class specialized; + try { + // try loading class without generating + specialized = runtime.getJRubyClassLoader().loadClass(className.replace('/', '.')); + } catch (ClassNotFoundException cnfe) { + // generate specialized class + specialized = generateInternal(className, size); + } + + try { + ObjectAllocator allocator = (ObjectAllocator) specialized.getDeclaredClasses()[0].getConstructor().newInstance(); + + cna = new ClassAndAllocator(specialized, allocator); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + if (cache) { + specializedClasses.put(size, cna); + } + + return cna; + } + + private static String genericClassName(int size) { + return GENERATED_PACKAGE + "RubyObject" + size; + } + + private static String uniqueClassName(RubyClass klass) { + String className = klass.getName(); + + if (className.startsWith("#")) { + className = "Anonymous" + Integer.toHexString(System.identityHashCode(klass)); + } else { + className = className.replace("::", "/"); + } + + return GENERATED_PACKAGE + className; + } + + /** + * Emit all generic RubyObject specializations to disk, so they do not need to generate at runtime. + */ + public static void main(String[] args) throws Throwable { + String targetPath = args[0]; + + Files.createDirectories(Paths.get(targetPath, GENERATED_PACKAGE)); + + int maxVars = Options.REIFY_VARIABLES_MAX.load(); + for (int i = 0; i <= maxVars; i++) { + String clsPath = genericClassName(i); + JiteClass jcls = generateJiteClass(clsPath, i); + Files.write(Paths.get(targetPath, clsPath + ".class"), jcls.toBytes(JDKVersion.V1_8)); + Files.write(Paths.get(targetPath, clsPath + "Allocator.class"), jcls.getChildClasses().get(0).toBytes(JDKVersion.V1_8)); + } + } + + private Class generateInternal(final String clsPath, int size) { + final JiteClass jiteClass = generateJiteClass(clsPath, size); + + Class specializedClass = defineClass(jiteClass); + defineClass(jiteClass.getChildClasses().get(0)); + + return specializedClass; + } + + private static JiteClass generateJiteClass(String clsPath, int size) { // ensure only one thread will attempt to generate and define the new class final String baseName = p(RubyObject.class); @@ -221,11 +258,7 @@ private Class generateInternal(int size, final String clsPath) { }}); }}); }}; - - Class specializedClass = defineClass(jiteClass); - defineClass(jiteClass.getChildClasses().get(0)); - - return specializedClass; + return jiteClass; } private static void genGetSwitch(String clsPath, int size, CodeBlock block, int offsetVar) {