Skip to content

Commit 082dbf7

Browse files
authored
gh-133395: add option for extension modules to specialize BINARY_OP/SUBSCR, apply to arrays (#133396)
1 parent 07f416a commit 082dbf7

14 files changed

+171
-44
lines changed

Include/cpython/object.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ typedef struct {
143143
* backwards-compatibility */
144144
typedef Py_ssize_t printfunc;
145145

146+
/* Specialize a binary op by setting the descriptor pointer */
147+
struct _PyBinopSpecializationDescr;
148+
typedef int (*binop_specialize_func)(PyObject *v, PyObject *w, int oparg,
149+
struct _PyBinopSpecializationDescr **descr);
150+
146151
// If this structure is modified, Doc/includes/typestruct.h should be updated
147152
// as well.
148153
struct _typeobject {
@@ -233,6 +238,13 @@ struct _typeobject {
233238
/* bitset of which type-watchers care about this type */
234239
unsigned char tp_watched;
235240

241+
/* callback that may specialize BINARY_OP
242+
* this is an experimental API based on the ideas in the paper
243+
* Cross Module Quickening - The Curious Case of C Extensions
244+
* by Felix Berlakovich and Stefan Brunthaler.
245+
*/
246+
binop_specialize_func tp_binop_specialize;
247+
236248
/* Number of tp_version_tag values used.
237249
* Set to _Py_ATTR_CACHE_UNUSED if the attribute cache is
238250
* disabled for this type (e.g. due to custom MRO entries).

Include/internal/pycore_code.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,13 +482,18 @@ adaptive_counter_backoff(_Py_BackoffCounter counter) {
482482
/* Specialization Extensions */
483483

484484
/* callbacks for an external specialization */
485+
486+
struct _PyBinopSpecializationDescr;
487+
485488
typedef int (*binaryopguardfunc)(PyObject *lhs, PyObject *rhs);
486-
typedef PyObject *(*binaryopactionfunc)(PyObject *lhs, PyObject *rhs);
489+
typedef PyObject* (*binaryopactionfunc)(PyObject *lhs, PyObject *rhs);
490+
typedef void (*binaryopfreefunc)(struct _PyBinopSpecializationDescr *descr);
487491

488-
typedef struct {
492+
typedef struct _PyBinopSpecializationDescr {
489493
int oparg;
490494
binaryopguardfunc guard;
491495
binaryopactionfunc action;
496+
binaryopfreefunc free;
492497
} _PyBinaryOpSpecializationDescr;
493498

494499
/* Comparison bit masks. */

Include/internal/pycore_opcode_metadata.h

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_metadata.h

Lines changed: 1 addition & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/typeslots.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,5 @@
9393
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030E0000
9494
/* New in 3.14 */
9595
#define Py_tp_token 83
96+
#define Py_tp_binop_specialize 84
9697
#endif

Lib/test/test_sys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1776,7 +1776,7 @@ def delx(self): del self.__x
17761776
check((1,2,3), vsize('') + self.P + 3*self.P)
17771777
# type
17781778
# static type: PyTypeObject
1779-
fmt = 'P2nPI13Pl4Pn9Pn12PIPc'
1779+
fmt = 'P2nPI13Pl4Pn9Pn12PI3Pc'
17801780
s = vsize(fmt)
17811781
check(int, s)
17821782
typeid = 'n' if support.Py_GIL_DISABLED else ''
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add option for extension modules to specialize ``BINARY_OP`` instructions.
2+
Applied to ``array`` objects.

Modules/arraymodule.c

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include "pycore_modsupport.h" // _PyArg_NoKeywords()
1515
#include "pycore_moduleobject.h" // _PyModule_GetState()
1616

17+
#include "opcode.h" // binary op opargs (NB_*)
18+
1719
#include <stddef.h> // offsetof()
1820
#include <stdbool.h>
1921

@@ -848,6 +850,10 @@ array_richcompare(PyObject *v, PyObject *w, int op)
848850
return res;
849851
}
850852

853+
static int
854+
array_binop_specialize(PyObject *v, PyObject *w, int oparg,
855+
_PyBinaryOpSpecializationDescr **descr);
856+
851857
static Py_ssize_t
852858
array_length(PyObject *op)
853859
{
@@ -2963,6 +2969,8 @@ static PyType_Slot array_slots[] = {
29632969
{Py_tp_alloc, PyType_GenericAlloc},
29642970
{Py_tp_new, array_new},
29652971
{Py_tp_traverse, array_tp_traverse},
2972+
{Py_tp_token, Py_TP_USE_SPEC},
2973+
{Py_tp_binop_specialize, array_binop_specialize},
29662974

29672975
/* as sequence */
29682976
{Py_sq_length, array_length},
@@ -2995,6 +3003,70 @@ static PyType_Spec array_spec = {
29953003
.slots = array_slots,
29963004
};
29973005

3006+
static inline int
3007+
array_subscr_guard(PyObject *lhs, PyObject *rhs)
3008+
{
3009+
PyObject *exc = PyErr_GetRaisedException();
3010+
int ret = PyType_GetBaseByToken(Py_TYPE(lhs), &array_spec, NULL);
3011+
if (ret < 0) {
3012+
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
3013+
PyErr_Clear();
3014+
ret = 0;
3015+
}
3016+
}
3017+
_PyErr_ChainExceptions1(exc);
3018+
return ret;
3019+
}
3020+
3021+
static PyObject *
3022+
array_subscr_action(PyObject *lhs, PyObject *rhs)
3023+
{
3024+
return array_subscr(lhs, rhs);
3025+
}
3026+
3027+
static void
3028+
array_subscr_free(_PyBinaryOpSpecializationDescr* descr)
3029+
{
3030+
if (descr != NULL) {
3031+
PyMem_Free(descr);
3032+
}
3033+
}
3034+
3035+
static int
3036+
array_binop_specialize(PyObject *v, PyObject *w, int oparg,
3037+
_PyBinaryOpSpecializationDescr **descr)
3038+
{
3039+
array_state *state = find_array_state_by_type(Py_TYPE(v));
3040+
3041+
if (!array_Check(v, state)) {
3042+
return 0;
3043+
}
3044+
3045+
*descr = NULL;
3046+
switch(oparg) {
3047+
case NB_SUBSCR:
3048+
if (array_subscr_guard(v, w)) {
3049+
*descr = (_PyBinaryOpSpecializationDescr*)PyMem_Malloc(
3050+
sizeof(_PyBinaryOpSpecializationDescr));
3051+
if (*descr == NULL) {
3052+
PyErr_NoMemory();
3053+
return -1;
3054+
}
3055+
**descr = (_PyBinaryOpSpecializationDescr) {
3056+
.oparg = oparg,
3057+
.guard = array_subscr_guard,
3058+
.action = array_subscr_action,
3059+
.free = array_subscr_free,
3060+
};
3061+
return 1;
3062+
}
3063+
break;
3064+
}
3065+
3066+
return 0;
3067+
}
3068+
3069+
29983070
/*********************** Array Iterator **************************/
29993071

30003072
/*[clinic input]

Objects/typeslots.inc

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/bytecodes.c

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -801,9 +801,19 @@ dummy_func(
801801
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
802802
_PyBinaryOpSpecializationDescr *d = (_PyBinaryOpSpecializationDescr*)descr;
803803
assert(INLINE_CACHE_ENTRIES_BINARY_OP == 5);
804-
assert(d && d->guard);
804+
assert(d);
805+
assert(d->guard);
805806
int res = d->guard(left_o, right_o);
806-
DEOPT_IF(!res);
807+
ERROR_IF(res < 0);
808+
if (res == 0) {
809+
if (d->free) {
810+
d->free(d);
811+
}
812+
_PyBinaryOpCache *cache = (_PyBinaryOpCache *)(this_instr+1);
813+
write_ptr(cache->external_cache, NULL);
814+
this_instr->op.code = BINARY_OP;
815+
DEOPT_IF(true);
816+
}
807817
}
808818

809819
pure op(_BINARY_OP_EXTEND, (descr/4, left, right -- res)) {
@@ -816,6 +826,7 @@ dummy_func(
816826

817827
PyObject *res_o = d->action(left_o, right_o);
818828
DECREF_INPUTS();
829+
ERROR_IF(res_o == NULL);
819830
res = PyStackRef_FromPyObjectSteal(res_o);
820831
}
821832

Python/executor_cases.c.h

Lines changed: 4 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/generated_cases.c.h

Lines changed: 24 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/optimizer_cases.c.h

Lines changed: 1 addition & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/specialize.c

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2534,7 +2534,7 @@ LONG_FLOAT_ACTION(compactlong_float_multiply, *)
25342534
LONG_FLOAT_ACTION(compactlong_float_true_div, /)
25352535
#undef LONG_FLOAT_ACTION
25362536

2537-
static _PyBinaryOpSpecializationDescr binaryop_extend_descrs[] = {
2537+
static const _PyBinaryOpSpecializationDescr binaryop_extend_builtins[] = {
25382538
/* long-long arithmetic */
25392539
{NB_OR, compactlongs_guard, compactlongs_or},
25402540
{NB_AND, compactlongs_guard, compactlongs_and},
@@ -2560,14 +2560,41 @@ static int
25602560
binary_op_extended_specialization(PyObject *lhs, PyObject *rhs, int oparg,
25612561
_PyBinaryOpSpecializationDescr **descr)
25622562
{
2563-
size_t n = sizeof(binaryop_extend_descrs)/sizeof(_PyBinaryOpSpecializationDescr);
2564-
for (size_t i = 0; i < n; i++) {
2565-
_PyBinaryOpSpecializationDescr *d = &binaryop_extend_descrs[i];
2563+
/* We are currently using this only for NB_SUBSCR, which is not
2564+
* commutative. Will need to revisit this function when we use
2565+
* this for operators which are.
2566+
*/
2567+
2568+
typedef _PyBinaryOpSpecializationDescr descr_type;
2569+
size_t size = Py_ARRAY_LENGTH(binaryop_extend_builtins);
2570+
for (size_t i = 0; i < size; i++) {
2571+
descr_type *d = (descr_type *)&binaryop_extend_builtins[i];
2572+
assert(d != NULL);
2573+
assert(d->guard != NULL);
25662574
if (d->oparg == oparg && d->guard(lhs, rhs)) {
25672575
*descr = d;
25682576
return 1;
25692577
}
25702578
}
2579+
2580+
PyTypeObject *lhs_type = Py_TYPE(lhs);
2581+
if (lhs_type->tp_binop_specialize != NULL) {
2582+
int ret = lhs_type->tp_binop_specialize(lhs, rhs, oparg, descr);
2583+
if (ret < 0) {
2584+
return -1;
2585+
}
2586+
if (ret == 1) {
2587+
if (*descr == NULL) {
2588+
PyErr_Format(
2589+
PyExc_ValueError,
2590+
"tp_binop_specialize of '%T' returned 1 with *descr == NULL",
2591+
lhs);
2592+
return -1;
2593+
}
2594+
(*descr)->oparg = oparg;
2595+
}
2596+
return ret;
2597+
}
25712598
return 0;
25722599
}
25732600

0 commit comments

Comments
 (0)