Skip to content

Commit 83e3c21

Browse files
committed
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.
1 parent ccb0757 commit 83e3c21

File tree

3 files changed

+111
-50
lines changed

3 files changed

+111
-50
lines changed

core/pom.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,16 @@
166166
'${project.build.outputDirectory}' ],
167167
'executable' => 'java',
168168
'classpathScope' => 'compile' )
169+
170+
execute_goals( 'exec',
171+
:id => 'specialized-object-generator',
172+
'arguments' => [ '-Djruby.bytecode.version=${base.java.version}',
173+
'-classpath',
174+
xml( '<classpath/>' ),
175+
'org.jruby.specialized.RubyObjectSpecializer',
176+
'${project.build.outputDirectory}' ],
177+
'executable' => 'java',
178+
'classpathScope' => 'compile' )
169179
end
170180
end
171181

core/pom.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,24 @@ DO NOT MODIFY - GENERATED CODE
417417
<classpathScope>compile</classpathScope>
418418
</configuration>
419419
</execution>
420+
<execution>
421+
<id>specialized-object-generator</id>
422+
<phase>process-classes</phase>
423+
<goals>
424+
<goal>exec</goal>
425+
</goals>
426+
<configuration>
427+
<arguments>
428+
<argument>-Djruby.bytecode.version=${base.java.version}</argument>
429+
<argument>-classpath</argument>
430+
<classpath />
431+
<argument>org.jruby.specialized.RubyObjectSpecializer</argument>
432+
<argument>${project.build.outputDirectory}</argument>
433+
</arguments>
434+
<executable>java</executable>
435+
<classpathScope>compile</classpathScope>
436+
</configuration>
437+
</execution>
420438
</executions>
421439
</plugin>
422440
<plugin>

core/src/main/java/org/jruby/specialized/RubyObjectSpecializer.java

Lines changed: 83 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,14 @@
3535
import org.jruby.runtime.Helpers;
3636
import org.jruby.runtime.ObjectAllocator;
3737
import org.jruby.runtime.builtin.IRubyObject;
38-
import org.jruby.util.ClassDefiningClassLoader;
3938
import org.jruby.util.cli.Options;
4039
import org.jruby.util.collections.NonBlockingHashMapLong;
4140
import org.objectweb.asm.Label;
4241
import org.objectweb.asm.tree.LabelNode;
4342

4443
import java.lang.invoke.MethodHandles;
44+
import java.nio.file.Files;
45+
import java.nio.file.Paths;
4546
import java.util.Set;
4647

