Skip to content

Commit 230d06b

Browse files
authored
Merge pull request #523 from python/main
bpo-26280: Port BINARY_SUBSCR to PEP 659 adaptive interpreter (pythonGH-27043)
2 parents 27bdda9 + 641345d commit 230d06b

File tree

7 files changed

+203
-23
lines changed

7 files changed

+203
-23
lines changed

Include/internal/pycore_code.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,17 +310,18 @@ too_many_cache_misses(_PyAdaptiveEntry *entry) {
310310
return entry->counter == saturating_zero();
311311
}
312312

313-
#define BACKOFF 64
313+
#define ADAPTIVE_CACHE_BACKOFF 64
314314

315315
static inline void
316316
cache_backoff(_PyAdaptiveEntry *entry) {
317-
entry->counter = BACKOFF;
317+
entry->counter = ADAPTIVE_CACHE_BACKOFF;
318318
}
319319

320320
/* Specialization functions */
321321

322322
int _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, SpecializedCacheEntry *cache);
323323
int _Py_Specialize_LoadGlobal(PyObject *globals, PyObject *builtins, _Py_CODEUNIT *instr, PyObject *name, SpecializedCacheEntry *cache);
324+
int _Py_Specialize_BinarySubscr(PyObject *sub, PyObject *container, _Py_CODEUNIT *instr);
324325

325326
#define SPECIALIZATION_STATS 0
326327
#define SPECIALIZATION_STATS_DETAILED 0

Include/opcode.h

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

Lib/opcode.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ def jabs_op(name, op):
220220
del def_op, name_op, jrel_op, jabs_op
221221

222222
_specialized_instructions = [
223+
"BINARY_SUBSCR_ADAPTIVE",
224+
"BINARY_SUBSCR_LIST_INT",
225+
"BINARY_SUBSCR_TUPLE_INT",
226+
"BINARY_SUBSCR_DICT",
223227
"JUMP_ABSOLUTE_QUICK",
224228
"LOAD_ATTR_ADAPTIVE",
225229
"LOAD_ATTR_SPLIT_KEYS",
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Implement adaptive specialization for BINARY_SUBSCR
2+
3+
Three specialized forms of BINARY_SUBSCR are added:
4+
5+
* BINARY_SUBSCR_LIST_INT
6+
7+
* BINARY_SUBSCR_TUPLE_INT
8+
9+
* BINARY_SUBSCR_DICT

Python/ceval.c

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "pycore_ceval.h" // _PyEval_SignalAsyncExc()
1616
#include "pycore_code.h"
1717
#include "pycore_initconfig.h" // _PyStatus_OK()
18+
#include "pycore_long.h" // _PyLong_GetZero()
1819
#include "pycore_object.h" // _PyObject_GC_TRACK()
1920
#include "pycore_moduleobject.h"
2021
#include "pycore_pyerrors.h" // _PyErr_Fetch()
@@ -1398,6 +1399,8 @@ eval_frame_handle_pending(PyThreadState *tstate)
13981399

13991400
#define DEOPT_IF(cond, instname) if (cond) { goto instname ## _miss; }
14001401

1402+
#define UPDATE_PREV_INSTR_OPARG(instr, oparg) ((uint8_t*)(instr))[-1] = (oparg)
1403+
14011404
#define GLOBALS() specials[FRAME_SPECIALS_GLOBALS_OFFSET]
14021405
#define BUILTINS() specials[FRAME_SPECIALS_BUILTINS_OFFSET]
14031406
#define LOCALS() specials[FRAME_SPECIALS_LOCALS_OFFSET]
@@ -1913,6 +1916,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
19131916
}
19141917

19151918
case TARGET(BINARY_SUBSCR): {
1919+
PREDICTED(BINARY_SUBSCR);
1920+
STAT_INC(BINARY_SUBSCR, unquickened);
19161921
PyObject *sub = POP();
19171922
PyObject *container = TOP();
19181923
PyObject *res = PyObject_GetItem(container, sub);
@@ -1924,6 +1929,91 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
19241929
DISPATCH();
19251930
}
19261931

