Skip to content

Commit c5fb69e

Browse files
authored
Merge pull request jruby#8433 from headius/specialized_object_classloader
Root specialized object classloader at JRuby classloader
2 parents fe763ca + 83e3c21 commit c5fb69e

File tree

6 files changed

+153
-63
lines changed

6 files changed

+153
-63
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/Ruby.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import org.jruby.runtime.MethodIndex;
7070
import org.jruby.runtime.TraceEventManager;
7171
import org.jruby.runtime.invokedynamic.InvokeDynamicSupport;
72+
import org.jruby.specialized.RubyObjectSpecializer;
7273
import org.jruby.util.JavaNameMangler;
7374
import org.jruby.util.MRIRecursionGuard;
7475
import org.jruby.util.StringSupport;
@@ -343,6 +344,9 @@ private Ruby(RubyInstanceConfig config) {
343344
objectClass.setConstant("Module", moduleClass);
344345
objectClass.setConstant("Refinement", refinementClass);
345346

347+
// specializer for RubyObject subclasses
348+
objectSpecializer = new RubyObjectSpecializer(this);
349+
346350
// Initialize Kernel and include into Object
347351
RubyModule kernel = kernelModule = RubyKernel.createKernelModule(this);
348352
objectClass.includeModule(kernelModule);
@@ -2619,6 +2623,10 @@ public JavaSupport getJavaSupport() {
26192623
return javaSupport;
26202624
}
26212625

2626+
public RubyObjectSpecializer getObjectSpecializer() {
2627+
return objectSpecializer;
2628+
}
2629+
26222630
public static ClassLoader getClassLoader() {
26232631
// we try to getService the classloader that loaded JRuby, falling back on System
26242632
ClassLoader loader = Ruby.class.getClassLoader();
@@ -5547,6 +5555,9 @@ public RubyClass getData() {
55475555
private final JavaSupport javaSupport;
55485556
private final JRubyClassLoader jrubyClassLoader;
55495557

5558+
// Object Specializer
5559+
private final RubyObjectSpecializer objectSpecializer;
5560+
55505561
// Management/monitoring
55515562
private final BeanManager beanManager;
55525563

core/src/main/java/org/jruby/RubyObject.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
176176
System.err.println(klass + ";" + foundVariables);
177177
}
178178

179-
allocator = RubyObjectSpecializer.specializeForVariables(klass, foundVariables);
179+
allocator = runtime.getObjectSpecializer().specializeForVariables(klass, foundVariables);
180180

181181
// invalidate metaclass so new allocator is picked up for specialized .new
182182
klass.metaClass.invalidateCacheDescendants();

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

Lines changed: 94 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +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;
39-
import org.jruby.util.OneShotClassLoader;
4038
import org.jruby.util.cli.Options;
4139
import org.jruby.util.collections.NonBlockingHashMapLong;
4240
import org.objectweb.asm.Label;
4341
import org.objectweb.asm.tree.LabelNode;
4442

45-
import java.lang.invoke.CallSite;
46-
import java.lang.invoke.LambdaMetafactory;
47-
import java.lang.invoke.MethodHandle;
4843
import java.lang.invoke.MethodHandles;
49-
import java.lang.invoke.MethodType;
44+
import java.nio.file.Files;
45+
import java.nio.file.Paths;
5046
import java.util.Set;
5147

5248
import static org.jruby.util.CodegenUtils.ci;
@@ -59,14 +55,19 @@
5955
public class RubyObjectSpecializer {
6056

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

63-
private static ClassAndAllocator getClassForSize(int size) {
64-
return SPECIALIZED_CLASSES.get(size);
60+
private final Ruby runtime;
61+
62+
private ClassAndAllocator getClassForSize(int size) {
63+
return specializedClasses.get(size);
6564
}
6665

67-
private static final NonBlockingHashMapLong<ClassAndAllocator> SPECIALIZED_CLASSES = new NonBlockingHashMapLong<>();
66+
private final NonBlockingHashMapLong<ClassAndAllocator> specializedClasses = new NonBlockingHashMapLong<>();
6867

69-
private static final ClassDefiningClassLoader LOADER = new OneShotClassLoader(Ruby.getClassLoader());
68+
public RubyObjectSpecializer(Ruby runtime) {
69+
this.runtime = runtime;
70+
}
7071

7172
static class ClassAndAllocator {
7273
final Class cls;
@@ -78,57 +79,21 @@ static class ClassAndAllocator {
7879
}
7980
}
8081

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

87-
ClassAndAllocator cna = null;
88-
String className = null;
86+
ClassAndAllocator cna;
8987

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

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

@@ -155,7 +120,78 @@ public static ObjectAllocator specializeForVariables(RubyClass klass, Set<String
155120
return cna.allocator;
156121
}
157122

158-
private static 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) {
159195
// ensure only one thread will attempt to generate and define the new class
160196
final String baseName = p(RubyObject.class);
161197

@@ -222,11 +258,7 @@ private static Class generateInternal(int size, final String clsPath) {
222258
}});
223259
}});
224260
}};
225-
226-
Class specializedClass = defineClass(jiteClass);
227-
defineClass(jiteClass.getChildClasses().get(0));
228-
229-
return specializedClass;
261+
return jiteClass;
230262
}
231263

232264
private static void genGetSwitch(String clsPath, int size, CodeBlock block, int offsetVar) {
@@ -264,8 +296,8 @@ private static void genPutSwitch(String clsPath, int size, CodeBlock block, int
264296
block.label(defaultError);
265297
}
266298

267-
private static Class defineClass(JiteClass jiteClass) {
268-
return LOADER.defineClass(classNameFromJiteClass(jiteClass), jiteClass.toBytes(JDKVersion.V1_8));
299+
private Class defineClass(JiteClass jiteClass) {
300+
return runtime.getJRubyClassLoader().defineClass(classNameFromJiteClass(jiteClass), jiteClass.toBytes(JDKVersion.V1_8));
269301
}
270302

271303
private static String classNameFromJiteClass(JiteClass jiteClass) {

spec/java_integration/interfaces/implementation_spec.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,25 @@ def bar
827827

828828
expect(java_cls.interfaces).to include(java.lang.Runnable.java_class)
829829
end
830+
831+
832+
describe "that extends a specializable RubyObject" do
833+
class C1
834+
end
835+
836+
it "produces a Java class that extends that specialized type" do
837+
# construct C1 first to use specialized class
838+
c1obj = C1.new
839+
840+
c2 = Class.new(C1) do
841+
include ReturnsInterface
842+
end
843+
844+
c2obj = c2.new
845+
846+
expect(JRuby.ref(c2obj).getClass.getSuperclass).to eq(JRuby.ref(c1obj).getClass)
847+
end
848+
end
830849
end
831850

832851
describe "A class that extends a DelegateClass" do

0 commit comments

Comments
 (0)