From 83e3c21c716784fbe4926eb2bf32efd11e9f13e0 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 6 Nov 2024 19:20:13 -0600 Subject: [PATCH] Pre-generate specialized object shapes This patch generates all the specialized widths of RubyObject that we support, up to the configured maximum. This avoids racing to to generate these classes and potentially speeds up early boot stages. --- core/pom.rb | 10 ++ core/pom.xml | 18 +++ .../specialized/RubyObjectSpecializer.java | 133 +++++++++++------- 3 files changed, 111 insertions(+), 50 deletions(-) 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) {