From b21ea7ab56b301e2129637f7f21bb1436a523d65 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Fri, 28 Aug 2026 09:14:54 +0200 Subject: [PATCH 01/10] Remove AsNativePrimitiveNode --- .../modules/cext/PythonCextLongBuiltins.java | 109 +++-- .../cext/capi/CApiMemberAccessNodes.java | 36 +- .../cext/capi/ExternalFunctionNodes.java | 20 +- .../objects/cext/common/CExtCommonNodes.java | 374 ------------------ 4 files changed, 106 insertions(+), 433 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java index 8d2b01e021..f3ce0bb630 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java @@ -78,9 +78,10 @@ import com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonInternalNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; -import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.ConvertPIntToPrimitiveNode; +import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonInternalNode; +import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; +import com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.TransformPExceptionToNativeCachedNode; -import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodesFactory.ConvertPIntToPrimitiveNodeGen; import com.oracle.graal.python.builtins.objects.ints.IntBuiltins; import com.oracle.graal.python.builtins.objects.ints.IntNodes; import com.oracle.graal.python.builtins.objects.ints.PInt; @@ -93,6 +94,11 @@ import com.oracle.graal.python.nodes.classes.IsSubtypeNode; import com.oracle.graal.python.nodes.object.GetClassNode; import com.oracle.graal.python.nodes.util.CastToJavaBigIntegerNode; +import com.oracle.graal.python.lib.PyLongCheckNode; +import com.oracle.graal.python.lib.PyNumberIndexNode; +import com.oracle.graal.python.runtime.nativeaccess.NativeMemory; +import com.oracle.graal.python.nodes.ErrorMessages; +import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.runtime.exception.PException; import com.oracle.graal.python.runtime.nativeaccess.NativeMemory; import com.oracle.graal.python.runtime.object.PFactory; @@ -153,26 +159,19 @@ abstract static class GraalPyPrivate_Long_AsPrimitive extends CApiTernaryBuiltin @Specialization static Object doGeneric(Object object, int mode, long targetTypeSize, @Bind Node inliningTarget, - @Cached IsSubtypeNode isSubtypeNode, - @Cached GetClassNode getClassNode, - @Cached ConvertPIntToPrimitiveNode convertPIntToPrimitiveNode, - @Cached CastToNativeLongNode castToNativeLongNode, + @Cached PyLongCheckNode longCheckNode, + @Cached PyNumberIndexNode indexNode, @Cached PRaiseNode raiseNode) { - try { - /* - * The 'mode' parameter is usually a constant since this function is primarily used - * in 'PyLong_As*' API functions that pass a fixed mode. So, there is not need to - * profile the value and even if it is not constant, it is profiled implicitly. - */ - if (requiredPInt(mode) && !isSubtypeNode.execute(getClassNode.execute(inliningTarget, object), PythonBuiltinClassType.PInt)) { - throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.INTEGER_REQUIRED); - } - // the 'ConvertPIntToPrimitiveNode' uses 'AsNativePrimitive' which does coercion - Object coerced = convertPIntToPrimitiveNode.execute(inliningTarget, object, signed(mode), PInt.intValueExact(targetTypeSize), exact(mode)); - return castToNativeLongNode.execute(inliningTarget, coerced); - } catch (OverflowException e) { - throw CompilerDirectives.shouldNotReachHere(); + /* + * The 'mode' parameter is usually a constant since this function is primarily used + * in 'PyLong_As*' API functions that pass a fixed mode. So, there is no need to + * profile the value and even if it is not constant, it is profiled implicitly. + */ + if (requiredPInt(mode) && !longCheckNode.execute(inliningTarget, object)) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.INTEGER_REQUIRED); } + Object index = indexNode.execute(null, inliningTarget, object); + return convertBuiltinInteger(inliningTarget, index, signed(mode), (int) targetTypeSize, exact(mode), raiseNode); } private static int signed(int mode) { @@ -186,6 +185,62 @@ private static boolean requiredPInt(int mode) { private static boolean exact(int mode) { return (mode & 0x4) == 0; } + + private static long convertBuiltinInteger(Node inliningTarget, Object object, int signed, int targetTypeSize, boolean exact, PRaiseNode raiseNode) { + if (targetTypeSize != Integer.BYTES && targetTypeSize != Long.BYTES) { + throw raiseNode.raise(inliningTarget, PythonBuiltinClassType.SystemError, ErrorMessages.UNSUPPORTED_TARGET_SIZE, targetTypeSize); + } + if (object instanceof Integer value) { + return convertLong(inliningTarget, value, signed, targetTypeSize, exact, raiseNode); + } else if (object instanceof Long value) { + return convertLong(inliningTarget, value, signed, targetTypeSize, exact, raiseNode); + } else if (object instanceof PInt value) { + if (!exact) { + return targetTypeSize == Integer.BYTES ? value.intValue() : value.longValue(); + } + if (signed == 0 && value.isNegative()) { + throw raiseNegativeValue(inliningTarget, raiseNode); + } + try { + if (targetTypeSize == Integer.BYTES) { + if (signed != 0) { + return value.intValueExact(); + } else if (value.bitLength() <= Integer.SIZE) { + return value.intValue(); + } + } else if (signed != 0) { + return value.longValueExact(); + } else if (value.bitLength() <= Long.SIZE) { + return value.longValue(); + } + } catch (OverflowException e) { + // fall through + } + throw raiseOverflow(inliningTarget, raiseNode, targetTypeSize); + } + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.INDEX_RETURNED_NON_INT, object); + } + + private static long convertLong(Node inliningTarget, long value, int signed, int targetTypeSize, boolean exact, PRaiseNode raiseNode) { + if (!exact) { + return targetTypeSize == Integer.BYTES ? (int) value : value; + } + if (signed == 0 && value < 0) { + throw raiseNegativeValue(inliningTarget, raiseNode); + } + if (targetTypeSize == Integer.BYTES && (signed != 0 ? (int) value != value : Integer.toUnsignedLong((int) value) != value)) { + throw raiseOverflow(inliningTarget, raiseNode, targetTypeSize); + } + return value; + } + + private static PException raiseNegativeValue(Node inliningTarget, PRaiseNode raiseNode) { + throw raiseNode.raise(inliningTarget, OverflowError, ErrorMessages.CANNOT_CONVERT_NEGATIVE_VALUE_TO_UNSIGNED_INT); + } + + private static PException raiseOverflow(Node inliningTarget, PRaiseNode raiseNode, int targetTypeSize) { + throw raiseNode.raise(inliningTarget, OverflowError, ErrorMessages.PYTHON_INT_TOO_LARGE_TO_CONV_TO_C_TYPE, targetTypeSize); + } } @CApiBuiltin(ret = PyObjectTransfer, args = {LONG_LONG}, call = Ignored) @@ -219,7 +274,6 @@ static long GraalPyPrivate_Long_NumBits(long objPtr) { @CApiBuiltin(ret = Pointer, args = {PyObject}, call = Ignored) abstract static class GraalPyPrivate_Long_AsVoidPtr extends CApiUnaryBuiltinNode { - @Child private ConvertPIntToPrimitiveNode asPrimitiveNode; @Child private TransformPExceptionToNativeCachedNode transformExceptionToNativeNode; @Specialization @@ -252,18 +306,11 @@ long doPointer(PInt n, @Fallback long doGeneric(Object n, @Bind Node inliningTarget, + @Cached PyNumberIndexNode indexNode, @Exclusive @Cached PRaiseNode raiseNode) { - if (asPrimitiveNode == null) { - CompilerDirectives.transferToInterpreterAndInvalidate(); - asPrimitiveNode = insert(ConvertPIntToPrimitiveNodeGen.create()); - } try { - try { - return asPrimitiveNode.executeLongCached(n, 0, Long.BYTES); - } catch (UnexpectedResultException e) { - transformOverflow(inliningTarget, raiseNode); - return 0; - } + Object index = indexNode.execute(null, inliningTarget, n); + return GraalPyPrivate_Long_AsPrimitive.convertBuiltinInteger(inliningTarget, index, 0, Long.BYTES, true, raiseNode); } catch (PException e) { ensureTransformExcNode().execute(e); return 0; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiMemberAccessNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiMemberAccessNodes.java index 2ffd7de03f..759d36510c 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiMemberAccessNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiMemberAccessNodes.java @@ -68,7 +68,6 @@ import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeNode; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.AsNativeCharNode; -import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.AsNativePrimitiveNode; import com.oracle.graal.python.builtins.objects.cext.common.CExtToJavaNode; import com.oracle.graal.python.builtins.objects.cext.structs.CConstants; import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess; @@ -78,8 +77,11 @@ import com.oracle.graal.python.builtins.objects.ints.PInt; import com.oracle.graal.python.builtins.objects.type.TypeNodes.IsSameTypeNode; import com.oracle.graal.python.lib.PyFloatAsDoubleNode; +import com.oracle.graal.python.lib.PyLongAsLongNode; +import com.oracle.graal.python.lib.PyNumberIndexNode; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.nodes.util.CastToJavaUnsignedLongNode; import com.oracle.graal.python.nodes.function.BuiltinFunctionRootNode; import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; @@ -363,8 +365,9 @@ abstract static class WriteByteNode extends WriteTypeNode { @Specialization static void write(long pointer, Object newValue, - @Cached AsNativePrimitiveNode asLong) { - NativeMemory.writeByte(pointer, (byte) asLong.toInt64(newValue, true)); + @Bind Node inliningTarget, + @Cached PyLongAsLongNode asLong) { + NativeMemory.writeByte(pointer, (byte) asLong.execute(null, inliningTarget, newValue)); } } @@ -373,8 +376,9 @@ abstract static class WriteShortNode extends WriteTypeNode { @Specialization static void write(long pointer, Object newValue, - @Cached AsNativePrimitiveNode asLong) { - NativeMemory.writeShort(pointer, (short) asLong.toInt64(newValue, true)); + @Bind Node inliningTarget, + @Cached PyLongAsLongNode asLong) { + NativeMemory.writeShort(pointer, (short) asLong.execute(null, inliningTarget, newValue)); } } @@ -383,8 +387,9 @@ abstract static class WriteIntNode extends WriteTypeNode { @Specialization static void write(long pointer, Object newValue, - @Cached AsNativePrimitiveNode asLong) { - NativeMemory.writeInt(pointer, (int) asLong.toInt64(newValue, true)); + @Bind Node inliningTarget, + @Cached PyLongAsLongNode asLong) { + NativeMemory.writeInt(pointer, (int) asLong.execute(null, inliningTarget, newValue)); } } @@ -394,10 +399,10 @@ abstract static class WriteLongNode extends WriteTypeNode { @Specialization static void write(long pointer, Object newValue, @Bind Node inliningTarget, - @Cached AsNativePrimitiveNode asLong, + @Cached PyLongAsLongNode asLong, @Cached IsBuiltinObjectProfile exceptionProfile) { try { - NativeMemory.writeLong(pointer, asLong.toInt64(newValue, true)); + NativeMemory.writeLong(pointer, asLong.execute(null, inliningTarget, newValue)); } catch (PException e) { /* * Special case: if conversion raises an OverflowError, CPython still assigns the @@ -417,20 +422,22 @@ abstract static class WriteUIntNode extends WriteTypeNode { @Specialization static void write(long pointer, Object newValue, @Bind Node inliningTarget, - @Cached AsNativePrimitiveNode asLong, + @Cached PyNumberIndexNode indexNode, + @Cached CastToJavaUnsignedLongNode asUnsignedLong, + @Cached PyLongAsLongNode asLong, @Cached IsBuiltinObjectProfile exceptionProfile) { /* * This emulates the arguably buggy behavior from CPython where it accepts MIN_LONG to * MAX_ULONG values. */ try { - NativeMemory.writeInt(pointer, (int) asLong.toUInt64(newValue, true)); + NativeMemory.writeInt(pointer, (int) asUnsignedLong.execute(inliningTarget, indexNode.execute(null, inliningTarget, newValue))); } catch (PException e) { /* * Special case: accept signed long as well. */ e.expectOverflowError(inliningTarget, exceptionProfile); - NativeMemory.writeInt(pointer, (int) asLong.toInt64(newValue, true)); + NativeMemory.writeInt(pointer, (int) asLong.execute(null, inliningTarget, newValue)); // swallowing the exception } } @@ -442,10 +449,11 @@ abstract static class WriteULongNode extends WriteTypeNode { @Specialization static void write(long pointer, Object newValue, @Bind Node inliningTarget, - @Cached AsNativePrimitiveNode asLong, + @Cached PyNumberIndexNode indexNode, + @Cached CastToJavaUnsignedLongNode asUnsignedLong, @Cached IsBuiltinObjectProfile exceptionProfile) { try { - NativeMemory.writeLong(pointer, asLong.toUInt64(newValue, true)); + NativeMemory.writeLong(pointer, asUnsignedLong.execute(inliningTarget, indexNode.execute(null, inliningTarget, newValue))); } catch (PException e) { /* * Special case: if conversion raises an OverflowError, CPython still assigns the diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/ExternalFunctionNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/ExternalFunctionNodes.java index 9dbbb0cecf..994b0f3b82 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/ExternalFunctionNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/ExternalFunctionNodes.java @@ -98,6 +98,7 @@ import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.lib.PyLongAsIntNodeGen; import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; import com.oracle.graal.python.builtins.objects.cext.capi.CApiGCSupport.PyObjectGCTrackNode; import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.AsCharPointerNode; @@ -119,11 +120,9 @@ import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.GraalPyUnicodeObjectUtil; -import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.ConvertPIntToPrimitiveNode; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.GetIndexNode; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.ReadAndClearNativeException; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.TransformExceptionFromNativeNode; -import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodesFactory.ConvertPIntToPrimitiveNodeGen; import com.oracle.graal.python.builtins.objects.cext.common.CExtToJavaNode; import com.oracle.graal.python.builtins.objects.cext.common.CExtToNativeNode; import com.oracle.graal.python.builtins.objects.cext.common.NativeCExtSymbol; @@ -186,7 +185,6 @@ import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.api.nodes.UnexpectedResultException; import com.oracle.truffle.api.profiles.BranchProfile; import com.oracle.truffle.api.profiles.ConditionProfile; import com.oracle.truffle.api.profiles.InlinedConditionProfile; @@ -1540,13 +1538,11 @@ public abstract static class RichCmpFuncRootNode extends ObjectWrapperDescriptor private static final Signature SIGNATURE = createSignature(false, -1, tsArray("self", "other", "op"), true, false); @Child private ReadIndexedArgumentNode readOtherNode; @Child private ReadIndexedArgumentNode readOpNode; - @Child private ConvertPIntToPrimitiveNode asSsizeTNode; RichCmpFuncRootNode(PythonLanguage language, TruffleString name, PExternalFunctionWrapper provider) { super(language, name, provider); this.readOtherNode = ReadIndexedArgumentNode.create(1); this.readOpNode = ReadIndexedArgumentNode.create(2); - this.asSsizeTNode = ConvertPIntToPrimitiveNodeGen.create(); } @InvokeExternalFunction(value = ExternalFunctionSignature.RICHCMPFUNC, argConversions = {PythonToNativeNode.class, PythonToNativeNode.class, int.class}) @@ -1554,15 +1550,11 @@ public abstract static class RichCmpFuncRootNode extends ObjectWrapperDescriptor @Override protected Object readArgumentsAndInvokeExternalFunction(VirtualFrame frame, NativeFunctionPointer boundFunction) { - try { - Object self = readSelf(frame); - assert EnsurePythonObjectNode.doesNotNeedPromotion(self); - Object arg1 = ensurePythonObject(readOtherNode.execute(frame)); - Object arg2 = ensurePythonObject(readOpNode.execute(frame)); - return returnNativeObjectToPython(invokeExternalFunction(frame, boundFunction, self, arg1, asSsizeTNode.executeIntCached(arg2, 1, Integer.BYTES))); - } catch (UnexpectedResultException e) { - throw CompilerDirectives.shouldNotReachHere(); - } + Object self = readSelf(frame); + assert EnsurePythonObjectNode.doesNotNeedPromotion(self); + Object arg1 = ensurePythonObject(readOtherNode.execute(frame)); + Object arg2 = ensurePythonObject(readOpNode.execute(frame)); + return returnNativeObjectToPython(invokeExternalFunction(frame, boundFunction, self, arg1, PyLongAsIntNodeGen.getUncached().execute(frame, this, arg2))); } @Override diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtCommonNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtCommonNodes.java index d3689529fd..875c6a92b1 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtCommonNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtCommonNodes.java @@ -40,7 +40,6 @@ */ package com.oracle.graal.python.builtins.objects.cext.common; -import static com.oracle.graal.python.builtins.PythonBuiltinClassType.OverflowError; import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readPtrField; import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.writePtrField; import static com.oracle.graal.python.builtins.objects.str.StringUtils.byteIndexToCodepointIndex; @@ -87,7 +86,6 @@ import com.oracle.graal.python.lib.PyNumberIndexNode; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PConstructAndRaiseNode; -import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PNodeWithContext; import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.util.CannotCastException; @@ -114,13 +112,11 @@ import com.oracle.truffle.api.dsl.GenerateCached; import com.oracle.truffle.api.dsl.GenerateInline; import com.oracle.truffle.api.dsl.GenerateUncached; -import com.oracle.truffle.api.dsl.ImportStatic; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.api.nodes.UnexpectedResultException; import com.oracle.truffle.api.profiles.InlinedBranchProfile; import com.oracle.truffle.api.profiles.InlinedConditionProfile; import com.oracle.truffle.api.strings.TruffleString; @@ -342,53 +338,6 @@ static int[] read4(Node inliningTarget, long array, int length, @SuppressWarning } } - @GenerateInline(inlineByDefault = true) - @GenerateCached - @GenerateUncached - @ImportStatic(PGuards.class) - public abstract static class ConvertPIntToPrimitiveNode extends Node { - - public abstract Object execute(Node inliningTarget, Object o, int signed, int targetTypeSize, boolean exact); - - public final Object execute(Node inliningTarget, Object o, int signed, int targetTypeSize) { - return execute(inliningTarget, o, signed, targetTypeSize, true); - } - - public final long executeLongCached(Object o, int signed, int targetTypeSize, boolean exact) throws UnexpectedResultException { - return PGuards.expectLong(execute(this, o, signed, targetTypeSize, exact)); - } - - public final int executeIntCached(Object o, int signed, int targetTypeSize, boolean exact) throws UnexpectedResultException { - return PGuards.expectInteger(execute(this, o, signed, targetTypeSize, exact)); - } - - public final long executeLongCached(Object o, int signed, int targetTypeSize) throws UnexpectedResultException { - return PGuards.expectLong(execute(this, o, signed, targetTypeSize, true)); - } - - public final int executeIntCached(Object o, int signed, int targetTypeSize) throws UnexpectedResultException { - return PGuards.expectInteger(execute(this, o, signed, targetTypeSize, true)); - } - - @Specialization - static Object doInt(int value, int signed, int targetTypeSize, boolean exact, - @Shared @Cached(inline = false) AsNativePrimitiveNode asNativePrimitiveNode) { - return asNativePrimitiveNode.execute(value, signed, targetTypeSize, exact); - } - - @Specialization - static Object doLong(long value, int signed, int targetTypeSize, boolean exact, - @Shared @Cached(inline = false) AsNativePrimitiveNode asNativePrimitiveNode) { - return asNativePrimitiveNode.execute(value, signed, targetTypeSize, exact); - } - - @Specialization(replaces = {"doInt", "doLong"}) - static Object doOther(Object obj, int signed, int targetTypeSize, boolean exact, - @Shared @Cached(inline = false) AsNativePrimitiveNode asNativePrimitiveNode) { - return asNativePrimitiveNode.execute(obj, signed, targetTypeSize, exact); - } - } - /** * Use this node to transform an exception to native if a Python exception was thrown during an * upcall and before returning to native code. This node will reify the exception appropriately @@ -591,304 +540,6 @@ public static byte[] getByteArray(long ptr, long n) throws OverflowException { } /** - * Converts a Python object (i.e. {@code PyObject*}) to a C integer value ({@code int} or - * {@code long}).
- * This node is used to implement {@code PyLong_AsLong} or similar C API functions and does - * coercion and may raise a Python exception if coercion fails.
- * Allowed {@code targetTypeSize} values are {@code 4} and {@code 8}.
- * If {@code exact} is {@code false}, then casting can be lossy without raising an error. - */ - @GenerateUncached - @ImportStatic(PGuards.class) - @GenerateInline(false) // footprint reduction 32 -> 15, triggers GR-44020 - public abstract static class AsNativePrimitiveNode extends Node { - - public final int toInt32(Object value, boolean exact) { - return (int) execute(value, 1, 4, exact); - } - - public final int toUInt32(Object value, boolean exact) { - return (int) execute(value, 0, 4, exact); - } - - public final long toInt64(Object value, boolean exact) { - return (long) execute(value, 1, 8, exact); - } - - public final long toUInt64(Object value, boolean exact) { - return (long) execute(value, 0, 8, exact); - } - - public abstract Object execute(byte value, int signed, int targetTypeSize, boolean exact); - - public abstract Object execute(int value, int signed, int targetTypeSize, boolean exact); - - public abstract Object execute(long value, int signed, int targetTypeSize, boolean exact); - - public abstract Object execute(Object value, int signed, int targetTypeSize, boolean exact); - - @Specialization(guards = {"targetTypeSize == 4", "signed != 0"}) - @SuppressWarnings("unused") - static int doIntToInt32(int value, int signed, int targetTypeSize, boolean exact) { - return value; - } - - @Specialization(guards = {"targetTypeSize == 4", "signed == 0", "value >= 0"}) - @SuppressWarnings("unused") - static int doIntToUInt32Pos(int value, int signed, int targetTypeSize, boolean exact) { - return value; - } - - @Specialization(guards = {"targetTypeSize == 4", "signed == 0"}, replaces = "doIntToUInt32Pos") - @SuppressWarnings("unused") - static int doIntToUInt32(int value, int signed, int targetTypeSize, boolean exact, - @Bind Node inliningTarget, - @Shared("raiseNativeNode") @Cached PRaiseNode raiseNativeNode) { - if (exact && value < 0) { - throw raiseNegativeValue(inliningTarget, raiseNativeNode); - } - return value; - } - - @Specialization(guards = {"targetTypeSize == 8", "signed != 0"}) - @SuppressWarnings("unused") - static long doIntToInt64(int obj, int signed, int targetTypeSize, boolean exact) { - return obj; - } - - @Specialization(guards = {"targetTypeSize == 8", "signed == 0", "value >= 0"}) - @SuppressWarnings("unused") - static long doIntToUInt64Pos(int value, int signed, int targetTypeSize, boolean exact) { - return value; - } - - @Specialization(guards = {"targetTypeSize == 8", "signed == 0"}, replaces = "doIntToUInt64Pos") - @SuppressWarnings("unused") - static long doIntToUInt64(int value, int signed, int targetTypeSize, boolean exact, - @Bind Node inliningTarget, - @Shared("raiseNativeNode") @Cached PRaiseNode raiseNativeNode) { - if (exact && value < 0) { - throw raiseNegativeValue(inliningTarget, raiseNativeNode); - } - return value; - } - - @Specialization(guards = {"targetTypeSize == 8", "signed != 0"}) - @SuppressWarnings("unused") - static long doLongToInt64(long value, int signed, int targetTypeSize, boolean exact) { - return value; - } - - @Specialization(guards = {"targetTypeSize == 8", "signed == 0", "value >= 0"}) - @SuppressWarnings("unused") - static long doLongToUInt64Pos(long value, int signed, int targetTypeSize, boolean exact) { - return value; - } - - @Specialization(guards = {"targetTypeSize == 8", "signed == 0"}, replaces = "doLongToUInt64Pos") - @SuppressWarnings("unused") - static long doLongToUInt64(long value, int signed, int targetTypeSize, boolean exact, - @Bind Node inliningTarget, - @Shared("raiseNativeNode") @Cached PRaiseNode raiseNativeNode) { - if (exact && value < 0) { - throw raiseNegativeValue(inliningTarget, raiseNativeNode); - } - return value; - } - - @Specialization(guards = {"exact", "targetTypeSize == 4", "signed != 0"}) - @SuppressWarnings("unused") - static int doLongToInt32Exact(long obj, int signed, int targetTypeSize, boolean exact, - @Bind Node inliningTarget, - @Shared("raiseNode") @Cached PRaiseNode raiseNode) { - try { - return PInt.intValueExact(obj); - } catch (OverflowException e) { - throw raiseNode.raise(inliningTarget, PythonErrorType.OverflowError, ErrorMessages.PYTHON_INT_TOO_LARGE_TO_CONV_TO_C_TYPE, targetTypeSize); - } - } - - @Specialization(guards = {"exact", "targetTypeSize == 4", "signed == 0", "obj >= 0"}) - @SuppressWarnings("unused") - static int doLongToUInt32PosExact(long obj, int signed, int targetTypeSize, boolean exact, - @Bind Node inliningTarget, - @Shared("raiseNode") @Cached PRaiseNode raiseNode) { - if (Integer.toUnsignedLong((int) obj) == obj) { - return (int) obj; - } else { - throw raiseNode.raise(inliningTarget, PythonErrorType.OverflowError, ErrorMessages.PYTHON_INT_TOO_LARGE_TO_CONV_TO_C_TYPE, targetTypeSize); - } - } - - @Specialization(guards = {"exact", "targetTypeSize == 4", "signed == 0"}, replaces = "doLongToUInt32PosExact") - @SuppressWarnings("unused") - static int doLongToUInt32Exact(long obj, int signed, int targetTypeSize, boolean exact, - @Bind Node inliningTarget, - @Shared("raiseNode") @Cached PRaiseNode raiseNode) { - if (obj < 0) { - throw raiseNegativeValue(inliningTarget, raiseNode); - } - return doLongToUInt32PosExact(obj, signed, targetTypeSize, exact, inliningTarget, raiseNode); - } - - @Specialization(guards = {"!exact", "targetTypeSize == 4"}) - @SuppressWarnings("unused") - static int doLongToInt32Lossy(long obj, int signed, int targetTypeSize, boolean exact) { - return (int) obj; - } - - @Specialization(guards = {"exact", "targetTypeSize == 4"}) - @SuppressWarnings("unused") - @TruffleBoundary - static int doPIntTo32Bit(PInt obj, int signed, int targetTypeSize, boolean exact, - @Bind Node inliningTarget, - @Shared("raiseNode") @Cached PRaiseNode raiseNode) { - try { - if (signed != 0) { - return obj.intValueExact(); - } else if (obj.bitLength() <= 32) { - if (obj.isNegative()) { - throw raiseNegativeValue(inliningTarget, raiseNode); - } - return obj.intValue(); - } - } catch (OverflowException e) { - // fall through - } - throw raiseNode.raise(inliningTarget, PythonErrorType.OverflowError, ErrorMessages.PYTHON_INT_TOO_LARGE_TO_CONV_TO_C_TYPE, targetTypeSize); - } - - @Specialization(guards = {"exact", "targetTypeSize == 8"}) - @SuppressWarnings("unused") - @TruffleBoundary - static long doPIntTo64Bit(PInt obj, int signed, int targetTypeSize, boolean exact, - @Bind Node inliningTarget, - @Shared("raiseNode") @Cached PRaiseNode raiseNode) { - try { - if (signed != 0) { - return obj.longValueExact(); - } else if (obj.bitLength() <= 64) { - if (obj.isNegative()) { - throw raiseNegativeValue(inliningTarget, raiseNode); - } - return obj.longValue(); - } - } catch (OverflowException e) { - // fall through - } - throw raiseNode.raise(inliningTarget, PythonErrorType.OverflowError, ErrorMessages.PYTHON_INT_TOO_LARGE_TO_CONV_TO_C_TYPE, targetTypeSize); - } - - @Specialization(guards = {"!exact", "targetTypeSize == 4"}) - @SuppressWarnings("unused") - static int doPIntToInt32Lossy(PInt obj, int signed, int targetTypeSize, boolean exact) { - return obj.intValue(); - } - - @Specialization(guards = {"!exact", "targetTypeSize == 8"}) - @SuppressWarnings("unused") - static long doPIntToInt64Lossy(PInt obj, int signed, int targetTypeSize, boolean exact) { - return obj.longValue(); - } - - @Specialization(guards = {"targetTypeSize == 4 || targetTypeSize == 8"}, // - replaces = {"doIntToInt32", "doIntToUInt32Pos", "doIntToUInt32", // - "doIntToInt64", "doIntToUInt64Pos", "doIntToUInt64", // - "doLongToInt64", "doLongToUInt64Pos", "doLongToUInt64", // - "doLongToInt32Exact", "doLongToUInt32PosExact", "doLongToUInt32Exact", "doLongToInt32Lossy", // - "doPIntTo32Bit", "doPIntTo64Bit", "doPIntToInt32Lossy", "doPIntToInt64Lossy"}) - static Object doGeneric(Object obj, int signed, int targetTypeSize, boolean exact, - @Bind Node inliningTarget, - @Cached PyNumberIndexNode indexNode, - @Exclusive @Cached PRaiseNode raiseNode) { - Object result = indexNode.execute(null, inliningTarget, obj); - /* - * The easiest would be to recursively use this node and ensure that this generic case - * isn't taken but we cannot guarantee that because the uncached version will always try - * the generic case first. Hence, the 'toInt32' and 'toInt64' handle all cases in - * if-else style. This won't be as bad as it looks in source code because arguments - * 'signed', 'targetTypeSize', and 'exact' are usually constants. - */ - if (targetTypeSize == 4) { - return toInt32(inliningTarget, result, signed, exact, raiseNode); - } else if (targetTypeSize == 8) { - return toInt64(inliningTarget, result, signed, exact, raiseNode); - } - throw raiseNode.raise(inliningTarget, SystemError, ErrorMessages.UNSUPPORTED_TARGET_SIZE, targetTypeSize); - } - - @Specialization(guards = {"targetTypeSize != 4", "targetTypeSize != 8"}) - @SuppressWarnings("unused") - static int doUnsupportedTargetSize(Object obj, int signed, int targetTypeSize, boolean exact, - @Bind Node inliningTarget) { - throw PRaiseNode.raiseStatic(inliningTarget, SystemError, ErrorMessages.UNSUPPORTED_TARGET_SIZE, targetTypeSize); - } - - private static PException raiseNegativeValue(Node inliningTarget, PRaiseNode raiseNativeNode) { - throw raiseNativeNode.raise(inliningTarget, OverflowError, ErrorMessages.CANNOT_CONVERT_NEGATIVE_VALUE_TO_UNSIGNED_INT); - } - - /** - * Slow-path conversion of an object to a signed or unsigned 32-bit value. - */ - private static int toInt32(Node inliningTarget, Object object, int signed, boolean exact, - PRaiseNode raiseNode) { - if (object instanceof Integer) { - int ival = (int) object; - if (signed != 0) { - return ival; - } - return doIntToUInt32(ival, signed, 4, exact, inliningTarget, raiseNode); - } else if (object instanceof Long) { - long lval = (long) object; - if (exact) { - if (signed != 0) { - return doLongToInt32Exact(lval, 1, 4, true, inliningTarget, raiseNode); - } - return doLongToUInt32Exact(lval, signed, 4, true, inliningTarget, raiseNode); - } - return doLongToInt32Lossy(lval, 0, 4, false); - } else if (object instanceof PInt) { - PInt pval = (PInt) object; - if (exact) { - return doPIntTo32Bit(pval, signed, 4, true, inliningTarget, raiseNode); - } - return doPIntToInt32Lossy(pval, signed, 4, false); - } - throw raiseNode.raise(inliningTarget, PythonErrorType.TypeError, ErrorMessages.INDEX_RETURNED_NON_INT, object); - } - - /** - * Slow-path conversion of an object to a signed or unsigned 64-bit value. - */ - private static Object toInt64(Node inliningTarget, Object object, int signed, boolean exact, - PRaiseNode raiseNode) { - if (object instanceof Integer) { - Integer ival = (Integer) object; - if (signed != 0) { - return ival.longValue(); - } - return doIntToUInt64(ival, signed, 8, exact, inliningTarget, raiseNode); - } else if (object instanceof Long) { - long lval = (long) object; - if (signed != 0) { - return doLongToInt64(lval, 1, 8, exact); - } - return doLongToUInt64(lval, signed, 8, exact, inliningTarget, raiseNode); - } else if (object instanceof PInt) { - PInt pval = (PInt) object; - if (exact) { - return doPIntTo64Bit(pval, signed, 8, true, inliningTarget, raiseNode); - } - return doPIntToInt64Lossy(pval, signed, 8, false); - } - throw raiseNode.raise(inliningTarget, PythonErrorType.TypeError, ErrorMessages.INDEX_RETURNED_NON_INT, object); - } - } - - /** - * This node converts a C Boolean value to Python Boolean. - */ @GenerateInline(false) // footprint reduction 24 -> 5, inherits non-inlineable execute() @GenerateUncached public abstract static class NativePrimitiveAsPythonBooleanNode extends Node { @@ -957,31 +608,6 @@ static byte doGeneric(Object value, } /** - * Converts a Python object to a C primitive value with a fixed size and sign. - * - * @see AsNativePrimitiveNode - */ - public abstract static class AsFixedNativePrimitiveNode extends Node { - - private final int targetTypeSize; - private final int signed; - - protected AsFixedNativePrimitiveNode(int targetTypeSize, boolean signed) { - this.targetTypeSize = targetTypeSize; - this.signed = PInt.intValue(signed); - } - - public abstract Object execute(Object object); - - // Adding specializations for primitives does not make a lot of sense just to avoid - // un-/boxing in the interpreter since interop will force un-/boxing anyway. - @Specialization - Object doGeneric(Object value, - @Cached AsNativePrimitiveNode asNativePrimitiveNode) { - return asNativePrimitiveNode.execute(value, signed, targetTypeSize, true); - } - } - /** * Implements semantics of function {@code typeobject.c: getindex}. */ From eb78c2d9b1e4549165f8697820b290f4c4fc1206 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Fri, 28 Aug 2026 10:44:12 +0200 Subject: [PATCH 02/10] Align with CPython conversion semantics --- .../src/tests/cpyext/test_member.py | 33 +++- .../cext/capi/CApiMemberAccessNodes.java | 147 ++++++++++++------ 2 files changed, 130 insertions(+), 50 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_member.py b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_member.py index 1d17ba9ae6..402125aca5 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_member.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_member.py @@ -231,6 +231,15 @@ def test_member(self): import warnings warnings.simplefilter("ignore") + def assert_overflow(member, value, message, expected_value): + try: + setattr(obj, member, value) + except OverflowError as e: + assert str(e) == message + else: + assert False, "expected OverflowError" + assert getattr(obj, member) == expected_value + # char, uchar, short, ushort, int, uint, long, ulong, Py_ssize_t max_values = obj.get_max_values() min_values = obj.get_min_values() @@ -255,20 +264,34 @@ def test_member(self): assert val != min_values[i], "was: %r" % getattr(obj, m) assert_raises(TypeError, setattr, obj, m, "hello") assert_raises(OverflowError, setattr, obj, m, int(-1e40)) - assert_raises(OverflowError, setattr, obj, m, int(1e40)) + setattr(obj, m, 42) + message = "Python int too large to convert to C unsigned long" if m == "member_uint" else "Python int too large to convert to C long" + assert_overflow(m, int(1e40), message, 42) # T_LONG, T_ULONG, T_PYSSIZET max_values = (0x7FFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0x7FFFFFFFFFFFFFFF, 0x7FFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF) - err_values = (-1, 0xFFFFFFFFFFFFFFFF, -1, -1, 0xFFFFFFFFFFFFFFFF) + err_values = (-1, 42, -1, -1, 42) + err_messages = ("Python int too large to convert to C long", + "Python int too large to convert to C unsigned long", + "Python int too large to convert to C ssize_t", + "int too big to convert", + "int too big to convert") for i, m in enumerate(("member_long", "member_ulong", "member_pyssizet", "member_longlong", "member_ulonglong")): assert type(getattr(obj, m)) is int assert getattr(obj, m) == 0 assert_raises(TypeError, delattr, obj, m) setattr(obj, m, max_values[i]) assert getattr(obj, m) == max_values[i] - assert_raises(OverflowError, setattr, obj, m, max_values[i] + 1) - val = getattr(obj, m) - assert val == err_values[i], "member: %s ;; was: %r" % (m, val) + setattr(obj, m, 42) + assert_overflow(m, max_values[i] + 1, err_messages[i], err_values[i]) + + class Indexable: + def __index__(self): + return 42 + + obj.member_pyssizet = 42 + assert_raises(TypeError, setattr, obj, "member_pyssizet", Indexable()) + assert obj.member_pyssizet == -1 # T_FLOAT, T_DOUBLE for i, m in enumerate(("member_float", "member_double")): diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiMemberAccessNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiMemberAccessNodes.java index 759d36510c..ee88d8c7ff 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiMemberAccessNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiMemberAccessNodes.java @@ -77,19 +77,21 @@ import com.oracle.graal.python.builtins.objects.ints.PInt; import com.oracle.graal.python.builtins.objects.type.TypeNodes.IsSameTypeNode; import com.oracle.graal.python.lib.PyFloatAsDoubleNode; -import com.oracle.graal.python.lib.PyLongAsLongNode; +import com.oracle.graal.python.lib.PyLongAsLongAndOverflowNode; +import com.oracle.graal.python.lib.PyLongCheckNode; import com.oracle.graal.python.lib.PyNumberIndexNode; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PRaiseNode; -import com.oracle.graal.python.nodes.util.CastToJavaUnsignedLongNode; import com.oracle.graal.python.nodes.function.BuiltinFunctionRootNode; import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinObjectProfile; import com.oracle.graal.python.nodes.object.GetClassNode; +import com.oracle.graal.python.nodes.util.CastToJavaUnsignedLongNode; import com.oracle.graal.python.runtime.exception.PException; import com.oracle.graal.python.runtime.nativeaccess.NativeMemory; import com.oracle.graal.python.runtime.object.PFactory; +import com.oracle.graal.python.util.OverflowException; import com.oracle.graal.python.util.PythonUtils.PrototypeNodeFactory; import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; @@ -358,6 +360,41 @@ public static PBuiltinFunction createBuiltinFunction(PythonLanguage language, Tr abstract static class WriteTypeNode extends Node { abstract void execute(long pointer, Object newValue); + + static long asSignedLong(Object value, int memberType, Node inliningTarget, PyLongAsLongAndOverflowNode asLong, PRaiseNode raiseNode) { + try { + return asLong.execute(null, inliningTarget, value); + } catch (OverflowException e) { + if (memberType == T_LONGLONG) { + throw raiseNode.raise(inliningTarget, PythonBuiltinClassType.OverflowError, ErrorMessages.MESSAGE_INT_TO_BIG); + } + String targetType = memberType == T_PYSSIZET ? "C ssize_t" : "C long"; + throw raiseNode.raise(inliningTarget, PythonBuiltinClassType.OverflowError, ErrorMessages.PYTHON_INT_TOO_LARGE_TO_CONV_TO, targetType); + } + } + + static long asUnsignedLong(Object value, int memberType, Node inliningTarget, CastToJavaUnsignedLongNode asUnsignedLong, IsBuiltinObjectProfile exceptionProfile, + PRaiseNode raiseNode) { + try { + return asUnsignedLong.execute(inliningTarget, value); + } catch (PException e) { + e.expectOverflowError(inliningTarget, exceptionProfile); + if (memberType == T_ULONGLONG) { + throw raiseNode.raise(inliningTarget, PythonBuiltinClassType.OverflowError, ErrorMessages.MESSAGE_INT_TO_BIG); + } + throw raiseNode.raise(inliningTarget, PythonBuiltinClassType.OverflowError, ErrorMessages.PYTHON_INT_TOO_LARGE_TO_CONV_TO, "C unsigned long"); + } + } + + static boolean isNegative(Object value) { + if (value instanceof Integer intValue) { + return intValue < 0; + } else if (value instanceof Long longValue) { + return longValue < 0; + } else { + return ((PInt) value).isNegative(); + } + } } @GenerateInline(false) @@ -366,8 +403,9 @@ abstract static class WriteByteNode extends WriteTypeNode { @Specialization static void write(long pointer, Object newValue, @Bind Node inliningTarget, - @Cached PyLongAsLongNode asLong) { - NativeMemory.writeByte(pointer, (byte) asLong.execute(null, inliningTarget, newValue)); + @Cached PyLongAsLongAndOverflowNode asLong, + @Cached PRaiseNode raiseNode) { + NativeMemory.writeByte(pointer, (byte) asSignedLong(newValue, T_LONG, inliningTarget, asLong, raiseNode)); } } @@ -377,8 +415,9 @@ abstract static class WriteShortNode extends WriteTypeNode { @Specialization static void write(long pointer, Object newValue, @Bind Node inliningTarget, - @Cached PyLongAsLongNode asLong) { - NativeMemory.writeShort(pointer, (short) asLong.execute(null, inliningTarget, newValue)); + @Cached PyLongAsLongAndOverflowNode asLong, + @Cached PRaiseNode raiseNode) { + NativeMemory.writeShort(pointer, (short) asSignedLong(newValue, T_LONG, inliningTarget, asLong, raiseNode)); } } @@ -388,28 +427,41 @@ abstract static class WriteIntNode extends WriteTypeNode { @Specialization static void write(long pointer, Object newValue, @Bind Node inliningTarget, - @Cached PyLongAsLongNode asLong) { - NativeMemory.writeInt(pointer, (int) asLong.execute(null, inliningTarget, newValue)); + @Cached PyLongAsLongAndOverflowNode asLong, + @Cached PRaiseNode raiseNode) { + NativeMemory.writeInt(pointer, (int) asSignedLong(newValue, T_LONG, inliningTarget, asLong, raiseNode)); } } @GenerateInline(false) abstract static class WriteLongNode extends WriteTypeNode { + private final int type; + + WriteLongNode(int type) { + this.type = type; + } + + int getType() { + return type; + } + @Specialization static void write(long pointer, Object newValue, @Bind Node inliningTarget, - @Cached PyLongAsLongNode asLong, - @Cached IsBuiltinObjectProfile exceptionProfile) { + @Bind("getType()") int type, + @Cached PyLongAsLongAndOverflowNode asLong, + @Cached PRaiseNode raiseNode) { try { - NativeMemory.writeLong(pointer, asLong.execute(null, inliningTarget, newValue)); + if (type == T_PYSSIZET && !PyLongCheckNode.executeUncached(newValue)) { + throw raiseNode.raise(inliningTarget, PythonBuiltinClassType.TypeError, ErrorMessages.INTEGER_REQUIRED); + } + NativeMemory.writeLong(pointer, asSignedLong(newValue, type, inliningTarget, asLong, raiseNode)); } catch (PException e) { /* - * Special case: if conversion raises an OverflowError, CPython still assigns the - * error indication value -1 to the member. That looks rather like a bug but let's - * just do the same. + * PyMember_SetOne assigns the integer conversion's error indication value before + * checking whether the conversion raised an exception. */ - e.expectOverflowError(inliningTarget, exceptionProfile); NativeMemory.writeLong(pointer, -1); throw e; } @@ -424,46 +476,48 @@ static void write(long pointer, Object newValue, @Bind Node inliningTarget, @Cached PyNumberIndexNode indexNode, @Cached CastToJavaUnsignedLongNode asUnsignedLong, - @Cached PyLongAsLongNode asLong, - @Cached IsBuiltinObjectProfile exceptionProfile) { + @Cached PyLongAsLongAndOverflowNode asLong, + @Cached IsBuiltinObjectProfile exceptionProfile, + @Cached PRaiseNode raiseNode) { /* * This emulates the arguably buggy behavior from CPython where it accepts MIN_LONG to * MAX_ULONG values. */ - try { - NativeMemory.writeInt(pointer, (int) asUnsignedLong.execute(inliningTarget, indexNode.execute(null, inliningTarget, newValue))); - } catch (PException e) { - /* - * Special case: accept signed long as well. - */ - e.expectOverflowError(inliningTarget, exceptionProfile); - NativeMemory.writeInt(pointer, (int) asLong.execute(null, inliningTarget, newValue)); - // swallowing the exception - } + Object index = indexNode.execute(null, inliningTarget, newValue); + long value = isNegative(index) + ? asSignedLong(index, T_LONG, inliningTarget, asLong, raiseNode) + : asUnsignedLong(index, T_UINT, inliningTarget, asUnsignedLong, exceptionProfile, raiseNode); + NativeMemory.writeInt(pointer, (int) value); } } @GenerateInline(false) abstract static class WriteULongNode extends WriteTypeNode { + private final int type; + + WriteULongNode(int type) { + this.type = type; + } + + int getType() { + return type; + } + @Specialization static void write(long pointer, Object newValue, @Bind Node inliningTarget, + @Bind("getType()") int type, @Cached PyNumberIndexNode indexNode, @Cached CastToJavaUnsignedLongNode asUnsignedLong, - @Cached IsBuiltinObjectProfile exceptionProfile) { - try { - NativeMemory.writeLong(pointer, asUnsignedLong.execute(inliningTarget, indexNode.execute(null, inliningTarget, newValue))); - } catch (PException e) { - /* - * Special case: if conversion raises an OverflowError, CPython still assigns the - * error indication value -1 to the member. That looks rather like a bug but let's - * just do the same. - */ - e.expectOverflowError(inliningTarget, exceptionProfile); - NativeMemory.writeLong(pointer, -1); - throw e; - } + @Cached PyLongAsLongAndOverflowNode asLong, + @Cached IsBuiltinObjectProfile exceptionProfile, + @Cached PRaiseNode raiseNode) { + Object index = indexNode.execute(null, inliningTarget, newValue); + long value = isNegative(index) + ? asSignedLong(index, T_LONG, inliningTarget, asLong, raiseNode) + : asUnsignedLong(index, type, inliningTarget, asUnsignedLong, exceptionProfile, raiseNode); + NativeMemory.writeLong(pointer, value); } } @@ -548,7 +602,7 @@ private static WriteTypeNode getWriteNode(int type) { case T_UINT: return WriteUIntNodeGen.create(); case T_LONG: - return WriteLongNodeGen.create(); + return WriteLongNodeGen.create(type); case T_FLOAT: return WriteFloatNodeGen.create(); case T_DOUBLE: @@ -568,13 +622,16 @@ private static WriteTypeNode getWriteNode(int type) { case T_UBYTE: return WriteByteNodeGen.create(); case T_ULONG: - case T_ULONGLONG: - return WriteULongNodeGen.create(); + return WriteULongNodeGen.create(type); case T_LONGLONG: - case T_PYSSIZET: assert CStructs.long__long.size() == Long.BYTES; + return WriteLongNodeGen.create(type); + case T_PYSSIZET: assert CStructs.Py_ssize_t.size() == Long.BYTES; - return WriteLongNodeGen.create(); + return WriteLongNodeGen.create(type); + case T_ULONGLONG: + assert CStructs.long__long.size() == Long.BYTES; + return WriteULongNodeGen.create(type); default: throw CompilerDirectives.shouldNotReachHere("invalid member type"); } From bcd988f9c27375cd38e7e04e6ab9673d4a31ecee Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Wed, 2 Sep 2026 11:35:30 +0200 Subject: [PATCH 03/10] Improve PyLongCheckNode for native subclasses --- .../graal/python/lib/PyLongCheckNode.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyLongCheckNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyLongCheckNode.java index 20e4a72fd8..157dc2e556 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyLongCheckNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyLongCheckNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -40,11 +40,19 @@ */ package com.oracle.graal.python.lib; +import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyObject__ob_type; +import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readLongField; +import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readPtrField; + import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; +import com.oracle.graal.python.builtins.objects.cext.structs.CFields; import com.oracle.graal.python.builtins.objects.ints.PInt; +import com.oracle.graal.python.builtins.objects.type.TypeFlags; import com.oracle.graal.python.nodes.PNodeWithContext; import com.oracle.graal.python.nodes.SpecialMethodNames; import com.oracle.graal.python.nodes.classes.IsSubtypeNode; +import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinObjectProfile; import com.oracle.graal.python.nodes.object.GetClassNode; import com.oracle.graal.python.nodes.object.IsForeignObjectNode; import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff; @@ -92,6 +100,14 @@ static boolean doPInt(@SuppressWarnings("unused") PInt object) { return true; } + @Specialization + static boolean doNative(PythonAbstractNativeObject nativeObject) { + long obType = readPtrField(nativeObject.pointer, PyObject__ob_type); + boolean isLongSubclass = (readLongField(obType, CFields.PyTypeObject__tp_flags) & TypeFlags.LONG_SUBCLASS) != 0L; + assert IsBuiltinObjectProfile.profileObjectUncached(nativeObject, PythonBuiltinClassType.PInt) == isLongSubclass; + return isLongSubclass; + } + @Specialization @InliningCutoff static boolean doGeneric(Node inliningTarget, Object object, From ca145cbbb2c6e9d16accbb7401d734e5fba1f30d Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Wed, 2 Sep 2026 14:18:42 +0200 Subject: [PATCH 04/10] Fix style --- .../modules/cext/PythonCextLongBuiltins.java | 12 ----- .../objects/cext/common/CExtCommonNodes.java | 51 +------------------ 2 files changed, 1 insertion(+), 62 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java index f3ce0bb630..7e24b752c2 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java @@ -74,13 +74,9 @@ import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiTernaryBuiltinNode; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiUnaryBuiltinNode; import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes; -import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.CastToNativeLongNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonInternalNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; -import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonInternalNode; -import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; -import com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.TransformPExceptionToNativeCachedNode; import com.oracle.graal.python.builtins.objects.ints.IntBuiltins; import com.oracle.graal.python.builtins.objects.ints.IntNodes; @@ -91,14 +87,7 @@ import com.oracle.graal.python.lib.PyNumberIndexNode; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PRaiseNode; -import com.oracle.graal.python.nodes.classes.IsSubtypeNode; -import com.oracle.graal.python.nodes.object.GetClassNode; import com.oracle.graal.python.nodes.util.CastToJavaBigIntegerNode; -import com.oracle.graal.python.lib.PyLongCheckNode; -import com.oracle.graal.python.lib.PyNumberIndexNode; -import com.oracle.graal.python.runtime.nativeaccess.NativeMemory; -import com.oracle.graal.python.nodes.ErrorMessages; -import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.runtime.exception.PException; import com.oracle.graal.python.runtime.nativeaccess.NativeMemory; import com.oracle.graal.python.runtime.object.PFactory; @@ -113,7 +102,6 @@ import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.api.nodes.UnexpectedResultException; import com.oracle.truffle.api.profiles.InlinedBranchProfile; import com.oracle.truffle.api.profiles.InlinedConditionProfile; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtCommonNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtCommonNodes.java index 875c6a92b1..fcd3cb5820 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtCommonNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/CExtCommonNodes.java @@ -55,8 +55,8 @@ import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.readByteArrayElements; import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.readIntArrayElement; import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.readShortArrayElement; -import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; import static com.oracle.graal.python.util.PythonUtils.SURROGATE_CODE_POINT_SET; +import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; import static com.oracle.graal.python.util.PythonUtils.tsLiteral; import java.nio.charset.Charset; @@ -83,7 +83,6 @@ import com.oracle.graal.python.builtins.objects.type.TpSlots.GetObjectSlotsNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotLen.CallSlotLenNode; import com.oracle.graal.python.lib.PyNumberAsSizeNode; -import com.oracle.graal.python.lib.PyNumberIndexNode; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PConstructAndRaiseNode; import com.oracle.graal.python.nodes.PNodeWithContext; @@ -100,22 +99,17 @@ import com.oracle.graal.python.runtime.object.PFactory; import com.oracle.graal.python.util.OverflowException; import com.oracle.graal.python.util.PythonUtils; -import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff; import com.oracle.truffle.api.TruffleLogger; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; -import com.oracle.truffle.api.dsl.Cached.Exclusive; import com.oracle.truffle.api.dsl.Cached.Shared; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.GenerateCached; import com.oracle.truffle.api.dsl.GenerateInline; import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.api.interop.InteropLibrary; -import com.oracle.truffle.api.interop.UnsupportedMessageException; -import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.profiles.InlinedBranchProfile; import com.oracle.truffle.api.profiles.InlinedConditionProfile; @@ -539,49 +533,6 @@ public static byte[] getByteArray(long ptr, long n) throws OverflowException { return readByteArrayElements(ptr, 0, PInt.intValueExact(n)); } - /** - @GenerateInline(false) // footprint reduction 24 -> 5, inherits non-inlineable execute() - @GenerateUncached - public abstract static class NativePrimitiveAsPythonBooleanNode extends Node { - - public abstract Object execute(Object value); - - @Specialization - static Boolean doBoolean(Boolean b) { - return b; - } - - @Specialization - static Object doByte(byte b) { - return b != 0; - } - - @Specialization - static Object doShort(short i) { - return i != 0; - } - - @Specialization - static Object doLong(long l) { - // If the integer is out of byte range, we just to a lossy cast since that's the same - // semantics as we should just read a single byte. - return l != 0; - } - - @Specialization(replaces = {"doBoolean", "doByte", "doShort", "doLong"}, limit = "1") - static Object doGeneric(Object n, - @CachedLibrary("n") InteropLibrary lib) { - if (lib.fitsInLong(n)) { - try { - return lib.asLong(n) != 0; - } catch (UnsupportedMessageException e) { - // fall through - } - } - throw CompilerDirectives.shouldNotReachHere(); - } - } - /** * Converts a Python character (1-element Python string) into a UTF-8 encoded C {@code char}. * According to CPython, we need to encode the whole Python string before we access the first From 32905780898cdb9731c1a4a1abf6336d8160e7d4 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Mon, 7 Sep 2026 09:18:53 +0200 Subject: [PATCH 05/10] Use PyLongCheckNode in CastToJavaLongNode --- .../python/nodes/util/CastToJavaLongNode.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/util/CastToJavaLongNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/util/CastToJavaLongNode.java index 42d2c10365..abac711555 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/util/CastToJavaLongNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/util/CastToJavaLongNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -42,17 +42,14 @@ import static com.oracle.graal.python.builtins.PythonBuiltinClassType.NotImplementedError; -import com.oracle.graal.python.builtins.PythonBuiltinClassType; -import com.oracle.graal.python.builtins.objects.cext.PythonNativeObject; +import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; +import com.oracle.graal.python.lib.PyLongCheckNode; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PNodeWithContext; import com.oracle.graal.python.nodes.PRaiseNode; -import com.oracle.graal.python.nodes.classes.IsSubtypeNode; -import com.oracle.graal.python.nodes.object.GetClassNode; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff; -import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.GenerateCached; import com.oracle.truffle.api.dsl.GenerateInline; @@ -89,10 +86,8 @@ static long doLong(boolean x) { @Specialization @InliningCutoff - static long doNativeObject(Node inliningTarget, PythonNativeObject x, - @Cached GetClassNode getClassNode, - @Cached(inline = false) IsSubtypeNode isSubtypeNode) { - if (isSubtypeNode.execute(getClassNode.execute(inliningTarget, x), PythonBuiltinClassType.PInt)) { + static long doNativeObject(Node inliningTarget, PythonAbstractNativeObject x) { + if (PyLongCheckNode.doNative(x)) { CompilerDirectives.transferToInterpreterAndInvalidate(); throw PRaiseNode.raiseStatic(inliningTarget, NotImplementedError, ErrorMessages.CASTING_A_NATIVE_INT_OBJECT_IS_NOT_IMPLEMENTED_YET); } From f0da05fccd65d972e750c963e807590d9033a8ab Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Wed, 9 Sep 2026 11:33:33 +0200 Subject: [PATCH 06/10] Split PyLong primitive conversion upcalls --- .../src/longobject.c | 62 +++--- .../modules/cext/PythonCextLongBuiltins.java | 181 +++++++++++------- .../objects/cext/structs/CStructs.java | 1 + .../graal/python/lib/PyLongCheckNode.java | 2 +- 4 files changed, 141 insertions(+), 105 deletions(-) diff --git a/graalpython/com.oracle.graal.python.cext/src/longobject.c b/graalpython/com.oracle.graal.python.cext/src/longobject.c index 2292b26d49..31a3da82d2 100644 --- a/graalpython/com.oracle.graal.python.cext/src/longobject.c +++ b/graalpython/com.oracle.graal.python.cext/src/longobject.c @@ -25,24 +25,10 @@ #include // offsetof // GraalPy-specific defines -/* - * There are 4 different modes for 'PyLong_AsPrimitive: - * - MODE_COERCE_UNSIGNED - * Will coerce the object to a Python integer and returns it as unsigned primitive. - * - MODE_COERCE_SIGNED 1 - * Will coerce the object to a Python integer and returns it as signed primitive. - * - MODE_PINT_UNSIGNED 2 - * Requires the object to be a Python integer and returns it as unsigned primitive. - * - MODE_PINT_SIGNED 3 - * Requires the object to be a Python integer and returns it as signed primitive. - * - MODE_COERCE_MASK 4 - * Will coerce the object to a Python integer and does a lossy cast to an unsigned primitive. - */ -#define MODE_COERCE_UNSIGNED 0 -#define MODE_COERCE_SIGNED 1 -#define MODE_PINT_UNSIGNED 2 -#define MODE_PINT_SIGNED 3 -#define MODE_COERCE_MASK 4 +/* Modes for GraalPyPrivate_Long_AsPrimitive* conversion. */ +#define MODE_UNSIGNED 0 +#define MODE_SIGNED 1 +#define MODE_MASK 4 #if 0 // GraalPy change #include "clinic/longobject.c.h" @@ -486,12 +472,7 @@ PyLong_AsLongAndOverflow(PyObject *vv, int *overflow) if (points_to_py_int_handle(vv)) { return pointer_to_int64(vv); } - long result = (long) GraalPyPrivate_Long_AsPrimitive(vv, MODE_COERCE_SIGNED, sizeof(long)); - if (result == -1L && PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_OverflowError)) { - PyErr_Clear(); - *overflow = _PyLong_Sign(vv); - } - return result; + return (long) GraalPyPrivate_Long_AsPrimitiveAndOverflow(vv, sizeof(long), overflow); } /* Get a C long int from an int object or any object that has an __index__ @@ -504,7 +485,7 @@ PyLong_AsLong(PyObject *obj) if (points_to_py_int_handle(obj)) { return pointer_to_int64(obj); } - return (long) GraalPyPrivate_Long_AsPrimitive(obj, MODE_COERCE_SIGNED, sizeof(long)); + return (long) GraalPyPrivate_Long_AsPrimitiveWithCoercion(obj, MODE_SIGNED, sizeof(long)); } /* Get a C int from an int object or any object that has an __index__ @@ -533,7 +514,7 @@ PyLong_AsSsize_t(PyObject *vv) { if (points_to_py_int_handle(vv)) { return pointer_to_int64(vv); } - return (Py_ssize_t) GraalPyPrivate_Long_AsPrimitive(vv, MODE_PINT_SIGNED, sizeof(Py_ssize_t)); + return (Py_ssize_t) GraalPyPrivate_Long_AsPrimitive(vv, MODE_SIGNED, sizeof(Py_ssize_t)); } /* Get a C unsigned long int from an int object. @@ -555,7 +536,7 @@ PyLong_AsUnsignedLong(PyObject *vv) } return (unsigned long) value; } - return (unsigned long) GraalPyPrivate_Long_AsPrimitive(vv, MODE_PINT_UNSIGNED, sizeof(unsigned long)); + return (unsigned long) GraalPyPrivate_Long_AsPrimitive(vv, MODE_UNSIGNED, sizeof(unsigned long)); } /* Get a C size_t from an int object. Returns (size_t)-1 and sets @@ -573,7 +554,7 @@ PyLong_AsSize_t(PyObject *vv) } return (size_t) value; } - return (size_t) GraalPyPrivate_Long_AsPrimitive(vv, MODE_PINT_UNSIGNED, sizeof(size_t)); + return (size_t) GraalPyPrivate_Long_AsPrimitive(vv, MODE_UNSIGNED, sizeof(size_t)); } #if 0 // GraalPy change @@ -620,7 +601,7 @@ PyLong_AsUnsignedLongMask(PyObject *op) if (points_to_py_int_handle(op)) { return pointer_to_int64(op); } - return (unsigned long) GraalPyPrivate_Long_AsPrimitive(op, MODE_COERCE_MASK, sizeof(unsigned long)); + return (unsigned long) GraalPyPrivate_Long_AsPrimitiveWithCoercion(op, MODE_MASK, sizeof(unsigned long)); } int @@ -1307,7 +1288,7 @@ PyLong_AsLongLong(PyObject *vv) if (points_to_py_int_handle(vv)) { return pointer_to_int64(vv); } - return (long long) GraalPyPrivate_Long_AsPrimitive(vv, MODE_COERCE_SIGNED, sizeof(long long)); + return (long long) GraalPyPrivate_Long_AsPrimitiveWithCoercion(vv, MODE_SIGNED, sizeof(long long)); } /* Get a C unsigned long long int from an int object. @@ -1329,7 +1310,7 @@ PyLong_AsUnsignedLongLong(PyObject *vv) } return (unsigned long long) value; } - return (unsigned long long) GraalPyPrivate_Long_AsPrimitive(vv, MODE_PINT_UNSIGNED, sizeof(unsigned long long)); + return (unsigned long long) GraalPyPrivate_Long_AsPrimitive(vv, MODE_UNSIGNED, sizeof(unsigned long long)); } #if 0 // GraalPy change @@ -1377,7 +1358,7 @@ PyLong_AsUnsignedLongLongMask(PyObject *op) if (points_to_py_int_handle(op)) { return pointer_to_int64(op); } - return (unsigned long long) GraalPyPrivate_Long_AsPrimitive(op, MODE_COERCE_MASK, sizeof(unsigned long long)); + return (unsigned long long) GraalPyPrivate_Long_AsPrimitiveWithCoercion(op, MODE_MASK, sizeof(unsigned long long)); } /* Get a C long long int from an int object or any object that has an @@ -1394,14 +1375,15 @@ long long PyLong_AsLongLongAndOverflow(PyObject *vv, int *overflow) { // GraalPy change: different implementation - long long result = PyLong_AsLongLong(vv); - if (result == -1L && PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_OverflowError)) { - PyErr_Clear(); - *overflow = _PyLong_Sign(vv); - } else { - *overflow = 0; + *overflow = 0; + if (vv == NULL) { + PyErr_BadInternalCall(); + return -1; } - return result; + if (points_to_py_int_handle(vv)) { + return pointer_to_int64(vv); + } + return GraalPyPrivate_Long_AsPrimitiveAndOverflow(vv, sizeof(long long), overflow); } #if 0 // GraalPy change @@ -6510,7 +6492,7 @@ Py_ssize_t PyUnstable_Long_CompactValue(const PyLongObject *op) { if (points_to_py_int_handle(op)) { return pointer_to_int64(op); } - return GraalPyPrivate_Long_AsPrimitive((PyObject*) op, MODE_PINT_SIGNED, sizeof(Py_ssize_t)); + return GraalPyPrivate_Long_AsPrimitive((PyObject*) op, MODE_SIGNED, sizeof(Py_ssize_t)); } // GraalPy additions diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java index 7e24b752c2..0e993ca509 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java @@ -40,6 +40,7 @@ */ package com.oracle.graal.python.builtins.modules.cext; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.NotImplementedError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ValueError; import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Direct; @@ -73,6 +74,7 @@ import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiQuaternaryBuiltinNode; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiTernaryBuiltinNode; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiUnaryBuiltinNode; +import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonInternalNode; @@ -141,6 +143,60 @@ Object fromString(Object s, int base, } } + @CApiBuiltin(ret = LONG_LONG, args = {PyObject, SIZE_T, Pointer}, call = Ignored, acquireGil = false) + abstract static class GraalPyPrivate_Long_AsPrimitiveAndOverflow extends CApiTernaryBuiltinNode { + @Specialization + static long doGeneric(Object object, long targetTypeSize, long overflow, + @Bind Node inliningTarget, + @Cached PyLongCheckNode longCheckNode, + @Cached PyNumberIndexNode indexNode) { + Object integer; + if (longCheckNode.execute(inliningTarget, object)) { + integer = object; + } else { + integer = indexNode.execute(null, inliningTarget, object); + } + + try { + return convertBuiltinInteger(inliningTarget, integer, 1, (int) targetTypeSize, true); + } catch (OverflowException e) { + writeOverflow(integer, overflow); + return -1; + } + } + + @TruffleBoundary + private static void writeOverflow(Object value, long overflow) { + int sign; + if (value instanceof Long longValue) { + sign = Long.signum(longValue); + } else { + assert value instanceof PInt; + sign = ((PInt) value).isNegative() ? -1 : 1; + } + assert sign >= -1 && sign <= 1; + NativeMemory.writeInt(overflow, sign); + } + } + + @CApiBuiltin(ret = LONG_LONG, args = {PyObject, Int, SIZE_T}, call = Ignored) + abstract static class GraalPyPrivate_Long_AsPrimitiveWithCoercion extends CApiTernaryBuiltinNode { + @Specialization + static long doGeneric(Object object, int mode, long targetTypeSize, + @Bind Node inliningTarget, + @Cached PyLongCheckNode longCheckNode, + @Cached PyNumberIndexNode indexNode, + @Cached PRaiseNode raiseNode) { + Object integer; + if (longCheckNode.execute(inliningTarget, object)) { + integer = object; + } else { + integer = indexNode.execute(null, inliningTarget, object); + } + return convertAndRaise(inliningTarget, integer, mode, (int) targetTypeSize, raiseNode); + } + } + @CApiBuiltin(ret = LONG_LONG, args = {PyObject, Int, SIZE_T}, call = Ignored) abstract static class GraalPyPrivate_Long_AsPrimitive extends CApiTernaryBuiltinNode { @@ -148,87 +204,81 @@ abstract static class GraalPyPrivate_Long_AsPrimitive extends CApiTernaryBuiltin static Object doGeneric(Object object, int mode, long targetTypeSize, @Bind Node inliningTarget, @Cached PyLongCheckNode longCheckNode, - @Cached PyNumberIndexNode indexNode, @Cached PRaiseNode raiseNode) { - /* - * The 'mode' parameter is usually a constant since this function is primarily used - * in 'PyLong_As*' API functions that pass a fixed mode. So, there is no need to - * profile the value and even if it is not constant, it is profiled implicitly. - */ - if (requiredPInt(mode) && !longCheckNode.execute(inliningTarget, object)) { + if (!longCheckNode.execute(inliningTarget, object)) { throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.INTEGER_REQUIRED); } - Object index = indexNode.execute(null, inliningTarget, object); - return convertBuiltinInteger(inliningTarget, index, signed(mode), (int) targetTypeSize, exact(mode), raiseNode); + return convertAndRaise(inliningTarget, object, mode, (int) targetTypeSize, raiseNode); } + } - private static int signed(int mode) { - return mode & 0x1; + private static long convertAndRaise(Node inliningTarget, Object object, int mode, int targetTypeSize, PRaiseNode raiseNode) { + try { + return convertBuiltinInteger(inliningTarget, object, signed(mode), targetTypeSize, exact(mode)); + } catch (OverflowException e) { + throw raiseNode.raise(inliningTarget, OverflowError, ErrorMessages.PYTHON_INT_TOO_LARGE_TO_CONV_TO_C_TYPE, targetTypeSize); } + } - private static boolean requiredPInt(int mode) { - return (mode & 0x2) != 0; - } + private static int signed(int mode) { + return mode & 0x1; + } - private static boolean exact(int mode) { - return (mode & 0x4) == 0; - } + private static boolean exact(int mode) { + return (mode & 0x4) == 0; + } - private static long convertBuiltinInteger(Node inliningTarget, Object object, int signed, int targetTypeSize, boolean exact, PRaiseNode raiseNode) { - if (targetTypeSize != Integer.BYTES && targetTypeSize != Long.BYTES) { - throw raiseNode.raise(inliningTarget, PythonBuiltinClassType.SystemError, ErrorMessages.UNSUPPORTED_TARGET_SIZE, targetTypeSize); - } - if (object instanceof Integer value) { - return convertLong(inliningTarget, value, signed, targetTypeSize, exact, raiseNode); - } else if (object instanceof Long value) { - return convertLong(inliningTarget, value, signed, targetTypeSize, exact, raiseNode); - } else if (object instanceof PInt value) { - if (!exact) { - return targetTypeSize == Integer.BYTES ? value.intValue() : value.longValue(); - } - if (signed == 0 && value.isNegative()) { - throw raiseNegativeValue(inliningTarget, raiseNode); - } - try { - if (targetTypeSize == Integer.BYTES) { - if (signed != 0) { - return value.intValueExact(); - } else if (value.bitLength() <= Integer.SIZE) { - return value.intValue(); - } - } else if (signed != 0) { - return value.longValueExact(); - } else if (value.bitLength() <= Long.SIZE) { - return value.longValue(); - } - } catch (OverflowException e) { - // fall through - } - throw raiseOverflow(inliningTarget, raiseNode, targetTypeSize); - } - throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.INDEX_RETURNED_NON_INT, object); + @TruffleBoundary + private static long convertBuiltinInteger(Node inliningTarget, Object object, int signed, int targetTypeSize, boolean exact) throws OverflowException { + if (targetTypeSize != Integer.BYTES && targetTypeSize != Long.BYTES) { + throw PRaiseNode.raiseStatic(inliningTarget, PythonBuiltinClassType.SystemError, ErrorMessages.UNSUPPORTED_TARGET_SIZE, targetTypeSize); } - - private static long convertLong(Node inliningTarget, long value, int signed, int targetTypeSize, boolean exact, PRaiseNode raiseNode) { + if (object instanceof Boolean value) { + return value ? 1 : 0; + } else if (object instanceof Integer value) { + return convertLong(inliningTarget, value, signed, targetTypeSize, exact); + } else if (object instanceof Long value) { + return convertLong(inliningTarget, value, signed, targetTypeSize, exact); + } else if (object instanceof PInt value) { if (!exact) { - return targetTypeSize == Integer.BYTES ? (int) value : value; + return targetTypeSize == Integer.BYTES ? value.intValue() : value.longValue(); } - if (signed == 0 && value < 0) { - throw raiseNegativeValue(inliningTarget, raiseNode); + if (signed == 0 && value.isNegative()) { + throw raiseNegativeValue(inliningTarget); } - if (targetTypeSize == Integer.BYTES && (signed != 0 ? (int) value != value : Integer.toUnsignedLong((int) value) != value)) { - throw raiseOverflow(inliningTarget, raiseNode, targetTypeSize); + if (targetTypeSize == Integer.BYTES) { + if (signed != 0) { + return value.intValueExact(); + } else if (value.bitLength() <= Integer.SIZE) { + return value.intValue(); + } + } else if (signed != 0) { + return value.longValueExact(); + } else if (value.bitLength() <= Long.SIZE) { + return value.longValue(); } - return value; + throw OverflowException.INSTANCE; + } else if (object instanceof PythonAbstractNativeObject) { + throw PRaiseNode.raiseStatic(inliningTarget, NotImplementedError, ErrorMessages.CASTING_A_NATIVE_INT_OBJECT_IS_NOT_IMPLEMENTED_YET); } + throw PRaiseNode.raiseStatic(inliningTarget, TypeError, ErrorMessages.INDEX_RETURNED_NON_INT, object); + } - private static PException raiseNegativeValue(Node inliningTarget, PRaiseNode raiseNode) { - throw raiseNode.raise(inliningTarget, OverflowError, ErrorMessages.CANNOT_CONVERT_NEGATIVE_VALUE_TO_UNSIGNED_INT); + private static long convertLong(Node inliningTarget, long value, int signed, int targetTypeSize, boolean exact) throws OverflowException { + if (!exact) { + return targetTypeSize == Integer.BYTES ? (int) value : value; } - - private static PException raiseOverflow(Node inliningTarget, PRaiseNode raiseNode, int targetTypeSize) { - throw raiseNode.raise(inliningTarget, OverflowError, ErrorMessages.PYTHON_INT_TOO_LARGE_TO_CONV_TO_C_TYPE, targetTypeSize); + if (signed == 0 && value < 0) { + throw raiseNegativeValue(inliningTarget); } + if (targetTypeSize == Integer.BYTES && (signed != 0 ? (int) value != value : Integer.toUnsignedLong((int) value) != value)) { + throw OverflowException.INSTANCE; + } + return value; + } + + private static PException raiseNegativeValue(Node inliningTarget) { + throw PRaiseNode.raiseStatic(inliningTarget, OverflowError, ErrorMessages.CANNOT_CONVERT_NEGATIVE_VALUE_TO_UNSIGNED_INT); } @CApiBuiltin(ret = PyObjectTransfer, args = {LONG_LONG}, call = Ignored) @@ -298,7 +348,10 @@ long doGeneric(Object n, @Exclusive @Cached PRaiseNode raiseNode) { try { Object index = indexNode.execute(null, inliningTarget, n); - return GraalPyPrivate_Long_AsPrimitive.convertBuiltinInteger(inliningTarget, index, 0, Long.BYTES, true, raiseNode); + return convertBuiltinInteger(inliningTarget, index, 0, Long.BYTES, true); + } catch (OverflowException e) { + transformOverflow(inliningTarget, raiseNode); + return 0; } catch (PException e) { ensureTransformExcNode().execute(e); return 0; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructs.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructs.java index fff41f0c71..a5bea19834 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructs.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructs.java @@ -95,6 +95,7 @@ public enum CStructs { GraalPySingletons, GraalPyDeallocState, wchar_t, + long__, long__long, Py_ssize_t, GCState, diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyLongCheckNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyLongCheckNode.java index 157dc2e556..38f3bbc9f8 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyLongCheckNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyLongCheckNode.java @@ -101,7 +101,7 @@ static boolean doPInt(@SuppressWarnings("unused") PInt object) { } @Specialization - static boolean doNative(PythonAbstractNativeObject nativeObject) { + public static boolean doNative(PythonAbstractNativeObject nativeObject) { long obType = readPtrField(nativeObject.pointer, PyObject__ob_type); boolean isLongSubclass = (readLongField(obType, CFields.PyTypeObject__tp_flags) & TypeFlags.LONG_SUBCLASS) != 0L; assert IsBuiltinObjectProfile.profileObjectUncached(nativeObject, PythonBuiltinClassType.PInt) == isLongSubclass; From 2a070f7c8b6612ae13b037ca3e35ae0d52f40f37 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Wed, 9 Sep 2026 11:33:40 +0200 Subject: [PATCH 07/10] Expand PyLong conversion tests --- .../src/tests/cpyext/test_long.py | 100 +++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_long.py b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_long.py index 057259dd59..4e3335a01e 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_long.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_long.py @@ -48,6 +48,9 @@ max_long = 2 ** (long_bits - 1) - 1 min_long = -2 ** (long_bits - 1) max_ulong = 2 ** long_bits +longlong_bits = struct.calcsize('q') * 8 +max_longlong = 2 ** (longlong_bits - 1) - 1 +min_longlong = -2 ** (longlong_bits - 1) ulonglong_bits = struct.calcsize('Q') * 8 max_ulonglong = 2 ** ulonglong_bits size_t_bits = struct.calcsize('P') * 8 @@ -74,6 +77,13 @@ def _reference_as_long(args): return n +def _reference_as_longlong(args): + n = _reference_as_index(args[0]) + if n > max_longlong or n < min_longlong: + raise OverflowError("Python int too large to convert to C long") + return n + + def _reference_as_int(args): n = _reference_as_index(args[0]) if n > max_int or n < min_int: @@ -87,11 +97,30 @@ def _reference_as_unsigned_long(args): raise TypeError("an integer is required") if n < 0: raise OverflowError("can't convert negative value to unsigned int") - if n > max_ulong: + if n >= max_ulong: raise OverflowError("Python int too large to convert to C unsigned long") return n +def _reference_as_unsigned_longlong(args): + n = args[0] + if not isinstance(n, int): + raise TypeError("an integer is required") + if n < 0: + raise OverflowError("can't convert negative value to unsigned int") + if n >= max_ulonglong: + raise OverflowError("Python int too large to convert to C unsigned long") + return n + + +def _reference_as_unsigned_long_mask(args): + return _reference_as_index(args[0]) & (max_ulong - 1) + + +def _reference_as_unsigned_longlong_mask(args): + return _reference_as_index(args[0]) & (max_ulonglong - 1) + + def _reference_as_long_and_overflow(args): n = _reference_as_index(args[0]) if n > max_long: @@ -101,6 +130,15 @@ def _reference_as_long_and_overflow(args): return n, 0 +def _reference_as_longlong_and_overflow(args): + n = _reference_as_index(args[0]) + if n > max_longlong: + return -1, 1 + elif n < min_longlong: + return -1, -1 + return n, 0 + + def _reference_as_ssize_t(args): n = args[0] if not isinstance(n, int): @@ -201,8 +239,11 @@ class DummyNonInt(): class DummyIndexable: + def __init__(self, value=0xBEEF): + self.value = value + def __index__(self): - return 0xBEEF + return self.value def _int_examples(): @@ -216,6 +257,12 @@ def _int_examples(): (0x7fffffff,), (0xffffffff,), (-0xffffffff,), + (max_longlong,), + (max_longlong + 1,), + (min_longlong,), + (min_longlong - 1,), + (max_ulonglong - 1,), + (max_ulonglong,), (0x7fffffffffffffffffffffffffffffff,), (0xffffffffffffffffffffffffffffffff,), (-0xffffffffffffffffffffffffffffffff,), @@ -224,6 +271,8 @@ def _int_examples(): (0.3,), (DummyNonInt(),), (DummyIndexable(),), + (DummyIndexable(max_longlong + 1),), + (DummyIndexable(min_longlong - 1),), ] @@ -267,6 +316,15 @@ def test_native_long_subtype_has_native_layout(self): cmpfunc=unhandled_error_compare ) + test_PyLong_AsLongLong = CPyExtFunction( + _reference_as_longlong, + _int_examples, + resultspec="L", + argspec='O', + arguments=["PyObject* obj"], + cmpfunc=unhandled_error_compare + ) + test_PyLong_AsInt = CPyExtFunction( _reference_as_int, _int_examples, @@ -287,6 +345,17 @@ def test_native_long_subtype_has_native_layout(self): cmpfunc=unhandled_error_compare ) + test_PyLong_AsLongLongAndOverflow = CPyExtFunctionOutVars( + _reference_as_longlong_and_overflow, + _int_examples, + resultspec="Li", + argspec='O', + arguments=["PyObject* obj"], + resulttype="long long", + resultvars=["int overflow"], + cmpfunc=unhandled_error_compare + ) + test_PyLong_AsUnsignedLong = CPyExtFunction( _reference_as_unsigned_long, _int_examples, @@ -296,6 +365,33 @@ def test_native_long_subtype_has_native_layout(self): cmpfunc=unhandled_error_compare ) + test_PyLong_AsUnsignedLongLong = CPyExtFunction( + _reference_as_unsigned_longlong, + _int_examples, + resultspec="K", + argspec='O', + arguments=["PyObject* obj"], + cmpfunc=unhandled_error_compare + ) + + test_PyLong_AsUnsignedLongMask = CPyExtFunction( + _reference_as_unsigned_long_mask, + _int_examples, + resultspec="k", + argspec='O', + arguments=["PyObject* obj"], + cmpfunc=unhandled_error_compare + ) + + test_PyLong_AsUnsignedLongLongMask = CPyExtFunction( + _reference_as_unsigned_longlong_mask, + _int_examples, + resultspec="K", + argspec='O', + arguments=["PyObject* obj"], + cmpfunc=unhandled_error_compare + ) + test_PyLong_AsSsize_t = CPyExtFunction( _reference_as_ssize_t, _int_examples, From 3d40bf952b18ce786b2959844036176ecb4fc089 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Wed, 9 Sep 2026 20:08:37 +0200 Subject: [PATCH 08/10] Convert GraalPyPrivate_Long_FromLongLong to static builtin --- .../builtins/modules/cext/PythonCextLongBuiltins.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java index 0e993ca509..1f58e3a3ae 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java @@ -282,11 +282,8 @@ private static PException raiseNegativeValue(Node inliningTarget) { } @CApiBuiltin(ret = PyObjectTransfer, args = {LONG_LONG}, call = Ignored) - abstract static class GraalPyPrivate_Long_FromLongLong extends CApiUnaryBuiltinNode { - @Specialization - static long doSignedLong(long n) { - return n; - } + static long GraalPyPrivate_Long_FromLongLong(long n) { + return PythonToNativeInternalNode.executeNewRefUncached(n); } @CApiBuiltin(ret = PyObjectRawPointer, args = {UNSIGNED_LONG_LONG}, call = Ignored, acquireGil = false) From 0b39bb7c53e173cd6799b90170341abe50b34aef Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Fri, 11 Sep 2026 15:03:13 +0200 Subject: [PATCH 09/10] Restore inlining for PyLong primitive conversions --- .../python/builtins/modules/cext/PythonCextLongBuiltins.java | 1 - 1 file changed, 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java index 1f58e3a3ae..0b53261aa5 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java @@ -228,7 +228,6 @@ private static boolean exact(int mode) { return (mode & 0x4) == 0; } - @TruffleBoundary private static long convertBuiltinInteger(Node inliningTarget, Object object, int signed, int targetTypeSize, boolean exact) throws OverflowException { if (targetTypeSize != Integer.BYTES && targetTypeSize != Long.BYTES) { throw PRaiseNode.raiseStatic(inliningTarget, PythonBuiltinClassType.SystemError, ErrorMessages.UNSUPPORTED_TARGET_SIZE, targetTypeSize); From 7fadc4eb31e24cbc3959dc69949b818ca14bd2f5 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Mon, 14 Sep 2026 09:03:32 +0200 Subject: [PATCH 10/10] Use CApiTiming in static PyLong upcalls --- .../modules/cext/PythonCextLongBuiltins.java | 167 ++++++++++++------ 1 file changed, 112 insertions(+), 55 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java index 0b53261aa5..9d003abbea 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java @@ -77,6 +77,7 @@ import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor; +import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTiming; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonInternalNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.TransformPExceptionToNativeCachedNode; @@ -109,10 +110,17 @@ public final class PythonCextLongBuiltins { + private static final CApiTiming TIMING_PYLONG_GETINFO = CApiTiming.create(false, "PyLong_GetInfo"); + @CApiBuiltin(ret = PyObjectTransfer, args = {}, call = Direct) static long PyLong_GetInfo() { - Object result = SysModuleBuiltins.createIntInfo(PythonLanguage.get(null)); - return PythonToNativeInternalNode.executeNewRefUncached(result); + CApiTiming.enter(); + try { + Object result = SysModuleBuiltins.createIntInfo(PythonLanguage.get(null)); + return PythonToNativeInternalNode.executeNewRefUncached(result); + } finally { + CApiTiming.exit(TIMING_PYLONG_GETINFO); + } } @CApiBuiltin(ret = Py_ssize_t, args = {PyLongObject}, call = Ignored) @@ -126,10 +134,17 @@ static long getDC(Object n, } } + private static final CApiTiming TIMING_GRAALPYPRIVATE_LONG_FROMDOUBLE = CApiTiming.create(false, "GraalPyPrivate_Long_FromDouble"); + @CApiBuiltin(ret = PyObjectRawPointer, args = {ArgDescriptor.Double}, call = Ignored, acquireGil = false) static long GraalPyPrivate_Long_FromDouble(double d) { - Object result = PyLongFromDoubleNode.executeUncached(d); - return PythonToNativeInternalNode.executeNewRefUncached(result); + CApiTiming.enter(); + try { + Object result = PyLongFromDoubleNode.executeUncached(d); + return PythonToNativeInternalNode.executeNewRefUncached(result); + } finally { + CApiTiming.exit(TIMING_GRAALPYPRIVATE_LONG_FROMDOUBLE); + } } @CApiBuiltin(ret = PyObjectTransfer, args = {ConstCharPtrAsTruffleString, Int}, call = Ignored) @@ -280,30 +295,51 @@ private static PException raiseNegativeValue(Node inliningTarget) { throw PRaiseNode.raiseStatic(inliningTarget, OverflowError, ErrorMessages.CANNOT_CONVERT_NEGATIVE_VALUE_TO_UNSIGNED_INT); } + private static final CApiTiming TIMING_GRAALPYPRIVATE_LONG_FROMLONGLONG = CApiTiming.create(false, "GraalPyPrivate_Long_FromLongLong"); + @CApiBuiltin(ret = PyObjectTransfer, args = {LONG_LONG}, call = Ignored) static long GraalPyPrivate_Long_FromLongLong(long n) { - return PythonToNativeInternalNode.executeNewRefUncached(n); + CApiTiming.enter(); + try { + return PythonToNativeInternalNode.executeNewRefUncached(n); + } finally { + CApiTiming.exit(TIMING_GRAALPYPRIVATE_LONG_FROMLONGLONG); + } } + private static final CApiTiming TIMING_GRAALPYPRIVATE_LONG_FROMUNSIGNEDLONGLONG = CApiTiming.create(false, "GraalPyPrivate_Long_FromUnsignedLongLong"); + @CApiBuiltin(ret = PyObjectRawPointer, args = {UNSIGNED_LONG_LONG}, call = Ignored, acquireGil = false) static long GraalPyPrivate_Long_FromUnsignedLongLong(long n) { - Object result = n >= 0 ? n : PFactory.createInt(PythonLanguage.get(null), PInt.longToUnsignedBigInteger(n)); - return PythonToNativeInternalNode.executeNewRefUncached(result); + CApiTiming.enter(); + try { + Object result = n >= 0 ? n : PFactory.createInt(PythonLanguage.get(null), PInt.longToUnsignedBigInteger(n)); + return PythonToNativeInternalNode.executeNewRefUncached(result); + } finally { + CApiTiming.exit(TIMING_GRAALPYPRIVATE_LONG_FROMUNSIGNEDLONGLONG); + } } + private static final CApiTiming TIMING_GRAALPYPRIVATE_LONG_NUMBITS = CApiTiming.create(false, "GraalPyPrivate_Long_NumBits"); + @CApiBuiltin(ret = SIZE_T, args = {PyObjectRawPointer}, call = Ignored, acquireGil = false) static long GraalPyPrivate_Long_NumBits(long objPtr) { - Object obj = NativeToPythonInternalNode.executeUncached(objPtr, false); - if (obj instanceof Integer value) { - return Integer.SIZE - Integer.numberOfLeadingZeros(Math.abs(value)); - } else if (obj instanceof Long value) { - return Long.SIZE - Long.numberOfLeadingZeros(Math.abs(value)); - } else if (obj instanceof PInt value) { - return value.bitLength(); - } else if (obj instanceof Boolean value) { - return value ? 1 : 0; + CApiTiming.enter(); + try { + Object obj = NativeToPythonInternalNode.executeUncached(objPtr, false); + if (obj instanceof Integer value) { + return Integer.SIZE - Integer.numberOfLeadingZeros(Math.abs(value)); + } else if (obj instanceof Long value) { + return Long.SIZE - Long.numberOfLeadingZeros(Math.abs(value)); + } else if (obj instanceof PInt value) { + return value.bitLength(); + } else if (obj instanceof Boolean value) { + return value ? 1 : 0; + } + throw CompilerDirectives.shouldNotReachHere(); + } finally { + CApiTiming.exit(TIMING_GRAALPYPRIVATE_LONG_NUMBITS); } - throw CompilerDirectives.shouldNotReachHere(); } @CApiBuiltin(ret = Pointer, args = {PyObject}, call = Ignored) @@ -449,32 +485,39 @@ static Object convert(long charPtr, long size, int littleEndian, int signed, private static final int ALLOW_INDEX = 16; private static final int PYLONG_BITS_IN_DIGIT = 30; + private static final CApiTiming TIMING_PYLONG_ASNATIVEBYTES = CApiTiming.create(false, "PyLong_AsNativeBytes"); + @CApiBuiltin(ret = Py_ssize_t, args = {PyObjectRawPointer, Pointer, Py_ssize_t, Int}, call = Direct) static long PyLong_AsNativeBytes(long objectPtr, long buffer, long size, int flags) { - if (objectPtr == 0 || size < 0) { - throw PythonCextBuiltins.badInternalCall("PyLong_AsNativeBytes", objectPtr == 0 ? "object" : "size"); - } + CApiTiming.enter(); + try { + if (objectPtr == 0 || size < 0) { + throw PythonCextBuiltins.badInternalCall("PyLong_AsNativeBytes", objectPtr == 0 ? "object" : "size"); + } - Object object = NativeToPythonInternalNode.executeUncached(objectPtr, false); - Object integer = object; - if (!PyLongCheckNode.executeUncached(object)) { - if (flags != -1 && (flags & ALLOW_INDEX) != 0) { - integer = PyNumberIndexNode.executeUncached(object); - } else { - throw PRaiseNode.raiseStatic(null, TypeError, ErrorMessages.INTEGER_REQUIRED_GOT, object); + Object object = NativeToPythonInternalNode.executeUncached(objectPtr, false); + Object integer = object; + if (!PyLongCheckNode.executeUncached(object)) { + if (flags != -1 && (flags & ALLOW_INDEX) != 0) { + integer = PyNumberIndexNode.executeUncached(object); + } else { + throw PRaiseNode.raiseStatic(null, TypeError, ErrorMessages.INTEGER_REQUIRED_GOT, object); + } } - } - BigInteger value = CastToJavaBigIntegerNode.executeUncached(integer); - if (flags != -1 && (flags & REJECT_NEGATIVE) != 0 && value.signum() < 0) { - throw PRaiseNode.raiseStatic(null, ValueError, ErrorMessages.CANNOT_CONVERT_NEGATIVE_INT); - } + BigInteger value = CastToJavaBigIntegerNode.executeUncached(integer); + if (flags != -1 && (flags & REJECT_NEGATIVE) != 0 && value.signum() < 0) { + throw PRaiseNode.raiseStatic(null, ValueError, ErrorMessages.CANNOT_CONVERT_NEGATIVE_INT); + } - boolean littleEndian = resolveEndianness(flags); - if (size > 0) { - writeNativeBytes(buffer, size, value, littleEndian); + boolean littleEndian = resolveEndianness(flags); + if (size > 0) { + writeNativeBytes(buffer, size, value, littleEndian); + } + return requiredNativeBytesSize(value, size, flags); + } finally { + CApiTiming.exit(TIMING_PYLONG_ASNATIVEBYTES); } - return requiredNativeBytesSize(value, size, flags); } @TruffleBoundary @@ -526,33 +569,47 @@ private static long requiredNativeBytesSize(BigInteger value, long size, int fla return result; } + private static final CApiTiming TIMING_PYLONG_FROMNATIVEBYTES = CApiTiming.create(false, "PyLong_FromNativeBytes"); + @CApiBuiltin(ret = PyObjectTransfer, args = {CONST_VOID_PTR, SIZE_T, Int}, call = Direct) static long PyLong_FromNativeBytes(long buffer, long size, int flags) { - if (buffer == 0) { - throw PythonCextBuiltins.badInternalCall("PyLong_FromNativeBytes", "buffer"); - } - if (size != (int) size) { - throw PRaiseNode.raiseStatic(null, OverflowError, ErrorMessages.BYTE_ARRAY_TOO_LONG_TO_CONVERT_TO_INT); + CApiTiming.enter(); + try { + if (buffer == 0) { + throw PythonCextBuiltins.badInternalCall("PyLong_FromNativeBytes", "buffer"); + } + if (size != (int) size) { + throw PRaiseNode.raiseStatic(null, OverflowError, ErrorMessages.BYTE_ARRAY_TOO_LONG_TO_CONVERT_TO_INT); + } + boolean littleEndian = resolveEndianness(flags); + boolean signed = flags == -1 || (flags & UNSIGNED_BUFFER) == 0; + byte[] bytes = readByteArrayElements(buffer, 0, (int) size); + Object result = IntNodes.PyLongFromByteArray.executeUncached(bytes, littleEndian, signed); + return PythonToNativeInternalNode.executeNewRefUncached(result); + } finally { + CApiTiming.exit(TIMING_PYLONG_FROMNATIVEBYTES); } - boolean littleEndian = resolveEndianness(flags); - boolean signed = flags == -1 || (flags & UNSIGNED_BUFFER) == 0; - byte[] bytes = readByteArrayElements(buffer, 0, (int) size); - Object result = IntNodes.PyLongFromByteArray.executeUncached(bytes, littleEndian, signed); - return PythonToNativeInternalNode.executeNewRefUncached(result); } + private static final CApiTiming TIMING_PYLONG_FROMUNSIGNEDNATIVEBYTES = CApiTiming.create(false, "PyLong_FromUnsignedNativeBytes"); + @CApiBuiltin(ret = PyObjectTransfer, args = {CONST_VOID_PTR, SIZE_T, Int}, call = Direct) static long PyLong_FromUnsignedNativeBytes(long buffer, long size, int flags) { - if (buffer == 0) { - throw PythonCextBuiltins.badInternalCall("PyLong_FromUnsignedNativeBytes", "buffer"); - } - if (size != (int) size) { - throw PRaiseNode.raiseStatic(null, OverflowError, ErrorMessages.BYTE_ARRAY_TOO_LONG_TO_CONVERT_TO_INT); + CApiTiming.enter(); + try { + if (buffer == 0) { + throw PythonCextBuiltins.badInternalCall("PyLong_FromUnsignedNativeBytes", "buffer"); + } + if (size != (int) size) { + throw PRaiseNode.raiseStatic(null, OverflowError, ErrorMessages.BYTE_ARRAY_TOO_LONG_TO_CONVERT_TO_INT); + } + boolean littleEndian = resolveEndianness(flags); + byte[] bytes = readByteArrayElements(buffer, 0, (int) size); + Object result = IntNodes.PyLongFromByteArray.executeUncached(bytes, littleEndian, false); + return PythonToNativeInternalNode.executeNewRefUncached(result); + } finally { + CApiTiming.exit(TIMING_PYLONG_FROMUNSIGNEDNATIVEBYTES); } - boolean littleEndian = resolveEndianness(flags); - byte[] bytes = readByteArrayElements(buffer, 0, (int) size); - Object result = IntNodes.PyLongFromByteArray.executeUncached(bytes, littleEndian, false); - return PythonToNativeInternalNode.executeNewRefUncached(result); } private static boolean resolveEndianness(int flags) {