Skip to content

Commit 61565f2

Browse files
committed
[GR-69714]: Introduce storing 32-bit values into the PyObject* handles.
PullRequest: graalpython/3997
2 parents 7609c43 + cb82feb commit 61565f2

File tree

29 files changed

+411
-281
lines changed

29 files changed

+411
-281
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,4 @@ pom-mx.xml
9797
/*-venv/
9898
.aider*
9999
*.iprof.gz
100+
compile_commands.json
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3+
#
4+
# The Universal Permissive License (UPL), Version 1.0
5+
#
6+
# Subject to the condition set forth below, permission is hereby granted to any
7+
# person obtaining a copy of this software, associated documentation and/or
8+
# data (collectively the "Software"), free of charge and under any and all
9+
# copyright rights in the Software, and any and all patent rights owned or
10+
# freely licensable by each licensor hereunder covering either (i) the
11+
# unmodified Software as contributed to or provided by such licensor, or (ii)
12+
# the Larger Works (as defined below), to deal in both
13+
#
14+
# (a) the Software, and
15+
#
16+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
17+
# one is included with the Software each a "Larger Work" to which the Software
18+
# is contributed by such licensors),
19+
#
20+
# without restriction, including without limitation the rights to copy, create
21+
# derivative works of, display, perform, and distribute the Software and make,
22+
# use, sell, offer for sale, import, export, have made, and have sold the
23+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
24+
# either these or other terms.
25+
#
26+
# This license is subject to the following condition:
27+
#
28+
# The above copyright notice and either this complete permission notice or at a
29+
# minimum a reference to the UPL must be included in all copies or substantial
30+
# portions of the Software.
31+
#
32+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
# SOFTWARE.
39+
40+
code = """
41+
#include "Python.h"
42+
43+
PyObject* simple_method(PyObject* mod, PyObject* arg) {
44+
return PyFloat_FromDouble(PyFloat_AsDouble(arg));
45+
}
46+
47+
static PyMethodDef MyModuleMethods[] = {
48+
{"as_double", simple_method, METH_O, NULL},
49+
{NULL, NULL, 0, NULL} // Sentinel
50+
};
51+
52+
static PyModuleDef c_min_method_module = {
53+
PyModuleDef_HEAD_INIT,
54+
"int_float_method_module",
55+
NULL,
56+
-1,
57+
MyModuleMethods
58+
};
59+
60+
PyMODINIT_FUNC
61+
PyInit_c_min_method_module(void)
62+
{
63+
return PyModule_Create(&c_min_method_module);
64+
}
65+
"""
66+
67+
68+
ccompile("c_min_method_module", code)
69+
from c_min_method_module import as_double
70+
71+
# ~igv~: function_root_count_at
72+
def count(num):
73+
print("###### NUM: " + str(num))
74+
for i in range(num):
75+
if 42 != as_double(42):
76+
raise RuntimeError()
77+
return 0
78+
79+
80+
def measure(num):
81+
result = count(num)
82+
return result
83+
84+
85+
def __benchmark__(num=1000000):
86+
return measure(num)

graalpython/com.oracle.graal.python.cext/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ function(require_var var)
3636
endfunction()
3737

3838
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
39+
add_custom_target(compile_commands_target ALL
40+
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/compile_commands.json" "${CMAKE_SOURCE_DIR}")
3941

4042
require_var(GRAALPY_PARENT_DIR)
4143
require_var(CAPI_INC_DIR)

graalpython/com.oracle.graal.python.cext/compile_flags.txt

Lines changed: 0 additions & 2 deletions
This file was deleted.

graalpython/com.oracle.graal.python.cext/include/cpython/pystate.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2020, 2024, Oracle and/or its affiliates.
1+
/* Copyright (c) 2020, 2025, Oracle and/or its affiliates.
22
* Copyright (C) 1996-2020 Python Software Foundation
33
*
44
* Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2

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 int32_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_int64(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/include/internal/pycore_long.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,22 @@ extern void _PyLong_FiniTypes(PyInterpreterState *interp);
6464
/* GraalVM change
6565
#define _PyLong_SMALL_INTS _Py_SINGLETON(small_ints)
6666
*/
67-
#define _PyLong_SMALL_INT_PTRS (PyThreadState_GET()->small_ints)
6867

6968
// Return a borrowed reference to the zero singleton.
7069
// The function cannot return NULL.
7170
static inline PyObject* _PyLong_GetZero(void)
7271
/* GraalVM change
7372
{ return (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS]; }
7473
*/
75-
{ return _PyLong_SMALL_INT_PTRS[_PY_NSMALLNEGINTS]; }
74+
{ return int32_to_pointer(0); }
7675

7776
// Return a borrowed reference to the one singleton.
7877
// The function cannot return NULL.
7978
static inline PyObject* _PyLong_GetOne(void)
8079
/* GraalVM change
8180
{ return (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS+1]; }
8281
*/
83-
{ return _PyLong_SMALL_INT_PTRS[_PY_NSMALLNEGINTS+1]; }
82+
{ return int32_to_pointer(1); }
8483

8584
static inline PyObject* _PyLong_FromUnsignedChar(unsigned char i)
8685
{

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_int64(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;

0 commit comments

Comments
 (0)