From d0f236a793d1cbdce47c6286177243c51e73ccee Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Thu, 27 Feb 2025 08:40:14 -0800 Subject: [PATCH 01/12] Add failing regression tests --- Lib/test/test_capi/test_opt.py | 62 ++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 2a9b777862c84a..5e7100e95deca2 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -1437,6 +1437,68 @@ def crash_addition(): crash_addition() + def test_narrow_type_to_constant_bool_false(self): + def f(n): + trace = [] + for i in range(n): + # f is always False, but we can only prove that it's a bool: + f = i == TIER2_THRESHOLD + trace.append("A") + if not f: # Kept. + trace.append("B") + if not f: # Removed! + trace.append("C") + trace.append("D") + if f: # Removed! + trace.append("X") + trace.append("E") + trace.append("F") + if f: # Removed! + trace.append("X") + trace.append("G") + return trace + + trace, ex = self._run_with_optimizer(f, TIER2_THRESHOLD) + self.assertEqual(trace, list("ABCDEFG") * TIER2_THRESHOLD) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + # Only one guard remains: + self.assertEqual(uops.count("_GUARD_IS_FALSE_POP"), 1) + self.assertEqual(uops.count("_GUARD_IS_TRUE_POP"), 0) + # But all of the appends we care about are still there: + self.assertEqual(uops.count("_CALL_LIST_APPEND"), len("ABCDEFG")) + + def test_narrow_type_to_constant_bool_true(self): + def f(n): + trace = [] + for i in range(n): + # f is always True, but we can only prove that it's a bool: + f = i != TIER2_THRESHOLD + trace.append("A") + if f: # Kept. + trace.append("B") + if not f: # Removed! + trace.append("X") + trace.append("C") + if f: # Removed! + trace.append("D") + trace.append("E") + trace.append("F") + if not f: # Removed! + trace.append("X") + trace.append("G") + return trace + + trace, ex = self._run_with_optimizer(f, TIER2_THRESHOLD) + self.assertEqual(trace, list("ABCDEFG") * TIER2_THRESHOLD) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + # Only one guard remains: + self.assertEqual(uops.count("_GUARD_IS_FALSE_POP"), 0) + self.assertEqual(uops.count("_GUARD_IS_TRUE_POP"), 1) + # But all of the appends we care about are still there: + self.assertEqual(uops.count("_CALL_LIST_APPEND"), len("ABCDEFG")) + def global_identity(x): return x From 7929a57172550232acbcf41fbaa59e7f09e5b4b1 Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Thu, 27 Feb 2025 09:17:59 -0800 Subject: [PATCH 02/12] Add "truth" symbols --- Include/internal/pycore_optimizer.h | 14 +++- Python/optimizer_analysis.c | 2 +- Python/optimizer_bytecodes.c | 109 ++++++++++++++------------- Python/optimizer_cases.c.h | 109 ++++++++++++++------------- Python/optimizer_symbols.c | 111 +++++++++++++++++++++------- 5 files changed, 209 insertions(+), 136 deletions(-) diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 25c3d3e5a22442..26308d25eb199c 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -172,6 +172,7 @@ typedef enum _JitSymType { JIT_SYM_KNOWN_CLASS_TAG = 6, JIT_SYM_KNOWN_VALUE_TAG = 7, JIT_SYM_TUPLE_TAG = 8, + JIT_SYM_TRUTH_TAG = 9, } JitSymType; typedef struct _jit_opt_known_class { @@ -198,12 +199,19 @@ typedef struct _jit_opt_tuple { uint16_t items[MAX_SYMBOLIC_TUPLE_SIZE]; } JitOptTuple; +typedef struct { + uint8_t tag; + bool not; + uint16_t value; +} JitOptTruth; + typedef union _jit_opt_symbol { uint8_t tag; JitOptKnownClass cls; JitOptKnownValue value; JitOptKnownVersion version; JitOptTuple tuple; + JitOptTruth truth; } JitOptSymbol; @@ -245,8 +253,8 @@ typedef struct _JitOptContext { extern bool _Py_uop_sym_is_null(JitOptSymbol *sym); extern bool _Py_uop_sym_is_not_null(JitOptSymbol *sym); -extern bool _Py_uop_sym_is_const(JitOptSymbol *sym); -extern PyObject *_Py_uop_sym_get_const(JitOptSymbol *sym); +extern bool _Py_uop_sym_is_const(JitOptContext *ctx, JitOptSymbol *sym); +extern PyObject *_Py_uop_sym_get_const(JitOptContext *ctx, JitOptSymbol *sym); extern JitOptSymbol *_Py_uop_sym_new_unknown(JitOptContext *ctx); extern JitOptSymbol *_Py_uop_sym_new_not_null(JitOptContext *ctx); extern JitOptSymbol *_Py_uop_sym_new_type( @@ -262,7 +270,7 @@ extern void _Py_uop_sym_set_type(JitOptContext *ctx, JitOptSymbol *sym, PyTypeOb extern bool _Py_uop_sym_set_type_version(JitOptContext *ctx, JitOptSymbol *sym, unsigned int version); extern void _Py_uop_sym_set_const(JitOptContext *ctx, JitOptSymbol *sym, PyObject *const_val); extern bool _Py_uop_sym_is_bottom(JitOptSymbol *sym); -extern int _Py_uop_sym_truthiness(JitOptSymbol *sym); +extern int _Py_uop_sym_truthiness(JitOptContext *ctx, JitOptSymbol *sym); extern PyTypeObject *_Py_uop_sym_get_type(JitOptSymbol *sym); extern bool _Py_uop_sym_is_immortal(JitOptSymbol *sym); extern JitOptSymbol *_Py_uop_sym_new_tuple(JitOptContext *ctx, int size, JitOptSymbol **args); diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 6c0aadb87e6741..9d1506b0dafa7f 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -389,7 +389,7 @@ optimize_to_bool( *result_ptr = value; return 1; } - int truthiness = sym_truthiness(value); + int truthiness = sym_truthiness(ctx, value); if (truthiness >= 0) { PyObject *load = truthiness ? Py_True : Py_False; REPLACE_OP(this_instr, _POP_TOP_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)load); diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 41eb59c931aaa7..0cd052d9fb01d5 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -198,11 +198,11 @@ dummy_func(void) { // Case C: res = sym_new_type(ctx, &PyFloat_Type); } - else if (!sym_is_const(right)) { + else if (!sym_is_const(ctx, right)) { // Case A or B... can't know without the sign of the RHS: res = sym_new_unknown(ctx); } - else if (_PyLong_IsNegative((PyLongObject *)sym_get_const(right))) { + else if (_PyLong_IsNegative((PyLongObject *)sym_get_const(ctx, right))) { // Case B: res = sym_new_type(ctx, &PyFloat_Type); } @@ -223,13 +223,13 @@ dummy_func(void) { } op(_BINARY_OP_ADD_INT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right) && + if (sym_is_const(ctx, left) && sym_is_const(ctx, right) && sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) { - assert(PyLong_CheckExact(sym_get_const(left))); - assert(PyLong_CheckExact(sym_get_const(right))); - PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(left), - (PyLongObject *)sym_get_const(right)); + assert(PyLong_CheckExact(sym_get_const(ctx, left))); + assert(PyLong_CheckExact(sym_get_const(ctx, right))); + PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(ctx, left), + (PyLongObject *)sym_get_const(ctx, right)); if (temp == NULL) { goto error; } @@ -244,13 +244,13 @@ dummy_func(void) { } op(_BINARY_OP_SUBTRACT_INT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right) && + if (sym_is_const(ctx, left) && sym_is_const(ctx, right) && sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) { - assert(PyLong_CheckExact(sym_get_const(left))); - assert(PyLong_CheckExact(sym_get_const(right))); - PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(left), - (PyLongObject *)sym_get_const(right)); + assert(PyLong_CheckExact(sym_get_const(ctx, left))); + assert(PyLong_CheckExact(sym_get_const(ctx, right))); + PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(ctx, left), + (PyLongObject *)sym_get_const(ctx, right)); if (temp == NULL) { goto error; } @@ -265,13 +265,13 @@ dummy_func(void) { } op(_BINARY_OP_MULTIPLY_INT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right) && + if (sym_is_const(ctx, left) && sym_is_const(ctx, right) && sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) { - assert(PyLong_CheckExact(sym_get_const(left))); - assert(PyLong_CheckExact(sym_get_const(right))); - PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(left), - (PyLongObject *)sym_get_const(right)); + assert(PyLong_CheckExact(sym_get_const(ctx, left))); + assert(PyLong_CheckExact(sym_get_const(ctx, right))); + PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(ctx, left), + (PyLongObject *)sym_get_const(ctx, right)); if (temp == NULL) { goto error; } @@ -286,14 +286,14 @@ dummy_func(void) { } op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right) && + if (sym_is_const(ctx, left) && sym_is_const(ctx, right) && sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) { - assert(PyFloat_CheckExact(sym_get_const(left))); - assert(PyFloat_CheckExact(sym_get_const(right))); + assert(PyFloat_CheckExact(sym_get_const(ctx, left))); + assert(PyFloat_CheckExact(sym_get_const(ctx, right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(sym_get_const(left)) + - PyFloat_AS_DOUBLE(sym_get_const(right))); + PyFloat_AS_DOUBLE(sym_get_const(ctx, left)) + + PyFloat_AS_DOUBLE(sym_get_const(ctx, right))); if (temp == NULL) { goto error; } @@ -308,14 +308,14 @@ dummy_func(void) { } op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right) && + if (sym_is_const(ctx, left) && sym_is_const(ctx, right) && sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) { - assert(PyFloat_CheckExact(sym_get_const(left))); - assert(PyFloat_CheckExact(sym_get_const(right))); + assert(PyFloat_CheckExact(sym_get_const(ctx, left))); + assert(PyFloat_CheckExact(sym_get_const(ctx, right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(sym_get_const(left)) - - PyFloat_AS_DOUBLE(sym_get_const(right))); + PyFloat_AS_DOUBLE(sym_get_const(ctx, left)) - + PyFloat_AS_DOUBLE(sym_get_const(ctx, right))); if (temp == NULL) { goto error; } @@ -330,14 +330,14 @@ dummy_func(void) { } op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right) && + if (sym_is_const(ctx, left) && sym_is_const(ctx, right) && sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) { - assert(PyFloat_CheckExact(sym_get_const(left))); - assert(PyFloat_CheckExact(sym_get_const(right))); + assert(PyFloat_CheckExact(sym_get_const(ctx, left))); + assert(PyFloat_CheckExact(sym_get_const(ctx, right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(sym_get_const(left)) * - PyFloat_AS_DOUBLE(sym_get_const(right))); + PyFloat_AS_DOUBLE(sym_get_const(ctx, left)) * + PyFloat_AS_DOUBLE(sym_get_const(ctx, right))); if (temp == NULL) { goto error; } @@ -352,9 +352,9 @@ dummy_func(void) { } op(_BINARY_OP_ADD_UNICODE, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right) && + if (sym_is_const(ctx, left) && sym_is_const(ctx, right) && sym_matches_type(left, &PyUnicode_Type) && sym_matches_type(right, &PyUnicode_Type)) { - PyObject *temp = PyUnicode_Concat(sym_get_const(left), sym_get_const(right)); + PyObject *temp = PyUnicode_Concat(sym_get_const(ctx, left), sym_get_const(ctx, right)); if (temp == NULL) { goto error; } @@ -368,9 +368,9 @@ dummy_func(void) { op(_BINARY_OP_INPLACE_ADD_UNICODE, (left, right -- )) { JitOptSymbol *res; - if (sym_is_const(left) && sym_is_const(right) && + if (sym_is_const(ctx, left) && sym_is_const(ctx, right) && sym_matches_type(left, &PyUnicode_Type) && sym_matches_type(right, &PyUnicode_Type)) { - PyObject *temp = PyUnicode_Concat(sym_get_const(left), sym_get_const(right)); + PyObject *temp = PyUnicode_Concat(sym_get_const(ctx, left), sym_get_const(ctx, right)); if (temp == NULL) { goto error; } @@ -512,8 +512,8 @@ dummy_func(void) { op(_CHECK_ATTR_MODULE_PUSH_KEYS, (dict_version/2, owner -- owner, mod_keys)) { (void)dict_version; mod_keys = sym_new_not_null(ctx); - if (sym_is_const(owner)) { - PyObject *cnst = sym_get_const(owner); + if (sym_is_const(ctx, owner)) { + PyObject *cnst = sym_get_const(ctx, owner); if (PyModule_CheckExact(cnst)) { PyModuleObject *mod = (PyModuleObject *)cnst; PyObject *dict = mod->md_dict; @@ -546,8 +546,8 @@ dummy_func(void) { attr = NULL; if (this_instr[-1].opcode == _NOP) { // Preceding _CHECK_ATTR_MODULE_PUSH_KEYS was removed: mod is const and dict is watched. - assert(sym_is_const(owner)); - PyModuleObject *mod = (PyModuleObject *)sym_get_const(owner); + assert(sym_is_const(ctx, owner)); + PyModuleObject *mod = (PyModuleObject *)sym_get_const(ctx, owner); assert(PyModule_CheckExact(mod)); PyObject *dict = mod->md_dict; PyObject *res = convert_global_to_const(this_instr, dict); @@ -614,19 +614,19 @@ dummy_func(void) { } op(_CHECK_FUNCTION_VERSION, (func_version/2, callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) { - if (sym_is_const(callable) && sym_matches_type(callable, &PyFunction_Type)) { - assert(PyFunction_Check(sym_get_const(callable))); + if (sym_is_const(ctx, callable) && sym_matches_type(callable, &PyFunction_Type)) { + assert(PyFunction_Check(sym_get_const(ctx, callable))); REPLACE_OP(this_instr, _CHECK_FUNCTION_VERSION_INLINE, 0, func_version); - this_instr->operand1 = (uintptr_t)sym_get_const(callable); + this_instr->operand1 = (uintptr_t)sym_get_const(ctx, callable); } sym_set_type(callable, &PyFunction_Type); } op(_CHECK_FUNCTION_EXACT_ARGS, (callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) { assert(sym_matches_type(callable, &PyFunction_Type)); - if (sym_is_const(callable)) { + if (sym_is_const(ctx, callable)) { if (sym_is_null(self_or_null) || sym_is_not_null(self_or_null)) { - PyFunctionObject *func = (PyFunctionObject *)sym_get_const(callable); + PyFunctionObject *func = (PyFunctionObject *)sym_get_const(ctx, callable); PyCodeObject *co = (PyCodeObject *)func->func_code; if (co->co_argcount == oparg + !sym_is_null(self_or_null)) { REPLACE_OP(this_instr, _NOP, 0 ,0); @@ -827,24 +827,26 @@ dummy_func(void) { } op(_GUARD_IS_TRUE_POP, (flag -- )) { - if (sym_is_const(flag)) { - PyObject *value = sym_get_const(flag); + if (sym_is_const(ctx, flag)) { + PyObject *value = sym_get_const(ctx, flag); assert(value != NULL); eliminate_pop_guard(this_instr, value != Py_True); } + sym_set_const(flag, Py_True); } op(_GUARD_IS_FALSE_POP, (flag -- )) { - if (sym_is_const(flag)) { - PyObject *value = sym_get_const(flag); + if (sym_is_const(ctx, flag)) { + PyObject *value = sym_get_const(ctx, flag); assert(value != NULL); eliminate_pop_guard(this_instr, value != Py_False); } + sym_set_const(flag, Py_False); } op(_GUARD_IS_NONE_POP, (flag -- )) { - if (sym_is_const(flag)) { - PyObject *value = sym_get_const(flag); + if (sym_is_const(ctx, flag)) { + PyObject *value = sym_get_const(ctx, flag); assert(value != NULL); eliminate_pop_guard(this_instr, !Py_IsNone(value)); } @@ -852,11 +854,12 @@ dummy_func(void) { assert(!sym_matches_type(flag, &_PyNone_Type)); eliminate_pop_guard(this_instr, true); } + sym_set_const(flag, Py_None); } op(_GUARD_IS_NOT_NONE_POP, (flag -- )) { - if (sym_is_const(flag)) { - PyObject *value = sym_get_const(flag); + if (sym_is_const(ctx, flag)) { + PyObject *value = sym_get_const(ctx, flag); assert(value != NULL); eliminate_pop_guard(this_instr, Py_IsNone(value)); } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 51d0fa63e64bc5..2e319b5d374142 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -268,15 +268,15 @@ JitOptSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right) && + if (sym_is_const(ctx, left) && sym_is_const(ctx, right) && sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) { - assert(PyLong_CheckExact(sym_get_const(left))); - assert(PyLong_CheckExact(sym_get_const(right))); + assert(PyLong_CheckExact(sym_get_const(ctx, left))); + assert(PyLong_CheckExact(sym_get_const(ctx, right))); stack_pointer += -2; assert(WITHIN_STACK_BOUNDS()); - PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(left), - (PyLongObject *)sym_get_const(right)); + PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(ctx, left), + (PyLongObject *)sym_get_const(ctx, right)); if (temp == NULL) { goto error; } @@ -303,15 +303,15 @@ JitOptSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right) && + if (sym_is_const(ctx, left) && sym_is_const(ctx, right) && sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) { - assert(PyLong_CheckExact(sym_get_const(left))); - assert(PyLong_CheckExact(sym_get_const(right))); + assert(PyLong_CheckExact(sym_get_const(ctx, left))); + assert(PyLong_CheckExact(sym_get_const(ctx, right))); stack_pointer += -2; assert(WITHIN_STACK_BOUNDS()); - PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(left), - (PyLongObject *)sym_get_const(right)); + PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(ctx, left), + (PyLongObject *)sym_get_const(ctx, right)); if (temp == NULL) { goto error; } @@ -338,15 +338,15 @@ JitOptSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right) && + if (sym_is_const(ctx, left) && sym_is_const(ctx, right) && sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) { - assert(PyLong_CheckExact(sym_get_const(left))); - assert(PyLong_CheckExact(sym_get_const(right))); + assert(PyLong_CheckExact(sym_get_const(ctx, left))); + assert(PyLong_CheckExact(sym_get_const(ctx, right))); stack_pointer += -2; assert(WITHIN_STACK_BOUNDS()); - PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(left), - (PyLongObject *)sym_get_const(right)); + PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(ctx, left), + (PyLongObject *)sym_get_const(ctx, right)); if (temp == NULL) { goto error; } @@ -404,14 +404,14 @@ JitOptSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right) && + if (sym_is_const(ctx, left) && sym_is_const(ctx, right) && sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) { - assert(PyFloat_CheckExact(sym_get_const(left))); - assert(PyFloat_CheckExact(sym_get_const(right))); + assert(PyFloat_CheckExact(sym_get_const(ctx, left))); + assert(PyFloat_CheckExact(sym_get_const(ctx, right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(sym_get_const(left)) * - PyFloat_AS_DOUBLE(sym_get_const(right))); + PyFloat_AS_DOUBLE(sym_get_const(ctx, left)) * + PyFloat_AS_DOUBLE(sym_get_const(ctx, right))); if (temp == NULL) { goto error; } @@ -438,14 +438,14 @@ JitOptSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right) && + if (sym_is_const(ctx, left) && sym_is_const(ctx, right) && sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) { - assert(PyFloat_CheckExact(sym_get_const(left))); - assert(PyFloat_CheckExact(sym_get_const(right))); + assert(PyFloat_CheckExact(sym_get_const(ctx, left))); + assert(PyFloat_CheckExact(sym_get_const(ctx, right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(sym_get_const(left)) + - PyFloat_AS_DOUBLE(sym_get_const(right))); + PyFloat_AS_DOUBLE(sym_get_const(ctx, left)) + + PyFloat_AS_DOUBLE(sym_get_const(ctx, right))); if (temp == NULL) { goto error; } @@ -472,14 +472,14 @@ JitOptSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right) && + if (sym_is_const(ctx, left) && sym_is_const(ctx, right) && sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) { - assert(PyFloat_CheckExact(sym_get_const(left))); - assert(PyFloat_CheckExact(sym_get_const(right))); + assert(PyFloat_CheckExact(sym_get_const(ctx, left))); + assert(PyFloat_CheckExact(sym_get_const(ctx, right))); PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(sym_get_const(left)) - - PyFloat_AS_DOUBLE(sym_get_const(right))); + PyFloat_AS_DOUBLE(sym_get_const(ctx, left)) - + PyFloat_AS_DOUBLE(sym_get_const(ctx, right))); if (temp == NULL) { goto error; } @@ -520,9 +520,9 @@ JitOptSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right) && + if (sym_is_const(ctx, left) && sym_is_const(ctx, right) && sym_matches_type(left, &PyUnicode_Type) && sym_matches_type(right, &PyUnicode_Type)) { - PyObject *temp = PyUnicode_Concat(sym_get_const(left), sym_get_const(right)); + PyObject *temp = PyUnicode_Concat(sym_get_const(ctx, left), sym_get_const(ctx, right)); if (temp == NULL) { goto error; } @@ -547,9 +547,9 @@ right = stack_pointer[-1]; left = stack_pointer[-2]; JitOptSymbol *res; - if (sym_is_const(left) && sym_is_const(right) && + if (sym_is_const(ctx, left) && sym_is_const(ctx, right) && sym_matches_type(left, &PyUnicode_Type) && sym_matches_type(right, &PyUnicode_Type)) { - PyObject *temp = PyUnicode_Concat(sym_get_const(left), sym_get_const(right)); + PyObject *temp = PyUnicode_Concat(sym_get_const(ctx, left), sym_get_const(ctx, right)); if (temp == NULL) { goto error; } @@ -1175,8 +1175,8 @@ uint32_t dict_version = (uint32_t)this_instr->operand0; (void)dict_version; mod_keys = sym_new_not_null(ctx); - if (sym_is_const(owner)) { - PyObject *cnst = sym_get_const(owner); + if (sym_is_const(ctx, owner)) { + PyObject *cnst = sym_get_const(ctx, owner); if (PyModule_CheckExact(cnst)) { PyModuleObject *mod = (PyModuleObject *)cnst; PyObject *dict = mod->md_dict; @@ -1208,8 +1208,8 @@ attr = NULL; if (this_instr[-1].opcode == _NOP) { // Preceding _CHECK_ATTR_MODULE_PUSH_KEYS was removed: mod is const and dict is watched. - assert(sym_is_const(owner)); - PyModuleObject *mod = (PyModuleObject *)sym_get_const(owner); + assert(sym_is_const(ctx, owner)); + PyModuleObject *mod = (PyModuleObject *)sym_get_const(ctx, owner); assert(PyModule_CheckExact(mod)); PyObject *dict = mod->md_dict; stack_pointer[-2] = attr; @@ -1721,10 +1721,10 @@ JitOptSymbol *callable; callable = stack_pointer[-2 - oparg]; uint32_t func_version = (uint32_t)this_instr->operand0; - if (sym_is_const(callable) && sym_matches_type(callable, &PyFunction_Type)) { - assert(PyFunction_Check(sym_get_const(callable))); + if (sym_is_const(ctx, callable) && sym_matches_type(callable, &PyFunction_Type)) { + assert(PyFunction_Check(sym_get_const(ctx, callable))); REPLACE_OP(this_instr, _CHECK_FUNCTION_VERSION_INLINE, 0, func_version); - this_instr->operand1 = (uintptr_t)sym_get_const(callable); + this_instr->operand1 = (uintptr_t)sym_get_const(ctx, callable); } sym_set_type(callable, &PyFunction_Type); break; @@ -1790,9 +1790,9 @@ self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; assert(sym_matches_type(callable, &PyFunction_Type)); - if (sym_is_const(callable)) { + if (sym_is_const(ctx, callable)) { if (sym_is_null(self_or_null) || sym_is_not_null(self_or_null)) { - PyFunctionObject *func = (PyFunctionObject *)sym_get_const(callable); + PyFunctionObject *func = (PyFunctionObject *)sym_get_const(ctx, callable); PyCodeObject *co = (PyCodeObject *)func->func_code; if (co->co_argcount == oparg + !sym_is_null(self_or_null)) { REPLACE_OP(this_instr, _NOP, 0 ,0); @@ -2226,12 +2226,12 @@ res = sym_new_type(ctx, &PyFloat_Type); } else { - if (!sym_is_const(right)) { + if (!sym_is_const(ctx, right)) { // Case A or B... can't know without the sign of the RHS: res = sym_new_unknown(ctx); } else { - if (_PyLong_IsNegative((PyLongObject *)sym_get_const(right))) { + if (_PyLong_IsNegative((PyLongObject *)sym_get_const(ctx, right))) { // Case B: res = sym_new_type(ctx, &PyFloat_Type); } @@ -2296,8 +2296,8 @@ case _GUARD_IS_TRUE_POP: { JitOptSymbol *flag; flag = stack_pointer[-1]; - if (sym_is_const(flag)) { - PyObject *value = sym_get_const(flag); + if (sym_is_const(ctx, flag)) { + PyObject *value = sym_get_const(ctx, flag); assert(value != NULL); stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); @@ -2305,6 +2305,7 @@ stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); } + sym_set_const(flag, Py_True); stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); break; @@ -2313,8 +2314,8 @@ case _GUARD_IS_FALSE_POP: { JitOptSymbol *flag; flag = stack_pointer[-1]; - if (sym_is_const(flag)) { - PyObject *value = sym_get_const(flag); + if (sym_is_const(ctx, flag)) { + PyObject *value = sym_get_const(ctx, flag); assert(value != NULL); stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); @@ -2322,6 +2323,7 @@ stack_pointer += 1; assert(WITHIN_STACK_BOUNDS()); } + sym_set_const(flag, Py_False); stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); break; @@ -2330,8 +2332,8 @@ case _GUARD_IS_NONE_POP: { JitOptSymbol *flag; flag = stack_pointer[-1]; - if (sym_is_const(flag)) { - PyObject *value = sym_get_const(flag); + if (sym_is_const(ctx, flag)) { + PyObject *value = sym_get_const(ctx, flag); assert(value != NULL); stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); @@ -2349,14 +2351,15 @@ stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); } + sym_set_const(flag, Py_None); break; } case _GUARD_IS_NOT_NONE_POP: { JitOptSymbol *flag; flag = stack_pointer[-1]; - if (sym_is_const(flag)) { - PyObject *value = sym_get_const(flag); + if (sym_is_const(ctx, flag)) { + PyObject *value = sym_get_const(ctx, flag); assert(value != NULL); stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index dcde8e7ce81577..8b48c9b65bf31f 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -48,6 +48,12 @@ static JitOptSymbol NO_SPACE_SYMBOL = { .tag = JIT_SYM_BOTTOM_TAG }; +static JitOptSymbol * +allocation_base(JitOptContext *ctx) +{ + return ctx->t_arena.arena; +} + JitOptSymbol * out_of_space(JitOptContext *ctx) { @@ -90,9 +96,16 @@ _Py_uop_sym_is_not_null(JitOptSymbol *sym) { } bool -_Py_uop_sym_is_const(JitOptSymbol *sym) +_Py_uop_sym_is_const(JitOptContext *ctx, JitOptSymbol *sym) { - return sym->tag == JIT_SYM_KNOWN_VALUE_TAG; + if (sym->tag == JIT_SYM_KNOWN_VALUE_TAG) { + return true; + } + if (sym->tag == JIT_SYM_TRUTH_TAG) { + JitOptSymbol *value = allocation_base(ctx) + sym->truth.value; + return _Py_uop_sym_truthiness(ctx, value) >= 0; + } + return false; } bool @@ -103,11 +116,19 @@ _Py_uop_sym_is_null(JitOptSymbol *sym) PyObject * -_Py_uop_sym_get_const(JitOptSymbol *sym) +_Py_uop_sym_get_const(JitOptContext *ctx, JitOptSymbol *sym) { if (sym->tag == JIT_SYM_KNOWN_VALUE_TAG) { return sym->value.value; } + if (sym->tag == JIT_SYM_TRUTH_TAG) { + JitOptSymbol *value = allocation_base(ctx) + sym->truth.value; + int truth = _Py_uop_sym_truthiness(ctx, value); + if (truth < 0) { + return NULL; + } + return (truth ^ sym->truth.not) ? Py_True : Py_False; + } return NULL; } @@ -153,6 +174,11 @@ _Py_uop_sym_set_type(JitOptContext *ctx, JitOptSymbol *sym, PyTypeObject *typ) sym->cls.version = 0; sym->cls.type = typ; return; + case JIT_SYM_TRUTH_TAG: + if (typ != &PyBool_Type) { + sym_set_bottom(ctx, sym); + } + return; } } @@ -193,6 +219,12 @@ _Py_uop_sym_set_type_version(JitOptContext *ctx, JitOptSymbol *sym, unsigned int sym->tag = JIT_SYM_TYPE_VERSION_TAG; sym->version.version = version; return true; + case JIT_SYM_TRUTH_TAG: + if (version != PyBool_Type.tp_version_tag) { + sym_set_bottom(ctx, sym); + return false; + } + return true; } Py_UNREACHABLE(); } @@ -240,6 +272,26 @@ _Py_uop_sym_set_const(JitOptContext *ctx, JitOptSymbol *sym, PyObject *const_val case JIT_SYM_UNKNOWN_TAG: make_const(sym, const_val); return; + case JIT_SYM_TRUTH_TAG: + if (!PyBool_Check(const_val) || _Py_uop_sym_get_const(ctx, sym) != const_val) { + sym_set_bottom(ctx, sym); + return; + } + JitOptSymbol *value = allocation_base(ctx) + sym->truth.value; + PyTypeObject *type = _Py_uop_sym_get_type(value); + if (const_val == (sym->truth.not ? Py_False : Py_True)) { + // value is truthy. This is only useful for bool: + if (type == &PyBool_Type) { + _Py_uop_sym_set_const(ctx, value, Py_True); + } + } + // value is falsey: + else if (type == &PyBool_Type) { + _Py_uop_sym_set_const(ctx, value, Py_False); + } + // TODO: More types (GH-130415)! + make_const(sym, const_val); + return; } } @@ -339,6 +391,8 @@ _Py_uop_sym_get_type(JitOptSymbol *sym) return Py_TYPE(sym->value.value); case JIT_SYM_TUPLE_TAG: return &PyTuple_Type; + case JIT_SYM_TRUTH_TAG: + return &PyBool_Type; } Py_UNREACHABLE(); } @@ -361,6 +415,8 @@ _Py_uop_sym_get_type_version(JitOptSymbol *sym) return Py_TYPE(sym->value.value)->tp_version_tag; case JIT_SYM_TUPLE_TAG: return PyTuple_Type.tp_version_tag; + case JIT_SYM_TRUTH_TAG: + return PyBool_Type.tp_version_tag; } Py_UNREACHABLE(); } @@ -379,6 +435,7 @@ _Py_uop_sym_has_type(JitOptSymbol *sym) case JIT_SYM_KNOWN_CLASS_TAG: case JIT_SYM_KNOWN_VALUE_TAG: case JIT_SYM_TUPLE_TAG: + case JIT_SYM_TRUTH_TAG: return true; } Py_UNREACHABLE(); @@ -398,7 +455,7 @@ _Py_uop_sym_matches_type_version(JitOptSymbol *sym, unsigned int version) } int -_Py_uop_sym_truthiness(JitOptSymbol *sym) +_Py_uop_sym_truthiness(JitOptContext *ctx, JitOptSymbol *sym) { switch(sym->tag) { case JIT_SYM_NULL_TAG: @@ -416,6 +473,14 @@ _Py_uop_sym_truthiness(JitOptSymbol *sym) break; case JIT_SYM_TUPLE_TAG: return sym->tuple.length != 0; + case JIT_SYM_TRUTH_TAG: + ; + JitOptSymbol *value = allocation_base(ctx) + sym->truth.value; + int truth = _Py_uop_sym_truthiness(ctx, value); + if (truth < 0) { + return truth; + } + return truth ^ sym->truth.not; } PyObject *value = sym->value.value; /* Only handle a few known safe types */ @@ -435,12 +500,6 @@ _Py_uop_sym_truthiness(JitOptSymbol *sym) return -1; } -static JitOptSymbol * -allocation_base(JitOptContext *ctx) -{ - return ctx->t_arena.arena; -} - JitOptSymbol * _Py_uop_sym_new_tuple(JitOptContext *ctx, int size, JitOptSymbol **args) { @@ -634,8 +693,8 @@ _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) TEST_PREDICATE(!_Py_uop_sym_is_null(sym), "top is NULL"); TEST_PREDICATE(!_Py_uop_sym_is_not_null(sym), "top is not NULL"); TEST_PREDICATE(!_Py_uop_sym_matches_type(sym, &PyLong_Type), "top matches a type"); - TEST_PREDICATE(!_Py_uop_sym_is_const(sym), "top is a constant"); - TEST_PREDICATE(_Py_uop_sym_get_const(sym) == NULL, "top as constant is not NULL"); + TEST_PREDICATE(!_Py_uop_sym_is_const(ctx, sym), "top is a constant"); + TEST_PREDICATE(_Py_uop_sym_get_const(ctx, sym) == NULL, "top as constant is not NULL"); TEST_PREDICATE(!_Py_uop_sym_is_bottom(sym), "top is bottom"); sym = make_bottom(ctx); @@ -645,8 +704,8 @@ _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) TEST_PREDICATE(!_Py_uop_sym_is_null(sym), "bottom is NULL is not false"); TEST_PREDICATE(!_Py_uop_sym_is_not_null(sym), "bottom is not NULL is not false"); TEST_PREDICATE(!_Py_uop_sym_matches_type(sym, &PyLong_Type), "bottom matches a type"); - TEST_PREDICATE(!_Py_uop_sym_is_const(sym), "bottom is a constant is not false"); - TEST_PREDICATE(_Py_uop_sym_get_const(sym) == NULL, "bottom as constant is not NULL"); + TEST_PREDICATE(!_Py_uop_sym_is_const(ctx, sym), "bottom is a constant is not false"); + TEST_PREDICATE(_Py_uop_sym_get_const(ctx, sym) == NULL, "bottom as constant is not NULL"); TEST_PREDICATE(_Py_uop_sym_is_bottom(sym), "bottom isn't bottom"); sym = _Py_uop_sym_new_type(ctx, &PyLong_Type); @@ -657,8 +716,8 @@ _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) TEST_PREDICATE(_Py_uop_sym_is_not_null(sym), "int isn't not NULL"); TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyLong_Type), "int isn't int"); TEST_PREDICATE(!_Py_uop_sym_matches_type(sym, &PyFloat_Type), "int matches float"); - TEST_PREDICATE(!_Py_uop_sym_is_const(sym), "int is a constant"); - TEST_PREDICATE(_Py_uop_sym_get_const(sym) == NULL, "int as constant is not NULL"); + TEST_PREDICATE(!_Py_uop_sym_is_const(ctx, sym), "int is a constant"); + TEST_PREDICATE(_Py_uop_sym_get_const(ctx, sym) == NULL, "int as constant is not NULL"); _Py_uop_sym_set_type(ctx, sym, &PyLong_Type); // Should be a no-op TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyLong_Type), "(int and int) isn't int"); @@ -679,19 +738,19 @@ _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) goto fail; } _Py_uop_sym_set_const(ctx, sym, val_42); - TEST_PREDICATE(_Py_uop_sym_truthiness(sym) == 1, "bool(42) is not True"); + TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, sym) == 1, "bool(42) is not True"); TEST_PREDICATE(!_Py_uop_sym_is_null(sym), "42 is NULL"); TEST_PREDICATE(_Py_uop_sym_is_not_null(sym), "42 isn't not NULL"); TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyLong_Type), "42 isn't an int"); TEST_PREDICATE(!_Py_uop_sym_matches_type(sym, &PyFloat_Type), "42 matches float"); - TEST_PREDICATE(_Py_uop_sym_is_const(sym), "42 is not a constant"); - TEST_PREDICATE(_Py_uop_sym_get_const(sym) != NULL, "42 as constant is NULL"); - TEST_PREDICATE(_Py_uop_sym_get_const(sym) == val_42, "42 as constant isn't 42"); + TEST_PREDICATE(_Py_uop_sym_is_const(ctx, sym), "42 is not a constant"); + TEST_PREDICATE(_Py_uop_sym_get_const(ctx, sym) != NULL, "42 as constant is NULL"); + TEST_PREDICATE(_Py_uop_sym_get_const(ctx, sym) == val_42, "42 as constant isn't 42"); TEST_PREDICATE(_Py_uop_sym_is_immortal(sym), "42 is not immortal"); _Py_uop_sym_set_type(ctx, sym, &PyLong_Type); // Should be a no-op TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyLong_Type), "(42 and 42) isn't an int"); - TEST_PREDICATE(_Py_uop_sym_get_const(sym) == val_42, "(42 and 42) as constant isn't 42"); + TEST_PREDICATE(_Py_uop_sym_get_const(ctx, sym) == val_42, "(42 and 42) as constant isn't 42"); _Py_uop_sym_set_type(ctx, sym, &PyFloat_Type); // Should make it bottom TEST_PREDICATE(_Py_uop_sym_is_bottom(sym), "(42 and float) isn't bottom"); @@ -709,11 +768,11 @@ _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) sym = _Py_uop_sym_new_const(ctx, Py_None); - TEST_PREDICATE(_Py_uop_sym_truthiness(sym) == 0, "bool(None) is not False"); + TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, sym) == 0, "bool(None) is not False"); sym = _Py_uop_sym_new_const(ctx, Py_False); - TEST_PREDICATE(_Py_uop_sym_truthiness(sym) == 0, "bool(False) is not False"); + TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, sym) == 0, "bool(False) is not False"); sym = _Py_uop_sym_new_const(ctx, PyLong_FromLong(0)); - TEST_PREDICATE(_Py_uop_sym_truthiness(sym) == 0, "bool(0) is not False"); + TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, sym) == 0, "bool(0) is not False"); JitOptSymbol *i1 = _Py_uop_sym_new_type(ctx, &PyFloat_Type); JitOptSymbol *i2 = _Py_uop_sym_new_const(ctx, val_43); @@ -724,14 +783,14 @@ _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) "tuple item does not match value used to create tuple" ); TEST_PREDICATE( - _Py_uop_sym_get_const(_Py_uop_sym_tuple_getitem(ctx, sym, 1)) == val_43, + _Py_uop_sym_get_const(ctx, _Py_uop_sym_tuple_getitem(ctx, sym, 1)) == val_43, "tuple item does not match value used to create tuple" ); PyObject *pair[2] = { val_42, val_43 }; PyObject *tuple = _PyTuple_FromArray(pair, 2); sym = _Py_uop_sym_new_const(ctx, tuple); TEST_PREDICATE( - _Py_uop_sym_get_const(_Py_uop_sym_tuple_getitem(ctx, sym, 1)) == val_43, + _Py_uop_sym_get_const(ctx, _Py_uop_sym_tuple_getitem(ctx, sym, 1)) == val_43, "tuple item does not match value used to create tuple" ); From 6c4588e5a05c5ae8721378e8f5b86d751a36d634 Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Thu, 27 Feb 2025 16:45:10 -0800 Subject: [PATCH 03/12] Fix up test --- Lib/test/test_capi/test_opt.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 5e7100e95deca2..ec989819642a97 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -1441,19 +1441,19 @@ def test_narrow_type_to_constant_bool_false(self): def f(n): trace = [] for i in range(n): - # f is always False, but we can only prove that it's a bool: - f = i == TIER2_THRESHOLD + # false *usually* False, but we can only prove that it's a bool: + false = i % 100 == 0 trace.append("A") - if not f: # Kept. + if not false: # Kept. trace.append("B") - if not f: # Removed! + if not false: # Removed! trace.append("C") trace.append("D") - if f: # Removed! + if false: # Removed! trace.append("X") trace.append("E") trace.append("F") - if f: # Removed! + if false: # Removed! trace.append("X") trace.append("G") return trace @@ -1472,19 +1472,19 @@ def test_narrow_type_to_constant_bool_true(self): def f(n): trace = [] for i in range(n): - # f is always True, but we can only prove that it's a bool: - f = i != TIER2_THRESHOLD + # true *usually* True, but we can only prove that it's a bool: + true = i % 100 != 0 trace.append("A") - if f: # Kept. + if true: # Kept. trace.append("B") - if not f: # Removed! + if not true: # Removed! trace.append("X") trace.append("C") - if f: # Removed! + if true: # Removed! trace.append("D") trace.append("E") trace.append("F") - if not f: # Removed! + if not true: # Removed! trace.append("X") trace.append("G") return trace From 1d17afe577b2ba8edde2fa8a8d1e8eb226ef803f Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Thu, 27 Feb 2025 16:46:03 -0800 Subject: [PATCH 04/12] is --- Lib/test/test_capi/test_opt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index ec989819642a97..7fafd30c8ccb8b 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -1441,7 +1441,7 @@ def test_narrow_type_to_constant_bool_false(self): def f(n): trace = [] for i in range(n): - # false *usually* False, but we can only prove that it's a bool: + # false is *usually* False, but we can only prove that it's a bool: false = i % 100 == 0 trace.append("A") if not false: # Kept. @@ -1472,7 +1472,7 @@ def test_narrow_type_to_constant_bool_true(self): def f(n): trace = [] for i in range(n): - # true *usually* True, but we can only prove that it's a bool: + # true is *usually* True, but we can only prove that it's a bool: true = i % 100 != 0 trace.append("A") if true: # Kept. From dcb294cb737b0d760796d5d5172177e32153e07f Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Thu, 27 Feb 2025 16:48:12 -0800 Subject: [PATCH 05/12] Add creation and narrowing of truths --- Include/internal/pycore_optimizer.h | 1 + Python/optimizer_analysis.c | 1 + Python/optimizer_symbols.c | 54 +++++++++++++++++++++++------ 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 26308d25eb199c..863bdce1865807 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -276,6 +276,7 @@ extern bool _Py_uop_sym_is_immortal(JitOptSymbol *sym); extern JitOptSymbol *_Py_uop_sym_new_tuple(JitOptContext *ctx, int size, JitOptSymbol **args); extern JitOptSymbol *_Py_uop_sym_tuple_getitem(JitOptContext *ctx, JitOptSymbol *sym, int item); extern int _Py_uop_sym_tuple_length(JitOptSymbol *sym); +extern JitOptSymbol *_Py_uop_sym_new_truth(JitOptContext *ctx, JitOptSymbol *value, bool not); extern void _Py_uop_abstractcontext_init(JitOptContext *ctx); extern void _Py_uop_abstractcontext_fini(JitOptContext *ctx); diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 9d1506b0dafa7f..7f917d9f7c41cc 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -376,6 +376,7 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, #define sym_tuple_getitem _Py_uop_sym_tuple_getitem #define sym_tuple_length _Py_uop_sym_tuple_length #define sym_is_immortal _Py_uop_sym_is_immortal +#define sym_new_truth _Py_uop_sym_new_truth static int optimize_to_bool( diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 8b48c9b65bf31f..7c4822693f5240 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -76,6 +76,12 @@ sym_new(JitOptContext *ctx) return self; } +static void make_const(JitOptSymbol *sym, PyObject *val) +{ + sym->tag = JIT_SYM_KNOWN_VALUE_TAG; + sym->value.value = Py_NewRef(val); +} + static inline void sym_set_bottom(JitOptContext *ctx, JitOptSymbol *sym) { @@ -103,7 +109,12 @@ _Py_uop_sym_is_const(JitOptContext *ctx, JitOptSymbol *sym) } if (sym->tag == JIT_SYM_TRUTH_TAG) { JitOptSymbol *value = allocation_base(ctx) + sym->truth.value; - return _Py_uop_sym_truthiness(ctx, value) >= 0; + int truth = _Py_uop_sym_truthiness(ctx, value); + if (truth < 0) { + return false; + } + make_const(sym, (truth ^ sym->truth.not) ? Py_True : Py_False); + return true; } return false; } @@ -127,7 +138,9 @@ _Py_uop_sym_get_const(JitOptContext *ctx, JitOptSymbol *sym) if (truth < 0) { return NULL; } - return (truth ^ sym->truth.not) ? Py_True : Py_False; + PyObject *res = (truth ^ sym->truth.not) ? Py_True : Py_False; + make_const(sym, res); + return res; } return NULL; } @@ -229,12 +242,6 @@ _Py_uop_sym_set_type_version(JitOptContext *ctx, JitOptSymbol *sym, unsigned int Py_UNREACHABLE(); } -static void make_const(JitOptSymbol *sym, PyObject *val) -{ - sym->tag = JIT_SYM_KNOWN_VALUE_TAG; - sym->value.value = Py_NewRef(val); -} - void _Py_uop_sym_set_const(JitOptContext *ctx, JitOptSymbol *sym, PyObject *const_val) { @@ -273,7 +280,10 @@ _Py_uop_sym_set_const(JitOptContext *ctx, JitOptSymbol *sym, PyObject *const_val make_const(sym, const_val); return; case JIT_SYM_TRUTH_TAG: - if (!PyBool_Check(const_val) || _Py_uop_sym_get_const(ctx, sym) != const_val) { + if (!PyBool_Check(const_val) || + (_Py_uop_sym_is_const(ctx, sym) && + _Py_uop_sym_get_const(ctx, sym) != const_val)) + { sym_set_bottom(ctx, sym); return; } @@ -480,7 +490,9 @@ _Py_uop_sym_truthiness(JitOptContext *ctx, JitOptSymbol *sym) if (truth < 0) { return truth; } - return truth ^ sym->truth.not; + truth ^= sym->truth.not; + make_const(sym, truth ? Py_True : Py_False); + return truth; } PyObject *value = sym->value.value; /* Only handle a few known safe types */ @@ -565,6 +577,28 @@ _Py_uop_sym_is_immortal(JitOptSymbol *sym) return false; } +JitOptSymbol * +_Py_uop_sym_new_truth(JitOptContext *ctx, JitOptSymbol *value, bool not) +{ + if (value->tag == JIT_SYM_TRUTH_TAG && value->truth.not == not) { + return value; + } + JitOptSymbol *res = sym_new(ctx); + if (res == NULL) { + return out_of_space(ctx); + } + int truth = _Py_uop_sym_truthiness(ctx, value); + if (truth < 0) { + res->tag = JIT_SYM_TRUTH_TAG; + res->truth.not = not; + res->truth.value = (uint16_t)(value - allocation_base(ctx)); + } + else { + make_const(res, (truth ^ not) ? Py_True : Py_False); + } + return res; +} + // 0 on success, -1 on error. _Py_UOpsAbstractFrame * _Py_uop_frame_new( From 41e77a88757ddd8439cdd87fb446837a3a3c0259 Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Thu, 27 Feb 2025 16:48:53 -0800 Subject: [PATCH 06/12] Use the new truth symbols --- Python/optimizer_bytecodes.c | 10 ++++++++-- Python/optimizer_cases.c.h | 9 ++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 0cd052d9fb01d5..a7a18610cfcd2d 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -34,6 +34,7 @@ typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame; #define sym_tuple_getitem _Py_uop_sym_tuple_getitem #define sym_tuple_length _Py_uop_sym_tuple_length #define sym_is_immortal _Py_uop_sym_is_immortal +#define sym_new_truth _Py_uop_sym_new_truth extern int optimize_to_bool( @@ -391,14 +392,14 @@ dummy_func(void) { op(_TO_BOOL, (value -- res)) { if (!optimize_to_bool(this_instr, ctx, value, &res)) { - res = sym_new_type(ctx, &PyBool_Type); + res = sym_new_truth(ctx, value, false); } } op(_TO_BOOL_BOOL, (value -- res)) { if (!optimize_to_bool(this_instr, ctx, value, &res)) { sym_set_type(value, &PyBool_Type); - res = value; + res = sym_new_truth(ctx, value, false); } } @@ -430,6 +431,11 @@ dummy_func(void) { } } + op(_UNARY_NOT, (value -- res)) { + sym_set_type(value, &PyBool_Type); + res = sym_new_truth(ctx, value, true); + } + op(_COMPARE_OP, (left, right -- res)) { if (oparg & 16) { res = sym_new_type(ctx, &PyBool_Type); diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 2e319b5d374142..5af5c607af8d0f 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -140,8 +140,11 @@ } case _UNARY_NOT: { + JitOptSymbol *value; JitOptSymbol *res; - res = sym_new_not_null(ctx); + value = stack_pointer[-1]; + sym_set_type(value, &PyBool_Type); + res = sym_new_truth(ctx, value, true); stack_pointer[-1] = res; break; } @@ -151,7 +154,7 @@ JitOptSymbol *res; value = stack_pointer[-1]; if (!optimize_to_bool(this_instr, ctx, value, &res)) { - res = sym_new_type(ctx, &PyBool_Type); + res = sym_new_truth(ctx, value, false); } stack_pointer[-1] = res; break; @@ -163,7 +166,7 @@ value = stack_pointer[-1]; if (!optimize_to_bool(this_instr, ctx, value, &res)) { sym_set_type(value, &PyBool_Type); - res = value; + res = sym_new_truth(ctx, value, false); } stack_pointer[-1] = res; break; From d0b1724362740cdddc62cabdbae639ee8c6e9bb1 Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Thu, 27 Feb 2025 16:55:44 -0800 Subject: [PATCH 07/12] Fix tests --- Lib/test/test_capi/test_opt.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 7fafd30c8ccb8b..b7083dbfb89db8 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -1441,8 +1441,8 @@ def test_narrow_type_to_constant_bool_false(self): def f(n): trace = [] for i in range(n): - # false is *usually* False, but we can only prove that it's a bool: - false = i % 100 == 0 + # false is always False, but we can only prove that it's a bool: + false = i == TIER2_THRESHOLD trace.append("A") if not false: # Kept. trace.append("B") @@ -1472,8 +1472,8 @@ def test_narrow_type_to_constant_bool_true(self): def f(n): trace = [] for i in range(n): - # true is *usually* True, but we can only prove that it's a bool: - true = i % 100 != 0 + # true always True, but we can only prove that it's a bool: + true = i != TIER2_THRESHOLD trace.append("A") if true: # Kept. trace.append("B") From c8214c96a0144d5f6f9f9dd55a2dd5258fe15e8c Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Thu, 27 Feb 2025 17:05:44 -0800 Subject: [PATCH 08/12] blurb add --- .../2025-02-27-17-05-05.gh-issue-130415.iijvfW.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-02-27-17-05-05.gh-issue-130415.iijvfW.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-27-17-05-05.gh-issue-130415.iijvfW.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-27-17-05-05.gh-issue-130415.iijvfW.rst new file mode 100644 index 00000000000000..cef42f8b6d8d90 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-27-17-05-05.gh-issue-130415.iijvfW.rst @@ -0,0 +1,2 @@ +Improve the experimental JIT's ability to narrow boolean values based on the +results of truth tests. From ffa559b2309e327f0cfd1637c38f237fda915da9 Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Fri, 28 Feb 2025 10:46:44 -0800 Subject: [PATCH 09/12] "truth" -> "truthiness" --- Include/internal/pycore_optimizer.h | 8 +-- ...-02-27-17-05-05.gh-issue-130415.iijvfW.rst | 2 +- Python/optimizer_analysis.c | 2 +- Python/optimizer_bytecodes.c | 8 +-- Python/optimizer_cases.c.h | 6 +- Python/optimizer_symbols.c | 68 +++++++++---------- 6 files changed, 47 insertions(+), 47 deletions(-) diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 863bdce1865807..c8a5dc0b597a7c 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -172,7 +172,7 @@ typedef enum _JitSymType { JIT_SYM_KNOWN_CLASS_TAG = 6, JIT_SYM_KNOWN_VALUE_TAG = 7, JIT_SYM_TUPLE_TAG = 8, - JIT_SYM_TRUTH_TAG = 9, + JIT_SYM_TRUTHINESS_TAG = 9, } JitSymType; typedef struct _jit_opt_known_class { @@ -203,7 +203,7 @@ typedef struct { uint8_t tag; bool not; uint16_t value; -} JitOptTruth; +} JitOptTruthiness; typedef union _jit_opt_symbol { uint8_t tag; @@ -211,7 +211,7 @@ typedef union _jit_opt_symbol { JitOptKnownValue value; JitOptKnownVersion version; JitOptTuple tuple; - JitOptTruth truth; + JitOptTruthiness truthiness; } JitOptSymbol; @@ -276,7 +276,7 @@ extern bool _Py_uop_sym_is_immortal(JitOptSymbol *sym); extern JitOptSymbol *_Py_uop_sym_new_tuple(JitOptContext *ctx, int size, JitOptSymbol **args); extern JitOptSymbol *_Py_uop_sym_tuple_getitem(JitOptContext *ctx, JitOptSymbol *sym, int item); extern int _Py_uop_sym_tuple_length(JitOptSymbol *sym); -extern JitOptSymbol *_Py_uop_sym_new_truth(JitOptContext *ctx, JitOptSymbol *value, bool not); +extern JitOptSymbol *_Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptSymbol *value, bool not); extern void _Py_uop_abstractcontext_init(JitOptContext *ctx); extern void _Py_uop_abstractcontext_fini(JitOptContext *ctx); diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-27-17-05-05.gh-issue-130415.iijvfW.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-27-17-05-05.gh-issue-130415.iijvfW.rst index cef42f8b6d8d90..f5b6d0e9c1a06a 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-27-17-05-05.gh-issue-130415.iijvfW.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-27-17-05-05.gh-issue-130415.iijvfW.rst @@ -1,2 +1,2 @@ Improve the experimental JIT's ability to narrow boolean values based on the -results of truth tests. +results of truthiness tests. diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 7f917d9f7c41cc..43df88711dbd77 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -376,7 +376,7 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, #define sym_tuple_getitem _Py_uop_sym_tuple_getitem #define sym_tuple_length _Py_uop_sym_tuple_length #define sym_is_immortal _Py_uop_sym_is_immortal -#define sym_new_truth _Py_uop_sym_new_truth +#define sym_new_truthiness _Py_uop_sym_new_truthiness static int optimize_to_bool( diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index a7a18610cfcd2d..2689801a45a13d 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -34,7 +34,7 @@ typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame; #define sym_tuple_getitem _Py_uop_sym_tuple_getitem #define sym_tuple_length _Py_uop_sym_tuple_length #define sym_is_immortal _Py_uop_sym_is_immortal -#define sym_new_truth _Py_uop_sym_new_truth +#define sym_new_truthiness _Py_uop_sym_new_truthiness extern int optimize_to_bool( @@ -392,14 +392,14 @@ dummy_func(void) { op(_TO_BOOL, (value -- res)) { if (!optimize_to_bool(this_instr, ctx, value, &res)) { - res = sym_new_truth(ctx, value, false); + res = sym_new_truthiness(ctx, value, false); } } op(_TO_BOOL_BOOL, (value -- res)) { if (!optimize_to_bool(this_instr, ctx, value, &res)) { sym_set_type(value, &PyBool_Type); - res = sym_new_truth(ctx, value, false); + res = sym_new_truthiness(ctx, value, false); } } @@ -433,7 +433,7 @@ dummy_func(void) { op(_UNARY_NOT, (value -- res)) { sym_set_type(value, &PyBool_Type); - res = sym_new_truth(ctx, value, true); + res = sym_new_truthiness(ctx, value, true); } op(_COMPARE_OP, (left, right -- res)) { diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 5af5c607af8d0f..e2e3bfef010b70 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -144,7 +144,7 @@ JitOptSymbol *res; value = stack_pointer[-1]; sym_set_type(value, &PyBool_Type); - res = sym_new_truth(ctx, value, true); + res = sym_new_truthiness(ctx, value, true); stack_pointer[-1] = res; break; } @@ -154,7 +154,7 @@ JitOptSymbol *res; value = stack_pointer[-1]; if (!optimize_to_bool(this_instr, ctx, value, &res)) { - res = sym_new_truth(ctx, value, false); + res = sym_new_truthiness(ctx, value, false); } stack_pointer[-1] = res; break; @@ -166,7 +166,7 @@ value = stack_pointer[-1]; if (!optimize_to_bool(this_instr, ctx, value, &res)) { sym_set_type(value, &PyBool_Type); - res = sym_new_truth(ctx, value, false); + res = sym_new_truthiness(ctx, value, false); } stack_pointer[-1] = res; break; diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 7c4822693f5240..95db896bccdd77 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -107,13 +107,13 @@ _Py_uop_sym_is_const(JitOptContext *ctx, JitOptSymbol *sym) if (sym->tag == JIT_SYM_KNOWN_VALUE_TAG) { return true; } - if (sym->tag == JIT_SYM_TRUTH_TAG) { - JitOptSymbol *value = allocation_base(ctx) + sym->truth.value; - int truth = _Py_uop_sym_truthiness(ctx, value); - if (truth < 0) { + if (sym->tag == JIT_SYM_TRUTHINESS_TAG) { + JitOptSymbol *value = allocation_base(ctx) + sym->truthiness.value; + int truthiness = _Py_uop_sym_truthiness(ctx, value); + if (truthiness < 0) { return false; } - make_const(sym, (truth ^ sym->truth.not) ? Py_True : Py_False); + make_const(sym, (truthiness ^ sym->truthiness.not) ? Py_True : Py_False); return true; } return false; @@ -132,13 +132,13 @@ _Py_uop_sym_get_const(JitOptContext *ctx, JitOptSymbol *sym) if (sym->tag == JIT_SYM_KNOWN_VALUE_TAG) { return sym->value.value; } - if (sym->tag == JIT_SYM_TRUTH_TAG) { - JitOptSymbol *value = allocation_base(ctx) + sym->truth.value; - int truth = _Py_uop_sym_truthiness(ctx, value); - if (truth < 0) { + if (sym->tag == JIT_SYM_TRUTHINESS_TAG) { + JitOptSymbol *value = allocation_base(ctx) + sym->truthiness.value; + int truthiness = _Py_uop_sym_truthiness(ctx, value); + if (truthiness < 0) { return NULL; } - PyObject *res = (truth ^ sym->truth.not) ? Py_True : Py_False; + PyObject *res = (truthiness ^ sym->truthiness.not) ? Py_True : Py_False; make_const(sym, res); return res; } @@ -187,7 +187,7 @@ _Py_uop_sym_set_type(JitOptContext *ctx, JitOptSymbol *sym, PyTypeObject *typ) sym->cls.version = 0; sym->cls.type = typ; return; - case JIT_SYM_TRUTH_TAG: + case JIT_SYM_TRUTHINESS_TAG: if (typ != &PyBool_Type) { sym_set_bottom(ctx, sym); } @@ -232,7 +232,7 @@ _Py_uop_sym_set_type_version(JitOptContext *ctx, JitOptSymbol *sym, unsigned int sym->tag = JIT_SYM_TYPE_VERSION_TAG; sym->version.version = version; return true; - case JIT_SYM_TRUTH_TAG: + case JIT_SYM_TRUTHINESS_TAG: if (version != PyBool_Type.tp_version_tag) { sym_set_bottom(ctx, sym); return false; @@ -279,7 +279,7 @@ _Py_uop_sym_set_const(JitOptContext *ctx, JitOptSymbol *sym, PyObject *const_val case JIT_SYM_UNKNOWN_TAG: make_const(sym, const_val); return; - case JIT_SYM_TRUTH_TAG: + case JIT_SYM_TRUTHINESS_TAG: if (!PyBool_Check(const_val) || (_Py_uop_sym_is_const(ctx, sym) && _Py_uop_sym_get_const(ctx, sym) != const_val)) @@ -287,9 +287,9 @@ _Py_uop_sym_set_const(JitOptContext *ctx, JitOptSymbol *sym, PyObject *const_val sym_set_bottom(ctx, sym); return; } - JitOptSymbol *value = allocation_base(ctx) + sym->truth.value; + JitOptSymbol *value = allocation_base(ctx) + sym->truthiness.value; PyTypeObject *type = _Py_uop_sym_get_type(value); - if (const_val == (sym->truth.not ? Py_False : Py_True)) { + if (const_val == (sym->truthiness.not ? Py_False : Py_True)) { // value is truthy. This is only useful for bool: if (type == &PyBool_Type) { _Py_uop_sym_set_const(ctx, value, Py_True); @@ -401,7 +401,7 @@ _Py_uop_sym_get_type(JitOptSymbol *sym) return Py_TYPE(sym->value.value); case JIT_SYM_TUPLE_TAG: return &PyTuple_Type; - case JIT_SYM_TRUTH_TAG: + case JIT_SYM_TRUTHINESS_TAG: return &PyBool_Type; } Py_UNREACHABLE(); @@ -425,7 +425,7 @@ _Py_uop_sym_get_type_version(JitOptSymbol *sym) return Py_TYPE(sym->value.value)->tp_version_tag; case JIT_SYM_TUPLE_TAG: return PyTuple_Type.tp_version_tag; - case JIT_SYM_TRUTH_TAG: + case JIT_SYM_TRUTHINESS_TAG: return PyBool_Type.tp_version_tag; } Py_UNREACHABLE(); @@ -445,7 +445,7 @@ _Py_uop_sym_has_type(JitOptSymbol *sym) case JIT_SYM_KNOWN_CLASS_TAG: case JIT_SYM_KNOWN_VALUE_TAG: case JIT_SYM_TUPLE_TAG: - case JIT_SYM_TRUTH_TAG: + case JIT_SYM_TRUTHINESS_TAG: return true; } Py_UNREACHABLE(); @@ -483,16 +483,16 @@ _Py_uop_sym_truthiness(JitOptContext *ctx, JitOptSymbol *sym) break; case JIT_SYM_TUPLE_TAG: return sym->tuple.length != 0; - case JIT_SYM_TRUTH_TAG: + case JIT_SYM_TRUTHINESS_TAG: ; - JitOptSymbol *value = allocation_base(ctx) + sym->truth.value; - int truth = _Py_uop_sym_truthiness(ctx, value); - if (truth < 0) { - return truth; + JitOptSymbol *value = allocation_base(ctx) + sym->truthiness.value; + int truthiness = _Py_uop_sym_truthiness(ctx, value); + if (truthiness < 0) { + return truthiness; } - truth ^= sym->truth.not; - make_const(sym, truth ? Py_True : Py_False); - return truth; + truthiness ^= sym->truthiness.not; + make_const(sym, truthiness ? Py_True : Py_False); + return truthiness; } PyObject *value = sym->value.value; /* Only handle a few known safe types */ @@ -578,23 +578,23 @@ _Py_uop_sym_is_immortal(JitOptSymbol *sym) } JitOptSymbol * -_Py_uop_sym_new_truth(JitOptContext *ctx, JitOptSymbol *value, bool not) +_Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptSymbol *value, bool not) { - if (value->tag == JIT_SYM_TRUTH_TAG && value->truth.not == not) { + if (value->tag == JIT_SYM_TRUTHINESS_TAG && value->truthiness.not == not) { return value; } JitOptSymbol *res = sym_new(ctx); if (res == NULL) { return out_of_space(ctx); } - int truth = _Py_uop_sym_truthiness(ctx, value); - if (truth < 0) { - res->tag = JIT_SYM_TRUTH_TAG; - res->truth.not = not; - res->truth.value = (uint16_t)(value - allocation_base(ctx)); + int truthiness = _Py_uop_sym_truthiness(ctx, value); + if (truthiness < 0) { + res->tag = JIT_SYM_TRUTHINESS_TAG; + res->truthiness.not = not; + res->truthiness.value = (uint16_t)(value - allocation_base(ctx)); } else { - make_const(res, (truth ^ not) ? Py_True : Py_False); + make_const(res, (truthiness ^ not) ? Py_True : Py_False); } return res; } From cbd4569dee37b5b2686b17b2ada6605a4739030e Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Fri, 28 Feb 2025 10:53:57 -0800 Subject: [PATCH 10/12] Fix some compiler warnings --- Python/optimizer_symbols.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 95db896bccdd77..8244613c14ff18 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -663,7 +663,7 @@ _Py_uop_abstractcontext_fini(JitOptContext *ctx) void _Py_uop_abstractcontext_init(JitOptContext *ctx) { - static_assert(sizeof(JitOptSymbol) <= 2*sizeof(uint64_t)); + static_assert(sizeof(JitOptSymbol) <= 2 * sizeof(uint64_t), "JitOptSymbol has grown"); ctx->limit = ctx->locals_and_stack + MAX_ABSTRACT_INTERP_SIZE; ctx->n_consumed = ctx->locals_and_stack; #ifdef Py_DEBUG // Aids debugging a little. There should never be NULL in the abstract interpreter. @@ -718,6 +718,7 @@ _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) _Py_uop_abstractcontext_init(ctx); PyObject *val_42 = NULL; PyObject *val_43 = NULL; + PyObject *tuple = NULL; // Use a single 'sym' variable so copy-pasting tests is easier. JitOptSymbol *sym = _Py_uop_sym_new_unknown(ctx); @@ -821,7 +822,7 @@ _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) "tuple item does not match value used to create tuple" ); PyObject *pair[2] = { val_42, val_43 }; - PyObject *tuple = _PyTuple_FromArray(pair, 2); + tuple = _PyTuple_FromArray(pair, 2); sym = _Py_uop_sym_new_const(ctx, tuple); TEST_PREDICATE( _Py_uop_sym_get_const(ctx, _Py_uop_sym_tuple_getitem(ctx, sym, 1)) == val_43, From 9f1b271a15eed495f43525e357605d76df3fb291 Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Fri, 28 Feb 2025 16:06:11 -0800 Subject: [PATCH 11/12] Invert the "not" arg for clarity --- Include/internal/pycore_optimizer.h | 2 +- Python/optimizer_bytecodes.c | 6 +++--- Python/optimizer_cases.c.h | 6 +++--- Python/optimizer_symbols.c | 4 +++- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index c8a5dc0b597a7c..79861167f65e63 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -276,7 +276,7 @@ extern bool _Py_uop_sym_is_immortal(JitOptSymbol *sym); extern JitOptSymbol *_Py_uop_sym_new_tuple(JitOptContext *ctx, int size, JitOptSymbol **args); extern JitOptSymbol *_Py_uop_sym_tuple_getitem(JitOptContext *ctx, JitOptSymbol *sym, int item); extern int _Py_uop_sym_tuple_length(JitOptSymbol *sym); -extern JitOptSymbol *_Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptSymbol *value, bool not); +extern JitOptSymbol *_Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptSymbol *value, bool truthy); extern void _Py_uop_abstractcontext_init(JitOptContext *ctx); extern void _Py_uop_abstractcontext_fini(JitOptContext *ctx); diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 2689801a45a13d..144dad861bbcc6 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -392,14 +392,14 @@ dummy_func(void) { op(_TO_BOOL, (value -- res)) { if (!optimize_to_bool(this_instr, ctx, value, &res)) { - res = sym_new_truthiness(ctx, value, false); + res = sym_new_truthiness(ctx, value, true); } } op(_TO_BOOL_BOOL, (value -- res)) { if (!optimize_to_bool(this_instr, ctx, value, &res)) { sym_set_type(value, &PyBool_Type); - res = sym_new_truthiness(ctx, value, false); + res = sym_new_truthiness(ctx, value, true); } } @@ -433,7 +433,7 @@ dummy_func(void) { op(_UNARY_NOT, (value -- res)) { sym_set_type(value, &PyBool_Type); - res = sym_new_truthiness(ctx, value, true); + res = sym_new_truthiness(ctx, value, false); } op(_COMPARE_OP, (left, right -- res)) { diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index e2e3bfef010b70..03c0c67f9ab9da 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -144,7 +144,7 @@ JitOptSymbol *res; value = stack_pointer[-1]; sym_set_type(value, &PyBool_Type); - res = sym_new_truthiness(ctx, value, true); + res = sym_new_truthiness(ctx, value, false); stack_pointer[-1] = res; break; } @@ -154,7 +154,7 @@ JitOptSymbol *res; value = stack_pointer[-1]; if (!optimize_to_bool(this_instr, ctx, value, &res)) { - res = sym_new_truthiness(ctx, value, false); + res = sym_new_truthiness(ctx, value, true); } stack_pointer[-1] = res; break; @@ -166,7 +166,7 @@ value = stack_pointer[-1]; if (!optimize_to_bool(this_instr, ctx, value, &res)) { sym_set_type(value, &PyBool_Type); - res = sym_new_truthiness(ctx, value, false); + res = sym_new_truthiness(ctx, value, true); } stack_pointer[-1] = res; break; diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 8244613c14ff18..cc65699635e498 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -578,8 +578,10 @@ _Py_uop_sym_is_immortal(JitOptSymbol *sym) } JitOptSymbol * -_Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptSymbol *value, bool not) +_Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptSymbol *value, bool truthy) { + // It's clearer to invert this in the signature: + bool not = !truthy; if (value->tag == JIT_SYM_TRUTHINESS_TAG && value->truthiness.not == not) { return value; } From 4edba1832f21a203b76def56d57b21dd0987ea81 Mon Sep 17 00:00:00 2001 From: Brandt Bucher Date: Sat, 1 Mar 2025 17:10:37 -0800 Subject: [PATCH 12/12] Add tests for truthiness symbol --- Python/optimizer_symbols.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index cc65699635e498..e8a3b918e56a94 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -574,6 +574,9 @@ _Py_uop_sym_is_immortal(JitOptSymbol *sym) if (sym->tag == JIT_SYM_KNOWN_CLASS_TAG) { return sym->cls.type == &PyBool_Type; } + if (sym->tag == JIT_SYM_TRUTHINESS_TAG) { + return true; + } return false; } @@ -830,7 +833,21 @@ _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) _Py_uop_sym_get_const(ctx, _Py_uop_sym_tuple_getitem(ctx, sym, 1)) == val_43, "tuple item does not match value used to create tuple" ); - + JitOptSymbol *value = _Py_uop_sym_new_type(ctx, &PyBool_Type); + sym = _Py_uop_sym_new_truthiness(ctx, value, false); + TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyBool_Type), "truthiness is not boolean"); + TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, sym) == -1, "truthiness is not unknown"); + TEST_PREDICATE(_Py_uop_sym_is_const(ctx, sym) == false, "truthiness is constant"); + TEST_PREDICATE(_Py_uop_sym_get_const(ctx, sym) == NULL, "truthiness is not NULL"); + TEST_PREDICATE(_Py_uop_sym_is_const(ctx, value) == false, "value is constant"); + TEST_PREDICATE(_Py_uop_sym_get_const(ctx, value) == NULL, "value is not NULL"); + _Py_uop_sym_set_const(ctx, sym, Py_False); + TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyBool_Type), "truthiness is not boolean"); + TEST_PREDICATE(_Py_uop_sym_truthiness(ctx, sym) == 0, "truthiness is not True"); + TEST_PREDICATE(_Py_uop_sym_is_const(ctx, sym) == true, "truthiness is not constant"); + TEST_PREDICATE(_Py_uop_sym_get_const(ctx, sym) == Py_False, "truthiness is not False"); + TEST_PREDICATE(_Py_uop_sym_is_const(ctx, value) == true, "value is not constant"); + TEST_PREDICATE(_Py_uop_sym_get_const(ctx, value) == Py_True, "value is not True"); _Py_uop_abstractcontext_fini(ctx); Py_DECREF(val_42); Py_DECREF(val_43);