1932+
case TARGET(BINARY_SUBSCR_ADAPTIVE): {
1933+
if (oparg == 0) {
1934+
PyObject *sub = TOP();
1935+
PyObject *container = SECOND();
1936+
next_instr--;
1937+
if (_Py_Specialize_BinarySubscr(container, sub, next_instr) < 0) {
1938+
goto error;
1939+
}
1940+
DISPATCH();
1941+
}
1942+
else {
1943+
STAT_INC(BINARY_SUBSCR, deferred);
1944+
// oparg is the adaptive cache counter
1945+
UPDATE_PREV_INSTR_OPARG(next_instr, oparg - 1);
1946+
assert(_Py_OPCODE(next_instr[-1]) == BINARY_SUBSCR_ADAPTIVE);
1947+
assert(_Py_OPARG(next_instr[-1]) == oparg - 1);
1948+
JUMP_TO_INSTRUCTION(BINARY_SUBSCR);
1949+
}
1950+
}
1951+
1952+
case TARGET(BINARY_SUBSCR_LIST_INT): {
1953+
PyObject *sub = TOP();
1954+
PyObject *list = SECOND();
1955+
DEOPT_IF(!PyLong_CheckExact(sub), BINARY_SUBSCR);
1956+
DEOPT_IF(!PyList_CheckExact(list), BINARY_SUBSCR);
1957+
1958+
// Deopt unless 0 <= sub < PyList_Size(list)
1959+
Py_ssize_t signed_magnitude = Py_SIZE(sub);
1960+
DEOPT_IF(((size_t)signed_magnitude) > 1, BINARY_SUBSCR);
1961+
assert(((PyLongObject *)_PyLong_GetZero())->ob_digit[0] == 0);
1962+
Py_ssize_t index = ((PyLongObject*)sub)->ob_digit[0];
1963+
DEOPT_IF(index >= PyList_GET_SIZE(list), BINARY_SUBSCR);
1964+
1965+
STAT_INC(BINARY_SUBSCR, hit);
1966+
PyObject *res = PyList_GET_ITEM(list, index);
1967+
assert(res != NULL);
1968+
Py_INCREF(res);
1969+
STACK_SHRINK(1);
1970+
Py_DECREF(sub);
1971+
SET_TOP(res);
1972+
Py_DECREF(list);
1973+
DISPATCH();
1974+
}
1975+
1976+
case TARGET(BINARY_SUBSCR_TUPLE_INT): {
1977+
PyObject *sub = TOP();
1978+
PyObject *tuple = SECOND();
1979+
DEOPT_IF(!PyLong_CheckExact(sub), BINARY_SUBSCR);
1980+
DEOPT_IF(!PyTuple_CheckExact(tuple), BINARY_SUBSCR);
1981+
1982+
// Deopt unless 0 <= sub < PyTuple_Size(list)
1983+
Py_ssize_t signed_magnitude = Py_SIZE(sub);
1984+
DEOPT_IF(((size_t)signed_magnitude) > 1, BINARY_SUBSCR);
1985+
assert(((PyLongObject *)_PyLong_GetZero())->ob_digit[0] == 0);
1986+
Py_ssize_t index = ((PyLongObject*)sub)->ob_digit[0];
1987+
DEOPT_IF(index >= PyTuple_GET_SIZE(tuple), BINARY_SUBSCR);
1988+
1989+
STAT_INC(BINARY_SUBSCR, hit);
1990+
PyObject *res = PyTuple_GET_ITEM(tuple, index);
1991+
assert(res != NULL);
1992+
Py_INCREF(res);
1993+
STACK_SHRINK(1);
1994+
Py_DECREF(sub);
1995+
SET_TOP(res);
1996+
Py_DECREF(tuple);
1997+
DISPATCH();
1998+
}
1999+
2000+
case TARGET(BINARY_SUBSCR_DICT): {
2001+
PyObject *dict = SECOND();
2002+
DEOPT_IF(!PyDict_CheckExact(SECOND()), BINARY_SUBSCR);
2003+
STAT_INC(BINARY_SUBSCR, hit);
2004+
PyObject *sub = TOP();
2005+
PyObject *res = PyDict_GetItemWithError(dict, sub);
2006+
if (res == NULL) {
2007+
goto binary_subscr_dict_error;
2008+
}
2009+
Py_INCREF(res);
2010+
STACK_SHRINK(1);
2011+
Py_DECREF(sub);
2012+
SET_TOP(res);
2013+
Py_DECREF(dict);
2014+
DISPATCH();
2015+
}
2016+
19272017
case TARGET(BINARY_LSHIFT): {
19282018
PyObject *right = POP();
19292019
PyObject *left = TOP();
@@ -4327,8 +4417,34 @@ opname ## _miss: \
43274417
JUMP_TO_INSTRUCTION(opname); \
43284418
}
43294419

4420+
#define MISS_WITH_OPARG_COUNTER(opname) \
4421+
opname ## _miss: \
4422+
{ \
4423+
STAT_INC(opname, miss); \
4424+
uint8_t oparg = saturating_decrement(_Py_OPARG(next_instr[-1])); \
4425+
UPDATE_PREV_INSTR_OPARG(next_instr, oparg); \
4426+
assert(_Py_OPARG(next_instr[-1]) == oparg); \
4427+
if (oparg == saturating_zero()) /* too many cache misses */ { \
4428+
oparg = ADAPTIVE_CACHE_BACKOFF; \
4429+
next_instr[-1] = _Py_MAKECODEUNIT(opname ## _ADAPTIVE, oparg); \
4430+
STAT_INC(opname, deopt); \
4431+
} \
4432+
JUMP_TO_INSTRUCTION(opname); \
4433+
}
4434+
43304435
MISS_WITH_CACHE(LOAD_ATTR)
43314436
MISS_WITH_CACHE(LOAD_GLOBAL)
4437+
MISS_WITH_OPARG_COUNTER(BINARY_SUBSCR)
4438+
4439+
binary_subscr_dict_error:
4440+
{
4441+
PyObject *sub = POP();
4442+
if (!_PyErr_Occurred(tstate)) {
4443+
_PyErr_SetKeyError(sub);
4444+
}
4445+
Py_DECREF(sub);
4446+
goto error;
4447+
}
43324448

43334449
error:
43344450
/* Double-check exception status. */

Python/opcode_targets.h

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

Python/specialize.c

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ _Py_PrintSpecializationStats(void)
7878
printf("Specialization stats:\n");
7979
print_stats(&_specialization_stats[LOAD_ATTR], "load_attr");
8080
print_stats(&_specialization_stats[LOAD_GLOBAL], "load_global");
81+
print_stats(&_specialization_stats[BINARY_SUBSCR], "binary_subscr");
8182
}
8283

