Skip to content

Commit 33da79b

Browse files
committed
Implement _PyLong_FromByteArray
1 parent f4e0578 commit 33da79b

File tree

9 files changed

+82
-48
lines changed

9 files changed

+82
-48
lines changed

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_long.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,3 +454,21 @@ class TestPyLong(CPyExtTestCase):
454454
arguments=["PyObject* n", "int base"],
455455
cmpfunc=unhandled_error_compare,
456456
)
457+
458+
test__PyLong_FromByteArray = CPyExtFunction(
459+
lambda args: int.from_bytes(args[0], 'little' if args[1] else 'big', signed=args[2]),
460+
lambda: (
461+
(b'', 1, 1),
462+
(b'\x00\x0009', 0, 1),
463+
(b'90\x00\x00\x00\x00\x00\x00', 1, 1),
464+
(b'\xff\xff\xcf\xc7', 0, 1),
465+
(b'\xff\xff\xff\xff\xff\xff\xff\xab', 0, 0),
466+
(b'\xab\xff\xff\xff\xff\xff\xff\xff', 1, 0),
467+
(b'\xff\xff\xff\xff\xff\xff\xff\xff', 0, 1),
468+
(b'\xff\xff\xff\xff\xff\xff\xff\xff', 1, 1),
469+
),
470+
resultspec="O",
471+
argspec="y#ii",
472+
arguments=["const char* bytes", "Py_ssize_t size", "int little_endian", "int is_signed"],
473+
cmpfunc=unhandled_error_compare,
474+
)

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextLongBuiltins.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError;
4444
import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Direct;
4545
import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Ignored;
46+
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_UNSIGNED_CHAR_PTR;
4647
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Int;
4748
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.LONG_LONG;
4849
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Pointer;
@@ -64,6 +65,7 @@
6465
import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApi5BuiltinNode;
6566
import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBinaryBuiltinNode;
6667
import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBuiltin;
68+
import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiQuaternaryBuiltinNode;
6769
import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiTernaryBuiltinNode;
6870
import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiUnaryBuiltinNode;
6971
import com.oracle.graal.python.builtins.objects.cext.PythonNativeVoidPtr;
@@ -76,6 +78,7 @@
7678
import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess;
7779
import com.oracle.graal.python.builtins.objects.ints.IntBuiltins;
7880
import com.oracle.graal.python.builtins.objects.ints.IntBuiltins.NegNode;
81+
import com.oracle.graal.python.builtins.objects.ints.IntNodes;
7982
import com.oracle.graal.python.builtins.objects.ints.PInt;
8083
import com.oracle.graal.python.lib.PyLongFromDoubleNode;
8184
import com.oracle.graal.python.nodes.ErrorMessages;
@@ -464,4 +467,20 @@ static Object convert(TruffleString s, int base,
464467
return intNode.executeWith(null, s, base);
465468
}
466469
}
470+
471+
@CApiBuiltin(ret = PyObjectTransfer, args = {CONST_UNSIGNED_CHAR_PTR, SIZE_T, Int, Int}, call = Direct)
472+
abstract static class _PyLong_FromByteArray extends CApiQuaternaryBuiltinNode {
473+
@Specialization
474+
static Object convert(Object charPtr, long size, int littleEndian, int signed,
475+
@Bind("this") Node inliningTarget,
476+
@Cached CStructAccess.ReadByteNode readByteNode,
477+
@Cached IntNodes.PyLongFromByteArray fromByteArray,
478+
@Cached PRaiseNode.Lazy raiseNode) {
479+
if (size != (int) size) {
480+
throw raiseNode.get(inliningTarget).raise(OverflowError, ErrorMessages.BYTE_ARRAY_TOO_LONG_TO_CONVERT_TO_INT);
481+
}
482+
byte[] bytes = readByteNode.readByteArray(charPtr, (int) size);
483+
return fromByteArray.execute(inliningTarget, bytes, littleEndian == 0, signed != 0);
484+
}
485+
}
467486
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cjkcodecs/MultibyteIncrementalEncoderBuiltins.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ static Object getstate(MultibyteIncrementalEncoderObject self,
286286
// for the encoder object.
287287
// memcpy(statebytes + statesize, self.state.c, MULTIBYTECODECSTATE);
288288
// statesize += MULTIBYTECODECSTATE;
289-
Object stateobj = fromByteArray.execute(inliningTarget, statebytes, false);
289+
Object stateobj = fromByteArray.execute(inliningTarget, statebytes, false, true);
290290
assert (stateobj instanceof PInt); // since statebytes.length > 8, we will get a PInt
291291
writeHiddenAttrNode.execute(inliningTarget, (PInt) stateobj, HiddenAttr.ENCODER_OBJECT, self.state);
292292
return stateobj;

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/pickle/PUnpickler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,7 @@ protected Object longFromBytes(byte[] data, boolean bigEndian) {
701701
CompilerDirectives.transferToInterpreterAndInvalidate();
702702
pyLongFromByteArray = insert(IntNodesFactory.PyLongFromByteArrayNodeGen.create());
703703
}
704-
return pyLongFromByteArray.executeCached(data, bigEndian);
704+
return pyLongFromByteArray.executeCached(data, bigEndian, true);
705705
}
706706

