Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,16 @@
import org.junit.Test;

import com.oracle.graal.python.PythonLanguage;
import com.oracle.graal.python.builtins.objects.PNone;
import com.oracle.graal.python.builtins.objects.common.DynamicObjectStorage;
import com.oracle.graal.python.builtins.objects.common.EconomicMapStorage;
import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageDelItem;
import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageLen;
import com.oracle.graal.python.builtins.objects.dict.PDict;
import com.oracle.graal.python.builtins.objects.object.PythonObject;
import com.oracle.graal.python.nodes.attributes.WriteAttributeToObjectNode;
import com.oracle.graal.python.nodes.attributes.WriteAttributeToPythonObjectNode;
import com.oracle.graal.python.nodes.object.GetOrCreateDictNode;
import com.oracle.graal.python.runtime.object.PFactory;
import com.oracle.graal.python.test.PythonTests;
import com.oracle.truffle.api.strings.TruffleString;
Expand Down Expand Up @@ -81,6 +87,48 @@ static int length(PDict dict) {
return HashingStorageLen.executeUncached(dict.getDictStorage());
}

@Test
public void dynamicStorageAttributeWrites() {
PythonObject object = PFactory.createSimpleNamespace(PythonLanguage.get(null));
WriteAttributeToPythonObjectNode.executeUncached(object, ts("key"), 1);
PDict dict = GetOrCreateDictNode.executeUncached(object);
assertEquals(1, length(dict));
WriteAttributeToObjectNode.getUncached().execute(object, ts("key"), PNone.NO_VALUE);
assertEquals(0, length(dict));
WriteAttributeToObjectNode.getUncached().execute(object, ts("key"), 2);
assertEquals(1, length(dict));
WriteAttributeToObjectNode.getUncached().execute(object, ts("key"), PNone.NO_VALUE);
assertEquals(0, length(dict));
}

@Test(expected = AssertionError.class)
public void directAttributeWriteRejectsBackingDict() {
PythonObject object = PFactory.createSimpleNamespace(PythonLanguage.get(null));
GetOrCreateDictNode.executeUncached(object);
WriteAttributeToPythonObjectNode.executeUncached(object, ts("key"), 1);
}

@Test(expected = AssertionError.class)
public void dynamicStorageDetectsStaleTemporaryWrapper() {
PythonObject object = PFactory.createSimpleNamespace(PythonLanguage.get(null));
DynamicObjectStorage storage = new DynamicObjectStorage(object);
assertEquals(0, HashingStorageLen.executeUncached(storage));
WriteAttributeToObjectNode.getUncached().execute(object, ts("key"), 1);
HashingStorageLen.executeUncached(storage);
}

@Test(expected = AssertionError.class)
public void dynamicStorageDetectsStaleTemporaryWrapperWithDict() {
PythonObject object = PFactory.createSimpleNamespace(PythonLanguage.get(null));
PDict dict = GetOrCreateDictNode.executeUncached(object);
DynamicObjectStorage storage = new DynamicObjectStorage(object);
assertEquals(0, HashingStorageLen.executeUncached(storage));
assertEquals(0, length(dict));
WriteAttributeToObjectNode.getUncached().execute(object, ts("key"), 1);
assertEquals(1, length(dict));
HashingStorageLen.executeUncached(storage);
}

