Skip to content
15 changes: 12 additions & 3 deletions Include/internal/pycore_optimizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;


Expand Down Expand Up @@ -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(
Expand All @@ -262,12 +270,13 @@ 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);
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);
Expand Down
62 changes: 62 additions & 0 deletions Lib/test/test_capi/test_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
# 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")
if not false: # Removed!
trace.append("C")
trace.append("D")
if false: # Removed!
trace.append("X")
trace.append("E")
trace.append("F")
if false: # 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):
# 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")
if not true: # Removed!
trace.append("X")
trace.append("C")
if true: # Removed!
trace.append("D")
trace.append("E")
trace.append("F")
if not true: # 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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Improve the experimental JIT's ability to narrow boolean values based on the
results of truth tests.
3 changes: 2 additions & 1 deletion Python/optimizer_analysis.c
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -389,7 +390,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);
Expand Down
Loading
Loading