4748
import static org.jruby.util.CodegenUtils.ci;
@@ -54,6 +55,7 @@
5455
public class RubyObjectSpecializer {
5556

5657
public static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
58+
private static final String GENERATED_PACKAGE = "org/jruby/gen/";
5759

5860
private final Ruby runtime;
5961

@@ -78,56 +80,20 @@ static class ClassAndAllocator {
7880
}
7981

8082
public ObjectAllocator specializeForVariables(RubyClass klass, Set<String> foundVariables) {
81-
int size = foundVariables.size();
83+
// clamp to max object width
84+
int size = Math.min(foundVariables.size(), Options.REIFY_VARIABLES_MAX.load());
8285

83-
// clamp to max object width (jruby/jruby#
84-
size = Math.min(size, Options.REIFY_VARIABLES_MAX.load());
85-
86-
ClassAndAllocator cna = null;
87-
String className = null;
86+
ClassAndAllocator cna;
8887

8988
if (Options.REIFY_VARIABLES_NAME.load()) {
90-
className = klass.getName();
91-
92-
if (className.startsWith("#")) {
93-
className = "Anonymous" + Integer.toHexString(System.identityHashCode(klass));
94-
} else {
95-
className = className.replace("::", "/");
96-
}
89+
// use Ruby class name for debugging, profiling
90+
cna = generateSpecializedRubyObject(uniqueClassName(klass), size, false);
9791
} else {
98-
// Generate class for specified size
92+
// Generic class for specified size
9993
cna = getClassForSize(size);
10094

10195
if (cna == null) {
102-
className = "RubyObject" + size;
103-
}
104-
}
105-
106-
// if we have a className, proceed to generate
107-
if (className != null) {
108-
final String clsPath = "org/jruby/gen/" + className;
109-
110-
synchronized (this) {
111-
Class specialized;
112-
try {
113-
// try loading class without generating
114-
specialized = runtime.getJRubyClassLoader().loadClass(clsPath.replace('/', '.'));
115-
} catch (ClassNotFoundException cnfe) {
116-
// generate specialized class
117-
specialized = generateInternal(size, clsPath);
118-
}
119-
120-
try {
121-
ObjectAllocator allocator = (ObjectAllocator) specialized.getDeclaredClasses()[0].getConstructor().newInstance();
122-
123-
cna = new ClassAndAllocator(specialized, allocator);
124-
125-
if (!Options.REIFY_VARIABLES_NAME.load()) {
126-
specializedClasses.put(size, cna);
127-
}
128-
} catch (Throwable t) {
129-
throw new RuntimeException(t);
130-
}
96+
cna = generateSpecializedRubyObject(genericClassName(size), size, true);
13197
}
13298
}
13399

@@ -154,7 +120,78 @@ public ObjectAllocator specializeForVariables(RubyClass klass, Set<String> found
154120
return cna.allocator;
155121
}
156122

157-
private Class generateInternal(int size, final String clsPath) {
123+
private ClassAndAllocator generateSpecializedRubyObject(String className, int size, boolean cache) {
124+
ClassAndAllocator cna;
125+
126+
synchronized (this) {
127+
Class specialized;
128+
try {
129+
// try loading class without generating
130+
specialized = runtime.getJRubyClassLoader().loadClass(className.replace('/', '.'));
131+
} catch (ClassNotFoundException cnfe) {
132+
// generate specialized class
133+
specialized = generateInternal(className, size);
134+
}
135+
136+
try {
137+
ObjectAllocator allocator = (ObjectAllocator) specialized.getDeclaredClasses()[0].getConstructor().newInstance();
138+
139+
cna = new ClassAndAllocator(specialized, allocator);
140+
} catch (Throwable t) {
141+
throw new RuntimeException(t);
142+
}
143+
}
144+
145+
if (cache) {
146+
specializedClasses.put(size, cna);
147+
}
148+
149+
return cna;
150+
}
151+
152+
private static String genericClassName(int size) {
153+
return GENERATED_PACKAGE + "RubyObject" + size;
154+
}
155+
156+
private static String uniqueClassName(RubyClass klass) {
157+
String className = klass.getName();
158+
159+
if (className.startsWith("#")) {
160+
className = "Anonymous" + Integer.toHexString(System.identityHashCode(klass));
161+
} else {
162+
className = className.replace("::", "/");
163+
}
164+
165+
return GENERATED_PACKAGE + className;
166+
}
167+
168+
/**
169+
* Emit all generic RubyObject specializations to disk, so they do not need to generate at runtime.
170+
*/
171+
public static void main(String[] args) throws Throwable {
172+
String targetPath = args[0];
173+
174+
Files.createDirectories(Paths.get(targetPath, GENERATED_PACKAGE));
175+
176+
int maxVars = Options.REIFY_VARIABLES_MAX.load();
177+
for (int i = 0; i <= maxVars; i++) {
178+
String clsPath = genericClassName(i);
179+
JiteClass jcls = generateJiteClass(clsPath, i);
180+
Files.write(Paths.get(targetPath, clsPath + ".class"), jcls.toBytes(JDKVersion.V1_8));
181+
Files.write(Paths.get(targetPath, clsPath + "Allocator.class"), jcls.getChildClasses().get(0).toBytes(JDKVersion.V1_8));
182+
}
183+
}
184+
185+
private Class generateInternal(final String clsPath, int size) {
186+
final JiteClass jiteClass = generateJiteClass(clsPath, size);
187+
188+
Class specializedClass = defineClass(jiteClass);
189+
defineClass(jiteClass.getChildClasses().get(0));
190+
191+
return specializedClass;
192+
}
193+
194+
private static JiteClass generateJiteClass(String clsPath, int size) {
158195
// ensure only one thread will attempt to generate and define the new class
159196
final String baseName = p(RubyObject.class);
160197

@@ -221,11 +258,7 @@ private Class generateInternal(int size, final String clsPath) {
221258
}});
222259
}});
223260
}};
224-
225-
Class specializedClass = defineClass(jiteClass);
226-
defineClass(jiteClass.getChildClasses().get(0));
227-
228-
return specializedClass;
261+
return jiteClass;
229262
}
230263

231264
private static void genGetSwitch(String clsPath, int size, CodeBlock block, int offsetVar) {

0 commit comments

Comments
 (0)