Skip to content

Commit 0c88fc7

Browse files
steve-stimfel
authored andcommitted
Fast-path for builtin reads, improve get/set attribute @operations fast-paths
1 parent a6b48c0 commit 0c88fc7

12 files changed

Lines changed: 273 additions & 118 deletions

File tree

graalpython/com.oracle.graal.python.test/src/tests/test_builtin.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import sys
88
import tempfile
99
import unittest
10+
import io
1011

1112
class MyIndexable(object):
1213
def __init__(self, value):
@@ -121,6 +122,53 @@ def test_builtin_constants(self):
121122
self.assertEqual(getattr(builtins, 'False'), False)
122123
self.assertEqual(getattr(builtins, 'True'), True)
123124

125+
def test_missing_builtin_lookup_after_warmup(self):
126+
def read_missing_builtin():
127+
return definitely_missing_builtin_for_review
128+
129+
for _ in range(20):
130+
with self.assertRaises(NameError):
131+
read_missing_builtin()
132+
133+
def test_instance_attr_state_machine_after_warmup(self):
134+
class PaddedFile:
135+
def __init__(self, fileobj, prepend=b""):
136+
self._buffer = prepend
137+
self._length = len(prepend)
138+
self.file = fileobj
139+
self._read = 0
140+
141+
def read(self, size):
142+
if self._read is None:
143+
return self.file.read(size)
144+
if self._read + size <= self._length:
145+
read = self._read
146+
self._read += size
147+
return self._buffer[read:self._read]
148+
read = self._read
149+
self._read = None
150+
return self._buffer[read:] + self.file.read(size - self._length + read)
151+
152+
def prepend(self, prepend=b""):
153+
if self._read is None:
154+
self._buffer = prepend
155+
else:
156+
self._read -= len(prepend)
157+
return
158+
self._length = len(self._buffer)
159+
self._read = 0
160+
161+
def exercise():
162+
padded = PaddedFile(io.BytesIO(b"cdef"), b"ab")
163+
self.assertEqual(padded.read(1), b"a")
164+
self.assertEqual(padded.read(3), b"bcd")
165+
padded.prepend(b"Z")
166+
self.assertEqual(padded.read(2), b"Ze")
167+
self.assertEqual(padded.read(2), b"f")
168+
169+
for _ in range(20):
170+
exercise()
171+
124172
def test_min(self):
125173
self.assertEqual(min((), default=1, key="adsf"), 1)
126174

graalpython/com.oracle.graal.python.test/src/tests/test_mro.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2021, 2025, Oracle and/or its affiliates.
1+
# Copyright (c) 2021, 2026, Oracle and/or its affiliates.
22
# Copyright (C) 1996-2020 Python Software Foundation
33
#
44
# Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2

graalpython/com.oracle.graal.python.test/src/tests/test_slot.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
22
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
33
#
44
# The Universal Permissive License (UPL), Version 1.0
@@ -173,5 +173,17 @@ class C:
173173
__slots__ = ('a', 'b')
174174
self.assertRaises(AttributeError, setattr, C(), 'c', 42)
175175

176+
def test_write_attr_without_dict_after_warmup(self):
177+
class C:
178+
__slots__ = ()
179+
180+
obj = C()
181+
182+
def write_attr():
183+
obj.x = 42
184+
185+
for _ in range(20):
186+
self.assertRaises(AttributeError, write_attr)
187+
176188
if __name__ == "__main__":
177189
unittest.main()

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/DynamicObjectStorage.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,18 @@
7575
import com.oracle.truffle.api.nodes.Node;
7676
import com.oracle.truffle.api.object.DynamicObject;
7777
import com.oracle.truffle.api.object.Shape;
78-
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
7978
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
8079
import com.oracle.truffle.api.strings.TruffleString;
8180