8384
#if SPECIALIZATION_STATS_DETAILED
@@ -162,12 +163,14 @@ get_cache_count(SpecializedCacheOrInstruction *quickened) {
162163
static uint8_t adaptive_opcodes[256] = {
163164
[LOAD_ATTR] = LOAD_ATTR_ADAPTIVE,
164165
[LOAD_GLOBAL] = LOAD_GLOBAL_ADAPTIVE,
166+
[BINARY_SUBSCR] = BINARY_SUBSCR_ADAPTIVE,
165167
};
166168

167169
/* The number of cache entries required for a "family" of instructions. */
168170
static uint8_t cache_requirements[256] = {
169171
[LOAD_ATTR] = 2, /* _PyAdaptiveEntry and _PyLoadAttrCache */
170172
[LOAD_GLOBAL] = 2, /* _PyAdaptiveEntry and _PyLoadGlobalCache */
173+
[BINARY_SUBSCR] = 0,
171174
};
172175

173176
/* Return the oparg for the cache_offset and instruction index.
@@ -251,7 +254,6 @@ optimize(SpecializedCacheOrInstruction *quickened, int len)
251254
previous_opcode = opcode;
252255
continue;
253256
}
254-
instructions[i] = _Py_MAKECODEUNIT(adaptive_opcode, new_oparg);
255257
previous_opcode = adaptive_opcode;
256258
int entries_needed = cache_requirements[opcode];
257259
if (entries_needed) {
@@ -261,7 +263,11 @@ optimize(SpecializedCacheOrInstruction *quickened, int len)
261263
_GetSpecializedCacheEntry(instructions, cache0_offset);
262264
cache->adaptive.original_oparg = oparg;
263265
cache->adaptive.counter = 0;
266+
} else {
267+
// oparg is the adaptive cache counter
268+
new_oparg = 0;
264269
}
270+
instructions[i] = _Py_MAKECODEUNIT(adaptive_opcode, new_oparg);
265271
}
266272
else {
267273
/* Super instructions don't use the cache,
@@ -637,3 +643,43 @@ _Py_Specialize_LoadGlobal(
637643
cache0->counter = saturating_start();
638644
return 0;
639645
}
646+
647+
int
648+
_Py_Specialize_BinarySubscr(
649+
PyObject *container, PyObject *sub, _Py_CODEUNIT *instr)
650+
{
651+
PyTypeObject *container_type = Py_TYPE(container);
652+
if (container_type == &PyList_Type) {
653+
if (PyLong_CheckExact(sub)) {
654+
*instr = _Py_MAKECODEUNIT(BINARY_SUBSCR_LIST_INT, saturating_start());
655+
goto success;
656+
} else {
657+
SPECIALIZATION_FAIL(BINARY_SUBSCR, Py_TYPE(container), sub, "list; non-integer subscr");
658+
}
659+
}
660+
if (container_type == &PyTuple_Type) {
661+
if (PyLong_CheckExact(sub)) {
662+
*instr = _Py_MAKECODEUNIT(BINARY_SUBSCR_TUPLE_INT, saturating_start());
663+
goto success;
664+
} else {
665+
SPECIALIZATION_FAIL(BINARY_SUBSCR, Py_TYPE(container), sub, "tuple; non-integer subscr");
666+
}
667+
}
668+
if (container_type == &PyDict_Type) {
669+
*instr = _Py_MAKECODEUNIT(BINARY_SUBSCR_DICT, saturating_start());
670+
goto success;
671+
}
672+
673+
SPECIALIZATION_FAIL(BINARY_SUBSCR, Py_TYPE(container), sub, "not list|tuple|dict");
674+
goto fail;
675+
fail:
676+
STAT_INC(BINARY_SUBSCR, specialization_failure);
677+
assert(!PyErr_Occurred());
678+
*instr = _Py_MAKECODEUNIT(_Py_OPCODE(*instr), ADAPTIVE_CACHE_BACKOFF);
679+
return 0;
680+
success:
681+
STAT_INC(BINARY_SUBSCR, specialization_success);
682+
assert(!PyErr_Occurred());
683+
return 0;
684+
}
685+

0 commit comments

Comments
 (0)