Skip to content

Commit 0472a49

Browse files
committed
update the IsBuiltinClassProfile to cache shapes so we can avoid the indirection to access the class in low-degree polymorphic cases
1 parent 7d585f0 commit 0472a49

File tree

1 file changed

+77
-8
lines changed

1 file changed

+77
-8
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/IsBuiltinClassProfile.java

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,19 @@
4040
*/
4141
package com.oracle.graal.python.nodes.object;
4242

43+
import com.oracle.graal.python.PythonLanguage;
4344
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
4445
import com.oracle.graal.python.builtins.objects.object.PythonObject;
45-
import com.oracle.graal.python.builtins.objects.type.PythonAbstractClass;
4646
import com.oracle.graal.python.builtins.objects.type.LazyPythonClass;
47+
import com.oracle.graal.python.builtins.objects.type.PythonAbstractClass;
4748
import com.oracle.graal.python.builtins.objects.type.PythonBuiltinClass;
4849
import com.oracle.graal.python.runtime.exception.PException;
50+
import com.oracle.truffle.api.Assumption;
4951
import com.oracle.truffle.api.CompilerDirectives;
5052
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
53+
import com.oracle.truffle.api.nodes.ExplodeLoop;
54+
import com.oracle.truffle.api.nodes.ExplodeLoop.LoopExplosionKind;
55+
import com.oracle.truffle.api.object.Shape;
5156

5257
public final class IsBuiltinClassProfile {
5358
@CompilationFinal private boolean isBuiltinType;
@@ -57,6 +62,33 @@ public final class IsBuiltinClassProfile {
5762
@CompilationFinal private boolean match;
5863
@CompilationFinal private boolean noMatch;
5964

65+
// n.b.: (tfel) We store the python class as a Shape property on the
66+
// DynamicObject representing the Python-level object. Thus, accessing the
67+
// python class incurs an indirection that we'd like to avoid if
68+
// possible. We use this cache to avoid the indirection. In the single
69+
// context case, we just cache all classes, in the multi-context case, we
70+
// only cache classes if they are builtin types that are shared across
71+
// contexts.
72+
private final Assumption singleContextAssumption = PythonLanguage.getCurrent().singleContextAssumption;
73+
private static final int CLASS_CACHE_SIZE = 3;
74+
@CompilationFinal(dimensions = 1) private ClassCache[] classCache = new ClassCache[CLASS_CACHE_SIZE];
75+
@CompilationFinal private boolean cacheUsedInSingleContext = false;
76+
77+
private static final class ClassCache {
78+
private final LazyPythonClass klass;
79+
private final Shape shape;
80+
81+
ClassCache(Shape shape, LazyPythonClass klass) {
82+
this.shape = shape;
83+
this.klass = klass;
84+
}
85+
}
86+
87+
private static final IsBuiltinClassProfile UNCACHED = new IsBuiltinClassProfile();
88+
static {
89+
UNCACHED.classCache = null;
90+
}
91+
6092
private IsBuiltinClassProfile() {
6193
// private constructor
6294
}
@@ -66,27 +98,64 @@ public static IsBuiltinClassProfile create() {
6698
}
6799

68100
public static IsBuiltinClassProfile getUncached() {
69-
return new IsBuiltinClassProfile();
101+
return UNCACHED;
70102
}
71103

72-
public boolean profileIsAnyBuiltinException(PException object) {
73-
return profileIsAnyBuiltinClass(object.getExceptionObject().getLazyPythonClass());
104+
@ExplodeLoop(kind = LoopExplosionKind.FULL_EXPLODE_UNTIL_RETURN)
105+
private LazyPythonClass getLazyPythonClass(PythonObject object) {
106+
if (classCache != null) {
107+
// we're still caching
108+
if (!singleContextAssumption.isValid() && cacheUsedInSingleContext) {
109+
// we previously used this cache in a single context, now we're
110+
// in a multi-context mode. Reset the cache.
111+
CompilerDirectives.transferToInterpreterAndInvalidate();
112+
cacheUsedInSingleContext = false;
113+
classCache = new ClassCache[CLASS_CACHE_SIZE];
114+
}
115+
for (int i = 0; i < classCache.length; i++) {
116+
ClassCache cache = classCache[i];
117+
if (cache == null) {
118+
CompilerDirectives.transferToInterpreterAndInvalidate();
119+
Shape shape = object.getStorage().getShape();
120+
LazyPythonClass klass = PythonObject.getLazyPythonClass(shape.getObjectType());
121+
if (klass instanceof PythonBuiltinClassType) {
122+
classCache[i] = new ClassCache(shape, klass);
123+
} else if (singleContextAssumption.isValid()) {
124+
// we're caching a non-builtin type, so if we switch to
125+
// a multi-context, the cache needs to be flushed.
126+
cacheUsedInSingleContext = true;
127+
classCache[i] = new ClassCache(shape, klass);
128+
} else {
129+
classCache = null;
130+
}
131+
return klass;
132+
} else if (cache.shape == object.getStorage().getShape()) {
133+
return cache.klass;
134+
}
135+
}
136+
}
137+
if (classCache != null) {
138+
// cache overflow, revert to generic access
139+
CompilerDirectives.transferToInterpreterAndInvalidate();
140+
classCache = null;
141+
}
142+
return object.getLazyPythonClass();
74143
}
75144

76145
public boolean profileIsAnyBuiltinObject(PythonObject object) {
77-
return profileIsAnyBuiltinClass(object.getLazyPythonClass());
146+
return profileIsAnyBuiltinClass(getLazyPythonClass(object));
78147
}
79148

80149
public boolean profileIsOtherBuiltinObject(PythonObject object, PythonBuiltinClassType type) {
81-
return profileIsOtherBuiltinClass(object.getLazyPythonClass(), type);
150+
return profileIsOtherBuiltinClass(getLazyPythonClass(object), type);
82151
}
83152

84153
public boolean profileException(PException object, PythonBuiltinClassType type) {
85-
return profileClass(object.getExceptionObject().getLazyPythonClass(), type);
154+
return profileClass(getLazyPythonClass(object.getExceptionObject()), type);
86155
}
87156

88157
public boolean profileObject(PythonObject object, PythonBuiltinClassType type) {
89-
return profileClass(object.getLazyPythonClass(), type);
158+
return profileClass(getLazyPythonClass(object), type);
90159

91160
}
92161

0 commit comments

Comments
 (0)