Skip to content

Commit 695ab61

Browse files
gh-132732: Automatically constant evaluate pure operations (GH-132733)
This adds a "macro" to the optimizer DSL called "REPLACE_OPCODE_IF_EVALUATES_PURE", which allows automatically constant evaluating a bytecode body if certain inputs have no side effects upon evaluations (such as ints, strings, and floats). Co-authored-by: Tomas R. <tomas.roun8@gmail.com>
1 parent c45f4f3 commit 695ab61

File tree

10 files changed

+706
-122
lines changed

10 files changed

+706
-122
lines changed

Include/internal/pycore_optimizer.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ extern "C" {
1010

1111
#include "pycore_typedefs.h" // _PyInterpreterFrame
1212
#include "pycore_uop_ids.h"
13-
#include "pycore_stackref.h"
13+
#include "pycore_stackref.h" // _PyStackRef
1414
#include <stdbool.h>
1515

1616

@@ -316,6 +316,9 @@ extern JitOptRef _Py_uop_sym_new_type(
316316
JitOptContext *ctx, PyTypeObject *typ);
317317

318318
extern JitOptRef _Py_uop_sym_new_const(JitOptContext *ctx, PyObject *const_val);
319+
extern JitOptRef _Py_uop_sym_new_const_steal(JitOptContext *ctx, PyObject *const_val);
320+
bool _Py_uop_sym_is_safe_const(JitOptContext *ctx, JitOptRef sym);
321+
_PyStackRef _Py_uop_sym_get_const_as_stackref(JitOptContext *ctx, JitOptRef sym);
319322
extern JitOptRef _Py_uop_sym_new_null(JitOptContext *ctx);
320323
extern bool _Py_uop_sym_has_type(JitOptRef sym);
321324
extern bool _Py_uop_sym_matches_type(JitOptRef sym, PyTypeObject *typ);

Include/internal/pycore_uop_metadata.h

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

Lib/test/test_generated_cases.py

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2224,5 +2224,202 @@ def test_validate_uop_unused_size_mismatch(self):
22242224
"Inputs must have equal sizes"):
22252225
self.run_cases_test(input, input2, output)
22262226

