Skip to content

Commit df6d1b8

Browse files
committed
[GR-26713] Optimize #is_a?(Class) like Java's instanceof
PullRequest: truffleruby/2104
2 parents aa11d31 + b9acd17 commit df6d1b8

File tree

2 files changed

+61
-6
lines changed

2 files changed

+61
-6
lines changed

src/main/java/org/truffleruby/core/klass/RubyClass.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010
package org.truffleruby.core.klass;
1111

12+
import java.util.Arrays;
1213
import java.util.Set;
1314

1415
import com.oracle.truffle.api.source.SourceSection;
@@ -28,13 +29,19 @@
2829
@ExportLibrary(InteropLibrary.class)
2930
public final class RubyClass extends RubyModule implements ObjectGraphNode {
3031

32+
private static final RubyClass[] EMPTY_CLASS_ARRAY = new RubyClass[0];
33+
3134
public final boolean isSingleton;
3235
/** If this is an object's metaclass, then nonSingletonClass is the logical class of the object. */
3336
public final RubyClass nonSingletonClass;
3437
public final RubyDynamicObject attached;
3538
public Shape instanceShape;
3639
/* a RubyClass, or nil for BasicObject, or null when not yet initialized */
3740
public Object superclass;
41+
public RubyClass[] ancestorClasses;
42+
43+
/** Depth from BasicObject (= 0) in the inheritance hierarchy. */
44+
public int depth;
3845

3946
public RubyClass(
4047
RubyClass classClass,
@@ -50,20 +57,27 @@ public RubyClass(
5057
assert isSingleton == (instanceShape == null);
5158
this.isSingleton = isSingleton;
5259
this.attached = attached;
53-
this.superclass = superclass;
5460
this.instanceShape = instanceShape;
5561

62+
if (superclass instanceof RubyClass) {
63+
updateSuperclass((RubyClass) superclass);
64+
} else { // BasicObject (nil superclass) or uninitialized class (null)
65+
this.depth = 0;
66+
this.superclass = superclass;
67+
this.ancestorClasses = EMPTY_CLASS_ARRAY;
68+
}
69+
5670
this.nonSingletonClass = computeNonSingletonClass(isSingleton, superclass);
5771
}
5872

59-
/** Special constructor to build the 'Class' RubyClass itself */
73+
74+
/** Special constructor to build the 'Class' RubyClass itself. The superclass is set later. */
6075
RubyClass(RubyContext context, Shape classShape) {
6176
super(context, classShape, "constructor only for the class Class");
6277
this.isSingleton = false;
6378
this.attached = null;
6479
this.superclass = null;
6580
this.instanceShape = classShape;
66-
6781
this.nonSingletonClass = this;
6882
}
6983

@@ -86,10 +100,19 @@ public boolean isInitialized() {
86100

87101
public void setSuperClass(RubyClass superclass) {
88102
assert this.superclass == null || this.superclass == superclass;
89-
this.superclass = superclass;
103+
updateSuperclass(superclass);
90104
fields.setSuperClass(superclass);
91105
}
92106

107+
private void updateSuperclass(RubyClass superclass) {
108+
final RubyClass[] superAncestors = superclass.ancestorClasses;
109+
final RubyClass[] ancestors = Arrays.copyOf(superAncestors, superAncestors.length + 1);
110+
ancestors[superAncestors.length] = superclass;
111+
this.superclass = superclass;
112+
this.depth = superclass.depth + 1;
113+
this.ancestorClasses = ancestors;
114+
}
115+
93116
@Override
94117
public void getAdjacentObjects(Set<Object> reachable) {
95118
super.getAdjacentObjects(reachable);

src/main/java/org/truffleruby/language/objects/IsANode.java

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010
package org.truffleruby.language.objects;
1111

12+
import com.oracle.truffle.api.profiles.ConditionProfile;
1213
import org.truffleruby.RubyLanguage;
1314
import org.truffleruby.core.klass.RubyClass;
1415
import org.truffleruby.core.module.ModuleOperations;
@@ -40,7 +41,7 @@ public static IsANode getUncached() {
4041
"module == cachedModule" },
4142
assumptions = "getHierarchyUnmodifiedAssumption(cachedModule)",
4243
limit = "getCacheLimit()")
43-
protected boolean isACached(Object self, RubyModule module,
44+
protected boolean isAMetaClassCached(Object self, RubyModule module,
4445
@Cached MetaClassNode metaClassNode,
4546
@Cached("metaClassNode.execute(self)") RubyClass cachedMetaClass,
4647
@Cached("module") RubyModule cachedModule,
@@ -52,7 +53,38 @@ public Assumption getHierarchyUnmodifiedAssumption(RubyModule module) {
5253
return module.fields.getHierarchyUnmodifiedAssumption();
5354
}
5455

55-
@Specialization(replaces = "isACached")
56+
@Specialization(
57+
guards = "klass == cachedClass",
58+
replaces = "isAMetaClassCached",
59+
limit = "getCacheLimit()")
60+
protected boolean isAClassCached(Object self, RubyClass klass,
61+
@Cached MetaClassNode metaClassNode,
62+
@Cached ConditionProfile isMetaClass,
63+
@Cached("klass") RubyClass cachedClass) {
64+
return isAClassUncached(self, klass, metaClassNode, isMetaClass);
65+
}
66+
67+
@Specialization(replaces = "isAClassCached")
68+
protected boolean isAClassUncached(Object self, RubyClass klass,
69+
@Cached MetaClassNode metaClassNode,
70+
@Cached ConditionProfile isMetaClass) {
71+
final RubyClass metaclass = metaClassNode.execute(self);
72+
73+
if (isMetaClass.profile(metaclass == klass)) {
74+
return true;
75+
}
76+
77+
assert metaclass.ancestorClasses != null;
78+
79+
final int depth = klass.depth;
80+
if (depth < metaclass.depth) { // < and not <= as we checked for equality above
81+
return metaclass.ancestorClasses[depth] == klass;
82+
}
83+
84+
return false;
85+
}
86+
87+
@Specialization(guards = "!isRubyClass(module)", replaces = "isAMetaClassCached")
5688
protected boolean isAUncached(Object self, RubyModule module,
5789
@Cached MetaClassNode metaClassNode) {
5890
return isA(metaClassNode.execute(self), module);

0 commit comments

Comments
 (0)