Skip to content

Commit 8f853c3

Browse files
committed
Use Object[] for __slots__ instead of HiddenAttrs
1 parent 41cc531 commit 8f853c3

File tree

12 files changed

+293
-117
lines changed

12 files changed

+293
-117
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3+
#
4+
# The Universal Permissive License (UPL), Version 1.0
5+
#
6+
# Subject to the condition set forth below, permission is hereby granted to any
7+
# person obtaining a copy of this software, associated documentation and/or
8+
# data (collectively the "Software"), free of charge and under any and all
9+
# copyright rights in the Software, and any and all patent rights owned or
10+
# freely licensable by each licensor hereunder covering either (i) the
11+
# unmodified Software as contributed to or provided by such licensor, or (ii)
12+
# the Larger Works (as defined below), to deal in both
13+
#
14+
# (a) the Software, and
15+
#
16+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
17+
# one is included with the Software each a "Larger Work" to which the Software
18+
# is contributed by such licensors),
19+
#
20+
# without restriction, including without limitation the rights to copy, create
21+
# derivative works of, display, perform, and distribute the Software and make,
22+
# use, sell, offer for sale, import, export, have made, and have sold the
23+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
24+
# either these or other terms.
25+
#
26+
# This license is subject to the following condition:
27+
#
28+
# The above copyright notice and either this complete permission notice or at a
29+
# minimum a reference to the UPL must be included in all copies or substantial
30+
# portions of the Software.
31+
#
32+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
# SOFTWARE.
39+
40+
from . import CPyExtTestCase, CPyExtHeapType
41+
__dir__ = __file__.rpartition("/")[0]
42+
43+
44+
class BaseWithSlots:
45+
__slots__ = ('a', 'b')
46+
47+
class AnotherBaseWithSlots:
48+
__slots__ = ('b',)
49+
50+
class EmptyBase:
51+
pass
52+
53+
# This is used to increase tp_basicsize in CPyExtHeapType which needs to be grater that the basic size of the
54+
# base class. We do not use the C struct in any way - in fact in CPython, the `space` member declared here
55+
# overlaps with __slots__ of the base class.
56+
def cmembers(i):
57+
return f"long space[{i}];"
58+
59+
class TestIndexedSlots(CPyExtTestCase):
60+
def test_slots_in_base(self):
61+
N1 = CPyExtHeapType('Na1', bases=(BaseWithSlots,), cmembers=cmembers(4))
62+
N2 = CPyExtHeapType('Na2', bases=(N1,), cmembers=cmembers(8))
63+
64+
class M1(EmptyBase, N2):
65+
pass
66+
67+
class M2(N2, EmptyBase):
68+
pass
69+
70+
for C in (N1, N2, M1, M2):
71+
x = C()
72+
x.a, x.b = 12, 13
73+
self.assertEqual((12, 13), (x.a, x.b))
74+
75+
def test_slots_in_subclass(self):
76+
N1 = CPyExtHeapType('Nb1', bases=(EmptyBase,), cmembers=cmembers(4))
77+
78+
class M1(N1):
79+
__slots__ = ('a', 'b')
80+
81+
class M2(N1):
82+
__slots__ = ('a', 'b')
83+
84+
class M3(M1):
85+
__slots__ = ('c',)
86+
87+
for C in (M1, M2):
88+
x = C()
89+
x.a, x.b = 12, 13
90+
self.assertEqual((12, 13), (x.a, x.b))
91+
92+
x = M3()
93+
x.a, x.b, x.c = 22, 23, 24
94+
self.assertEqual((22, 23, 24), (x.a, x.b, x.c))
95+
96+
97+
def test_slots_in_base_and_subclass(self):
98+
N1 = CPyExtHeapType('Nd1', bases=(BaseWithSlots,), cmembers=cmembers(4))
99+
100+
class M1(N1):
101+
__slots__ = ('c', 'd')
102+
103+
class M2(N1):
104+
__slots__ = ('c', 'd', 'a')
105+
106+
class M3(M1):
107+
__slots__ = ('b', 'a')
108+
109+
for C in (M1, M2, M3):
110+
x = C()
111+
x.a, x.b, x.c, x.d = 12, 13, 14, 15
112+
self.assertEqual((12, 13, 14, 15), (x.a, x.b, x.c, x.d))
113+
114+
def test_multi_bases(self):
115+
N1 = CPyExtHeapType('Ne1', bases=(BaseWithSlots, EmptyBase), cmembers=cmembers(6))
116+
N2 = CPyExtHeapType('Ne2', bases=(EmptyBase, BaseWithSlots), cmembers=cmembers(6))
117+
118+
class M1(N1):
119+
pass
120+
121+
class M2(N1):
122+
__slots__ = ('b',)
123+
124+
for C in (N1, N2, M1, M2):
125+
x = C()
126+
x.a, x.b = 12, 13
127+
self.assertEqual((12, 13), (x.a, x.b))
128+
129+
def test_layout_conflict(self):
130+
N1 = CPyExtHeapType('Nf1', bases=(BaseWithSlots,), cmembers=cmembers(4))
131+
self.assertRaises(TypeError, CPyExtHeapType, 'Nf2', bases=(AnotherBaseWithSlots, BaseWithSlots))
132+
self.assertRaises(TypeError, CPyExtHeapType, 'Nf3', bases=(AnotherBaseWithSlots, N1))

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/PythonLanguage.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@
7474
import com.oracle.graal.python.builtins.objects.type.PythonAbstractClass;
7575
import com.oracle.graal.python.builtins.objects.type.PythonManagedClass;
7676
import com.oracle.graal.python.builtins.objects.type.TpSlots;
77-
import com.oracle.graal.python.builtins.objects.type.TypeBuiltins;
7877
import com.oracle.graal.python.builtins.objects.type.slots.TpSlot;
7978
import com.oracle.graal.python.compiler.CodeUnit;
8079
import com.oracle.graal.python.compiler.CompilationUnit;
@@ -375,8 +374,6 @@ public boolean isSingleContext() {
375374
/** For fast access to the PythonThreadState object by the owning thread. */
376375
private final ContextThreadLocal<PythonThreadState> threadState = locals.createContextThreadLocal(PythonContext.PythonThreadState::new);
377376

378-
public final ConcurrentHashMap<String, HiddenAttr> typeHiddenAttrs = new ConcurrentHashMap<>(TypeBuiltins.INITIAL_HIDDEN_TYPE_ATTRS);
379-
380377
private final MroShape mroShapeRoot = MroShape.createRoot();
381378

382379
/**

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/PythonAbstractObject.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ public abstract class PythonAbstractObject extends DynamicObject implements Truf
198198

199199
protected static final Shape ABSTRACT_SHAPE = Shape.newBuilder().build();
200200

201+
private Object[] indexedSlots;
202+
201203
protected PythonAbstractObject(Shape shape) {
202204
super(shape);
203205
}
@@ -223,6 +225,14 @@ public final void clearNativeWrapper() {
223225
nativeWrapper = null;
224226
}
225227

228+
public Object[] getIndexedSlots() {
229+
return indexedSlots;
230+
}
231+
232+
public void setIndexedSlots(Object[] indexedSlots) {
233+
this.indexedSlots = indexedSlots;
234+
}
235+
226236
@ExportMessage
227237
public void writeMember(String key, Object value,
228238
@Bind("$node") Node inliningTarget,

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ public abstract class CExtNodes {
215215

216216
private static final String J_UNICODE = "unicode";
217217
private static final String J_SUBTYPE_NEW = "_subtype_new";
218+
private static final long SIZEOF_PY_OBJECT_PTR = Long.BYTES;
218219

219220
/**
220221
* For some builtin classes, the CPython approach to creating a subclass instance is to just
@@ -939,6 +940,8 @@ public static long lookupNativeI64MemberInMRO(Object cls, CFields nativeMemberNa
939940
MroSequenceStorage mroStorage = GetMroStorageNode.executeUncached(cls);
940941
int n = mroStorage.length();
941942

943+
boolean isBasicsizeOrWeaklistoffset = nativeMemberName == CFields.PyTypeObject__tp_basicsize || nativeMemberName == CFields.PyTypeObject__tp_weaklistoffset;
944+
long indexedSlotsSize = isBasicsizeOrWeaklistoffset && cls instanceof PythonManagedClass pmc ? pmc.getIndexedSlotCount() * SIZEOF_PY_OBJECT_PTR : 0;
942945
for (int i = 0; i < n; i++) {
943946
PythonAbstractClass mroCls = (PythonAbstractClass) SequenceStorageNodes.GetItemDynamicNode.executeUncached(mroStorage, i);
944947

@@ -950,15 +953,15 @@ public static long lookupNativeI64MemberInMRO(Object cls, CFields nativeMemberNa
950953
attr = ReadAttributeFromObjectNode.getUncachedForceType().execute(mroCls, CompilerDirectives.castExact(managedMemberName, TruffleString.class));
951954
}
952955
if (attr != NO_VALUE) {
953-
return PyNumberAsSizeNode.executeExactUncached(attr);
956+
return indexedSlotsSize + PyNumberAsSizeNode.executeExactUncached(attr);
954957
}
955958
} else {
956959
assert PGuards.isNativeClass(mroCls) : "invalid class inheritance structure; expected native class";
957-
return CStructAccess.ReadI64Node.getUncached().readFromObj((PythonNativeClass) mroCls, nativeMemberName);
960+
return indexedSlotsSize + CStructAccess.ReadI64Node.getUncached().readFromObj((PythonNativeClass) mroCls, nativeMemberName);
958961
}
959962
}
960963
// return the value from PyBaseObject - assumed to be 0 for vectorcall_offset
961-
return nativeMemberName == CFields.PyTypeObject__tp_basicsize || nativeMemberName == CFields.PyTypeObject__tp_weaklistoffset ? CStructs.PyObject.size() : 0L;
964+
return isBasicsizeOrWeaklistoffset ? indexedSlotsSize + CStructs.PyObject.size() : 0L;
962965
}
963966

964967
/**
@@ -986,6 +989,8 @@ static long doSingleContext(Object cls, CFields nativeMember, HiddenAttr managed
986989
CompilerAsserts.partialEvaluationConstant(builtinCallback);
987990

988991
Object current = cls;
992+
boolean isBasicsizeOrWeaklistoffset = nativeMember == CFields.PyTypeObject__tp_basicsize || nativeMember == CFields.PyTypeObject__tp_weaklistoffset;
993+
long indexedSlotsSize = isBasicsizeOrWeaklistoffset && cls instanceof PythonManagedClass pmc ? pmc.getIndexedSlotCount() * SIZEOF_PY_OBJECT_PTR : 0;
989994
do {
990995
if (current instanceof PythonBuiltinClassType pbct) {
991996
current = PythonContext.get(inliningTarget).lookupType(pbct);
@@ -995,16 +1000,16 @@ static long doSingleContext(Object cls, CFields nativeMember, HiddenAttr managed
9951000
} else if (PGuards.isManagedClass(current)) {
9961001
Object attr = readAttrNode.execute(inliningTarget, (PythonObject) current, managedMemberName, null);
9971002
if (attr != null) {
998-
return asSizeNode.executeExact(null, inliningTarget, attr);
1003+
return indexedSlotsSize + asSizeNode.executeExact(null, inliningTarget, attr);
9991004
}
10001005
} else {
10011006
assert PGuards.isNativeClass(current) : "invalid class inheritance structure; expected native class";
1002-
return getTypeMemberNode.readFromObj((PythonNativeClass) current, nativeMember);
1007+
return indexedSlotsSize + getTypeMemberNode.readFromObj((PythonNativeClass) current, nativeMember);
10031008
}
10041009
current = getBaseClassNode.execute(inliningTarget, current);
10051010
} while (current != null);
10061011
// return the value from PyBaseObject - assumed to be 0 for vectorcall_offset
1007-
return nativeMember == CFields.PyTypeObject__tp_basicsize || nativeMember == CFields.PyTypeObject__tp_weaklistoffset ? CStructs.PyObject.size() : 0L;
1012+
return isBasicsizeOrWeaklistoffset ? indexedSlotsSize + CStructs.PyObject.size() : 0L;
10081013
}
10091014
}
10101015

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/getsetdescriptor/DescriptorBuiltins.java

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@
5656
import com.oracle.graal.python.builtins.objects.PNone;
5757
import com.oracle.graal.python.builtins.objects.PythonAbstractObject;
5858
import com.oracle.graal.python.builtins.objects.str.StringUtils.SimpleTruffleStringFormatNode;
59+
import com.oracle.graal.python.builtins.objects.type.TypeNodes.GetIndexedSlotsCountNode;
5960
import com.oracle.graal.python.builtins.objects.type.TypeNodes.GetNameNode;
6061
import com.oracle.graal.python.nodes.ErrorMessages;
61-
import com.oracle.graal.python.nodes.HiddenAttr;
6262
import com.oracle.graal.python.nodes.PRaiseNode;
6363
import com.oracle.graal.python.nodes.attributes.GetAttributeNode.GetFixedAttributeNode;
6464
import com.oracle.graal.python.nodes.call.special.CallBinaryMethodNode;
@@ -106,10 +106,10 @@ static TruffleString doGetSetDescriptor(VirtualFrame frame, GetSetDescriptor sel
106106
}
107107

108108
@Specialization
109-
static TruffleString doHiddenAttrDescriptor(VirtualFrame frame, HiddenAttrDescriptor self,
109+
static TruffleString doIndexedSlotDescriptor(VirtualFrame frame, IndexedSlotDescriptor self,
110110
@Shared @Cached("create(T___QUALNAME__)") GetFixedAttributeNode readQualNameNode,
111111
@Shared("formatter") @Cached SimpleTruffleStringFormatNode simpleTruffleStringFormatNode) {
112-
return simpleTruffleStringFormatNode.format("%s.%s", toStr(readQualNameNode.executeObject(frame, self.getType())), self.getAttr().getName());
112+
return simpleTruffleStringFormatNode.format("%s.%s", toStr(readQualNameNode.executeObject(frame, self.getType())), self.getName());
113113
}
114114

115115
@TruffleBoundary
@@ -128,9 +128,8 @@ static TruffleString doGetSetDescriptor(GetSetDescriptor self) {
128128
}
129129

130130
@Specialization
131-
static TruffleString doHiddenAttrDescriptor(HiddenAttrDescriptor self,
132-
@Cached TruffleString.FromJavaStringNode fromJavaStringNode) {
133-
return fromJavaStringNode.execute(self.getAttr().getName(), TS_ENCODING);
131+
static TruffleString doIndexedSlotDescriptor(IndexedSlotDescriptor self) {
132+
return self.getName();
134133
}
135134
}
136135

@@ -172,15 +171,16 @@ Object doGetSetDescriptor(VirtualFrame frame, GetSetDescriptor descr, Object obj
172171
}
173172

174173
@Specialization
175-
Object doHiddenAttrDescriptor(HiddenAttrDescriptor descr, PythonAbstractObject obj,
174+
Object doIndexedSlotDescriptor(IndexedSlotDescriptor descr, PythonAbstractObject obj,
176175
@Bind("this") Node inliningTarget,
177176
@Exclusive @Cached PRaiseNode.Lazy raiseNode,
178-
@Cached HiddenAttr.ReadNode readNode) {
179-
Object val = readNode.execute(inliningTarget, obj, descr.getAttr(), PNone.NO_VALUE);
180-
if (val != PNone.NO_VALUE) {
177+
@Cached GetOrCreateIndexedSlots getSlotsNode) {
178+
Object[] slots = getSlotsNode.execute(inliningTarget, obj);
179+
Object val = slots[descr.getIndex()];
180+
if (val != null) {
181181
return val;
182182
}
183-
throw raiseNode.get(inliningTarget).raise(AttributeError, ErrorMessages.OBJ_N_HAS_NO_ATTR_S, descr.getType(), descr.getAttr().getName());
183+
throw raiseNode.get(inliningTarget).raise(AttributeError, ErrorMessages.OBJ_N_HAS_NO_ATTR_S, descr.getType(), descr.getName());
184184
}
185185
}
186186

@@ -202,10 +202,10 @@ Object doGetSetDescriptor(VirtualFrame frame, GetSetDescriptor descr, Object obj
202202
}
203203

204204
@Specialization
205-
static Object doHiddenAttrDescriptor(HiddenAttrDescriptor descr, PythonAbstractObject obj, Object value,
205+
static Object doIndexedSlotDescriptor(IndexedSlotDescriptor descr, PythonAbstractObject obj, Object value,
206206
@Bind("this") Node inliningTarget,
207-
@Cached HiddenAttr.WriteNode writeNode) {
208-
writeNode.execute(inliningTarget, obj, descr.getAttr(), value);
207+
@Cached GetOrCreateIndexedSlots getSlotsNode) {
208+
getSlotsNode.execute(inliningTarget, obj)[descr.getIndex()] = value;
209209
return true;
210210
}
211211
}
@@ -239,18 +239,41 @@ Object doGetSetDescriptor(VirtualFrame frame, GetSetDescriptor descr, Object obj
239239
}
240240

241241
@Specialization
242-
Object doHiddenAttrDescriptor(HiddenAttrDescriptor descr, PythonAbstractObject obj,
242+
Object doIndexedSlotDescriptor(IndexedSlotDescriptor descr, PythonAbstractObject obj,
243243
@Bind("this") Node inliningTarget,
244244
@Exclusive @Cached PRaiseNode.Lazy raiseNode,
245-
@Cached HiddenAttr.WriteNode writeNode,
246-
@Cached HiddenAttr.ReadNode readNode,
245+
@Cached GetOrCreateIndexedSlots getSlotsNode,
247246
@Cached InlinedConditionProfile profile) {
248247
// PyMember_SetOne - Check if the attribute is set.
249-
if (profile.profile(inliningTarget, readNode.execute(inliningTarget, obj, descr.getAttr(), PNone.NO_VALUE) != PNone.NO_VALUE)) {
250-
writeNode.execute(inliningTarget, obj, descr.getAttr(), PNone.NO_VALUE);
248+
Object[] slots = getSlotsNode.execute(inliningTarget, obj);
249+
if (profile.profile(inliningTarget, slots[descr.getIndex()] != null)) {
250+
slots[descr.getIndex()] = null;
251251
return PNone.NONE;
252252
}
253-
throw raiseNode.get(inliningTarget).raise(PythonBuiltinClassType.AttributeError, ErrorMessages.S, descr.getAttr().getName());
253+
throw raiseNode.get(inliningTarget).raise(PythonBuiltinClassType.AttributeError, ErrorMessages.S, descr.getName());
254+
}
255+
}
256+
257+
@GenerateInline
258+
@GenerateCached(false)
259+
@GenerateUncached
260+
abstract static class GetOrCreateIndexedSlots extends Node {
261+
abstract Object[] execute(Node inliningTarget, PythonAbstractObject object);
262+
263+
@Specialization(guards = "object.getIndexedSlots() != null")
264+
static Object[] doGet(PythonAbstractObject object) {
265+
return object.getIndexedSlots();
266+
}
267+
268+
@Specialization(guards = "object.getIndexedSlots() == null")
269+
static Object[] doCreate(Node inliningTarget, PythonAbstractObject object,
270+
@Cached GetIndexedSlotsCountNode getIndexedSlotsCountNode,
271+
@Cached GetClassNode getClassNode) {
272+
Object cls = getClassNode.execute(inliningTarget, object);
273+
int slotCount = getIndexedSlotsCountNode.execute(inliningTarget, cls);
274+
Object[] slots = new Object[slotCount];
275+
object.setIndexedSlots(slots);
276+
return slots;
254277
}
255278
}
256279
}

0 commit comments

Comments
 (0)