Skip to content

Commit

Permalink
Pre-generate specialized object shapes
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
headius committed Nov 18, 2024
1 parent ccb0757 commit 83e3c21
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 50 deletions.
10 changes: 10 additions & 0 deletions core/pom.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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( '<classpath/>' ),
'org.jruby.specialized.RubyObjectSpecializer',
'${project.build.outputDirectory}' ],
'executable' => 'java',
'classpathScope' => 'compile' )
end
end

Expand Down
18 changes: 18 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,24 @@ DO NOT MODIFY - GENERATED CODE
<classpathScope>compile</classpathScope>
</configuration>
</execution>
<execution>
<id>specialized-object-generator</id>
<phase>process-classes</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<arguments>
<argument>-Djruby.bytecode.version=${base.java.version}</argument>
<argument>-classpath</argument>
<classpath />
<argument>org.jruby.specialized.RubyObjectSpecializer</argument>
<argument>${project.build.outputDirectory}</argument>
</arguments>
<executable>java</executable>
<classpathScope>compile</classpathScope>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
Expand Down
133 changes: 83 additions & 50 deletions core/src/main/java/org/jruby/specialized/RubyObjectSpecializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -78,56 +80,20 @@ static class ClassAndAllocator {
}

public ObjectAllocator specializeForVariables(RubyClass klass, Set<String> 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);
}
}

Expand All @@ -154,7 +120,78 @@ public ObjectAllocator specializeForVariables(RubyClass klass, Set<String> 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);

Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 83e3c21

Please sign in to comment.