diff --git a/ci.jsonnet b/ci.jsonnet
index ed06e0e4f2..dab3791c05 100644
--- a/ci.jsonnet
+++ b/ci.jsonnet
@@ -137,6 +137,12 @@
local with_compiler = task_spec({
dynamic_imports +:: ["/compiler"],
}),
+ local unittest_args_gate(args) = task_spec({
+ tags:: "python-unittest",
+ environment +: {
+ GRAALPY_UNITTEST_ARGS: std.join(" ", args),
+ },
+ }),
// -----------------------------------------------------------------------------------------------------------------
//
@@ -166,6 +172,12 @@
"python-unittest-native-debug-build": gpgate + platform_spec(no_jobs) + native_debug_build_gate("python-unittest") + platform_spec({
"linux:amd64:jdk-latest" : tier3,
}),
+ "python-unittest-cached-interpreter": gpgate + unittest_args_gate(["--python.UncachedInterpreterThreshold=0"]) + platform_spec(no_jobs) + platform_spec({
+ "linux:amd64:jdk-latest" : tier3 + require(GPY_JVM_STANDALONE),
+ }),
+ "python-unittest-uncached-interpreter": gpgate + unittest_args_gate(["--python.ForceUncachedInterpreter=true"]) + platform_spec(no_jobs) + platform_spec({
+ "linux:amd64:jdk-latest" : tier3 + require(GPY_JVM_STANDALONE),
+ }),
"python-unittest-multi-context": gpgate + require(GPY_NATIVE_STANDALONE) + platform_spec(no_jobs) + platform_spec({
"linux:amd64:jdk-latest" : tier3,
"linux:aarch64:jdk-latest" : daily + t("02:00:00"),
diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_builtin.py b/graalpython/com.oracle.graal.python.test/src/tests/test_builtin.py
index ad7bc47f6c..be03e05622 100644
--- a/graalpython/com.oracle.graal.python.test/src/tests/test_builtin.py
+++ b/graalpython/com.oracle.graal.python.test/src/tests/test_builtin.py
@@ -7,6 +7,7 @@
import sys
import tempfile
import unittest
+import io
class MyIndexable(object):
def __init__(self, value):
@@ -121,6 +122,53 @@ def test_builtin_constants(self):
self.assertEqual(getattr(builtins, 'False'), False)
self.assertEqual(getattr(builtins, 'True'), True)
+ def test_missing_builtin_lookup_after_warmup(self):
+ def read_missing_builtin():
+ return definitely_missing_builtin_for_review
+
+ for _ in range(20):
+ with self.assertRaises(NameError):
+ read_missing_builtin()
+
+ def test_instance_attr_state_machine_after_warmup(self):
+ class PaddedFile:
+ def __init__(self, fileobj, prepend=b""):
+ self._buffer = prepend
+ self._length = len(prepend)
+ self.file = fileobj
+ self._read = 0
+
+ def read(self, size):
+ if self._read is None:
+ return self.file.read(size)
+ if self._read + size <= self._length:
+ read = self._read
+ self._read += size
+ return self._buffer[read:self._read]
+ read = self._read
+ self._read = None
+ return self._buffer[read:] + self.file.read(size - self._length + read)
+
+ def prepend(self, prepend=b""):
+ if self._read is None:
+ self._buffer = prepend
+ else:
+ self._read -= len(prepend)
+ return
+ self._length = len(self._buffer)
+ self._read = 0
+
+ def exercise():
+ padded = PaddedFile(io.BytesIO(b"cdef"), b"ab")
+ self.assertEqual(padded.read(1), b"a")
+ self.assertEqual(padded.read(3), b"bcd")
+ padded.prepend(b"Z")
+ self.assertEqual(padded.read(2), b"Ze")
+ self.assertEqual(padded.read(2), b"f")
+
+ for _ in range(20):
+ exercise()
+
def test_min(self):
self.assertEqual(min((), default=1, key="adsf"), 1)
diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_frame_tests.py b/graalpython/com.oracle.graal.python.test/src/tests/test_frame_tests.py
index 64381542f8..8551270f30 100644
--- a/graalpython/com.oracle.graal.python.test/src/tests/test_frame_tests.py
+++ b/graalpython/com.oracle.graal.python.test/src/tests/test_frame_tests.py
@@ -238,6 +238,24 @@ def foo():
assert e.__traceback__.tb_next.tb_frame.f_back.f_code == test_backref_from_traceback.__code__
+def test_backref_from_traceback_after_cached_transition():
+ def bar(should_raise):
+ if should_raise:
+ raise RuntimeError
+
+ def foo(should_raise):
+ bar(should_raise)
+
+ for _ in range(64): # we probably do not execute 64-times in uncached
+ foo(False)
+
+ try:
+ foo(True)
+ except Exception as e:
+ assert e.__traceback__.tb_next.tb_next.tb_frame.f_back.f_code == foo.__code__
+ assert e.__traceback__.tb_next.tb_frame.f_back.f_code == test_backref_from_traceback_after_cached_transition.__code__
+
+
def test_frame_from_another_thread():
import sys, threading
event1 = threading.Event()
diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_mro.py b/graalpython/com.oracle.graal.python.test/src/tests/test_mro.py
index 2364ee91fb..3b4b7da6f1 100644
--- a/graalpython/com.oracle.graal.python.test/src/tests/test_mro.py
+++ b/graalpython/com.oracle.graal.python.test/src/tests/test_mro.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2021, 2025, Oracle and/or its affiliates.
+# Copyright (c) 2021, 2026, Oracle and/or its affiliates.
# Copyright (C) 1996-2020 Python Software Foundation
#
# Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
@@ -182,12 +182,20 @@ def __eq__(self, other):
eq_called.append(1)
X.__bases__ = (Base2,)
+ class Descr:
+ def __init__(self, value):
+ self.value = value
+ def __get__(self, obj, owner=None):
+ return self.value
+ def __set__(self, obj, value):
+ pass
+
class Base(object):
- mykey = 'base 42'
+ mykey = Descr('base 42')
def __str__(self): return 'Base'
class Base2(object):
- mykey = 'base2 42'
+ mykey = Descr('base2 42')
def __str__(self): return 'Base2'
X = type('X', (Base,), {MyKey(): 5})
@@ -202,6 +210,8 @@ def __str__(self): return 'Base2'
eq_called = []
X = type('X', (Base,), {MyKey(): 5})
xobj = X()
+ xobj_dict = object.__getattribute__(xobj, "__dict__")
+ xobj_dict['mykey'] = 'false lead'
assert str(xobj) == 'Base'
assert xobj.mykey == 'base 42'
assert eq_called == [1]
diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_slot.py b/graalpython/com.oracle.graal.python.test/src/tests/test_slot.py
index 3d185a7291..37a45c5ba7 100644
--- a/graalpython/com.oracle.graal.python.test/src/tests/test_slot.py
+++ b/graalpython/com.oracle.graal.python.test/src/tests/test_slot.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# The Universal Permissive License (UPL), Version 1.0
@@ -173,5 +173,17 @@ class C:
__slots__ = ('a', 'b')
self.assertRaises(AttributeError, setattr, C(), 'c', 42)
+ def test_write_attr_without_dict_after_warmup(self):
+ class C:
+ __slots__ = ()
+
+ obj = C()
+
+ def write_attr():
+ obj.x = 42
+
+ for _ in range(20):
+ self.assertRaises(AttributeError, write_attr)
+
if __name__ == "__main__":
unittest.main()
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/DynamicObjectStorage.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/DynamicObjectStorage.java
index 872546b9dd..59caca476c 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/DynamicObjectStorage.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/DynamicObjectStorage.java
@@ -75,13 +75,18 @@
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.Shape;
-import com.oracle.truffle.api.profiles.InlinedBranchProfile;
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
import com.oracle.truffle.api.strings.TruffleString;
/**
* This storage keeps a reference to the MRO when used for a type dict. Writing to this storage will
* cause the appropriate attribute final assumptions to be invalidated.
+ *
+ * This storage may wrap a {@link PythonObject}; in that case the storage and the object can be
+ * used interchangeably. If the storage is transformed to other storage, or for some other reason
+ * requires that all accesses are routed through the {@link DynamicObjectStorage}, then the
+ * {@link PythonObject}'s {@link Shape} flags must be updated to include
+ * {@link PythonObject#HAS_MATERIALIZED_DICT}.
*/
public final class DynamicObjectStorage extends HashingStorage {
public static final int SIZE_THRESHOLD = 100;
@@ -268,6 +273,10 @@ void setStringKey(TruffleString key, Object value, DynamicObject.PutNode putNode
putNode.execute(store, key, assertNoJavaString(value));
}
+ boolean setStringKeyIfPresent(TruffleString key, Object value, DynamicObject.PutNode putNode) {
+ return putNode.executeIfPresent(store, key, assertNoJavaString(value));
+ }
+
boolean shouldTransitionOnPut() {
// For now, we do not use SIZE_THRESHOLD condition to transition storages that wrap
// dictionaries retrieved via object's __dict__
@@ -332,8 +341,7 @@ public static DynamicObjectStorage copy(DynamicObjectStorage receiver,
public abstract static class DynamicObjectStorageSetStringKey extends SpecializedSetStringKey {
@Specialization
static void doIt(Node inliningTarget, HashingStorage self, TruffleString key, Object value,
- @Cached DynamicObject.PutNode putNode,
- @Cached InlinedBranchProfile invalidateMro) {
+ @Cached DynamicObject.PutNode putNode) {
((DynamicObjectStorage) self).setStringKey(key, value, putNode);
}
}
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/HashingStorageNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/HashingStorageNodes.java
index 564d8a7b89..bdb39c0589 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/HashingStorageNodes.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/HashingStorageNodes.java
@@ -605,11 +605,11 @@ static Object domStringKey(Frame frame, Node inliningTarget, DynamicObjectStorag
if (val == PNone.NO_VALUE) {
return null;
} else {
- putNode.execute(store, key, PNone.NO_VALUE);
+ self.setStringKey(key, PNone.NO_VALUE, putNode);
return val;
}
} else {
- return putNode.executeIfPresent(store, key, PNone.NO_VALUE);
+ return self.setStringKeyIfPresent(key, PNone.NO_VALUE, putNode);
}
}
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/PythonModule.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/PythonModule.java
index 617bbe8c11..962a97f1c8 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/PythonModule.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/PythonModule.java
@@ -89,7 +89,7 @@ public PythonModule(Object clazz, Shape instanceShape) {
setAttribute(T___SPEC__, PNone.NO_VALUE);
setAttribute(T___CACHED__, PNone.NO_VALUE);
setAttribute(T___FILE__, PNone.NO_VALUE);
- GetOrCreateDictNode.executeUncached(this);
+ GetOrCreateDictNode.ensureModuleDict(this);
}
/**
@@ -105,7 +105,7 @@ private PythonModule(PythonLanguage lang, TruffleString moduleName) {
setAttribute(T___SPEC__, PNone.NONE);
setAttribute(T___CACHED__, PNone.NO_VALUE);
setAttribute(T___FILE__, PNone.NO_VALUE);
- GetOrCreateDictNode.executeUncached(this);
+ GetOrCreateDictNode.ensureModuleDict(lang, this);
}
/**
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/HiddenAttr.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/HiddenAttr.java
index 5a9a9f2c84..f5b17c320c 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/HiddenAttr.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/HiddenAttr.java
@@ -72,6 +72,8 @@
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.HiddenKey;
+import com.oracle.truffle.api.object.PropertyGetter;
+import com.oracle.truffle.api.object.Shape;
public final class HiddenAttr {
@@ -127,6 +129,10 @@ boolean hasLongValue() {
this == METHOD_DEF_PTR;
}
+ public PropertyGetter createPropertyGetter(Shape shape) {
+ return shape.makePropertyGetter(key);
+ }
+
@Override
public String toString() {
return getName();
@@ -192,10 +198,7 @@ static void doPythonObjectDict(PythonObject self, HiddenAttr attr, Object value,
}
private static boolean isGenericDict(PythonObject self, Object value) {
- if (value instanceof PDict dict && dict.getDictStorage() instanceof DynamicObjectStorage dynamicStorage) {
- return dynamicStorage.getStore() != self;
- }
- return true;
+ return !(value instanceof PDict dict && dict.getDictStorage() instanceof DynamicObjectStorage dom && dom.getStore() == self);
}
@Specialization(guards = "attr != DICT || !isPythonObject(self)")
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/PGuards.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/PGuards.java
index 74bd1f80a9..fcbbfa477e 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/PGuards.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/PGuards.java
@@ -109,6 +109,7 @@
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
+import com.oracle.truffle.api.object.Shape;
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.api.strings.TruffleString.CodeRange;
@@ -127,6 +128,7 @@ public static boolean isNone(Object value) {
return value == PNone.NONE;
}
+ @Idempotent
public static boolean isNoValue(Object object) {
return object == PNone.NO_VALUE;
}
@@ -531,4 +533,8 @@ public static boolean hasBuiltinDictIter(Node inliningTarget, PDict dict, GetPyt
return isBuiltinDict(dict) || getSlots.execute(inliningTarget, getClassNode.execute(inliningTarget, dict)).tp_iter() == DictBuiltins.SLOTS.tp_iter();
}
+ @Idempotent
+ public static boolean hasMaterializedDict(Shape s) {
+ return (s.getFlags() & PythonObject.HAS_MATERIALIZED_DICT) != 0;
+ }
}
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/LookupAttributeInMRONode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/LookupAttributeInMRONode.java
index 93885bcf16..d43fe87a89 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/LookupAttributeInMRONode.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/attributes/LookupAttributeInMRONode.java
@@ -44,12 +44,14 @@
import com.oracle.graal.python.builtins.Python3Core;
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
import com.oracle.graal.python.builtins.objects.PNone;
+import com.oracle.graal.python.builtins.objects.object.PythonObject;
import com.oracle.graal.python.builtins.objects.type.MroShape;
import com.oracle.graal.python.builtins.objects.type.MroShape.MroShapeLookupResult;
import com.oracle.graal.python.builtins.objects.type.PythonAbstractClass;
import com.oracle.graal.python.builtins.objects.type.PythonClass;
import com.oracle.graal.python.builtins.objects.type.TypeNodes.GetMroStorageNode;
import com.oracle.graal.python.builtins.objects.type.TypeNodes.IsSameTypeNode;
+import com.oracle.graal.python.nodes.PGuards;
import com.oracle.graal.python.nodes.PNodeWithContext;
import com.oracle.graal.python.runtime.PythonContext;
import com.oracle.graal.python.runtime.PythonOptions;
@@ -64,6 +66,7 @@
import com.oracle.truffle.api.dsl.Bind;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Cached.Shared;
+import com.oracle.truffle.api.dsl.GenerateCached;
import com.oracle.truffle.api.dsl.GenerateInline;
import com.oracle.truffle.api.dsl.GenerateUncached;
import com.oracle.truffle.api.dsl.Idempotent;
@@ -75,6 +78,7 @@
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.ExplodeLoop.LoopExplosionKind;
import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
import com.oracle.truffle.api.strings.TruffleString;
@@ -156,6 +160,7 @@ public static LookupAttributeInMRONode createForLookupOfUnmanagedClasses(Truffle
return LookupAttributeInMRONodeGen.create(key, true);
}
+ @NeverDefault
static Object findAttr(Python3Core core, PythonBuiltinClassType klass, TruffleString key) {
return findAttr(core, klass, key, ReadAttributeFromPythonObjectNode.getUncached());
}
@@ -202,7 +207,22 @@ public MROChangedException(Object result) {
}
}
+ @SuppressWarnings("serial")
+ public static class MROGenericDictException extends StacktracelessCheckedException {
+ private static final MROGenericDictException INSTANCE = new MROGenericDictException();
+ }
+
MroSequenceStorage.FinalAttributeAssumptionPair findAttrAndAssumptionInMRO(Object klass) throws MROChangedException {
+ try {
+ return findAttrAndAssumptionInMRO(this, klass, key, skipNonStaticBases, false);
+ } catch (MROGenericDictException ignore) {
+ throw CompilerDirectives.shouldNotReachHere();
+ }
+ }
+
+ @NeverDefault
+ static MroSequenceStorage.FinalAttributeAssumptionPair findAttrAndAssumptionInMRO(Node n, Object klass, TruffleString key, boolean skipNonStaticBases,
+ boolean mustNotSideEffect) throws MROChangedException, MROGenericDictException {
CompilerAsserts.neverPartOfCompilation();
// Regarding potential side effects to MRO caused by __eq__ of the keys in the dicts that we
// search through: CPython seems to read the MRO once and then compute the result also
@@ -214,11 +234,13 @@ MroSequenceStorage.FinalAttributeAssumptionPair findAttrAndAssumptionInMRO(Objec
if (assumptionNode != null) {
return assumptionNode;
}
- // Put a new assumption in place in case the MRO changes during the lookup
MroSequenceStorage.FinalAttributeAssumptionPair assumptionPair = new MroSequenceStorage.FinalAttributeAssumptionPair();
- mro.putFinalAttributeAssumption(key, assumptionPair);
+ if (!mustNotSideEffect) {
+ // Put a new assumption in place in case the MRO changes during the lookup
+ mro.putFinalAttributeAssumption(key, assumptionPair);
+ }
EncapsulatingNodeReference nodeRef = EncapsulatingNodeReference.getCurrent();
- Node prev = nodeRef.set(this);
+ Node prev = nodeRef.set(n);
Object result = PNone.NO_VALUE;
try {
for (int i = 0; i < mro.length(); i++) {
@@ -226,7 +248,16 @@ MroSequenceStorage.FinalAttributeAssumptionPair findAttrAndAssumptionInMRO(Objec
if (skipNonStaticBase(clsObj, skipNonStaticBases)) {
continue;
}
- Object value = ReadAttributeFromObjectNode.getUncached().execute(clsObj, key);
+ Object value;
+ if (mustNotSideEffect) {
+ if (clsObj instanceof PythonObject pyClsObj && !PGuards.hasMaterializedDict(pyClsObj.getShape())) {
+ value = DynamicObject.GetNode.getUncached().execute(pyClsObj, key, PNone.NO_VALUE);
+ } else {
+ throw MROGenericDictException.INSTANCE;
+ }
+ } else {
+ value = ReadAttributeFromObjectNode.getUncached().execute(clsObj, key);
+ }
if (value != PNone.NO_VALUE) {
result = value;
break;
@@ -240,6 +271,10 @@ MroSequenceStorage.FinalAttributeAssumptionPair findAttrAndAssumptionInMRO(Objec
// exception. This should abort the specialization
throw new MROChangedException(result);
}
+ if (mustNotSideEffect) {
+ // 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
+ mro.putFinalAttributeAssumption(key, assumptionPair);
+ }
assumptionPair.setValue(result);
return assumptionPair;
}
@@ -264,6 +299,45 @@ Object lookupSlowPath(Object klass,
return slowPathNode.execute(klass, key, skipNonStaticBases);
}
+ @GenerateInline
+ @GenerateCached(false)
+ @ImportStatic(LookupAttributeInMRONode.class)
+ public abstract static class CachedKeyFastPath extends PNodeWithContext {
+
+ public final Object execute(Node inliningTarget, Object klass, TruffleString key) {
+ CompilerAsserts.partialEvaluationConstant(klass);
+ CompilerAsserts.partialEvaluationConstant(key);
+ try {
+ return executeImpl(inliningTarget, klass, key);
+ } catch (MROChangedException e) {
+ throw CompilerDirectives.shouldNotReachHere();
+ } catch (MROGenericDictException e) {
+ CompilerDirectives.transferToInterpreterAndInvalidate();
+ return null;
+ }
+ }
+
+ public abstract Object executeImpl(Node inliningTarget, Object klass, TruffleString key) throws MROChangedException, MROGenericDictException;
+
+ @Specialization
+ static Object lookupPBCTCached(Node inliningTarget, PythonBuiltinClassType klass, TruffleString key,
+ @Bind PythonContext context,
+ @Cached("findAttr(context, klass, key)") Object cachedValue) {
+ return cachedValue;
+ }
+
+ @Specialization(assumptions = "cachedAttrInMROInfo.getAssumption()", guards = "isSingleContext()")
+ static Object lookupConstantMROCached(Node inliningTarget, Object klass, TruffleString key,
+ @Cached("findAttrAndAssumptionInMRO(inliningTarget, klass, key, false, true)") MroSequenceStorage.FinalAttributeAssumptionPair cachedAttrInMROInfo) {
+ return cachedAttrInMROInfo.getValue();
+ }
+
+ @Specialization(replaces = {"lookupPBCTCached", "lookupConstantMROCached"})
+ static Object noFastPath(Object klass, TruffleString key) {
+ return null;
+ }
+ }
+
public abstract static class SlowPath extends PNodeWithContext {
@Child private GetMroStorageNode getMroNode;
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java
index a9d31dc43c..528a599194 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java
@@ -318,6 +318,8 @@
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.strings.TruffleString;
+import com.oracle.truffle.api.strings.TruffleString.CodePointAtIndexUTF32Node;
+import com.oracle.truffle.api.strings.TruffleString.CodePointLengthNode;
import com.oracle.truffle.api.strings.TruffleStringBuilder;
import com.oracle.truffle.api.strings.TruffleStringBuilderUTF32;
@@ -587,7 +589,7 @@ public static void doExit(VirtualFrame frame, AbstractTruffleException ate,
@Bind PBytecodeDSLRootNode root,
@Bind BytecodeNode location) {
if (ate instanceof PException pe) {
- pe.notifyAddedTracebackFrame(!root.isInternal());
+ pe.notifyAddedTracebackFrame(frame, !root.isInternal());
}
// We cannot use instrumentation for exceptional exit
if (root.needsTraceAndProfileInstrumentation()) {
@@ -1931,39 +1933,37 @@ public static Object doIt(VirtualFrame frame,
}
}
- @Operation(storeBytecodeIndex = true)
+ @Operation(storeBytecodeIndex = false)
@ConstantOperand(type = TruffleString.class)
- @ImportStatic({PGuards.class, TpSlots.class, PythonUtils.class})
+ @ImportStatic({PGuards.class, TpSlots.class, PythonUtils.class, DynamicObjectStorage.class})
public static final class GetAttribute {
- // Builtin module object fast-path: we know there aren't any descriptors for other than
- // dunder (__xxx__) names
- public static Object loadModuleValue(PythonModule object, Shape cachedShape, PropertyGetter cachedPropertyGetter) {
- // GetClass.GetPythonObjectClassNode would cache on the shape if it can, and read the
- // dynamic type from there unless it observes objects where the type was changed. This
- // is rare enough that we can pay the price of a useless read here.
- Object type = cachedShape.getDynamicType();
- if (type != PythonBuiltinClassType.PythonModule) {
- return null;
- }
+ @Idempotent
+ public static boolean isBuiltinModule(Shape cachedShape) {
+ return cachedShape.getDynamicType() == PythonBuiltinClassType.PythonModule;
+ }
- assert object.checkDictFlags();
- if ((cachedShape.getFlags() & (PythonObject.HAS_MATERIALIZED_DICT)) == 0) {
- Object value = cachedPropertyGetter.get(object);
- return value == PNone.NO_VALUE ? null : value;
- }
+ @Idempotent
+ public static boolean canBeSpecialMethod(TruffleString key) {
+ return TpSlots.canBeSpecialMethod(key, CodePointLengthNode.getUncached(), CodePointAtIndexUTF32Node.getUncached());
+ }
- return null;
+ public static Object getValue(PropertyGetter getter, PythonObject obj) {
+ assert obj.checkDictFlags();
+ return getter.get(obj);
}
@ForceQuickening
- @Specialization(guards = {"cachedPropertyGetter != null", "cachedPropertyGetter.accepts(receiver)", "value != null",
- "!canBeSpecialMethod(key, codePointLengthNode, codePointAtIndexNode)"}, limit = "3")
+ @Specialization(guards = {
+ "!canBeSpecialMethod(cachedKey)", //
+ "!hasMaterializedDict(cachedShape)", "isBuiltinModule(cachedShape)", //
+ "getter != null", "getter.accepts(receiver)", //
+ "!isNoValue(value)"}, limit = "3")
static Object doModule(TruffleString key, PythonModule receiver,
+ @Cached("key") TruffleString cachedKey,
@Cached("receiver.getShape()") Shape cachedShape,
- @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter cachedPropertyGetter,
- @Exclusive @Cached TruffleString.CodePointLengthNode codePointLengthNode,
- @Exclusive @Cached TruffleString.CodePointAtIndexUTF32Node codePointAtIndexNode,
- @Bind("loadModuleValue(receiver, cachedShape, cachedPropertyGetter)") Object value) {
+ @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter getter,
+ @Bind("getValue(getter, receiver)") Object value) {
+ assert key == cachedKey; // should be a constant operand
return value;
}
@@ -1971,91 +1971,97 @@ static Object doModule(TruffleString key, PythonModule receiver,
// (__xxx__), so we can skip descriptor check + we need to check the __get__ (tp_descr_get)
// on the resulting value (this is common situation)
public static Object loadTypeInstanceValue(VirtualFrame frame, Node inliningTarget, PythonManagedClass object, GetObjectSlotsNode getValueSlotsNode,
- CallSlotDescrGet callSlotDescrGet, Shape cachedShape, PropertyGetter cachedPropertyGetter, InlinedBranchProfile hasNonDescriptorValueProfile) {
- Object type = cachedShape.getDynamicType();
- if (type != PythonBuiltinClassType.PythonClass) {
- return null;
- }
+ CallSlotDescrGet callSlotDescrGet, PropertyGetter cachedPropertyGetter, InlinedBranchProfile hasNonDescriptorValueProfile) {
assert object.checkDictFlags();
- if ((cachedShape.getFlags() & (PythonObject.HAS_MATERIALIZED_DICT)) == 0) {
- Object value = cachedPropertyGetter.get(object);
- if (value != PNone.NO_VALUE && value != null) {
- var valueGet = getValueSlotsNode.execute(inliningTarget, value).tp_descr_get();
- if (valueGet == null) {
- hasNonDescriptorValueProfile.enter(inliningTarget);
- return value;
- } else {
- return callSlotDescrGet.execute(frame, inliningTarget, valueGet, value, PNone.NO_VALUE, object);
- }
+ Object value = cachedPropertyGetter.get(object);
+ if (value != PNone.NO_VALUE) {
+ var valueGet = getValueSlotsNode.execute(inliningTarget, value).tp_descr_get();
+ if (valueGet == null) {
+ hasNonDescriptorValueProfile.enter(inliningTarget);
+ return value;
+ } else {
+ return callSlotDescrGet.execute(frame, inliningTarget, valueGet, value, PNone.NO_VALUE, object);
}
}
return null;
}
+ @Idempotent
+ public static boolean isBuiltinType(Shape cachedShape) {
+ return cachedShape.getDynamicType() == PythonBuiltinClassType.PythonClass;
+ }
+
@ForceQuickening
- @Specialization(guards = {"cachedPropertyGetter != null", "cachedPropertyGetter.accepts(receiver)", "value != null",
- "!canBeSpecialMethod(key, codePointLengthNode, codePointAtIndexNode)"}, limit = "3")
+ @StoreBytecodeIndex // we may be calling the descriptor
+ @Specialization(guards = {
+ "!canBeSpecialMethod(cachedKey)", //
+ "!hasMaterializedDict(cachedShape)", "isBuiltinType(cachedShape)", //
+ "getter != null", "getter.accepts(receiver)", //
+ "value != null"}, limit = "3")
static Object doType(VirtualFrame frame, TruffleString key, PythonManagedClass receiver,
+ @Cached("key") TruffleString cachedKey,
@Cached("receiver.getShape()") Shape cachedShape,
- @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter cachedPropertyGetter,
+ @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter getter,
@Cached GetObjectSlotsNode getObjectSlotsNode,
@Cached CallSlotDescrGet callSlotDescrGet,
@Cached InlinedBranchProfile hasNonDescriptorValueProfile,
- @Exclusive @Cached TruffleString.CodePointLengthNode codePointLengthNode,
- @Exclusive @Cached TruffleString.CodePointAtIndexUTF32Node codePointAtIndexNode,
- @Bind("loadTypeInstanceValue(frame, $node, receiver, getObjectSlotsNode, callSlotDescrGet, cachedShape, cachedPropertyGetter, hasNonDescriptorValueProfile)") Object value) {
+ @Bind("loadTypeInstanceValue(frame, $node, receiver, getObjectSlotsNode, callSlotDescrGet, getter, hasNonDescriptorValueProfile)") Object value) {
+ assert key == cachedKey; // should be a constant operand
return value;
}
- // Object instance field fast-path: for cases where there is no descriptor, and it's just
- // simple DOM property read
- public static Object loadInstanceValue(Node inliningTarget, PythonObject object, LookupAttributeInMRONode getDesc, Shape cachedShape, PropertyGetter cachedPropertyGetter,
+ // The convention is that if klass is null, then the object is assumed to be of a builtin type that has the object's or module's tp_getattro - we do not need to recheck that dynamically
+ public static Object loadInstanceValue(Node inliningTarget, PythonObject object, PythonManagedClass klass,
+ TruffleString key, LookupAttributeInMRONode.CachedKeyFastPath getDesc, Shape cachedShape, PropertyGetter cachedPropertyGetter,
InlineWeakValueProfile slotsValueProfile) {
- TpSlots slots;
- Object type = cachedShape.getDynamicType();
- // If this path works out, PropertyGetter.accepts() guards on the shape.
- // After PE the final dynamicType field should dominate the branch and PE should remove
- // the slots branch it doesn't need. The
- // PythonBuiltinClassType slots are final, so PE can use that, but PythonManagedClass
- // slots are not, so we should probably profile?
- if (type instanceof PythonBuiltinClassType pbct) {
- slots = pbct.getSlots();
- } else if (type instanceof PythonManagedClass klass) {
- slots = slotsValueProfile.execute(inliningTarget, klass.getTpSlots());
- } else {
- return null;
- }
- // The next check will fold after PE if the pbct was constant, which is implied by the
- // guard in getDesc
- if (slots.tp_getattro() == ObjectBuiltins.SLOTS.tp_getattro() ||
- slots.tp_getattro() == ModuleBuiltins.SLOTS.tp_getattro()) {
- Object descr = getDesc.execute(type);
+ if (klass == null || hasObjectOrModuleGetattro(inliningTarget, klass, slotsValueProfile)) {
+ Object descr = getDesc.execute(inliningTarget, cachedShape.getDynamicType(), key);
if (descr == PNone.NO_VALUE) {
assert object.checkDictFlags();
- if ((cachedShape.getFlags() & (PythonObject.HAS_MATERIALIZED_DICT)) == 0) {
- Object value = cachedPropertyGetter.get(object);
- // Note: the NO_VALUE check is harmless for PE, because it leads to a deopt
- // anyway
- return value == PNone.NO_VALUE ? null : value;
- }
+ Object value = cachedPropertyGetter.get(object);
+ return value == PNone.NO_VALUE ? null : value;
}
}
return null;
}
+ private static boolean hasObjectOrModuleGetattro(Node inliningTarget, PythonManagedClass klass, InlineWeakValueProfile slotsValueProfile) {
+ TpSlots slots = slotsValueProfile.execute(inliningTarget, klass.getTpSlots());
+ return hasObjectOrModuleGetattro(slots);
+ }
+
+ private static boolean hasObjectOrModuleGetattro(TpSlots slots) {
+ return slots.tp_getattro() == ObjectBuiltins.SLOTS.tp_getattro() || slots.tp_getattro() == ModuleBuiltins.SLOTS.tp_getattro();
+ }
+
+ public static PythonManagedClass getManagedClassOrNull(Shape cachedShape) {
+ return cachedShape.getDynamicType() instanceof PythonManagedClass managedClass ? managedClass : null;
+ }
+
+ @Idempotent
+ public static boolean isBuiltinWithObjectOrModuleGetattro(Shape cachedShape) {
+ return cachedShape.getDynamicType() instanceof PythonBuiltinClassType type && hasObjectOrModuleGetattro(type.getSlots());
+ }
+
@ForceQuickening
- @Specialization(guards = {"cachedPropertyGetter != null", "cachedPropertyGetter.accepts(receiver)", "value != null"}, replaces = "doModule", limit = "3")
+ @StoreBytecodeIndex // looking up attribute in MRO may have side effects
+ @Specialization(guards = {
+ "!hasMaterializedDict(cachedShape)", "managedClass != null || isBuiltinWithObjectOrModuleGetattro(cachedShape)", //
+ "getter != null", "getter.accepts(receiver)", //
+ "value != null"}, replaces = "doModule", limit = "3")
static Object doInstanceValue(TruffleString key, PythonObject receiver,
@Bind Node inliningTarget,
@Cached("receiver.getShape()") Shape cachedShape,
- @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter cachedPropertyGetter,
- @Cached("create(key)") LookupAttributeInMRONode getDesc,
+ @Cached("getManagedClassOrNull(cachedShape)") PythonManagedClass managedClass,
+ @Cached("getPropertyGetterWithFinalAssumption(cachedShape, key)") PropertyGetter getter,
+ @Cached LookupAttributeInMRONode.CachedKeyFastPath getDesc,
@Cached InlineWeakValueProfile slotsValueProfile,
- @Bind("loadInstanceValue(inliningTarget, receiver, getDesc, cachedShape, cachedPropertyGetter, slotsValueProfile)") Object value) {
+ @Bind("loadInstanceValue(inliningTarget, receiver, managedClass, key, getDesc, cachedShape, getter, slotsValueProfile)") Object value) {
return value;
}
@Specialization(excludeForUncached = true, replaces = {"doInstanceValue", "doType"})
+ @StoreBytecodeIndex
public static Object doIt(VirtualFrame frame,
TruffleString key,
Object obj,
@@ -2065,6 +2071,7 @@ public static Object doIt(VirtualFrame frame,
@Specialization(replaces = "doIt")
@InliningCutoff
+ @StoreBytecodeIndex
public static Object doItUncached(VirtualFrame frame, TruffleString key, Object obj,
@Bind Node inliningTargetForDummy,
@Cached PyObjectGetAttr dummyToForceStoreBCI) {
@@ -2074,34 +2081,56 @@ public static Object doItUncached(VirtualFrame frame, TruffleString key, Object
@Operation(storeBytecodeIndex = true)
@ConstantOperand(type = TruffleString.class)
+ @ImportStatic(PGuards.class)
public static final class SetAttribute {
- public static boolean canStoreInstanceValue(Node inliningTarget, PythonObject object, Shape cachedShape, LookupAttributeInMRONode getDesc,
+ @NonIdempotent
+ public static boolean canStoreInstanceValue(Node inliningTarget, TruffleString key, PythonManagedClass managedClass, Shape cachedShape, LookupAttributeInMRONode.CachedKeyFastPath getDesc,
GetObjectSlotsNode getDescSlotsNode, InlineWeakValueProfile slotsValueProfile) {
- TpSlots slots;
- Object type = cachedShape.getDynamicType();
- if (type instanceof PythonBuiltinClassType pbct) {
- slots = pbct.getSlots();
- } else if (type instanceof PythonManagedClass klass) {
- slots = slotsValueProfile.execute(inliningTarget, klass.getTpSlots());
- } else {
- return false;
+ if (managedClass == null || slotsValueProfile.execute(inliningTarget, managedClass.getTpSlots()).tp_setattro() == ObjectBuiltins.SLOTS.tp_setattro()) {
+ Object descr = getDesc.execute(inliningTarget, cachedShape.getDynamicType(), key);
+ return descr != null && (descr == PNone.NO_VALUE || getDescSlotsNode.execute(inliningTarget, descr).tp_descr_set() == null);
}
- if (slots.tp_setattro() == ObjectBuiltins.SLOTS.tp_setattro()) {
- Object descr = getDesc.execute(type);
- if (descr == PNone.NO_VALUE || getDescSlotsNode.execute(inliningTarget, descr).tp_descr_set() == null) {
- assert object.checkDictFlags();
- return (cachedShape.getFlags() & (PythonObject.HAS_MATERIALIZED_DICT | PythonObject.HAS_SLOTS_BUT_NO_DICT_FLAG)) == 0;
+ return false;
+ }
+
+ public static boolean canSkipDescriptorCheck(Shape cachedShape, TruffleString key) {
+ if (cachedShape.getDynamicType() instanceof PythonBuiltinClassType type && type.getSlots().tp_setattro() == ObjectBuiltins.SLOTS.tp_setattro()) {
+ Object descr = LookupAttributeInMRONode.Dynamic.getUncached().execute(type, key);
+ if (descr == PNone.NO_VALUE) {
+ return true;
}
+ return descr instanceof PythonObject pyDescr && pyDescr.getPythonClass() instanceof PythonBuiltinClassType descrType &&
+ descrType.getSlots().tp_descr_set() == null;
}
return false;
}
+ @Idempotent
+ public static boolean isBuiltinWithObjectSetattro(Shape cachedShape) {
+ return cachedShape.getDynamicType() instanceof PythonBuiltinClassType type && type.getSlots().tp_setattro() == ObjectBuiltins.SLOTS.tp_setattro();
+ }
+
+ public static PythonManagedClass getManagedClassOrNull(Shape cachedShape) {
+ return cachedShape.getDynamicType() instanceof PythonManagedClass managedClass ? managedClass : null;
+ }
+
+ @Idempotent
+ public static boolean hasNoSlotsOrMaterializedDict(Shape cachedShape) {
+ return (cachedShape.getFlags() & (PythonObject.HAS_MATERIALIZED_DICT | PythonObject.HAS_SLOTS_BUT_NO_DICT_FLAG)) == 0;
+ }
+
@ForceQuickening
- @Specialization(guards = {"cachedShape.check(receiver)", "canStoreInstanceValue(inliningTarget, receiver, cachedShape, getDesc, getDescSlotsNode, slotsValueProfile)"}, limit = "3")
+ @Specialization(guards = {
+ "hasNoSlotsOrMaterializedDict(cachedShape)", "managedClass != null || isBuiltinWithObjectSetattro(cachedShape)", //
+ "cachedShape.check(receiver)", //
+ "skipDescriptorCheck || canStoreInstanceValue(inliningTarget, cachedKey, managedClass, cachedShape, getDesc, getDescSlotsNode, slotsValueProfile)"}, limit = "3")
static void doInstanceValue(TruffleString key, Object value, PythonObject receiver,
@Bind Node inliningTarget,
+ @Cached("key") TruffleString cachedKey,
@Cached("receiver.getShape()") Shape cachedShape,
- @Cached("create(key)") LookupAttributeInMRONode getDesc,
+ @Cached("getManagedClassOrNull(cachedShape)") PythonManagedClass managedClass,
+ @Cached("canSkipDescriptorCheck(cachedShape, key)") boolean skipDescriptorCheck,
+ @Cached LookupAttributeInMRONode.CachedKeyFastPath getDesc,
@Cached GetObjectSlotsNode getDescSlotsNode,
@Cached InlineWeakValueProfile slotsValueProfile,
@Cached DynamicObject.PutNode putNode) {
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/ReadBuiltinNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/ReadBuiltinNode.java
index 20e2d18bd4..517fa98d45 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/ReadBuiltinNode.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/ReadBuiltinNode.java
@@ -41,42 +41,64 @@
package com.oracle.graal.python.nodes.frame;
import com.oracle.graal.python.builtins.objects.PNone;
+import com.oracle.graal.python.builtins.objects.common.DynamicObjectStorage;
import com.oracle.graal.python.builtins.objects.module.PythonModule;
import com.oracle.graal.python.nodes.BuiltinNames;
+import com.oracle.graal.python.nodes.PGuards;
import com.oracle.graal.python.nodes.PNodeWithContext;
import com.oracle.graal.python.nodes.PRaiseNode;
import com.oracle.graal.python.nodes.attributes.ReadAttributeFromModuleNode;
+import com.oracle.graal.python.nodes.object.GetDictIfExistsNode;
import com.oracle.graal.python.runtime.PythonContext;
import com.oracle.graal.python.runtime.exception.PException;
+import com.oracle.graal.python.util.PythonUtils;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff;
import com.oracle.truffle.api.dsl.Bind;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Cached.Exclusive;
-import com.oracle.truffle.api.dsl.Cached.Shared;
import com.oracle.truffle.api.dsl.GenerateInline;
import com.oracle.truffle.api.dsl.GenerateUncached;
+import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.NeverDefault;
+import com.oracle.truffle.api.dsl.NonIdempotent;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.api.object.PropertyGetter;
+import com.oracle.truffle.api.object.Shape;
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
import com.oracle.truffle.api.strings.TruffleString;
@GenerateUncached
@GenerateInline(false) // footprint reduction 40 -> 21
+@ImportStatic({GetDictIfExistsNode.class, DynamicObjectStorage.class, PGuards.class, PythonUtils.class})
public abstract class ReadBuiltinNode extends PNodeWithContext {
public abstract Object execute(TruffleString attributeId);
- // TODO: (tfel) Think about how we can globally catch writes to the builtin
- // module so we can treat anything read from it as constant here.
- @Specialization(guards = "isSingleContext(this)")
+ @NonIdempotent
+ static boolean getterAccepts(PropertyGetter getter, PythonModule mod) {
+ return getter.accepts(mod);
+ }
+
+ @NonIdempotent
+ static Object getterGet(PropertyGetter getter, PythonModule mod) {
+ assert mod.checkDictFlags();
+ return getter.get(mod);
+ }
+
+ // Fast-path: caches builtins PythonModule object (single context), checks that it's __dict__
+ // hasn't changed shape and cached property getter to reads the value directly
+ @Specialization(excludeForUncached = true, guards = {"isSingleContext(this)",
+ "!hasMaterializedDict(cachedShape)", //
+ "getter != null", //
+ "getterAccepts(getter, builtins)", // shape check including the HAS_MATERIALIZED_DICT flag
+ "!isNoValue(value)"})
Object returnBuiltinFromConstantModule(TruffleString attributeId,
- @Bind Node inliningTarget,
- @Exclusive @Cached PRaiseNode raiseNode,
- @Exclusive @Cached InlinedConditionProfile isBuiltinProfile,
- @Shared @Cached ReadAttributeFromModuleNode readFromBuiltinsNode,
- @Cached(value = "getBuiltins()", allowUncached = true) PythonModule builtins) {
- return readBuiltinFromModule(attributeId, raiseNode, inliningTarget, isBuiltinProfile, builtins, readFromBuiltinsNode);
+ @Cached("getBuiltins()") PythonModule builtins,
+ @Cached("builtins.getShape()") Shape cachedShape,
+ @Cached(value = "getPropertyGetterWithFinalAssumption(cachedShape, attributeId)", neverDefault = false) PropertyGetter getter,
+ @Bind("getterGet(getter, builtins)") Object value) {
+ return value;
}
@InliningCutoff
@@ -90,10 +112,10 @@ Object returnBuiltin(TruffleString attributeId,
@Bind Node inliningTarget,
@Exclusive @Cached PRaiseNode raiseNode,
@Exclusive @Cached InlinedConditionProfile isBuiltinProfile,
- @Shared @Cached ReadAttributeFromModuleNode readFromBuiltinsNode,
+ @Cached ReadAttributeFromModuleNode readFromBuiltinsNode,
@Exclusive @Cached InlinedConditionProfile ctxInitializedProfile) {
PythonModule builtins = getBuiltins(inliningTarget, ctxInitializedProfile);
- return returnBuiltinFromConstantModule(attributeId, inliningTarget, raiseNode, isBuiltinProfile, readFromBuiltinsNode, builtins);
+ return readBuiltinFromModule(attributeId, raiseNode, inliningTarget, isBuiltinProfile, builtins, readFromBuiltinsNode);
}
private static Object readBuiltinFromModule(TruffleString attributeId, PRaiseNode raiseNode, Node inliningTarget,
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetDictIfExistsNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetDictIfExistsNode.java
index d3d0bfaf34..56d92d68ad 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetDictIfExistsNode.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetDictIfExistsNode.java
@@ -61,13 +61,13 @@
import com.oracle.graal.python.builtins.objects.object.PythonObject;
import com.oracle.graal.python.builtins.objects.type.PythonManagedClass;
import com.oracle.graal.python.builtins.objects.type.TypeNodes.IsTypeNode;
-import com.oracle.graal.python.runtime.nativeaccess.NativeFunctionPointer;
import com.oracle.graal.python.nodes.ErrorMessages;
import com.oracle.graal.python.nodes.HiddenAttr;
import com.oracle.graal.python.nodes.HiddenAttr.ReadNode;
import com.oracle.graal.python.nodes.PNodeWithContext;
import com.oracle.graal.python.nodes.PRaiseNode;
import com.oracle.graal.python.runtime.IndirectCallData.BoundaryCallData;
+import com.oracle.graal.python.runtime.nativeaccess.NativeFunctionPointer;
import com.oracle.graal.python.runtime.object.PFactory;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff;
@@ -80,6 +80,8 @@
import com.oracle.truffle.api.dsl.NeverDefault;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.api.object.DynamicObject;
+import com.oracle.truffle.api.object.PropertyGetter;
import com.oracle.truffle.api.object.Shape;
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
@@ -93,8 +95,18 @@ public static GetDictIfExistsNode create() {
public abstract PDict execute(Object object);
+ public abstract PDict execute(PythonAbstractNativeObject object);
+
public abstract PDict execute(PythonObject object);
+ /**
+ * Use this node when the shape is already cached. Use
+ * {@link PropertyGetter#accepts(DynamicObject)} to check the cached shape in a guard. Note that this does not initialize the final property assumption!
+ */
+ public static PropertyGetter createDictPropertyGetter(Shape shape) {
+ return HiddenAttr.DICT.createPropertyGetter(shape);
+ }
+
@Specialization(guards = {"object.getShape() == cachedShape", "hasNoDict(cachedShape)"}, limit = "1")
static PDict getNoDictCachedShape(@SuppressWarnings("unused") PythonObject object,
@SuppressWarnings("unused") @Cached("object.getShape()") Shape cachedShape) {
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetOrCreateDictNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetOrCreateDictNode.java
index cdf3323ca0..e8a8abeb98 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetOrCreateDictNode.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/object/GetOrCreateDictNode.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
@@ -44,6 +44,7 @@
import com.oracle.graal.python.PythonLanguage;
import com.oracle.graal.python.builtins.objects.dict.PDict;
+import com.oracle.graal.python.builtins.objects.module.PythonModule;
import com.oracle.graal.python.builtins.objects.object.PythonObject;
import com.oracle.graal.python.nodes.ErrorMessages;
import com.oracle.graal.python.nodes.PGuards;
@@ -78,6 +79,15 @@ public static PDict executeUncached(Object object) {
return GetOrCreateDictNodeGen.getUncached().execute(null, object);
}
+ public static void ensureModuleDict(PythonModule module) {
+ ensureModuleDict(PythonLanguage.get(null), module);
+ }
+
+ public static void ensureModuleDict(PythonLanguage language, PythonModule module) {
+ var dict = PFactory.createDictFixedStorage(language, module);
+ SetDictNode.executeUncached(module, dict);
+ }
+
@Specialization
static PDict doPythonObject(Node inliningTarget, PythonObject object,
@Shared("getDict") @Cached(inline = false) GetDictIfExistsNode getDictIfExistsNode,
diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/exception/PException.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/exception/PException.java
index ce299c781c..ebdc51fd95 100644
--- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/exception/PException.java
+++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/exception/PException.java
@@ -363,6 +363,18 @@ private void markFrameEscaped() {
}
}
+ private void markFrameEscaped(Frame frame) {
+ if (frame != null) {
+ PFrame.Reference currentFrameInfo = PArguments.getCurrentFrameInfo(frame);
+ if (currentFrameInfo != null) {
+ currentFrameInfo.markAsEscaped();
+ if (escapedFrameThread == null) {
+ escapedFrameThread = Thread.currentThread();
+ }
+ }
+ }
+ }
+
public Thread getEscapedFrameThread() {
return escapedFrameThread;
}
@@ -401,9 +413,10 @@ public int getTracebackFrameCount() {
return tracebackFrameCount;
}
- public void notifyAddedTracebackFrame(boolean visible) {
+ public void notifyAddedTracebackFrame(Frame frame, boolean visible) {
if (visible) {
tracebackFrameCount++;
+ markFrameEscaped(frame);
}
}
diff --git a/mx.graalpython/mx_graalpython.py b/mx.graalpython/mx_graalpython.py
index 68c3b6c0d3..1f5dd4463f 100644
--- a/mx.graalpython/mx_graalpython.py
+++ b/mx.graalpython/mx_graalpython.py
@@ -1589,11 +1589,15 @@ def run_python_unittests(python_binary, args=None, paths=None, exclude=None, env
parallelism = str(min(os.cpu_count() or 1, parallel))
args = args or []
+ extra_args = shlex.split(os.environ.get("GRAALPY_UNITTEST_ARGS", ""))
+ if extra_args:
+ mx.log("Adding GraalPy unittest args from GRAALPY_UNITTEST_ARGS: " + shlex.join(extra_args))
args = [
"--vm.ea",
"--experimental-options=true",
"--python.EnableDebuggingBuiltins",
*args,
+ *extra_args,
]
if env is None: