Skip to content

Commit 827558f

Browse files
committed
Avoid upcalls for long and float objects where we can
1 parent c5f61d4 commit 827558f

File tree

13 files changed

+253
-51
lines changed

13 files changed

+253
-51
lines changed

graalpython/com.oracle.graal.python.cext/include/graalpy/handles.h

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2024, 2025, 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
@@ -45,12 +45,50 @@
4545
#include <object.h>
4646

4747
#define MANAGED_REFCNT 10
48-
#define HANDLE_BASE 0x8000000000000000ULL
48+
49+
/*
50+
* We cannot do NaN tagging. Even if we rely on running systems that use at
51+
* most 48 bits for their adresses (so not running with PML5/la57 enabled), we
52+
* do not control all allocations, so PyObject* allocated somewhere else will
53+
* not be tagged and thus are indistinguishable from subnormal floating point
54+
* values or +0.0 for NULL values. Additionally, we know that at least cffi
55+
* uses tagging of the low 3 alignment bits o PyObject* and thus those also
56+
* need to be 0 for anything we hand out. So we can realistically only use the
57+
* upper 16 and lower 45 bits, which isn't enough to do NaN tagging (unless we
58+
* want to do weird stuff like disallow certain double bit patterns). So the
59+
* thing we can do is to tag managed pointers, 32-bit integers, and double
60+
* values that fit into single-precision floats without loss.
61+
*/
62+
63+
// Aligned with the same constants in CApiTransitions. Update comment there if
64+
// you change or move these.
65+
#define HANDLE_TAG_BIT ((uint64_t)(1ULL << 63))
66+
#define INTEGER_TAG_BIT ((uint64_t)(1ULL << 62))
67+
#define FLOAT_TAG_BIT ((uint64_t)(1ULL << 61))
68+
4969
// IMMORTAL_REFCNT value is aligned with include/object.h
5070
#define IMMORTAL_REFCNT 0xFFFFFFFFL
71+
#define _35_BIT_MASK (0xFFFFFFFFULL << 3)
72+
73+
#define points_to_py_handle_space(PTR) (((uint64_t)(uintptr_t)(PTR)) & HANDLE_TAG_BIT)
74+
#define points_to_py_int_handle(PTR) (((uint64_t)(uintptr_t)(PTR)) & INTEGER_TAG_BIT)
75+
#define points_to_py_float_handle(PTR) (((uint64_t)(uintptr_t)(PTR)) & FLOAT_TAG_BIT)
76+
77+
#define stub_to_pointer(STUB_PTR) ((uintptr_t)(((uint64_t)(uintptr_t)(PTR)) | HANDLE_TAG_BIT))
78+
#define int_to_pointer(INT) ((uintptr_t)((((uint64_t)(uint32_t)(INT) << 3) & _35_BIT_MASK) | HANDLE_TAG_BIT | INTEGER_TAG_BIT))
79+
static inline PyObject* float_to_pointer(float dbl) {
80+
uint32_t float_bits;
81+
memcpy(&float_bits, &dbl, sizeof(float));
82+
return (PyObject *)(uintptr_t)(((((uint64_t)float_bits) << 3) & _35_BIT_MASK) | HANDLE_TAG_BIT | FLOAT_TAG_BIT);
83+
}
5184

52-
#define points_to_py_handle_space(PTR) ((((uintptr_t) (PTR)) & HANDLE_BASE) != 0)
53-
#define stub_to_pointer(STUB_PTR) (((uintptr_t) (STUB_PTR)) | HANDLE_BASE)
54-
#define pointer_to_stub(PTR) ((PyObject *)(((uintptr_t) (PTR)) & ~HANDLE_BASE))
85+
#define pointer_to_stub(PTR) ((PyObject*)(((uint64_t)(uintptr_t)(PTR)) & ~HANDLE_TAG_BIT))
86+
#define pointer_to_long(PTR) ((int64_t)(int32_t)(((uint64_t)(uintptr_t)(PTR)) >> 3))
87+
static inline double pointer_to_double(PyObject* ptr) {
88+
uint32_t float_bits = (uint32_t)(((uint64_t)(uintptr_t)ptr) >> 3);
89+
float value;
90+
memcpy(&value, &float_bits, sizeof(float));
91+
return (double)value;
92+
}
5593

5694
#endif /* SRC_HANDLES_H_ */

graalpython/com.oracle.graal.python.cext/include/internal/pycore_gc.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ static inline PyGC_Head* _Py_AS_GC(PyObject *op) {
3333
#define _PyGC_Head_UNUSED PyGC_Head
3434

3535
// GraalPy change
36-
#define _PyGCHead_UNTAG(PTR) ((PyGC_Head *)(((uintptr_t) (PTR)) & ~HANDLE_BASE))
36+
#define _PyGCHead_UNTAG(PTR) ((PyGC_Head *)(((uintptr_t) (PTR)) & ~HANDLE_TAG_BIT))
3737

3838
/* True if the object is currently tracked by the GC. */
3939
static inline int _PyObject_GC_IS_TRACKED(PyObject *op) {

graalpython/com.oracle.graal.python.cext/src/floatobject.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,20 @@ PyFloat_GetInfo(void)
145145

146146
return floatinfo;
147147
}
148+
#endif
148149

149150
PyObject *
150151
PyFloat_FromDouble(double fval)
151152
{
152153
PyFloatObject *op;
154+
// GraalPy: different implementation
155+
if (fval == (float)fval) {
156+
return float_to_pointer(fval);
157+
} else {
158+
return GraalPyPrivate_Float_FromDouble(fval);
159+
}
160+
}
161+
#if 0 // GraalPy change
153162
#if PyFloat_MAXFREELIST > 0
154163
struct _Py_float_state *state = get_float_state();
155164
op = state->free_list;
@@ -323,6 +332,9 @@ PyFloat_AsDouble(PyObject *op)
323332

324333
// GraalPy change: upcall for managed
325334
if (points_to_py_handle_space(op)) {
335+
if (points_to_py_int_handle(op)) {
336+
return (double)pointer_to_long(op);
337+
}
326338
return GraalPyPrivate_Float_AsDouble(op);
327339
}
328340

@@ -2661,6 +2673,9 @@ PyFloat_Unpack8(const char *data, int le)
26612673

26622674
double GraalPyFloat_AS_DOUBLE(PyObject *op) {
26632675
if (points_to_py_handle_space(op)) {
2676+
if (points_to_py_float_handle(op)) {
2677+
return pointer_to_double(op);
2678+
}
26642679
return ((GraalPyFloatObject*) pointer_to_stub(op))->ob_fval;
26652680
} else {
26662681
return _PyFloat_CAST(op)->ob_fval;

graalpython/com.oracle.graal.python.cext/src/longobject.c

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ PyLong_FromLong(long ival)
337337
if (IS_SMALL_INT(ival)) {
338338
return get_small_int((sdigit)ival);
339339
}
340-
return GraalPyPrivate_Long_FromLongLong((long long) ival);
340+
return PyLong_FromLongLong((long long) ival);
341341
}
342342

343343
#if 0 // GraalPy change
@@ -477,6 +477,10 @@ PyLong_AsLongAndOverflow(PyObject *vv, int *overflow)
477477
PyErr_BadInternalCall();
478478
return -1;
479479
}
480+
if (points_to_py_int_handle(vv)) {
481+
*overflow = 0;
482+
return pointer_to_long(vv);
483+
}
480484
long result = (long) GraalPyPrivate_Long_AsPrimitive(vv, MODE_COERCE_SIGNED, sizeof(long));
481485
if (result == -1L && PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_OverflowError)) {
482486
PyErr_Clear();
@@ -494,6 +498,9 @@ long
494498
PyLong_AsLong(PyObject *obj)
495499
{
496500
// GraalPy change: different implementation
501+
if (points_to_py_int_handle(obj)) {
502+
return pointer_to_long(obj);
503+
}
497504
return (long) GraalPyPrivate_Long_AsPrimitive(obj, MODE_COERCE_SIGNED, sizeof(long));
498505
}
499506

@@ -520,6 +527,9 @@ _PyLong_AsInt(PyObject *obj)
520527

521528
Py_ssize_t
522529
PyLong_AsSsize_t(PyObject *vv) {
530+
if (points_to_py_int_handle(vv)) {
531+
return pointer_to_long(vv);
532+
}
523533
return (Py_ssize_t) GraalPyPrivate_Long_AsPrimitive(vv, MODE_PINT_SIGNED, sizeof(Py_ssize_t));
524534
}
525535

@@ -534,6 +544,14 @@ PyLong_AsUnsignedLong(PyObject *vv)
534544
PyErr_BadInternalCall();
535545
return (unsigned long) -1;
536546
}
547+
if (points_to_py_int_handle(vv)) {
548+
long value = pointer_to_long(vv);
549+
if (value < 0) {
550+
PyErr_SetString(PyExc_OverflowError, "can't convert negative value to unsigned int");
551+
return (unsigned long) -1;
552+
}
553+
return (unsigned long) value;
554+
}
537555
return (unsigned long) GraalPyPrivate_Long_AsPrimitive(vv, MODE_PINT_UNSIGNED, sizeof(unsigned long));
538556
}
539557

@@ -544,6 +562,14 @@ size_t
544562
PyLong_AsSize_t(PyObject *vv)
545563
{
546564
// GraalPy change: different implementation
565+
if (points_to_py_int_handle(vv)) {
566+
long value = pointer_to_long(vv);
567+
if (value < 0) {
568+
PyErr_SetString(PyExc_OverflowError, "can't convert negative value to unsigned int");
569+
return (size_t) -1;
570+
}
571+
return (size_t) value;
572+
}
547573
return (size_t) GraalPyPrivate_Long_AsPrimitive(vv, MODE_PINT_UNSIGNED, sizeof(size_t));
548574
}
549575

@@ -588,6 +614,9 @@ PyLong_AsUnsignedLongMask(PyObject *op)
588614
PyErr_BadInternalCall();
589615
return (unsigned long) -1;
590616
}
617+
if (points_to_py_int_handle(op)) {
618+
return pointer_to_long(op);
619+
}
591620
return (unsigned long) GraalPyPrivate_Long_AsPrimitive(op, MODE_COERCE_MASK, sizeof(unsigned long));
592621
}
593622

@@ -957,7 +986,11 @@ PyLong_FromLongLong(long long ival)
957986
if (IS_SMALL_INT(ival)) {
958987
return get_small_int((sdigit)ival);
959988
}
960-
return GraalPyPrivate_Long_FromLongLong(ival);
989+
if ((int)ival == ival) {
990+
return int_to_pointer(ival);
991+
} else {
992+
return GraalPyPrivate_Long_FromLongLong(ival);
993+
}
961994
}
962995

963996
/* Create a new int object from a C Py_ssize_t. */
@@ -969,7 +1002,7 @@ PyLong_FromSsize_t(Py_ssize_t ival)
9691002
if (IS_SMALL_INT(ival)) {
9701003
return get_small_int((sdigit)ival);
9711004
}
972-
return GraalPyPrivate_Long_FromLongLong((long long) ival);
1005+
return PyLong_FromLongLong((long long) ival);
9731006
}
9741007

9751008
/* Get a C long long int from an int object or any object that has an
@@ -983,6 +1016,9 @@ PyLong_AsLongLong(PyObject *vv)
9831016
PyErr_BadInternalCall();
9841017
return -1;
9851018
}
1019+
if (points_to_py_int_handle(vv)) {
1020+
return pointer_to_long(vv);
1021+
}
9861022
return (long long) GraalPyPrivate_Long_AsPrimitive(vv, MODE_COERCE_SIGNED, sizeof(long long));
9871023
}
9881024

@@ -997,6 +1033,14 @@ PyLong_AsUnsignedLongLong(PyObject *vv)
9971033
PyErr_BadInternalCall();
9981034
return (unsigned long long)-1;
9991035
}
1036+
if (points_to_py_int_handle(vv)) {
1037+
long value = pointer_to_long(vv);
1038+
if (value < 0) {
1039+
PyErr_SetString(PyExc_OverflowError, "can't convert negative value to unsigned int");
1040+
return (unsigned long long) -1;
1041+
}
1042+
return (unsigned long long) value;
1043+
}
10001044
return (unsigned long long) GraalPyPrivate_Long_AsPrimitive(vv, MODE_PINT_UNSIGNED, sizeof(unsigned long long));
10011045
}
10021046

@@ -1042,6 +1086,9 @@ PyLong_AsUnsignedLongLongMask(PyObject *op)
10421086
PyErr_BadInternalCall();
10431087
return (unsigned long long)-1;
10441088
}
1089+
if (points_to_py_int_handle(op)) {
1090+
return pointer_to_long(op);
1091+
}
10451092
return (unsigned long long) GraalPyPrivate_Long_AsPrimitive(op, MODE_COERCE_MASK, sizeof(unsigned long long));
10461093
}
10471094

@@ -6102,12 +6149,31 @@ PyUnstable_Long_CompactValue(const PyLongObject* op) {
61026149

61036150
#undef PyUnstable_Long_CompactValue
61046151

6105-
Py_ssize_t
6106-
PyUnstable_Long_CompactValue(const PyLongObject *op) {
6152+
Py_ssize_t PyUnstable_Long_CompactValue(const PyLongObject *op) {
6153+
if (points_to_py_int_handle(op)) {
6154+
return pointer_to_long(op);
6155+
}
61076156
return GraalPyPrivate_Long_AsPrimitive((PyObject*) op, MODE_PINT_SIGNED, sizeof(Py_ssize_t));
61086157
}
61096158

61106159
// GraalPy additions
61116160
uintptr_t GraalPyPrivate_Long_lv_tag(const PyLongObject *op) {
6161+
if (points_to_py_int_handle(op)) {
6162+
int64_t t = pointer_to_long(op);
6163+
if (t == 0) {
6164+
return SIGN_ZERO;
6165+
}
6166+
int64_t sign = 0;
6167+
if (t < 0) {
6168+
sign = SIGN_NEGATIVE;
6169+
t = -t;
6170+
}
6171+
int64_t size = 0;
6172+
while (t != 0) {
6173+
++size;
6174+
t >>= PYLONG_BITS_IN_DIGIT;
6175+
}
6176+
return (size << NON_SIZE_BITS) | sign;
6177+
}
61126178
return GET_SLOT_SPECIAL(op, PyLongObject, long_value_lv_tag, long_value.lv_tag);
61136179
}

graalpython/com.oracle.graal.python.cext/src/object.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ _Py_IncRef(PyObject *o)
309309
{
310310
// GraalPy change: different implementation
311311
const Py_ssize_t refcnt = Py_REFCNT(o);
312-
if (refcnt != IMMORTAL_REFCNT)
312+
if (refcnt != _Py_IMMORTAL_REFCNT)
313313
{
314314
Py_SET_REFCNT(o, refcnt + 1);
315315
if (refcnt == MANAGED_REFCNT) {
@@ -337,7 +337,7 @@ _Py_DecRef(PyObject *o)
337337
return;
338338
}
339339
const Py_ssize_t refcnt = Py_REFCNT(o);
340-
if (refcnt != IMMORTAL_REFCNT) {
340+
if (refcnt != _Py_IMMORTAL_REFCNT) {
341341
const Py_ssize_t updated_refcnt = refcnt - 1;
342342
Py_SET_REFCNT(o, updated_refcnt);
343343
if (updated_refcnt != 0) {
@@ -531,6 +531,9 @@ int
531531
_PyObject_IsFreed(PyObject *op)
532532
{
533533
if (points_to_py_handle_space(op)) {
534+
if (points_to_py_float_handle(op) || points_to_py_int_handle(op)) {
535+
return 0;
536+
}
534537
return GraalPyPrivate_Object_IsFreed(op);
535538
}
536539
#if 0 // GraalPy change
@@ -2847,6 +2850,9 @@ Py_ssize_t Py_REFCNT(PyObject *obj) {
28472850
Py_ssize_t res;
28482851
if (points_to_py_handle_space(obj))
28492852
{
2853+
if (points_to_py_int_handle(obj) || points_to_py_float_handle(obj)) {
2854+
return _Py_IMMORTAL_REFCNT;
2855+
}
28502856
res = pointer_to_stub(obj)->ob_refcnt;
28512857
#ifndef NDEBUG
28522858
if (GraalPyPrivate_Debug_CAPI() && GraalPyPrivate_GET_PyObject_ob_refcnt(obj) != res)
@@ -2897,6 +2903,12 @@ PyTypeObject* GraalPy_TYPE(PyObject *a) {
28972903
PyTypeObject *res;
28982904
if (points_to_py_handle_space(a))
28992905
{
2906+
if (points_to_py_float_handle(a)) {
2907+
return &PyFloat_Type;
2908+
}
2909+
if (points_to_py_int_handle(a)) {
2910+
return &PyLong_Type;
2911+
}
29002912
res = pointer_to_stub(a)->ob_type;
29012913
#ifndef NDEBUG
29022914
if (GraalPyPrivate_Debug_CAPI() && GraalPyPrivate_GET_PyObject_ob_type(a) != res)

graalpython/com.oracle.graal.python.cext/src/typeobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5418,7 +5418,7 @@ type_is_gc(PyTypeObject *type)
54185418
* just test for 'points_to_py_handle_space' but this is not applicable for
54195419
* 'PyTypeObject' because there we really allocate and fill a native
54205420
* mirror. */
5421-
return type->tp_flags & Py_TPFLAGS_HEAPTYPE && Py_REFCNT(type) != IMMORTAL_REFCNT;
5421+
return type->tp_flags & Py_TPFLAGS_HEAPTYPE && Py_REFCNT(type) != _Py_IMMORTAL_REFCNT;
54225422
}
54235423

54245424

graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/nodes/MemMoveNodeTests.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,10 @@ public Object execute(VirtualFrame frame) {
113113

114114
}.getCallTarget().call();
115115

116-
Assert.assertEquals(1, GetItemScalarNode.executeUncached(storage, 0));
117-
Assert.assertEquals(2, GetItemScalarNode.executeUncached(storage, 1));
118-
Assert.assertEquals(2, GetItemScalarNode.executeUncached(storage, 2));
119-
Assert.assertEquals(2, GetItemScalarNode.executeUncached(storage, 3));
120-
Assert.assertEquals(3, GetItemScalarNode.executeUncached(storage, 4));
116+
Assert.assertEquals(1L, ((Number) GetItemScalarNode.executeUncached(storage, 0)).longValue());
117+
Assert.assertEquals(2L, ((Number) GetItemScalarNode.executeUncached(storage, 1)).longValue());
118+
Assert.assertEquals(2L, ((Number) GetItemScalarNode.executeUncached(storage, 2)).longValue());
119+
Assert.assertEquals(2L, ((Number) GetItemScalarNode.executeUncached(storage, 3)).longValue());
120+
Assert.assertEquals(3L, ((Number) GetItemScalarNode.executeUncached(storage, 4)).longValue());
121121
}
122122
}

0 commit comments

Comments
 (0)