2227+
def test_pure_uop_body_copied_in(self):
2228+
# Note: any non-escaping call works.
2229+
# In this case, we use PyStackRef_IsNone.
2230+
input = """
2231+
pure op(OP, (foo -- res)) {
2232+
res = PyStackRef_IsNone(foo);
2233+
}
2234+
"""
2235+
input2 = """
2236+
op(OP, (foo -- res)) {
2237+
REPLACE_OPCODE_IF_EVALUATES_PURE(foo);
2238+
res = sym_new_known(ctx, foo);
2239+
}
2240+
"""
2241+
output = """
2242+
case OP: {
2243+
JitOptRef foo;
2244+
JitOptRef res;
2245+
foo = stack_pointer[-1];
2246+
if (
2247+
sym_is_safe_const(ctx, foo)
2248+
) {
2249+
JitOptRef foo_sym = foo;
2250+
_PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym);
2251+
_PyStackRef res_stackref;
2252+
/* Start of uop copied from bytecodes for constant evaluation */
2253+
res_stackref = PyStackRef_IsNone(foo);
2254+
/* End of uop copied from bytecodes for constant evaluation */
2255+
res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
2256+
stack_pointer[-1] = res;
2257+
break;
2258+
}
2259+
res = sym_new_known(ctx, foo);
2260+
stack_pointer[-1] = res;
2261+
break;
2262+
}
2263+
"""
2264+
self.run_cases_test(input, input2, output)
2265+
2266+
def test_pure_uop_body_copied_in_deopt(self):
2267+
# Note: any non-escaping call works.
2268+
# In this case, we use PyStackRef_IsNone.
2269+
input = """
2270+
pure op(OP, (foo -- res)) {
2271+
DEOPT_IF(PyStackRef_IsNull(foo));
2272+
res = foo;
2273+
}
2274+
"""
2275+
input2 = """
2276+
op(OP, (foo -- res)) {
2277+
REPLACE_OPCODE_IF_EVALUATES_PURE(foo);
2278+
res = foo;
2279+
}
2280+
"""
2281+
output = """
2282+
case OP: {
2283+
JitOptRef foo;
2284+
JitOptRef res;
2285+
foo = stack_pointer[-1];
2286+
if (
2287+
sym_is_safe_const(ctx, foo)
2288+
) {
2289+
JitOptRef foo_sym = foo;
2290+
_PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym);
2291+
_PyStackRef res_stackref;
2292+
/* Start of uop copied from bytecodes for constant evaluation */
2293+
if (PyStackRef_IsNull(foo)) {
2294+
ctx->done = true;
2295+
break;
2296+
}
2297+
res_stackref = foo;
2298+
/* End of uop copied from bytecodes for constant evaluation */
2299+
res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
2300+
stack_pointer[-1] = res;
2301+
break;
2302+
}
2303+
res = foo;
2304+
stack_pointer[-1] = res;
2305+
break;
2306+
}
2307+
"""
2308+
self.run_cases_test(input, input2, output)
2309+
2310+
def test_pure_uop_body_copied_in_error_if(self):
2311+
# Note: any non-escaping call works.
2312+
# In this case, we use PyStackRef_IsNone.
2313+
input = """
2314+
pure op(OP, (foo -- res)) {
2315+
ERROR_IF(PyStackRef_IsNull(foo));
2316+
res = foo;
2317+
}
2318+
"""
2319+
input2 = """
2320+
op(OP, (foo -- res)) {
2321+
REPLACE_OPCODE_IF_EVALUATES_PURE(foo);
2322+
res = foo;
2323+
}
2324+
"""
2325+
output = """
2326+
case OP: {
2327+
JitOptRef foo;
2328+
JitOptRef res;
2329+
foo = stack_pointer[-1];
2330+
if (
2331+
sym_is_safe_const(ctx, foo)
2332+
) {
2333+
JitOptRef foo_sym = foo;
2334+
_PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym);
2335+
_PyStackRef res_stackref;
2336+
/* Start of uop copied from bytecodes for constant evaluation */
2337+
if (PyStackRef_IsNull(foo)) {
2338+
goto error;
2339+
}
2340+
res_stackref = foo;
2341+
/* End of uop copied from bytecodes for constant evaluation */
2342+
res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
2343+
stack_pointer[-1] = res;
2344+
break;
2345+
}
2346+
res = foo;
2347+
stack_pointer[-1] = res;
2348+
break;
2349+
}
2350+
"""
2351+
self.run_cases_test(input, input2, output)
2352+
2353+
2354+
def test_replace_opcode_uop_body_copied_in_complex(self):
2355+
input = """
2356+
pure op(OP, (foo -- res)) {
2357+
if (foo) {
2358+
res = PyStackRef_IsNone(foo);
2359+
}
2360+
else {
2361+
res = 1;
2362+
}
2363+
}
2364+
"""
2365+
input2 = """
2366+
op(OP, (foo -- res)) {
2367+
REPLACE_OPCODE_IF_EVALUATES_PURE(foo);
2368+
res = sym_new_known(ctx, foo);
2369+
}
2370+
"""
2371+
output = """
2372+
case OP: {
2373+
JitOptRef foo;
2374+
JitOptRef res;
2375+
foo = stack_pointer[-1];
2376+
if (
2377+
sym_is_safe_const(ctx, foo)
2378+
) {
2379+
JitOptRef foo_sym = foo;
2380+
_PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym);
2381+
_PyStackRef res_stackref;
2382+
/* Start of uop copied from bytecodes for constant evaluation */
2383+
if (foo) {
2384+
res_stackref = PyStackRef_IsNone(foo);
2385+
}
2386+
else {
2387+
res_stackref = 1;
2388+
}
2389+
/* End of uop copied from bytecodes for constant evaluation */
2390+
res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref));
2391+
stack_pointer[-1] = res;
2392+
break;
2393+
}
2394+
res = sym_new_known(ctx, foo);
2395+
stack_pointer[-1] = res;
2396+
break;
2397+
}
2398+
"""
2399+
self.run_cases_test(input, input2, output)
2400+
2401+
def test_replace_opocode_uop_reject_array_effects(self):
2402+
input = """
2403+
pure op(OP, (foo[2] -- res)) {
2404+
if (foo) {
2405+
res = PyStackRef_IsNone(foo);
2406+
}
2407+
else {
2408+
res = 1;
2409+
}
2410+
}
2411+
"""
2412+
input2 = """
2413+
op(OP, (foo[2] -- res)) {
2414+
REPLACE_OPCODE_IF_EVALUATES_PURE(foo);
2415+
res = sym_new_unknown(ctx);
2416+
}
2417+
"""
2418+
output = """
2419+
"""
2420+
with self.assertRaisesRegex(SyntaxError,
2421+
"Pure evaluation cannot take array-like inputs"):
2422+
self.run_cases_test(input, input2, output)
2423+
22272424
if __name__ == "__main__":
22282425
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Automatically constant evaluate bytecode operations marked as pure in the JIT optimizer.

Python/bytecodes.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,7 @@ dummy_func(
850850
DEOPT_IF(!res);
851851
}
852852

853-
pure op(_BINARY_OP_EXTEND, (descr/4, left, right -- res)) {
853+
op(_BINARY_OP_EXTEND, (descr/4, left, right -- res)) {
854854
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
855855
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
856856
assert(INLINE_CACHE_ENTRIES_BINARY_OP == 5);

Python/optimizer_analysis.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
#include "pycore_function.h"
2727
#include "pycore_uop_ids.h"
2828
#include "pycore_range.h"
29+
#include "pycore_unicodeobject.h"
30+
#include "pycore_ceval.h"
2931

3032
#include <stdarg.h>
3133
#include <stdbool.h>
@@ -321,7 +323,10 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer,
321323
/* Shortened forms for convenience, used in optimizer_bytecodes.c */
322324
#define sym_is_not_null _Py_uop_sym_is_not_null
323325
#define sym_is_const _Py_uop_sym_is_const
326+
#define sym_is_safe_const _Py_uop_sym_is_safe_const
324327
#define sym_get_const _Py_uop_sym_get_const
328+
#define sym_new_const_steal _Py_uop_sym_new_const_steal
329+
#define sym_get_const_as_stackref _Py_uop_sym_get_const_as_stackref
325330
#define sym_new_unknown _Py_uop_sym_new_unknown
326331
#define sym_new_not_null _Py_uop_sym_new_not_null
327332
#define sym_new_type _Py_uop_sym_new_type
@@ -350,6 +355,8 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer,
350355
#define sym_new_compact_int _Py_uop_sym_new_compact_int
351356
#define sym_new_truthiness _Py_uop_sym_new_truthiness
352357

358+
#define JUMP_TO_LABEL(label) goto label;
359+
353360
static int
354361
optimize_to_bool(
355362
_PyUOpInstruction *this_instr,

0 commit comments

Comments
 (0)