Skip to content

Commit a4a84e3

Browse files
committed
[GR-51613] Avoid generic PyObject_RichCompare.
PullRequest: graalpython/3172
2 parents 12e2e93 + 897cfdb commit a4a84e3

File tree

24 files changed

+1051
-1047
lines changed

24 files changed

+1051
-1047
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
42+
#ifndef SRC_HANDLES_H_
43+
#define SRC_HANDLES_H_
44+
45+
#include <object.h>
46+
47+
#define MANAGED_REFCNT 10
48+
#define HANDLE_BASE 0x8000000000000000ULL
49+
#define IMMORTAL_REFCNT (INT64_MAX >> 1)
50+
51+
#ifdef GRAALVM_PYTHON_LLVM_MANAGED
52+
#include <graalvm/llvm/polyglot.h>
53+
#define points_to_py_handle_space(PTR) polyglot_is_value((PTR))
54+
#define stub_to_pointer(STUB_PTR) (STUB_PTR)
55+
#define pointer_to_stub(O) (O)
56+
#else /* GRAALVM_PYTHON_LLVM_MANAGED */
57+
58+
#define points_to_py_handle_space(PTR) ((((uintptr_t) (PTR)) & HANDLE_BASE) != 0)
59+
#define stub_to_pointer(STUB_PTR) (((uintptr_t) (STUB_PTR)) | HANDLE_BASE)
60+
#define pointer_to_stub(PTR) ((PyObject *)(((uintptr_t) (PTR)) & ~HANDLE_BASE))
61+
62+
#endif /* GRAALVM_PYTHON_LLVM_MANAGED */
63+
64+
#endif /* SRC_HANDLES_H_ */

graalpython/com.oracle.graal.python.cext/include/object.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2018, 2023, Oracle and/or its affiliates.
1+
/* Copyright (c) 2018, 2024, Oracle and/or its affiliates.
22
* Copyright (C) 1996-2020 Python Software Foundation
33
*
44
* Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
@@ -57,6 +57,8 @@ A standard interface exists for objects that contain an array of items
5757
whose size is determined when the object is allocated.
5858
*/
5959

60+
#include "graalpy/handles.h"
61+
6062
/* Py_DEBUG implies Py_REF_DEBUG. */
6163
#if defined(Py_DEBUG) && !defined(Py_REF_DEBUG)
6264
# define Py_REF_DEBUG
@@ -135,10 +137,15 @@ PyAPI_FUNC(int) Py_Is(PyObject *x, PyObject *y);
135137
PyAPI_FUNC(Py_ssize_t) _Py_REFCNT(const PyObject *ob);
136138
#define Py_REFCNT(ob) _Py_REFCNT(_PyObject_CAST_CONST(ob))
137139

138-
139140
PyAPI_FUNC(PyTypeObject*) _Py_TYPE(const PyObject *ob);
141+
140142
// bpo-39573: The Py_SET_TYPE() function must be used to set an object type.
143+
144+
#if defined(GRAALVM_PYTHON) && !defined(GRAALVM_PYTHON_LLVM_MANAGED) && defined(NDEBUG)
145+
#define Py_TYPE(ob) (pointer_to_stub(ob)->ob_type)
146+
#else
141147
#define Py_TYPE(ob) _Py_TYPE(_PyObject_CAST_CONST(ob))
148+
#endif
142149

143150
PyAPI_FUNC(Py_ssize_t) _Py_SIZE(const PyVarObject *ob);
144151
// bpo-39573: The Py_SET_SIZE() function must be used to set an object size.

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

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2296,10 +2296,6 @@ PyAPI_FUNC(Py_ssize_t) PyObject_LengthHint(PyObject* a, Py_ssize_t b) {
22962296
PyAPI_FUNC(PyObject*) PyObject_Repr(PyObject* a) {
22972297
return GraalPyObject_Repr(a);
22982298
}
2299-
#undef PyObject_RichCompare
2300-
PyAPI_FUNC(PyObject*) PyObject_RichCompare(PyObject* a, PyObject* b, int c) {
2301-
return GraalPyObject_RichCompare(a, b, c);
2302-
}
23032299
#undef PyObject_SetArenaAllocator
23042300
PyAPI_FUNC(void) PyObject_SetArenaAllocator(PyObjectArenaAllocator* a) {
23052301
FUNC_NOT_IMPLEMENTED
@@ -3288,10 +3284,6 @@ PyAPI_FUNC(char*) Py_EncodeLocale(const wchar_t* a, size_t* b) {
32883284
PyAPI_FUNC(void) Py_EndInterpreter(PyThreadState* a) {
32893285
FUNC_NOT_IMPLEMENTED
32903286
}
3291-
#undef Py_EnterRecursiveCall
3292-
PyAPI_FUNC(int) Py_EnterRecursiveCall(const char* a) {
3293-
return GraalPy_EnterRecursiveCall(a);
3294-
}
32953287
#undef Py_Exit
32963288
PyAPI_FUNC(void) Py_Exit(int a) {
32973289
FUNC_NOT_IMPLEMENTED
@@ -3376,10 +3368,6 @@ PyAPI_FUNC(void) Py_InitializeEx(int a) {
33763368
PyAPI_FUNC(PyStatus) Py_InitializeFromConfig(const PyConfig* a) {
33773369
FUNC_NOT_IMPLEMENTED
33783370
}
3379-
#undef Py_LeaveRecursiveCall
3380-
PyAPI_FUNC(void) Py_LeaveRecursiveCall() {
3381-
GraalPy_LeaveRecursiveCall();
3382-
}
33833371
#undef Py_Main
33843372
PyAPI_FUNC(int) Py_Main(int a, wchar_t** b) {
33853373
FUNC_NOT_IMPLEMENTED

graalpython/com.oracle.graal.python.cext/src/capi.h

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,6 @@ typedef struct {
251251
BUILTIN(PyObject_IsTrue, int, PyObject*) \
252252
BUILTIN(PyObject_LengthHint, Py_ssize_t, PyObject*, Py_ssize_t) \
253253
BUILTIN(PyObject_Repr, PyObject*, PyObject*) \
254-
BUILTIN(PyObject_RichCompare, PyObject*, PyObject*, PyObject*, int) \
255254
BUILTIN(PyObject_SetDoc, int, PyObject*, const char*) \
256255
BUILTIN(PyObject_SetItem, int, PyObject*, PyObject*, PyObject*) \
257256
BUILTIN(PyObject_Size, Py_ssize_t, PyObject*) \
@@ -368,6 +367,7 @@ typedef struct {
368367
BUILTIN(PyTruffle_Debug, int, void*) \
369368
BUILTIN(PyTruffle_DebugTrace, void) \
370369
BUILTIN(PyTruffle_Ellipsis, PyObject*) \
370+
BUILTIN(PyTruffle_EnterRecursiveCall, int, const char*) \
371371
BUILTIN(PyTruffle_False, PyObject*) \
372372
BUILTIN(PyTruffle_FatalErrorFunc, void, const char*, const char*, int) \
373373
BUILTIN(PyTruffle_FileSystemDefaultEncoding, PyObject*) \
@@ -376,6 +376,7 @@ typedef struct {
376376
BUILTIN(PyTruffle_GetMaxNativeMemory, size_t) \
377377
BUILTIN(PyTruffle_HashConstant, long, int) \
378378
BUILTIN(PyTruffle_InitBuiltinTypesAndStructs, void, void*) \
379+
BUILTIN(PyTruffle_LeaveRecursiveCall, void) \
379380
BUILTIN(PyTruffle_LogString, void, int, const char*) \
380381
BUILTIN(PyTruffle_MemoryViewFromBuffer, PyObject*, void*, PyObject*, Py_ssize_t, int, Py_ssize_t, const char*, int, void*, void*, void*, void*) \
381382
BUILTIN(PyTruffle_Native_Options, int) \
@@ -438,9 +439,7 @@ typedef struct {
438439
BUILTIN(Py_CompileString, PyObject*, const char*, const char*, int) \
439440
BUILTIN(Py_CompileStringExFlags, PyObject*, const char*, const char*, int, PyCompilerFlags*, int) \
440441
BUILTIN(Py_CompileStringObject, PyObject*, const char*, PyObject*, int, PyCompilerFlags*, int) \
441-
BUILTIN(Py_EnterRecursiveCall, int, const char*) \
442442
BUILTIN(Py_GenericAlias, PyObject*, PyObject*, PyObject*) \
443-
BUILTIN(Py_LeaveRecursiveCall, void) \
444443
BUILTIN(Py_get_PyASCIIObject_length, Py_ssize_t, PyASCIIObject*) \
445444
BUILTIN(Py_get_PyASCIIObject_state_ascii, unsigned int, PyASCIIObject*) \
446445
BUILTIN(Py_get_PyASCIIObject_state_compact, unsigned int, PyASCIIObject*) \
@@ -802,27 +801,6 @@ static inline int get_method_flags_wrapper(int flags) {
802801
return JWRAPPER_UNSUPPORTED;
803802
}
804803

805-
#define MANAGED_REFCNT 10
806-
#define HANDLE_BASE 0x8000000000000000ULL
807-
#define IMMORTAL_REFCNT (INT64_MAX >> 1)
808-
809-
#ifdef GRAALVM_PYTHON_LLVM_MANAGED
810-
#define points_to_py_handle_space(PTR) polyglot_is_value((PTR))
811-
#else /* GRAALVM_PYTHON_LLVM_MANAGED */
812-
813-
#define points_to_py_handle_space(PTR) ((((uintptr_t) (PTR)) & HANDLE_BASE) != 0)
814-
815-
static MUST_INLINE PyObject *stub_to_pointer(PyObject *stub_ptr)
816-
{
817-
return ((uintptr_t) stub_ptr) | HANDLE_BASE;
818-
}
819-
820-
static MUST_INLINE PyObject *pointer_to_stub(PyObject *o)
821-
{
822-
return ((uintptr_t) o) & ~HANDLE_BASE;
823-
}
824-
#endif /* GRAALVM_PYTHON_LLVM_MANAGED */
825-
826804
void register_native_slots(PyTypeObject* managed_class, PyGetSetDef* getsets, PyMemberDef* members);
827805

828806
// export the SizeT arg parse functions, because we use them in contrast to cpython on windows for core modules that we link dynamically

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

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 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
@@ -93,3 +93,33 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
9393
defs, defcount,
9494
kwdefs, closure);
9595
}
96+
97+
#ifndef GRAALVM_PYTHON_LLVM_MANAGED
98+
#if defined(__GNUC__)
99+
static __thread int _tls_recursion_depth = 0;
100+
#elif defined(_MSC_VER)
101+
static __declspec(thread) int _tls_recursion_depth = 0;
102+
#else
103+
#error "don't know how to declare thread local variable"
104+
#endif
105+
#endif /* GRAALVM_PYTHON_LLVM_MANAGED */
106+
107+
int Py_EnterRecursiveCall(const char *where) {
108+
#ifdef GRAALVM_PYTHON_LLVM_MANAGED
109+
return GraalPyTruffle_EnterRecursiveCall(where);
110+
#else /* GRAALVM_PYTHON_LLVM_MANAGED */
111+
if (++_tls_recursion_depth > Py_DEFAULT_RECURSION_LIMIT) {
112+
PyErr_SetString(PyExc_RecursionError, where);
113+
return -1;
114+
}
115+
return 0;
116+
#endif /* GRAALVM_PYTHON_LLVM_MANAGED */
117+
}
118+
119+
void Py_LeaveRecursiveCall() {
120+
#ifdef GRAALVM_PYTHON_LLVM_MANAGED
121+
GraalPyTruffle_LeaveRecursiveCall();
122+
#else /* GRAALVM_PYTHON_LLVM_MANAGED */
123+
_tls_recursion_depth--;
124+
#endif /* GRAALVM_PYTHON_LLVM_MANAGED */
125+
}

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

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,115 @@ PyObject_Print(PyObject *op, FILE *fp, int flags)
187187
return ret;
188188
}
189189

190-
// 751
190+
/* For Python 3.0.1 and later, the old three-way comparison has been
191+
completely removed in favour of rich comparisons. PyObject_Compare() and
192+
PyObject_Cmp() are gone, and the builtin cmp function no longer exists.
193+
The old tp_compare slot has been renamed to tp_as_async, and should no
194+
longer be used. Use tp_richcompare instead.
195+
196+
See (*) below for practical amendments.
197+
198+
tp_richcompare gets called with a first argument of the appropriate type
199+
and a second object of an arbitrary type. We never do any kind of
200+
coercion.
201+
202+
The tp_richcompare slot should return an object, as follows:
203+
204+
NULL if an exception occurred
205+
NotImplemented if the requested comparison is not implemented
206+
any other false value if the requested comparison is false
207+
any other true value if the requested comparison is true
208+
209+
The PyObject_RichCompare[Bool]() wrappers raise TypeError when they get
210+
NotImplemented.
211+
212+
(*) Practical amendments:
213+
214+
- If rich comparison returns NotImplemented, == and != are decided by
215+
comparing the object pointer (i.e. falling back to the base object
216+
implementation).
217+
218+
*/
219+
220+
/* Map rich comparison operators to their swapped version, e.g. LT <--> GT */
221+
int _Py_SwappedOp[] = {Py_GT, Py_GE, Py_EQ, Py_NE, Py_LT, Py_LE};
222+
223+
static const char * const opstrings[] = {"<", "<=", "==", "!=", ">", ">="};
224+
225+
/* Perform a rich comparison, raising TypeError when the requested comparison
226+
operator is not supported. */
227+
static PyObject *
228+
do_richcompare(PyObject *v, PyObject *w, int op)
229+
{
230+
richcmpfunc f;
231+
PyObject *res;
232+
int checked_reverse_op = 0;
233+
234+
if (!Py_IS_TYPE(v, Py_TYPE(w)) &&
235+
PyType_IsSubtype(Py_TYPE(w), Py_TYPE(v)) &&
236+
(f = Py_TYPE(w)->tp_richcompare) != NULL) {
237+
checked_reverse_op = 1;
238+
res = (*f)(w, v, _Py_SwappedOp[op]);
239+
if (res != Py_NotImplemented)
240+
return res;
241+
Py_DECREF(res);
242+
}
243+
if ((f = Py_TYPE(v)->tp_richcompare) != NULL) {
244+
res = (*f)(v, w, op);
245+
if (res != Py_NotImplemented)
246+
return res;
247+
Py_DECREF(res);
248+
}
249+
if (!checked_reverse_op && (f = Py_TYPE(w)->tp_richcompare) != NULL) {
250+
res = (*f)(w, v, _Py_SwappedOp[op]);
251+
if (res != Py_NotImplemented)
252+
return res;
253+
Py_DECREF(res);
254+
}
255+
/* If neither object implements it, provide a sensible default
256+
for == and !=, but raise an exception for ordering. */
257+
switch (op) {
258+
case Py_EQ:
259+
res = (v == w) ? Py_True : Py_False;
260+
break;
261+
case Py_NE:
262+
res = (v != w) ? Py_True : Py_False;
263+
break;
264+
default:
265+
PyErr_Format(PyExc_TypeError,
266+
"'%s' not supported between instances of '%.100s' and '%.100s'",
267+
opstrings[op],
268+
Py_TYPE(v)->tp_name,
269+
Py_TYPE(w)->tp_name);
270+
return NULL;
271+
}
272+
Py_INCREF(res);
273+
return res;
274+
}
275+
276+
/* Perform a rich comparison with object result. This wraps do_richcompare()
277+
with a check for NULL arguments and a recursion check. */
278+
279+
PyObject *
280+
PyObject_RichCompare(PyObject *v, PyObject *w, int op)
281+
{
282+
assert(Py_LT <= op && op <= Py_GE);
283+
if (v == NULL || w == NULL) {
284+
if (!PyErr_Occurred()) {
285+
PyErr_BadInternalCall();
286+
}
287+
return NULL;
288+
}
289+
if (Py_EnterRecursiveCall(" in comparison")) {
290+
return NULL;
291+
}
292+
PyObject *res = do_richcompare(v, w, op);
293+
Py_LeaveRecursiveCall();
294+
return res;
295+
}
296+
297+
/* Perform a rich comparison with integer result. This wraps
298+
PyObject_RichCompare(), returning -1 for error, 0 for false, 1 for true. */
191299
int
192300
PyObject_RichCompareBool(PyObject *v, PyObject *w, int op)
193301
{

0 commit comments

Comments
 (0)