@Test
public void economicMapStorageTransition() {
PDict dict = PFactory.createDict(PythonLanguage.get(null));
Expand Down
124 changes: 124 additions & 0 deletions graalpython/com.oracle.graal.python.test/src/tests/test_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,130 @@

graalpy_only = unittest.skipUnless(sys.implementation.name == "graalpy", "GraalPy-specific dict storage test")


def test_namespace_reinitialization_with_backing_dict():
from types import SimpleNamespace

obj = SimpleNamespace(a=1)
mapping = obj.__dict__
assert len(mapping) == 1
obj.__init__(b=2)
assert len(mapping) == 2
assert mapping == {"a": 1, "b": 2}
mapping.clear()
mapping = obj.__dict__
assert len(mapping) == 0
obj.__init__(c=3)
assert mapping == {"c": 3}
assert obj.c == 3


def test_namespace_reinitialization_with_positional_mapping_and_backing_dict():
from types import SimpleNamespace

obj = SimpleNamespace(a=1)
mapping = obj.__dict__
assert len(mapping) == 1
mapping.clear()
assert len(mapping) == 0
obj.__init__({"b": 2}, c=3)
assert len(mapping) == 2
assert mapping == {"b": 2, "c": 3}
assert obj.__dict__ is mapping


def test_namespace_replace_with_backing_dict():
from types import SimpleNamespace

class Namespace(SimpleNamespace):
def __init__(self):
self.seed = 1
assert len(self.__dict__) == 1

obj = Namespace()
obj.extra = 2
result = obj.__replace__(extra=3)
assert result.__dict__ == {"seed": 1, "extra": 3}
assert len(result.__dict__) == 2


def test_object_dict_length_after_attribute_mutations():
class Object:
pass

obj = Object()
obj.deleted = 1
del obj.deleted # Ordinary objects do not maintain HAS_NO_VALUE_PROPERTIES.
mapping = obj.__dict__
assert len(mapping) == 0
for _ in range(3):
obj.value = 1
assert len(mapping) == 1
obj.value = 2
assert len(mapping) == 1
del obj.value
assert len(mapping) == 0
mapping.update(a=1, b=2)
assert len(mapping) == 2
assert mapping.pop("a") == 1
assert len(mapping) == 1
mapping.clear()
assert len(mapping) == 0
mapping = obj.__dict__


def test_type_dict_length_and_dir_after_mutations():
for size in (3, 1000):
for delete_before_dict in (False, True):
cls = type("ManyAttributes", (), {f"attr_{i}": i for i in range(size)})
if delete_before_dict:
del cls.attr_0
mapping = cls.__dict__
expected = set(mapping)
for _ in range(3):
assert len(mapping) == len(expected)
assert set(dir(cls)) == expected | set(dir(object))
cls.attr_0 = 42
expected.add("attr_0")
assert len(mapping) == len(expected)
assert "attr_0" in dir(cls)
del cls.attr_0
expected.remove("attr_0")
assert len(mapping) == len(expected)
assert "attr_0" not in dir(cls)
copied = mapping.copy()
assert len(copied) == len(expected)
assert set(copied) == expected


@graalpy_only
def test_dynamic_storage_cached_length_mutations():
import __graalpython__

mapping = __graalpython__.set_storage_strategy({}, "dynamicobject")
assert len(mapping) == 0
for _ in range(3):
mapping["a"] = 1
assert len(mapping) == 1
mapping["a"] = 2
assert len(mapping) == 1
del mapping["a"]
assert len(mapping) == 0
mapping.setdefault("a", 3)
assert len(mapping) == 1
mapping.update(b=4)
assert len(mapping) == 2
assert mapping.pop("a") == 3
assert len(mapping) == 1
copied = mapping.copy()
assert len(copied) == 1
copied["c"] = 5
assert len(copied) == 2
assert len(mapping) == 1
mapping.clear()
assert len(mapping) == 0


def assert_raises(err, fn, *args, **kwargs):
raised = False
try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@
import com.oracle.graal.python.nodes.BuiltinNames;
import com.oracle.graal.python.nodes.PConstructAndRaiseNode;
import com.oracle.graal.python.nodes.attributes.ReadAttributeFromPythonObjectNode;
import com.oracle.graal.python.nodes.attributes.WriteAttributeToPythonObjectNode;
import com.oracle.graal.python.nodes.attributes.WriteAttributeToObjectNode;
import com.oracle.graal.python.nodes.call.CallDispatchers;
import com.oracle.graal.python.nodes.object.GetForeignObjectClassNode;
import com.oracle.graal.python.nodes.statement.AbstractImportNode;
Expand Down Expand Up @@ -974,7 +974,7 @@ private void initializeImportlib() {
boolean useFrozenModules = bootstrap != null;

PyObjectCallMethodObjArgs callNode = PyObjectCallMethodObjArgs.getUncached();
WriteAttributeToPythonObjectNode writeNode = WriteAttributeToPythonObjectNode.getUncached();
WriteAttributeToObjectNode writeNode = WriteAttributeToObjectNode.getUncached();
ReadAttributeFromPythonObjectNode readNode = ReadAttributeFromPythonObjectNode.getUncached();
PyDictSetItem setItem = PyDictSetItem.getUncached();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
import com.oracle.graal.python.nodes.ErrorMessages;
import com.oracle.graal.python.nodes.PConstructAndRaiseNode;
import com.oracle.graal.python.nodes.PRaiseNode;
import com.oracle.graal.python.nodes.attributes.WriteAttributeToPythonObjectNode;
import com.oracle.graal.python.nodes.attributes.WriteAttributeToObjectNode;
import com.oracle.graal.python.nodes.call.CallDispatchers;
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
import com.oracle.graal.python.nodes.function.PythonBuiltinNode;
Expand Down Expand Up @@ -627,13 +627,13 @@ public static PythonModule importFrozenModuleObject(Node inliningTarget, PConstr

if (info.isPackage) {
/* Set __path__ to the empty list */
WriteAttributeToPythonObjectNode.getUncached().execute(module, T___PATH__, PFactory.createList(core.getLanguage()));
WriteAttributeToObjectNode.getUncached().execute(module, T___PATH__, PFactory.createList(core.getLanguage()));
}

CallDispatchers.SimpleIndirectInvokeNode.executeUncached(code.getRootCallTarget(), PArguments.withGlobals(code, module));

Object origName = info.origName == null ? PNone.NONE : info.origName;
WriteAttributeToPythonObjectNode.getUncached().execute(module, T___ORIGNAME__, origName);
WriteAttributeToObjectNode.getUncached().execute(module, T___ORIGNAME__, origName);

return module;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@
import com.oracle.graal.python.builtins.objects.module.PythonModule;
import com.oracle.graal.python.builtins.objects.object.PythonObject;
import com.oracle.graal.python.builtins.objects.tuple.PTuple;
import com.oracle.graal.python.builtins.objects.type.PythonBuiltinClass;
import com.oracle.graal.python.builtins.objects.type.PythonManagedClass;
import com.oracle.graal.python.builtins.objects.type.TpSlots;
import com.oracle.graal.python.builtins.objects.type.TypeNodes;
Expand All @@ -156,8 +155,6 @@
import com.oracle.graal.python.nodes.PRaiseNode;
import com.oracle.graal.python.nodes.argument.keywords.ExpandKeywordStarargsNode;
import com.oracle.graal.python.nodes.argument.positional.ExecutePositionalStarargsNode;
import com.oracle.graal.python.nodes.attributes.WriteAttributeToObjectNode;
import com.oracle.graal.python.nodes.attributes.WriteAttributeToPythonObjectNode;
import com.oracle.graal.python.nodes.classes.IsSubtypeNode;
import com.oracle.graal.python.nodes.frame.GetCurrentFrameRef;
import com.oracle.graal.python.nodes.object.GetClassNode;
Expand Down Expand Up @@ -185,7 +182,6 @@
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Cached.Exclusive;
import com.oracle.truffle.api.dsl.GenerateCached;
import com.oracle.truffle.api.dsl.GenerateInline;
import com.oracle.truffle.api.dsl.GenerateUncached;
Expand Down Expand Up @@ -858,31 +854,6 @@ static long GraalPyPrivate_Type(long typeNamePtr) {
throw PRaiseNode.raiseStatic(null, PythonErrorType.KeyError, ErrorMessages.APOSTROPHE_S, typeName);
}

@GenerateInline
@GenerateCached(false)
abstract static class PyObjectSetAttrNode extends PNodeWithContext {

abstract void execute(Node inliningTarget, Object object, TruffleString key, Object value);

@Specialization
static void doBuiltinClass(PythonBuiltinClass object, TruffleString key, Object value,
@Exclusive @Cached WriteAttributeToObjectNode writeAttrNode) {
writeAttrNode.execute(object, key, value);
}

@Specialization
static void doNativeClass(PythonNativeClass object, TruffleString key, Object value,
@Exclusive @Cached WriteAttributeToObjectNode writeAttrNode) {
writeAttrNode.execute(object, key, value);
}

@Specialization(guards = {"!isPythonBuiltinClass(object)"})
static void doObject(PythonObject object, TruffleString key, Object value,
@Exclusive @Cached WriteAttributeToPythonObjectNode writeAttrToPythonObjectNode) {
writeAttrToPythonObjectNode.execute(object, key, value);
}
}

@CApiBuiltin(ret = Void, args = {PyTypeObjectRawPointer}, call = Ignored)
@TruffleBoundary
static void GraalPyPrivate_AddInheritedSlots(long pythonClassPtr) {
Expand Down
Loading
Loading