4444import com .oracle .graal .python .builtins .Python3Core ;
4545import com .oracle .graal .python .builtins .PythonBuiltinClassType ;
4646import com .oracle .graal .python .builtins .objects .PNone ;
47+ import com .oracle .graal .python .builtins .objects .object .PythonObject ;
4748import com .oracle .graal .python .builtins .objects .type .MroShape ;
4849import com .oracle .graal .python .builtins .objects .type .MroShape .MroShapeLookupResult ;
4950import com .oracle .graal .python .builtins .objects .type .PythonAbstractClass ;
5051import com .oracle .graal .python .builtins .objects .type .PythonClass ;
5152import com .oracle .graal .python .builtins .objects .type .TypeNodes .GetMroStorageNode ;
5253import com .oracle .graal .python .builtins .objects .type .TypeNodes .IsSameTypeNode ;
54+ import com .oracle .graal .python .nodes .PGuards ;
5355import com .oracle .graal .python .nodes .PNodeWithContext ;
5456import com .oracle .graal .python .runtime .PythonContext ;
5557import com .oracle .graal .python .runtime .PythonOptions ;
6466import com .oracle .truffle .api .dsl .Bind ;
6567import com .oracle .truffle .api .dsl .Cached ;
6668import com .oracle .truffle .api .dsl .Cached .Shared ;
69+ import com .oracle .truffle .api .dsl .GenerateCached ;
6770import com .oracle .truffle .api .dsl .GenerateInline ;
6871import com .oracle .truffle .api .dsl .GenerateUncached ;
6972import com .oracle .truffle .api .dsl .Idempotent ;
7578import com .oracle .truffle .api .nodes .ExplodeLoop ;
7679import com .oracle .truffle .api .nodes .ExplodeLoop .LoopExplosionKind ;
7780import com .oracle .truffle .api .nodes .Node ;
81+ import com .oracle .truffle .api .object .DynamicObject ;
7882import com .oracle .truffle .api .profiles .InlinedConditionProfile ;
7983import com .oracle .truffle .api .strings .TruffleString ;
8084
@@ -156,6 +160,7 @@ public static LookupAttributeInMRONode createForLookupOfUnmanagedClasses(Truffle
156160 return LookupAttributeInMRONodeGen .create (key , true );
157161 }
158162
163+ @ NeverDefault
159164 static Object findAttr (Python3Core core , PythonBuiltinClassType klass , TruffleString key ) {
160165 return findAttr (core , klass , key , ReadAttributeFromPythonObjectNode .getUncached ());
161166 }
@@ -202,7 +207,22 @@ public MROChangedException(Object result) {
202207 }
203208 }
204209
210+ @ SuppressWarnings ("serial" )
211+ public static class MROGenericDictException extends StacktracelessCheckedException {
212+ private static final MROGenericDictException INSTANCE = new MROGenericDictException ();
213+ }
214+
205215 MroSequenceStorage .FinalAttributeAssumptionPair findAttrAndAssumptionInMRO (Object klass ) throws MROChangedException {
216+ try {
217+ return findAttrAndAssumptionInMRO (this , klass , key , skipNonStaticBases , false );
218+ } catch (MROGenericDictException ignore ) {
219+ throw CompilerDirectives .shouldNotReachHere ();
220+ }
221+ }
222+
223+ @ NeverDefault
224+ static MroSequenceStorage .FinalAttributeAssumptionPair findAttrAndAssumptionInMRO (Node n , Object klass , TruffleString key , boolean skipNonStaticBases ,
225+ boolean mustNotSideEffect ) throws MROChangedException , MROGenericDictException {
206226 CompilerAsserts .neverPartOfCompilation ();
207227 // Regarding potential side effects to MRO caused by __eq__ of the keys in the dicts that we
208228 // search through: CPython seems to read the MRO once and then compute the result also
@@ -214,19 +234,30 @@ MroSequenceStorage.FinalAttributeAssumptionPair findAttrAndAssumptionInMRO(Objec
214234 if (assumptionNode != null ) {
215235 return assumptionNode ;
216236 }
217- // Put a new assumption in place in case the MRO changes during the lookup
218237 MroSequenceStorage .FinalAttributeAssumptionPair assumptionPair = new MroSequenceStorage .FinalAttributeAssumptionPair ();
219- mro .putFinalAttributeAssumption (key , assumptionPair );
238+ if (!mustNotSideEffect ) {
239+ // Put a new assumption in place in case the MRO changes during the lookup
240+ mro .putFinalAttributeAssumption (key , assumptionPair );
241+ }
220242 EncapsulatingNodeReference nodeRef = EncapsulatingNodeReference .getCurrent ();
221- Node prev = nodeRef .set (this );
243+ Node prev = nodeRef .set (n );
222244 Object result = PNone .NO_VALUE ;
223245 try {
224246 for (int i = 0 ; i < mro .length (); i ++) {
225247 PythonAbstractClass clsObj = mro .getPythonClassItemNormalized (i );
226248 if (skipNonStaticBase (clsObj , skipNonStaticBases )) {
227249 continue ;
228250 }
229- Object value = ReadAttributeFromObjectNode .getUncached ().execute (clsObj , key );
251+ Object value ;
252+ if (mustNotSideEffect ) {
253+ if (clsObj instanceof PythonObject pyClsObj && !PGuards .hasMaterializedDict (pyClsObj .getShape ())) {
254+ value = DynamicObject .GetNode .getUncached ().execute (pyClsObj , key , PNone .NO_VALUE );
255+ } else {
256+ throw MROGenericDictException .INSTANCE ;
257+ }
258+ } else {
259+ value = ReadAttributeFromObjectNode .getUncached ().execute (clsObj , key );
260+ }
230261 if (value != PNone .NO_VALUE ) {
231262 result = value ;
232263 break ;
@@ -240,6 +271,10 @@ MroSequenceStorage.FinalAttributeAssumptionPair findAttrAndAssumptionInMRO(Objec
240271 // exception. This should abort the specialization
241272 throw new MROChangedException (result );
242273 }
274+ if (mustNotSideEffect ) {
275+ // must connect the assumption with the MRO here, otherwise we may end up with half initialized FinalAttributeAssumptionPair if we had to bail out because we found a generic dict
276+ mro .putFinalAttributeAssumption (key , assumptionPair );
277+ }
243278 assumptionPair .setValue (result );
244279 return assumptionPair ;
245280 }
@@ -264,6 +299,45 @@ Object lookupSlowPath(Object klass,
264299 return slowPathNode .execute (klass , key , skipNonStaticBases );
265300 }
266301
302+ @ GenerateInline
303+ @ GenerateCached (false )
304+ @ ImportStatic (LookupAttributeInMRONode .class )
305+ public abstract static class CachedKeyFastPath extends PNodeWithContext {
306+
307+ public final Object execute (Node inliningTarget , Object klass , TruffleString key ) {
308+ CompilerAsserts .partialEvaluationConstant (klass );
309+ CompilerAsserts .partialEvaluationConstant (key );
310+ try {
311+ return executeImpl (inliningTarget , klass , key );
312+ } catch (MROChangedException e ) {
313+ throw CompilerDirectives .shouldNotReachHere ();
314+ } catch (MROGenericDictException e ) {
315+ CompilerDirectives .transferToInterpreterAndInvalidate ();
316+ return null ;
317+ }
318+ }
319+
320+ public abstract Object executeImpl (Node inliningTarget , Object klass , TruffleString key ) throws MROChangedException , MROGenericDictException ;
321+
322+ @ Specialization
323+ static Object lookupPBCTCached (Node inliningTarget , PythonBuiltinClassType klass , TruffleString key ,
324+ @ Bind PythonContext context ,
325+ @ Cached ("findAttr(context, klass, key)" ) Object cachedValue ) {
326+ return cachedValue ;
327+ }
328+
329+ @ Specialization (assumptions = "cachedAttrInMROInfo.getAssumption()" , guards = "isSingleContext()" )
330+ static Object lookupConstantMROCached (Node inliningTarget , Object klass , TruffleString key ,
331+ @ Cached ("findAttrAndAssumptionInMRO(inliningTarget, klass, key, false, true)" ) MroSequenceStorage .FinalAttributeAssumptionPair cachedAttrInMROInfo ) {
332+ return cachedAttrInMROInfo .getValue ();
333+ }
334+
335+ @ Specialization (replaces = {"lookupPBCTCached" , "lookupConstantMROCached" })
336+ static Object noFastPath (Object klass , TruffleString key ) {
337+ return null ;
338+ }
339+ }
340+
267341 public abstract static class SlowPath extends PNodeWithContext {
268342
269343 @ Child private GetMroStorageNode getMroNode ;
0 commit comments