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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy