Skip to content

Commit 41da134

Browse files
committed
Remove LazyPythonClass
* Use static PythonAbstractClass.isInstance * Clean up PythonAbstractClass specializations
1 parent 2098999 commit 41da134

22 files changed

+141
-180
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import com.oracle.graal.python.PythonLanguage;
3232
import com.oracle.graal.python.builtins.objects.function.PArguments.ThreadState;
3333
import com.oracle.graal.python.builtins.objects.object.PythonObjectLibrary;
34-
import com.oracle.graal.python.builtins.objects.type.LazyPythonClass;
3534
import com.oracle.graal.python.builtins.objects.type.PythonBuiltinClass;
3635
import com.oracle.graal.python.nodes.BuiltinNames;
3736
import com.oracle.graal.python.nodes.classes.IsSubtypeNode;
@@ -44,6 +43,7 @@
4443
import com.oracle.truffle.api.dsl.Specialization;
4544
import com.oracle.truffle.api.interop.ArityException;
4645
import com.oracle.truffle.api.interop.InteropLibrary;
46+
import com.oracle.truffle.api.interop.TruffleObject;
4747
import com.oracle.truffle.api.interop.UnknownIdentifierException;
4848
import com.oracle.truffle.api.interop.UnsupportedMessageException;
4949
import com.oracle.truffle.api.interop.UnsupportedTypeException;
@@ -54,7 +54,7 @@
5454