8281
/**
8382
* This storage keeps a reference to the MRO when used for a type dict. Writing to this storage will
8483
* cause the appropriate <it>attribute final</it> assumptions to be invalidated.
84+
* <p/>
85+
* This storage may wrap a {@link PythonObject}; in that case the storage and the object can be
86+
* used interchangeably. If the storage is transformed to other storage, or for some other reason
87+
* requires that all accesses are routed through the {@link DynamicObjectStorage}, then the
88+
* {@link PythonObject}'s {@link Shape} flags must be updated to include
89+
* {@link PythonObject#HAS_MATERIALIZED_DICT}.
8590
*/
8691
public final class DynamicObjectStorage extends HashingStorage {
8792
public static final int SIZE_THRESHOLD = 100;
@@ -268,6 +273,10 @@ void setStringKey(TruffleString key, Object value, DynamicObject.PutNode putNode
268273
putNode.execute(store, key, assertNoJavaString(value));
269274
}
270275

276+
boolean setStringKeyIfPresent(TruffleString key, Object value, DynamicObject.PutNode putNode) {
277+
return putNode.executeIfPresent(store, key, assertNoJavaString(value));
278+
}
279+
271280
boolean shouldTransitionOnPut() {
272281
// For now, we do not use SIZE_THRESHOLD condition to transition storages that wrap
273282
// dictionaries retrieved via object's __dict__
@@ -332,8 +341,7 @@ public static DynamicObjectStorage copy(DynamicObjectStorage receiver,
332341
public abstract static class DynamicObjectStorageSetStringKey extends SpecializedSetStringKey {
333342
@Specialization
334343
static void doIt(Node inliningTarget, HashingStorage self, TruffleString key, Object value,
335-
@Cached DynamicObject.PutNode putNode,
336-
@Cached InlinedBranchProfile invalidateMro) {
344+
@Cached DynamicObject.PutNode putNode) {
337345
((DynamicObjectStorage) self).setStringKey(key, value, putNode);
338346
}
339347
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/common/HashingStorageNodes.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -605,11 +605,11 @@ static Object domStringKey(Frame frame, Node inliningTarget, DynamicObjectStorag
605605
if (val == PNone.NO_VALUE) {
606606
return null;
607607
} else {
608-
putNode.execute(store, key, PNone.NO_VALUE);
608+
self.setStringKey(key, PNone.NO_VALUE, putNode);
609609
return val;
610610
}
611611
} else {
612-
return putNode.executeIfPresent(store, key, PNone.NO_VALUE);
612+
return self.setStringKeyIfPresent(key, PNone.NO_VALUE, putNode);
613613
}
614614
}
615615

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/module/PythonModule.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public PythonModule(Object clazz, Shape instanceShape) {
8989
setAttribute(T___SPEC__, PNone.NO_VALUE);
9090
setAttribute(T___CACHED__, PNone.NO_VALUE);
9191
setAttribute(T___FILE__, PNone.NO_VALUE);
92-
GetOrCreateDictNode.executeUncached(this);
92+
GetOrCreateDictNode.ensureModuleDict(this);
9393
}
9494

9595
/**
@@ -105,7 +105,7 @@ private PythonModule(PythonLanguage lang, TruffleString moduleName) {
105105
setAttribute(T___SPEC__, PNone.NONE);
106106
setAttribute(T___CACHED__, PNone.NO_VALUE);
107107
setAttribute(T___FILE__, PNone.NO_VALUE);
108-
GetOrCreateDictNode.executeUncached(this);
108+
GetOrCreateDictNode.ensureModuleDict(lang, this);
109109
}
110110

111111
/**

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/HiddenAttr.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@
7272
import com.oracle.truffle.api.nodes.UnexpectedResultException;
7373
import com.oracle.truffle.api.object.DynamicObject;
7474
import com.oracle.truffle.api.object.HiddenKey;
75+
import com.oracle.truffle.api.object.PropertyGetter;
76+
import com.oracle.truffle.api.object.Shape;
7577

7678
public final class HiddenAttr {
7779

@@ -127,6 +129,10 @@ boolean hasLongValue() {
127129
this == METHOD_DEF_PTR;
128130
}
129131

132+
public PropertyGetter createPropertyGetter(Shape shape) {
133+
return shape.makePropertyGetter(key);
134+
}
135+
130136
@Override
131137
public String toString() {
132138
return getName();
@@ -192,10 +198,7 @@ static void doPythonObjectDict(PythonObject self, HiddenAttr attr, Object value,
192198
}
193199

194200
private static boolean isGenericDict(PythonObject self, Object value) {
195-
if (value instanceof PDict dict && dict.getDictStorage() instanceof DynamicObjectStorage dynamicStorage) {
196-
return dynamicStorage.getStore() != self;
197-
}
198-
return true;
201+
return !(value instanceof PDict dict && dict.getDictStorage() instanceof DynamicObjectStorage dom && dom.getStore() == self);
199202
}
200203

201204
@Specialization(guards = "attr != DICT || !isPythonObject(self)")

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/PGuards.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
import com.oracle.truffle.api.exception.AbstractTruffleException;
110110
import com.oracle.truffle.api.nodes.Node;
111111
import com.oracle.truffle.api.nodes.UnexpectedResultException;
112+
import com.oracle.truffle.api.object.Shape;
112113
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
113114
import com.oracle.truffle.api.strings.TruffleString;
114115
import com.oracle.truffle.api.strings.TruffleString.CodeRange;
@@ -127,6 +128,7 @@ public static boolean isNone(Object value) {
127128
return value == PNone.NONE;
128129
}
129130

131+
@Idempotent
130132
public static boolean isNoValue(Object object) {
131133
return object == PNone.NO_VALUE;
132134
}
@@ -531,4 +533,8 @@ public static boolean hasBuiltinDictIter(Node inliningTarget, PDict dict, GetPyt
531533
return isBuiltinDict(dict) || getSlots.execute(inliningTarget, getClassNode.execute(inliningTarget, dict)).tp_iter() == DictBuiltins.SLOTS.tp_iter();
532534
}
533535

536+
@Idempotent
537+
public static boolean hasMaterializedDict(Shape s) {
538+
return (s.getFlags() & PythonObject.HAS_MATERIALIZED_DICT) != 0;
539+
}
534540
}

0 commit comments

Comments
 (0)