From 3a96db269897f9ef87657997e3c3c80fbd0f035c Mon Sep 17 00:00:00 2001 From: stepan Date: Wed, 23 Sep 2026 13:05:22 +0200 Subject: [PATCH 1/2] Add microbenchmarks for super attribute and method lookup --- .../python/micro/super-explicit-attribute.py | 56 ++++++++++++++++++ .../python/micro/super-explicit-method.py | 57 +++++++++++++++++++ .../python/micro/super-zero-attribute.py | 56 ++++++++++++++++++ .../python/micro/super-zero-method.py | 57 +++++++++++++++++++ mx.graalpython/mx_graalpython_bench_param.py | 8 +++ 5 files changed, 234 insertions(+) create mode 100644 graalpython/com.oracle.graal.python.benchmarks/python/micro/super-explicit-attribute.py create mode 100644 graalpython/com.oracle.graal.python.benchmarks/python/micro/super-explicit-method.py create mode 100644 graalpython/com.oracle.graal.python.benchmarks/python/micro/super-zero-attribute.py create mode 100644 graalpython/com.oracle.graal.python.benchmarks/python/micro/super-zero-method.py diff --git a/graalpython/com.oracle.graal.python.benchmarks/python/micro/super-explicit-attribute.py b/graalpython/com.oracle.graal.python.benchmarks/python/micro/super-explicit-attribute.py new file mode 100644 index 0000000000..31685bd1d5 --- /dev/null +++ b/graalpython/com.oracle.graal.python.benchmarks/python/micro/super-explicit-attribute.py @@ -0,0 +1,56 @@ +# Copyright (c) 2026, 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 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +class Base: + value = 1 + + +class Derived(Base): + def read(self): + return super(Derived, self).value + + +def __benchmark__(num=1_000_000): + obj = Derived() + result = 0 + for _ in range(num): + result -= obj.read() + result += obj.read() + return result diff --git a/graalpython/com.oracle.graal.python.benchmarks/python/micro/super-explicit-method.py b/graalpython/com.oracle.graal.python.benchmarks/python/micro/super-explicit-method.py new file mode 100644 index 0000000000..daf1f938bb --- /dev/null +++ b/graalpython/com.oracle.graal.python.benchmarks/python/micro/super-explicit-method.py @@ -0,0 +1,57 @@ +# Copyright (c) 2026, 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 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +class Base: + def identity(self, value): + return value + + +class Derived(Base): + def call(self): + return super(Derived, self).identity(1) + + +def __benchmark__(num=1_000_000): + obj = Derived() + result = 0 + for _ in range(num): + result -= obj.call() + result += obj.call() + return result diff --git a/graalpython/com.oracle.graal.python.benchmarks/python/micro/super-zero-attribute.py b/graalpython/com.oracle.graal.python.benchmarks/python/micro/super-zero-attribute.py new file mode 100644 index 0000000000..37757b24b5 --- /dev/null +++ b/graalpython/com.oracle.graal.python.benchmarks/python/micro/super-zero-attribute.py @@ -0,0 +1,56 @@ +# Copyright (c) 2026, 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 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +class Base: + value = 1 + + +class Derived(Base): + def read(self): + return super().value + + +def __benchmark__(num=1_000_000): + obj = Derived() + result = 0 + for _ in range(num): + result -= obj.read() + result += obj.read() + return result diff --git a/graalpython/com.oracle.graal.python.benchmarks/python/micro/super-zero-method.py b/graalpython/com.oracle.graal.python.benchmarks/python/micro/super-zero-method.py new file mode 100644 index 0000000000..e821c65d83 --- /dev/null +++ b/graalpython/com.oracle.graal.python.benchmarks/python/micro/super-zero-method.py @@ -0,0 +1,57 @@ +# Copyright (c) 2026, 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 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +class Base: + def identity(self, value): + return value + + +class Derived(Base): + def call(self): + return super().identity(1) + + +def __benchmark__(num=1_000_000): + obj = Derived() + result = 0 + for _ in range(num): + result -= obj.call() + result += obj.call() + return result diff --git a/mx.graalpython/mx_graalpython_bench_param.py b/mx.graalpython/mx_graalpython_bench_param.py index 3a8515246f..3d93118e05 100644 --- a/mx.graalpython/mx_graalpython_bench_param.py +++ b/mx.graalpython/mx_graalpython_bench_param.py @@ -67,6 +67,10 @@ 'attribute-access-polymorphic': ITER_10 + ['1000'], 'attribute-access': ITER_15 + ['5000'], 'attribute-access-super': ITER_20 + ['5_000'], + 'super-zero-attribute': ITER_10 + ['950_000_000'], + 'super-explicit-attribute': ITER_10 + ['950_000_000'], + 'super-zero-method': ITER_10 + ['950_000_000'], + 'super-explicit-method': ITER_10 + ['950_000_000'], 'attribute-bool': ITER_10 + ['3000'], 'boolean-logic-sized': ITER_15 + ['5_000'], 'builtin-len-tuple-sized': ITER_100 + ['1_000_000_000'], @@ -143,6 +147,10 @@ 'attribute-access-polymorphic': ITER_6 + WARMUP_2 + ['20'], 'attribute-access': ITER_6 + WARMUP_2 + ['100'], 'attribute-access-super': ITER_6 + WARMUP_2 + ['40'], + 'super-zero-attribute': ITER_6 + WARMUP_2 + ['1_000_000'], + 'super-explicit-attribute': ITER_6 + WARMUP_2 + ['1_000_000'], + 'super-zero-method': ITER_6 + WARMUP_2 + ['1_000_000'], + 'super-explicit-method': ITER_6 + WARMUP_2 + ['1_000_000'], 'attribute-bool': ITER_6 + WARMUP_2 + ['2'], 'boolean-logic-sized': ITER_6 + WARMUP_2 + ['10'], 'builtin-len-tuple-sized': ITER_6 + WARMUP_2 + ['10_000_000'], From fc880456f3b4d5e5356858c18c30e0aa05c5c548 Mon Sep 17 00:00:00 2001 From: stepan Date: Wed, 23 Sep 2026 13:14:45 +0200 Subject: [PATCH 2/2] Cache super MRO suffix lookups using attribute assumptions --- .../src/tests/test_super.py | 248 ++++++++++++++++++ .../objects/superobject/SuperBuiltins.java | 82 ++---- .../attributes/LookupAttributeInMRONode.java | 122 +++++++++ .../sequence/storage/MroSequenceStorage.java | 1 + 4 files changed, 394 insertions(+), 59 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_super.py b/graalpython/com.oracle.graal.python.test/src/tests/test_super.py index d89c17b70e..79c1e4f890 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_super.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_super.py @@ -97,3 +97,251 @@ class B(A): assert type(bound) is MySuper assert MySuper.news == [()] assert MySuper.calls == [()] + + +def test_super_lookup_mro_change_during_dict_key_equality(): + calls = [] + + class Key(str): + def __hash__(self): + return hash("missing") + + def __eq__(self, other): + calls.append(other) + Derived.__bases__ = (Replacement,) + return False + + Base = type("Base", (), {Key("collision"): 42}) + + class Replacement: + missing = 42 + + class Derived(Base): + def read(self): + return super().missing + + try: + Derived().read() + except AttributeError: + pass + else: + assert False + assert calls == ["missing"] + assert Derived().read() == 42 + + +def test_super_lookup_suffix_mro_change_during_dict_key_equality(): + calls = [] + + class Key(str): + def __hash__(self): + return hash("value") + + def __eq__(self, other): + calls.append(other) + Base.__bases__ = (Replacement,) + return False + + class Original: + value = "original" + + class Replacement: + value = "replacement" + + Base = type("Base", (Original,), {Key("collision"): 42}) + + class Derived(Base): + def read(self): + return super().value + + obj = Derived() + assert obj.read() == "original" + assert calls == ["value"] + assert obj.read() == "replacement" + + +def test_super_lookup_shadowed_attribute_invalidation(): + class A: + value = "A" + + class B(A): + value = "B" + + class C(B): + value = "C" + + def read(self): + return super().value + + obj = C() + for _ in range(10): + assert obj.value == "C" + assert obj.read() == "B" + + C.value = "new C" + assert obj.read() == "B" + A.value = "new A" + assert obj.read() == "B" + B.value = "new B" + assert obj.read() == "new B" + del B.value + assert obj.read() == "new A" + + +def test_super_lookup_missing_attribute_invalidation(): + class A: + pass + + class B(A): + pass + + class C(B): + def read(self): + return super().value + + obj = C() + for _ in range(10): + try: + obj.read() + except AttributeError: + pass + else: + assert False + + C.value = "C" + try: + obj.read() + except AttributeError: + pass + else: + assert False + A.value = "A" + assert obj.read() == "A" + B.value = "B" + assert obj.read() == "B" + del B.value + assert obj.read() == "A" + + +def test_super_lookup_different_start_types_invalidation(): + class A: + value = "A" + + class B(A): + value = "B" + + class C(B): + value = "C" + + obj = C() + + def read(start): + return super(start, obj).value + + for _ in range(10): + assert read(C) == "B" + assert read(B) == "A" + + C.value = "new C" + assert read(C) == "B" + assert read(B) == "A" + B.value = "new B" + assert read(C) == "new B" + assert read(B) == "A" + A.value = "new A" + assert read(C) == "new B" + assert read(B) == "new A" + del B.value + assert read(C) == "new A" + + +def test_super_lookup_shares_suffix_lookup(): + class A: + value = "A" + + class B(A): + value = "B" + + class C(B): + value = "C" + + obj = C() + + def read(): + return super(C, obj).value + + for _ in range(10): + assert read() == "B" + assert B.value == "B" + assert C.value == "C" + del B.value + assert read() == "A" + assert B.value == "A" + assert C.value == "C" + + +def test_super_lookup_receiver_mro_change(): + class A: + value = "A" + + class B: + value = "B" + + class C(A): + def read(self): + return super().value + + obj = C() + for _ in range(10): + assert obj.read() == "A" + C.__bases__ = (B,) + assert obj.read() == "B" + assert A.value == "A" + + +def test_super_lookup_diamond_suffix(): + class A: + value = "A" + + class B(A): + pass + + class C(A): + value = "C" + + class D(B, C): + def read(self): + return super().value + + obj = D() + for _ in range(10): + assert B.value == "A" + assert obj.read() == "C" + C.value = "new C" + assert obj.read() == "new C" + del C.value + assert obj.read() == "A" + + +def test_super_lookup_reordered_suffix(): + class A: + value = "A" + + class B: + value = "B" + + class C(A, B): + pass + + class Meta(type): + def mro(cls): + return [cls, C, B, A, object] + + class D(C, metaclass=Meta): + def read(self): + return super().value + + obj = D() + for _ in range(10): + assert C.value == "A" + assert obj.read() == "B" diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/superobject/SuperBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/superobject/SuperBuiltins.java index b5a2d4e18a..48c94129f6 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/superobject/SuperBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/superobject/SuperBuiltins.java @@ -70,13 +70,9 @@ import com.oracle.graal.python.builtins.objects.str.StringUtils.SimpleTruffleStringFormatNode; import com.oracle.graal.python.builtins.objects.superobject.SuperBuiltinsFactory.GetObjectNodeGen; import com.oracle.graal.python.builtins.objects.superobject.SuperBuiltinsFactory.GetTypeNodeGen; -import com.oracle.graal.python.builtins.objects.type.PythonAbstractClass; import com.oracle.graal.python.builtins.objects.type.TpSlots; import com.oracle.graal.python.builtins.objects.type.TpSlots.GetObjectSlotsNode; import com.oracle.graal.python.builtins.objects.type.TypeNodes; -import com.oracle.graal.python.builtins.objects.type.TypeNodes.GetMroNode; -import com.oracle.graal.python.builtins.objects.type.TypeNodes.IsSameTypeNode; -import com.oracle.graal.python.builtins.objects.type.TypeNodesFactory.IsSameTypeNodeGen; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotDescrGet.CallSlotDescrGet; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotDescrGet.DescrGetBuiltinNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotGetAttr.GetAttrBuiltinNode; @@ -86,7 +82,7 @@ import com.oracle.graal.python.nodes.PNodeWithContext; import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.SpecialAttributeNames; -import com.oracle.graal.python.nodes.attributes.ReadAttributeFromObjectNode; +import com.oracle.graal.python.nodes.attributes.LookupAttributeInMRONode; import com.oracle.graal.python.nodes.bytecode_dsl.BytecodeDSLFrameInfo; import com.oracle.graal.python.nodes.bytecode_dsl.PBytecodeDSLRootNode; import com.oracle.graal.python.nodes.call.CallNode; @@ -125,7 +121,6 @@ import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.profiles.BranchProfile; import com.oracle.truffle.api.profiles.InlinedConditionProfile; -import com.oracle.truffle.api.profiles.InlinedIntValueProfile; import com.oracle.truffle.api.strings.TruffleString; @CoreFunctions(extendClasses = PythonBuiltinClassType.Super) @@ -484,13 +479,10 @@ static Object doIt(Node inliningTarget, SuperObject self, Object obj, @Slot(value = SlotKind.tp_getattro, isComplex = true) @GenerateNodeFactory public abstract static class GetattributeNode extends GetAttrBuiltinNode { - @Child private ReadAttributeFromObjectNode readFromDict = ReadAttributeFromObjectNode.create(); @Child private CallSlotDescrGet callGetSlotNode; @Child private GetTypeNode getType; @Child private GetObjectNode getObject = GetObjectNodeGen.create(); @Child private ObjectBuiltins.GetAttributeNode objectGetattributeNode; - @Child private GetMroNode getMroNode; - @Child private IsSameTypeNode isSameTypeNode; private Object genericGetAttr(VirtualFrame frame, Object object, Object attr) { if (objectGetattributeNode == null) { @@ -507,7 +499,7 @@ Object get(VirtualFrame frame, SuperObject self, Object attr, @Cached TruffleString.EqualNode equalNode, @Cached GetObjectTypeNode getObjectType, @Cached CastToTruffleStringChecked1Node castToTruffleStringNode, - @Cached InlinedIntValueProfile mroLenProfile, + @Cached LookupAttributeInMRONode.Super lookupNode, @Cached InlinedConditionProfile hasDescrGetProfile, @Cached InlinedConditionProfile getObjectIsStartObjectProfile, @Cached IsForeignObjectNode isForeignObjectNode, @@ -533,45 +525,32 @@ Object get(VirtualFrame frame, SuperObject self, Object attr, } Object type = getType.executeCached(self); - PythonAbstractClass[] mro = getMro(startType); - /* No need to check the last one: it's gonna be skipped anyway. */ - int i = 0; - int n = mroLenProfile.profile(inliningTarget, mro.length); - for (i = 0; i + 1 < n; i++) { - if (isSameType(type, mro[i])) { - break; - } - } - i++; /* skip su->type (if any) */ - if (i >= n) { + Object res = lookupNode.execute(type, startType, stringAttr); + if (res == LookupAttributeInMRONode.Super.NO_MRO_SUFFIX) { return genericGetAttr(frame, self, stringAttr); } - for (; i < n; i++) { - PythonAbstractClass tmp = mro[i]; - Object res = readFromDict.execute(tmp, stringAttr); - if (res != PNone.NO_VALUE) { - TpSlots resSlots = getSlotsNode.execute(inliningTarget, res); - if (hasDescrGetProfile.profile(inliningTarget, resSlots.tp_descr_get() != null)) { - /* - * Only pass 'obj' param if this is instance-mode super (See SF ID #743627) - */ - // acts as a branch profile - if (callGetSlotNode == null) { - CompilerDirectives.transferToInterpreterAndInvalidate(); - callGetSlotNode = insert(CallSlotDescrGet.create()); - } - Object object = getObject.executeCached(self); - Object obj; - if (getObjectIsStartObjectProfile.profile(inliningTarget, object == startType)) { - obj = PNone.NO_VALUE; - } else { - obj = object; - } - res = callGetSlotNode.executeCached(frame, resSlots.tp_descr_get(), res, obj, startType); + if (res != PNone.NO_VALUE) { + TpSlots resSlots = getSlotsNode.execute(inliningTarget, res); + if (hasDescrGetProfile.profile(inliningTarget, resSlots.tp_descr_get() != null)) { + /* + * Only pass 'obj' param if this is instance-mode super (See SF ID #743627) + */ + // acts as a branch profile + if (callGetSlotNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + callGetSlotNode = insert(CallSlotDescrGet.create()); + } + Object object = getObject.executeCached(self); + Object obj; + if (getObjectIsStartObjectProfile.profile(inliningTarget, object == startType)) { + obj = PNone.NO_VALUE; + } else { + obj = object; } - return res; + res = callGetSlotNode.executeCached(frame, resSlots.tp_descr_get(), res, obj, startType); } + return res; } Object object = getObject.executeCached(self); @@ -586,21 +565,6 @@ Object get(VirtualFrame frame, SuperObject self, Object attr, return genericGetAttr(frame, self, stringAttr); } - private boolean isSameType(Object execute, Object abstractPythonClass) { - if (isSameTypeNode == null) { - CompilerDirectives.transferToInterpreterAndInvalidate(); - isSameTypeNode = insert(IsSameTypeNodeGen.create()); - } - return isSameTypeNode.executeCached(execute, abstractPythonClass); - } - - private PythonAbstractClass[] getMro(Object clazz) { - if (getMroNode == null) { - CompilerDirectives.transferToInterpreterAndInvalidate(); - getMroNode = insert(GetMroNode.create()); - } - return getMroNode.executeCached(clazz); - } } @Builtin(name = J___THISCLASS__, minNumOfPositionalArgs = 1, isGetter = true) 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 7a61d90480..5ee2ad63ca 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 @@ -72,6 +72,7 @@ import com.oracle.truffle.api.dsl.Idempotent; 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.ReportPolymorphism.Megamorphic; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.nodes.EncapsulatingNodeReference; @@ -139,6 +140,127 @@ public static LookupAttributeInMRONode.Dynamic getUncached() { } private final boolean skipNonStaticBases; + + /** + * The suffix lookup used by super. Its result depends on the starting type as well as the + * receiver MRO. Reuse the ordinary lookup cache of the next class only when its MRO is exactly + * the suffix being searched, and separately guard the receiver's MRO against changes. + */ + @GenerateUncached + @GenerateInline(false) + public abstract static class Super extends PNodeWithContext { + /** Distinguish an empty suffix from a searched suffix with a missing attribute. */ + public static final Object NO_MRO_SUFFIX = new Object(); + + public final Object execute(Object type, Object objectType, TruffleString key) { + try { + return executeInternal(type, objectType, key); + } catch (MROChangedException e) { + // This exception can occur only during specialization + CompilerDirectives.transferToInterpreterAndInvalidate(); + return e.result; + } + } + + protected abstract Object executeInternal(Object type, Object objectType, TruffleString key) throws MROChangedException; + + @Specialization(guards = {"isSingleContext()", "type == cachedType", "objectType == cachedObjectType", "key == cachedKey", + "cachedResult != null", "result != null"}, assumptions = {"cachedResult.mroStable", "cachedResult.attributeStable"}, limit = "3") + static Object cached(Object type, Object objectType, TruffleString key, + @Bind Node inliningTarget, + @Cached(value = "type", weak = true) Object cachedType, + @Cached(value = "objectType", weak = true) Object cachedObjectType, + @Cached("key") TruffleString cachedKey, + @Cached("createCache(inliningTarget, cachedType, cachedObjectType, cachedKey)") SuperLookupResult cachedResult, + @Bind("getCachedValue(cachedResult)") Object result) { + return result; + } + + @NonIdempotent + static Object getCachedValue(SuperLookupResult result) { + return result == null || result.attribute.getAssumption() == null ? null : result.attribute.getValue(); + } + + @Specialization(replaces = "cached") + static Object generic(Object type, Object objectType, TruffleString key, + @Bind Node inliningTarget, + @Cached GetMroStorageNode getMroNode, + @Cached IsSameTypeNode isSameTypeNode, + @Cached ReadAttributeFromObjectNode readAttrNode) { + PythonAbstractClass[] mro = getMroNode.execute(inliningTarget, objectType).getInternalClassArray(); + int i = 0; + while (i < mro.length && !isSameTypeNode.execute(inliningTarget, type, mro[i])) { + i++; + } + if (i + 1 >= mro.length) { + return NO_MRO_SUFFIX; + } + for (i++; i < mro.length; i++) { + Object value = readAttrNode.execute(mro[i], key); + if (value != PNone.NO_VALUE) { + return value; + } + } + return PNone.NO_VALUE; + } + + static final class SuperLookupResult { + final Assumption mroStable; + final Assumption attributeStable; + final MroSequenceStorage.FinalAttributeAssumptionPair attribute; + + SuperLookupResult(Assumption mroStable, MroSequenceStorage.FinalAttributeAssumptionPair attribute) { + this.mroStable = mroStable; + this.attributeStable = attribute.getAssumption(); + this.attribute = attribute; + } + } + + static SuperLookupResult createCache(Node node, Object type, Object objectType, TruffleString key) throws MROChangedException { + CompilerAsserts.neverPartOfCompilation(); + MroSequenceStorage mro = GetMroStorageNode.executeUncached(objectType); + PythonAbstractClass[] classes = mro.getInternalClassArray(); + int i = 0; + while (i < classes.length && !IsSameTypeNode.executeUncached(type, classes[i])) { + i++; + } + int start = i + 1; + if (start >= classes.length) { + return null; + } + MroSequenceStorage suffix = GetMroStorageNode.executeUncached(classes[start]); + if (suffix.length() != classes.length - start) { + return null; + } + for (i = 0; i < suffix.length(); i++) { + if (!IsSameTypeNode.executeUncached(classes[start + i], suffix.getPythonClassItemNormalized(i))) { + return null; + } + } + // The attribute assumption belongs to the suffix class. Changing the receiver's + // __bases__ can select a different suffix without invalidating that assumption, so + // we must also guard the receiver's MRO. + Assumption mroStable = mro.getLookupStableAssumption(); + if (!mroStable.isValid()) { + return null; + } + try { + MroSequenceStorage.FinalAttributeAssumptionPair attribute = findAttrAndAssumptionInMRO(node, classes[start], key, false, false); + if (attribute.getAssumption() == null) { + return null; + } + if (!mroStable.isValid()) { + // Key comparisons may change only the receiver MRO. Return the computed + // result without caching it or repeating the side effects in generic lookup. + throw new MROChangedException(attribute.getValue()); + } + return new SuperLookupResult(mroStable, attribute); + } catch (MROGenericDictException ignore) { + throw CompilerDirectives.shouldNotReachHere(); + } + } + } + final TruffleString key; public LookupAttributeInMRONode(TruffleString key, boolean skipNonStaticBases) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/sequence/storage/MroSequenceStorage.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/sequence/storage/MroSequenceStorage.java index 6c316ace5c..cfda4f9c6e 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/sequence/storage/MroSequenceStorage.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/sequence/storage/MroSequenceStorage.java @@ -92,6 +92,7 @@ public void invalidate() { @NonIdempotent public Object getValue() { + assert value != null; return value.get(); }