707707
protected void setAttribute(VirtualFrame frame, Object object, Object key, Object value) {

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@
5656
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_PY_SSIZE_T_PTR;
5757
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_PY_UCS4;
5858
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_PY_UNICODE;
59-
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_UNSIGNED_CHAR_PTR;
6059
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_VOID_PTR;
6160
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_VOID_PTR_LIST;
6261
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.CONST_WCHAR_PTR;
@@ -1066,7 +1065,6 @@ public final class CApiFunction {
10661065
@CApiBuiltin(name = "_PyLong_DivmodNear", ret = PyObject, args = {PyObject, PyObject}, call = NotImplemented)
10671066
@CApiBuiltin(name = "_PyLong_Format", ret = PyObject, args = {PyObject, Int}, call = NotImplemented)
10681067
@CApiBuiltin(name = "_PyLong_Frexp", ret = Double, args = {PyLongObject, PY_SSIZE_T_PTR}, call = NotImplemented)
1069-
@CApiBuiltin(name = "_PyLong_FromByteArray", ret = PyObject, args = {CONST_UNSIGNED_CHAR_PTR, SIZE_T, Int, Int}, call = NotImplemented)
10701068
@CApiBuiltin(name = "_PyLong_FromBytes", ret = PyObject, args = {ConstCharPtrAsTruffleString, Py_ssize_t, Int}, call = NotImplemented)
10711069
@CApiBuiltin(name = "_PyLong_GCD", ret = PyObject, args = {PyObject, PyObject}, call = NotImplemented)
10721070
@CApiBuiltin(name = "_PyLong_Lshift", ret = PyObject, args = {PyObject, SIZE_T}, call = NotImplemented)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ public enum ArgDescriptor {
184184
CONST_PY_UNICODE("const Py_UNICODE*"),
185185
CONST_PYCONFIG_PTR("const PyConfig*"),
186186
CONST_PYPRECONFIG_PTR("const PyPreConfig*"),
187-
CONST_UNSIGNED_CHAR_PTR("const unsigned char*"),
187+
CONST_UNSIGNED_CHAR_PTR(ArgBehavior.Pointer, "const unsigned char*"),
188188
CONST_VOID_PTR(ArgBehavior.Pointer, "const void*"),
189189
CONST_VOID_PTR_LIST("const void**"),
190190
CONST_WCHAR_PTR(ArgBehavior.Pointer, "const wchar_t*"),

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/ints/IntNodes.java

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -173,52 +173,40 @@ static byte[] doPInt(Node inliningTarget, PInt value, int size, boolean bigEndia
173173
}
174174

175175
/**
176-
* Equivalent to CPython's {@code _PyLong_FromByteArray}. View the n unsigned bytes as a binary
177-
* integer in base 256, and return a Python int with the same numeric value.
176+
* Equivalent to CPython's {@code _PyLong_FromByteArray}.
178177
*/
179178
@GenerateInline(inlineByDefault = true)
180179
public abstract static class PyLongFromByteArray extends Node {
181-
public abstract Object execute(Node inliningTarget, byte[] data, boolean bigEndian);
180+
public abstract Object execute(Node inliningTarget, byte[] data, boolean bigEndian, boolean signed);
182181

183-
public final Object executeCached(byte[] data, boolean bigEndian) {
184-
return execute(this, data, bigEndian);
185-
}
186-
187-
protected static boolean fitsInLong(byte[] data) {
188-
return data.length <= Long.BYTES;
182+
public final Object executeCached(byte[] data, boolean bigEndian, boolean signed) {
183+
return execute(this, data, bigEndian, signed);
189184
}
190185

191186
protected static int asWellSizedData(int len) {
192-
switch (len) {
193-
case 1:
194-
case 2:
195-
case 4:
196-
case 8:
197-
return len;
198-
default:
199-
return -1;
200-
}
187+
return switch (len) {
188+
case 1, 2, 4, 8 -> len;
189+
default -> -1;
190+
};
201191
}
202192

203-
@Specialization(guards = "data.length == cachedDataLen", limit = "4")
204-
static Object doLong(byte[] data, boolean bigEndian,
193+
@Specialization(guards = {"!signed", "data.length == cachedDataLen"}, limit = "4")
194+
static long doLong(byte[] data, boolean bigEndian, @SuppressWarnings("unused") boolean signed,
205195
@Cached("asWellSizedData(data.length)") int cachedDataLen) {
206196
NumericSupport support = bigEndian ? NumericSupport.bigEndian() : NumericSupport.littleEndian();
207197
return support.getLong(data, 0, cachedDataLen);
208198
}
209199

210-
@Specialization(guards = "fitsInLong(data)")
211-
static long doArbitraryBytesLong(byte[] data, boolean bigEndian) {
212-
NumericSupport support = bigEndian ? NumericSupport.bigEndian() : NumericSupport.littleEndian();
213-
BigInteger integer = support.getBigInteger(data, 0);
214-
return PInt.longValue(integer);
215-
}
216-
217-
@Specialization(guards = "!fitsInLong(data)")
218-
static Object doPInt(byte[] data, boolean bigEndian,
200+
@Specialization
201+
static Object doOther(byte[] data, boolean bigEndian, boolean signed,
219202
@Cached(inline = false) PythonObjectFactory factory) {
220203
NumericSupport support = bigEndian ? NumericSupport.bigEndian() : NumericSupport.littleEndian();
221-
return factory.createInt(support.getBigInteger(data, 0));
204+
BigInteger integer = support.getBigInteger(data, signed);
205+
if (PInt.bigIntegerFitsInLong(integer)) {
206+
return PInt.longValue(integer);
207+
} else {
208+
return factory.createInt(support.getBigInteger(data, signed));
209+
}
222210
}
223211
}
224212
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,6 +1383,7 @@ public abstract class ErrorMessages {
13831383
public static final TruffleString INVALID_CONVERSION_SPECIFICATION = tsLiteral("Invalid conversion specification");
13841384
public static final TruffleString INTERNAL_EXCEPTION_OCCURED = tsLiteral("internal exception occurred");
13851385
public static final TruffleString ATTRIBUTE_TYPE_VALUE_MUST_BE_BOOL = tsLiteral("attribute type value must be bool");
1386+
public static final TruffleString BYTE_ARRAY_TOO_LONG_TO_CONVERT_TO_INT = tsLiteral("byte array too long to convert to int");
13861387

13871388
public static final TruffleString INVALID_SEQ_ITEM = tsLiteral("sequence item %d: expected str instance, %p found");
13881389
public static final TruffleString NEW_X_ISNT_TYPE_OBJ = tsLiteral("%s.__new__(X): X is not a type object (%p)");

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/NumericSupport.java

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -289,22 +289,32 @@ public void putLong(byte[] buffer, int index, long value, int numBytes) throws I
289289
}
290290
}
291291

292-
public BigInteger getBigInteger(byte[] buffer, int index) {
293-
return getBigInteger(buffer, index, buffer.length - index);
292+
public BigInteger getBigInteger(byte[] buffer, boolean signed) {
293+
return getBigInteger(buffer, 0, buffer.length, signed);
294294
}
295295

296296
@TruffleBoundary
297-
public BigInteger getBigInteger(byte[] buffer, int index, int numBytes) throws IndexOutOfBoundsException {
298-
assert numBytes <= buffer.length - index;
299-
final byte[] bytes;
300-
if (index == 0 && numBytes == buffer.length) {
301-
bytes = PythonUtils.arrayCopyOfRange(buffer, index, index + numBytes);
302-
} else {
303-
bytes = buffer;
297+
public BigInteger getBigInteger(byte[] buffer, int offset, int length, boolean signed) throws IndexOutOfBoundsException {
298+
assert length <= buffer.length - offset;
299+
if (length == 0) {
300+
return BigInteger.ZERO;
304301
}
305-
// bytes are always in big endian order
306-
if (!bigEndian) {
307-
reverse(bytes);
302+
/*
303+
* BigInteger always expects signed big-endian. For unsigned, we prepend a 0 byte to the
304+
* number as a dummy positive sign.
305+
*/
306+
if (bigEndian && signed) {
307+
return new BigInteger(buffer, offset, length);
308+
}
309+
int dstOffset = signed ? 0 : 1;
310+
byte[] bytes = new byte[length + dstOffset];
311+
if (bigEndian) {
312+
System.arraycopy(buffer, offset, bytes, dstOffset, length);
313+
} else {
314+
// Need to reverse to make it big endian
315+
for (int i = 0; i < length; i++) {
316+
bytes[bytes.length - i - 1] = buffer[offset + i];
317+
}
308318
}
309319
return new BigInteger(bytes);
310320
}

0 commit comments

Comments
 (0)