5555
@ExportLibrary(InteropLibrary.class)
5656
@ExportLibrary(PythonObjectLibrary.class)
57-
public enum PythonBuiltinClassType implements LazyPythonClass {
57+
public enum PythonBuiltinClassType implements TruffleObject {
5858

5959
ForeignObject(BuiltinNames.FOREIGN),
6060
Boolean("bool", BuiltinNames.BUILTINS, false),

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinConstructors.java

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1705,7 +1705,7 @@ Object doBuiltinTypeType(PythonBuiltinClassType self, Object[] varargs, PKeyword
17051705
Object doNativeObjectIndirect(PythonManagedClass self, Object[] varargs, PKeyword[] kwargs,
17061706
@Cached("create()") GetMroNode getMroNode) {
17071707
checkExcessArgs(self, varargs, kwargs);
1708-
PythonNativeClass nativeBaseClass = findFirstNativeBaseClass(getMroNode.execute(self));
1708+
Object nativeBaseClass = findFirstNativeBaseClass(getMroNode.execute(self));
17091709
return callNativeGenericNewNode(nativeBaseClass, varargs, kwargs);
17101710
}
17111711

@@ -1721,10 +1721,10 @@ Object fallback(Object o, Object[] varargs, PKeyword[] kwargs) {
17211721
throw raise(TypeError, ErrorMessages.IS_NOT_TYPE_OBJ, "object.__new__(X): X", o);
17221722
}
17231723

1724-
private static PythonNativeClass findFirstNativeBaseClass(PythonAbstractClass[] methodResolutionOrder) {
1725-
for (PythonAbstractClass cls : methodResolutionOrder) {
1724+
private static Object findFirstNativeBaseClass(PythonAbstractClass[] methodResolutionOrder) {
1725+
for (Object cls : methodResolutionOrder) {
17261726
if (PGuards.isNativeClass(cls)) {
1727-
return PythonNativeClass.cast(cls);
1727+
return cls;
17281728
}
17291729
}
17301730
CompilerDirectives.transferToInterpreterAndInvalidate();
@@ -2312,7 +2312,6 @@ private String getModuleNameFromGlobals(PythonObject globals, HashingStorageLibr
23122312
}
23132313
}
23142314

2315-
@SuppressWarnings("try")
23162315
private PythonClass typeMetaclass(VirtualFrame frame, String name, PTuple bases, PDict namespace, Object metaclass, HashingStorageLibrary nslib) {
23172316

23182317
Object[] array = ensureGetObjectArrayNode().execute(bases);
@@ -2325,7 +2324,7 @@ private PythonClass typeMetaclass(VirtualFrame frame, String name, PTuple bases,
23252324
basesArray = new PythonAbstractClass[array.length];
23262325
for (int i = 0; i < array.length; i++) {
23272326
// TODO: deal with non-class bases
2328-
if (!(array[i] instanceof PythonAbstractClass)) {
2327+
if (!PGuards.isPythonClass(array[i])) {
23292328
throw raise(NotImplementedError, "creating a class with non-class bases");
23302329
} else {
23312330
basesArray[i] = (PythonAbstractClass) array[i];
@@ -3175,24 +3174,24 @@ private void denyInstantiationAfterInitialization() {
31753174
}
31763175
}
31773176

3178-
@Specialization(guards = {"!isNoValue(get)", "!isNoValue(set)"})
3177+
@Specialization(guards = {"isPythonClass(owner)", "!isNoValue(get)", "!isNoValue(set)"})
31793178
@TruffleBoundary
3180-
Object call(@SuppressWarnings("unused") Object getSetClass, Object get, Object set, String name, PythonAbstractClass owner) {
3179+
Object call(@SuppressWarnings("unused") Object getSetClass, Object get, Object set, String name, Object owner) {
31813180
denyInstantiationAfterInitialization();
31823181
return factory().createGetSetDescriptor(get, set, name, owner);
31833182
}
31843183

3185-
@Specialization(guards = {"!isNoValue(get)", "isNoValue(set)"})
3184+
@Specialization(guards = {"isPythonClass(owner)", "!isNoValue(get)", "isNoValue(set)"})
31863185
@TruffleBoundary
3187-
Object call(@SuppressWarnings("unused") Object getSetClass, Object get, @SuppressWarnings("unused") PNone set, String name, PythonAbstractClass owner) {
3186+
Object call(@SuppressWarnings("unused") Object getSetClass, Object get, @SuppressWarnings("unused") PNone set, String name, Object owner) {
31883187
denyInstantiationAfterInitialization();
31893188
return factory().createGetSetDescriptor(get, null, name, owner);
31903189
}
31913190

3192-
@Specialization(guards = {"isNoValue(get)", "isNoValue(set)"})
3191+
@Specialization(guards = {"isPythonClass(owner)", "isNoValue(get)", "isNoValue(set)"})
31933192
@TruffleBoundary
31943193
@SuppressWarnings("unused")
3195-
Object call(Object getSetClass, PNone get, PNone set, String name, PythonAbstractClass owner) {
3194+
Object call(Object getSetClass, PNone get, PNone set, String name, Object owner) {
31963195
denyInstantiationAfterInitialization();
31973196
return factory().createGetSetDescriptor(null, null, name, owner);
31983197
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinFunctions.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@
107107
import com.oracle.graal.python.builtins.objects.set.PFrozenSet;
108108
import com.oracle.graal.python.builtins.objects.str.PString;
109109
import com.oracle.graal.python.builtins.objects.tuple.PTuple;
110-
import com.oracle.graal.python.builtins.objects.type.PythonAbstractClass;
111110
import com.oracle.graal.python.builtins.objects.type.PythonBuiltinClass;
112111
import com.oracle.graal.python.builtins.objects.type.TypeBuiltins;
113112
import com.oracle.graal.python.builtins.objects.type.TypeNodes;
@@ -1142,8 +1141,8 @@ private boolean isInstanceCheckInternal(VirtualFrame frame, Object instance, Obj
11421141

11431142
public abstract boolean executeWith(VirtualFrame frame, Object instance, Object cls);
11441143

1145-
@Specialization
1146-
boolean isInstance(VirtualFrame frame, Object instance, PythonAbstractClass cls,
1144+
@Specialization(guards = "isPythonClass(cls)")
1145+
boolean isInstance(VirtualFrame frame, Object instance, Object cls,
11471146
@Cached TypeNodes.IsSameTypeNode isSameTypeNode,
11481147
@Cached IsSubtypeNode isSubtypeNode) {
11491148
Object instanceClass = getClassNode.execute(instance);

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/PythonCextBuiltins.java

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,6 @@
126126
import com.oracle.graal.python.builtins.objects.cext.CExtNodes.ToNewRefNode;
127127
import com.oracle.graal.python.builtins.objects.cext.CExtNodes.TransformExceptionToNativeNode;
128128
import com.oracle.graal.python.builtins.objects.cext.CExtNodes.VoidPtrToJavaNode;
129-
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.CastToNativeLongNodeGen;
130-
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.MayRaiseBinaryNodeGen;
131-
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.MayRaiseTernaryNodeGen;
132-
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.MayRaiseUnaryNodeGen;
133-
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.PRaiseNativeNodeGen;
134-
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.ToJavaNodeGen;
135-
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.TransformExceptionToNativeNodeGen;
136129
import com.oracle.graal.python.builtins.objects.cext.DynamicObjectNativeWrapper;
137130
import com.oracle.graal.python.builtins.objects.cext.DynamicObjectNativeWrapper.PrimitiveNativeWrapper;
138131
import com.oracle.graal.python.builtins.objects.cext.HandleCache;
@@ -145,11 +138,17 @@
145138
import com.oracle.graal.python.builtins.objects.cext.PythonClassNativeWrapper;
146139
import com.oracle.graal.python.builtins.objects.cext.PythonNativeClass;
147140
import com.oracle.graal.python.builtins.objects.cext.PythonNativeNull;
148-
import com.oracle.graal.python.builtins.objects.cext.PythonNativeObject;
149141
import com.oracle.graal.python.builtins.objects.cext.PythonNativeVoidPtr;
150142
import com.oracle.graal.python.builtins.objects.cext.PythonNativeWrapper;
151143
import com.oracle.graal.python.builtins.objects.cext.PythonNativeWrapperLibrary;
152144
import com.oracle.graal.python.builtins.objects.cext.UnicodeObjectNodes.UnicodeAsWideCharNode;
145+
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.CastToNativeLongNodeGen;
146+
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.MayRaiseBinaryNodeGen;
147+
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.MayRaiseTernaryNodeGen;
148+
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.MayRaiseUnaryNodeGen;
149+
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.PRaiseNativeNodeGen;
150+
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.ToJavaNodeGen;
151+
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.TransformExceptionToNativeNodeGen;
153152
import com.oracle.graal.python.builtins.objects.cext.capi.CApiContext;
154153
import com.oracle.graal.python.builtins.objects.cext.capi.CApiContext.AllocInfo;
155154
import com.oracle.graal.python.builtins.objects.cext.capi.NativeReferenceCache;
@@ -161,11 +160,11 @@
161160
import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.EncodeNativeStringNode;
162161
import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.PCallCExtFunction;
163162
import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.UnicodeFromWcharNode;
164-
import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodesFactory.ConvertPIntToPrimitiveNodeGen;
165163
import com.oracle.graal.python.builtins.objects.cext.common.CExtContext;
166164
import com.oracle.graal.python.builtins.objects.cext.common.CExtParseArgumentsNode;
167165
import com.oracle.graal.python.builtins.objects.cext.common.CExtParseArgumentsNode.SplitFormatStringNode;
168166
import com.oracle.graal.python.builtins.objects.cext.common.VaListWrapper;
167+
import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodesFactory.ConvertPIntToPrimitiveNodeGen;
169168
import com.oracle.graal.python.builtins.objects.code.PCode;
170169
import com.oracle.graal.python.builtins.objects.common.HashingCollectionNodes;
171170
import com.oracle.graal.python.builtins.objects.common.IndexNodes.NormalizeIndexNode;
@@ -2732,8 +2731,8 @@ int add(VirtualFrame frame, Object self, @SuppressWarnings("unused") Object o,
27322731
@TypeSystemReference(PythonTypes.class)
27332732
public abstract static class PyTruffle_Compute_Mro extends PythonBinaryBuiltinNode {
27342733

2735-
@Specialization
2736-
Object doIt(PythonNativeObject self, String className) {
2734+
@Specialization(guards = "isNativeObject(self)")
2735+
Object doIt(Object self, String className) {
27372736
PythonAbstractClass[] doSlowPath = TypeNodes.ComputeMroNode.doSlowPath(PythonNativeClass.cast(self));
27382737
return factory().createTuple(new MroSequenceStorage(className, doSlowPath));
27392738
}
@@ -2744,19 +2743,19 @@ Object doIt(PythonNativeObject self, String className) {
27442743
@TypeSystemReference(PythonTypes.class)
27452744
public abstract static class PyTruffle_Type_Modified extends PythonTernaryBuiltinNode {
27462745

2747-
@Specialization(guards = "isNoValue(mroTuple)")
2748-
Object doIt(PythonNativeClass clazz, String name, @SuppressWarnings("unused") PNone mroTuple) {
2749-
CyclicAssumption nativeClassStableAssumption = getContext().getNativeClassStableAssumption(clazz, false);
2746+
@Specialization(guards = {"isNativeClass(clazz)", "isNoValue(mroTuple)"})
2747+
Object doIt(Object clazz, String name, @SuppressWarnings("unused") PNone mroTuple) {
2748+
CyclicAssumption nativeClassStableAssumption = getContext().getNativeClassStableAssumption((PythonNativeClass) clazz, false);
27502749
if (nativeClassStableAssumption != null) {
27512750
nativeClassStableAssumption.invalidate("PyType_Modified(\"" + name + "\") (without MRO) called");
27522751
}
27532752
return PNone.NONE;
27542753
}
27552754

2756-
@Specialization
2757-
Object doIt(PythonNativeClass clazz, String name, PTuple mroTuple,
2755+
@Specialization(guards = "isNativeClass(clazz)")
2756+
Object doIt(Object clazz, String name, PTuple mroTuple,
27582757
@Cached("createClassProfile()") ValueProfile profile) {
2759-
CyclicAssumption nativeClassStableAssumption = getContext().getNativeClassStableAssumption(clazz, false);
2758+
CyclicAssumption nativeClassStableAssumption = getContext().getNativeClassStableAssumption((PythonNativeClass) clazz, false);
27602759
if (nativeClassStableAssumption != null) {
27612760
nativeClassStableAssumption.invalidate("PyType_Modified(\"" + name + "\") called");
27622761
}

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
import com.oracle.graal.python.builtins.modules.PythonCextBuiltins;
6060
import com.oracle.graal.python.builtins.objects.PNone;
6161
import com.oracle.graal.python.builtins.objects.PythonAbstractObject;
62+
import com.oracle.graal.python.builtins.objects.cext.DynamicObjectNativeWrapper.PrimitiveNativeWrapper;
63+
import com.oracle.graal.python.builtins.objects.cext.DynamicObjectNativeWrapper.PythonObjectNativeWrapper;
6264
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.AllToJavaNodeGen;
6365
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.AllToSulongNodeGen;
6466
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.AsPythonObjectNodeGen;
@@ -75,8 +77,6 @@
7577
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.TernaryFirstThirdToSulongNodeGen;
7678
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.TransformExceptionToNativeNodeGen;
7779
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.WrapVoidPtrNodeGen;
78-
import com.oracle.graal.python.builtins.objects.cext.DynamicObjectNativeWrapper.PrimitiveNativeWrapper;
79-
import com.oracle.graal.python.builtins.objects.cext.DynamicObjectNativeWrapper.PythonObjectNativeWrapper;
8080
import com.oracle.graal.python.builtins.objects.cext.capi.CApiContext;
8181
import com.oracle.graal.python.builtins.objects.cext.capi.NativeReferenceCache.ResolveNativeReferenceNode;
8282
import com.oracle.graal.python.builtins.objects.cext.capi.PyTruffleObjectFree.FreeNode;
@@ -2466,10 +2466,12 @@ public MayRaiseNodeFactory(T node) {
24662466
this.nodeClass = determineNodeClass(node);
24672467
}
24682468

2469+
@Override
24692470
public T createNode(Object... arguments) {
24702471
return NodeUtil.cloneNode(node);
24712472
}
24722473

2474+
@Override
24732475
public Class<T> getNodeClass() {
24742476
return nodeClass;
24752477
}
@@ -2486,10 +2488,12 @@ private static <T> Class<T> determineNodeClass(T node) {
24862488
return nodeClass;
24872489
}
24882490

2491+
@Override
24892492
public List<List<Class<?>>> getNodeSignatures() {
24902493
throw new IllegalAccessError();
24912494
}
24922495

2496+
@Override
24932497
public List<Class<? extends Node>> getExecutionSignature() {
24942498
throw new IllegalAccessError();
24952499
}
@@ -2831,10 +2835,10 @@ static Object getNativeNullWithoutModule(@SuppressWarnings("unused") Object modu
28312835
@GenerateUncached
28322836
public abstract static class LookupNativeMemberInMRONode extends Node {
28332837

2834-
public abstract Object execute(PythonAbstractClass cls, NativeMember nativeMemberName, Object managedMemberName);
2838+
public abstract Object execute(Object cls, NativeMember nativeMemberName, Object managedMemberName);
28352839

28362840
@Specialization
2837-
static Object doSingleContext(PythonAbstractClass cls, NativeMember nativeMemberName, Object managedMemberName,
2841+
static Object doSingleContext(Object cls, NativeMember nativeMemberName, Object managedMemberName,
28382842
@Cached GetMroStorageNode getMroNode,
28392843
@Cached SequenceStorageNodes.LenNode lenNode,
28402844
@Cached SequenceStorageNodes.GetItemDynamicNode getItemNode,

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

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -347,11 +347,11 @@ static Object doTpBase(PythonManagedClass object, @SuppressWarnings("unused") Py
347347
return toSulongNode.execute(getNativeNullNode.execute());
348348
}
349349

350-
private static PythonAbstractClass ensureClassObject(PythonContext context, Object klass) {
350+
private static Object ensureClassObject(PythonContext context, Object klass) {
351351
if (klass instanceof PythonBuiltinClassType) {
352352
return context.getCore().lookupType((PythonBuiltinClassType) klass);
353353
}
354-
return (PythonAbstractClass) klass;
354+
return klass;
355355
}
356356

357357
@Specialization(guards = "eq(TP_ALLOC, key)")
@@ -1012,8 +1012,9 @@ static long doTpFlags(PythonManagedClass object, @SuppressWarnings("unused") Pyt
10121012
return flags;
10131013
}
10141014

1015-
@Specialization(guards = "eq(TP_BASICSIZE, key)")
1016-
static long doTpBasicsize(PythonAbstractClass object, @SuppressWarnings("unused") PythonNativeWrapper nativeWrapper, @SuppressWarnings("unused") String key, long basicsize,
1015+
@Specialization(guards = {"isPythonClass(object)", "eq(TP_BASICSIZE, key)"})
1016+
1017+
static long doTpBasicsize(Object object, @SuppressWarnings("unused") PythonNativeWrapper nativeWrapper, @SuppressWarnings("unused") String key, long basicsize,
10171018
@Cached WriteAttributeToObjectNode writeAttrNode,
10181019
@Cached IsBuiltinClassProfile profile) {
10191020
if (profile.profileClass(object, PythonBuiltinClassType.PythonClass)) {
@@ -1024,8 +1025,8 @@ static long doTpBasicsize(PythonAbstractClass object, @SuppressWarnings("unused"
10241025
return basicsize;
10251026
}
10261027

1027-
@Specialization(guards = "eq(TP_ALLOC, key)")
1028-
static Object doTpAlloc(PythonAbstractClass object, @SuppressWarnings("unused") PythonNativeWrapper nativeWrapper, @SuppressWarnings("unused") String key, Object allocFunc,
1028+
@Specialization(guards = {"isPythonClass(object)", "eq(TP_ALLOC, key)"})
1029+
static Object doTpAlloc(Object object, @SuppressWarnings("unused") PythonNativeWrapper nativeWrapper, @SuppressWarnings("unused") String key, Object allocFunc,
10291030
@Cached WriteAttributeToObjectNode writeAttrNode,
10301031
@Cached CExtNodes.AsPythonObjectNode asPythonObjectNode) {
10311032
writeAttrNode.execute(object, TypeBuiltins.TYPE_ALLOC, asPythonObjectNode.execute(allocFunc));
@@ -1045,16 +1046,16 @@ private SubclassAddState(HashingStorage storage, HashingStorageLibrary lib, Set<
10451046
}
10461047
}
10471048

1048-
@Specialization(guards = "eq(TP_DEALLOC, key)")
1049-
static Object doTpDelloc(PythonAbstractClass object, @SuppressWarnings("unused") PythonNativeWrapper nativeWrapper, @SuppressWarnings("unused") String key, Object deallocFunc,
1049+
@Specialization(guards = {"isPythonClass(object)", "eq(TP_DEALLOC, key)"})
1050+
static Object doTpDelloc(Object object, @SuppressWarnings("unused") PythonNativeWrapper nativeWrapper, @SuppressWarnings("unused") String key, Object deallocFunc,
10501051
@Cached WriteAttributeToObjectNode writeAttrNode,
10511052
@Cached CExtNodes.AsPythonObjectNode asPythonObjectNode) {
10521053
writeAttrNode.execute(object, TypeBuiltins.TYPE_DEALLOC, asPythonObjectNode.execute(deallocFunc));
10531054
return deallocFunc;
10541055
}
10551056

1056-
@Specialization(guards = "eq(TP_FREE, key)")
1057-
static Object doTpFree(PythonAbstractClass object, @SuppressWarnings("unused") PythonNativeWrapper nativeWrapper, @SuppressWarnings("unused") String key, Object freeFunc,
1057+
@Specialization(guards = {"isPythonClass(object)", "eq(TP_FREE, key)"})
1058+
static Object doTpFree(Object object, @SuppressWarnings("unused") PythonNativeWrapper nativeWrapper, @SuppressWarnings("unused") String key, Object freeFunc,
10581059
@Cached WriteAttributeToObjectNode writeAttrNode,
10591060
@Cached CExtNodes.AsPythonObjectNode asPythonObjectNode) {
10601061
writeAttrNode.execute(object, TypeBuiltins.TYPE_FREE, asPythonObjectNode.execute(freeFunc));

0 commit comments

Comments
 (0)