Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 7afa476

Browse files
authoredMar 2, 2025
GH-130415: Use boolean guards to narrow types to values in the JIT (GH-130659)
1 parent c6513f7 commit 7afa476

File tree

7 files changed

+348
-166
lines changed

7 files changed

+348
-166
lines changed
 

‎Include/internal/pycore_optimizer.h

+12-3
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ typedef enum _JitSymType {
172172
JIT_SYM_KNOWN_CLASS_TAG = 6,
173173
JIT_SYM_KNOWN_VALUE_TAG = 7,
174174
JIT_SYM_TUPLE_TAG = 8,
175+
JIT_SYM_TRUTHINESS_TAG = 9,
175176
} JitSymType;
176177

177178
typedef struct _jit_opt_known_class {
@@ -198,12 +199,19 @@ typedef struct _jit_opt_tuple {
198199
uint16_t items[MAX_SYMBOLIC_TUPLE_SIZE];
199200
} JitOptTuple;
200201

202+
typedef struct {
203+
uint8_t tag;
204+
bool not;
205+
uint16_t value;
206+
} JitOptTruthiness;
207+
201208
typedef union _jit_opt_symbol {
202209
uint8_t tag;
203210
JitOptKnownClass cls;
204211
JitOptKnownValue value;
205212
JitOptKnownVersion version;
206213
JitOptTuple tuple;
214+
JitOptTruthiness truthiness;
207215
} JitOptSymbol;
208216

209217

@@ -245,8 +253,8 @@ typedef struct _JitOptContext {
245253

246254
extern bool _Py_uop_sym_is_null(JitOptSymbol *sym);
247255
extern bool _Py_uop_sym_is_not_null(JitOptSymbol *sym);
248-
extern bool _Py_uop_sym_is_const(JitOptSymbol *sym);
249-
extern PyObject *_Py_uop_sym_get_const(JitOptSymbol *sym);
256+
extern bool _Py_uop_sym_is_const(JitOptContext *ctx, JitOptSymbol *sym);
257+
extern PyObject *_Py_uop_sym_get_const(JitOptContext *ctx, JitOptSymbol *sym);
250258
extern JitOptSymbol *_Py_uop_sym_new_unknown(JitOptContext *ctx);
251259
extern JitOptSymbol *_Py_uop_sym_new_not_null(JitOptContext *ctx);
252260
extern JitOptSymbol *_Py_uop_sym_new_type(
@@ -262,12 +270,13 @@ extern void _Py_uop_sym_set_type(JitOptContext *ctx, JitOptSymbol *sym, PyTypeOb
262270
extern bool _Py_uop_sym_set_type_version(JitOptContext *ctx, JitOptSymbol *sym, unsigned int version);
263271
extern void _Py_uop_sym_set_const(JitOptContext *ctx, JitOptSymbol *sym, PyObject *const_val);
264272
extern bool _Py_uop_sym_is_bottom(JitOptSymbol *sym);
265-
extern int _Py_uop_sym_truthiness(JitOptSymbol *sym);
273+
extern int _Py_uop_sym_truthiness(JitOptContext *ctx, JitOptSymbol *sym);
266274
extern PyTypeObject *_Py_uop_sym_get_type(JitOptSymbol *sym);
267275
extern bool _Py_uop_sym_is_immortal(JitOptSymbol *sym);
268276
extern JitOptSymbol *_Py_uop_sym_new_tuple(JitOptContext *ctx, int size, JitOptSymbol **args);
269277
extern JitOptSymbol *_Py_uop_sym_tuple_getitem(JitOptContext *ctx, JitOptSymbol *sym, int item);
270278
extern int _Py_uop_sym_tuple_length(JitOptSymbol *sym);
279+
extern JitOptSymbol *_Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptSymbol *value, bool truthy);
271280

272281
extern void _Py_uop_abstractcontext_init(JitOptContext *ctx);
273282
extern void _Py_uop_abstractcontext_fini(JitOptContext *ctx);

‎Lib/test/test_capi/test_opt.py

+62
Original file line numberDiff line numberDiff line change
@@ -1437,6 +1437,68 @@ def crash_addition():
14371437

14381438
crash_addition()
14391439

1440+
def test_narrow_type_to_constant_bool_false(self):
1441+
def f(n):
1442+
trace = []
1443+
for i in range(n):
1444+
# false is always False, but we can only prove that it's a bool:
1445+
false = i == TIER2_THRESHOLD
1446+
trace.append("A")
1447+
if not false: # Kept.
1448+
trace.append("B")
1449+
if not false: # Removed!
1450+
trace.append("C")
1451+
trace.append("D")
1452+
if false: # Removed!
1453+
trace.append("X")
1454+
trace.append("E")
1455+
trace.append("F")
1456+
if false: # Removed!
1457+
trace.append("X")
1458+
trace.append("G")
1459+
return trace
1460+
1461+
trace, ex = self._run_with_optimizer(f, TIER2_THRESHOLD)
1462+
self.assertEqual(trace, list("ABCDEFG") * TIER2_THRESHOLD)
1463+
self.assertIsNotNone(ex)
1464+
uops = get_opnames(ex)
1465+
# Only one guard remains:
1466+
self.assertEqual(uops.count("_GUARD_IS_FALSE_POP"), 1)
1467+
self.assertEqual(uops.count("_GUARD_IS_TRUE_POP"), 0)
1468+
# But all of the appends we care about are still there:
1469+
self.assertEqual(uops.count("_CALL_LIST_APPEND"), len("ABCDEFG"))
1470+
1471+
def test_narrow_type_to_constant_bool_true(self):
1472+
def f(n):
1473+
trace = []
1474+
for i in range(n):
1475+
# true always True, but we can only prove that it's a bool:
1476+
true = i != TIER2_THRESHOLD
1477+
trace.append("A")
1478+
if true: # Kept.
1479+
trace.append("B")
1480+
if not true: # Removed!
1481+
trace.append("X")
1482+
trace.append("C")
1483+
if true: # Removed!
1484+
trace.append("D")
1485+
trace.append("E")
1486+
trace.append("F")
1487+
if not true: # Removed!
1488+
trace.append("X")
1489+
trace.append("G")
1490+
return trace
1491+
1492+
trace, ex = self._run_with_optimizer(f, TIER2_THRESHOLD)
1493+
self.assertEqual(trace, list("ABCDEFG") * TIER2_THRESHOLD)
1494+
self.assertIsNotNone(ex)
1495+
uops = get_opnames(ex)
1496+
# Only one guard remains:
1497+
self.assertEqual(uops.count("_GUARD_IS_FALSE_POP"), 0)
1498+
self.assertEqual(uops.count("_GUARD_IS_TRUE_POP"), 1)
1499+
# But all of the appends we care about are still there:
1500+
self.assertEqual(uops.count("_CALL_LIST_APPEND"), len("ABCDEFG"))
1501+
14401502

14411503
def global_identity(x):
14421504
return x
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve the experimental JIT's ability to narrow boolean values based on the
2+
results of truthiness tests.

‎Python/optimizer_analysis.c

+2-21
Original file line numberDiff line numberDiff line change
@@ -136,26 +136,6 @@ incorrect_keys(_PyUOpInstruction *inst, PyObject *obj)
136136
return 0;
137137
}
138138

139-
static int
140-
check_next_uop(_PyUOpInstruction *buffer, int size, int pc, uint16_t expected)
141-
{
142-
if (pc + 1 >= size) {
143-
DPRINTF(1, "Cannot rewrite %s at pc %d: buffer too small\n",
144-
_PyOpcode_uop_name[buffer[pc].opcode], pc);
145-
return 0;
146-
}
147-
uint16_t next_opcode = buffer[pc + 1].opcode;
148-
if (next_opcode != expected) {
149-
DPRINTF(1,
150-
"Cannot rewrite %s at pc %d: unexpected next opcode %s, "
151-
"expected %s\n",
152-
_PyOpcode_uop_name[buffer[pc].opcode], pc,
153-
_PyOpcode_uop_name[next_opcode], _PyOpcode_uop_name[expected]);
154-
return 0;
155-
}
156-
return 1;
157-
}
158-
159139
/* Returns 1 if successfully optimized
160140
* 0 if the trace is not suitable for optimization (yet)
161141
* -1 if there was an error. */
@@ -363,6 +343,7 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer,
363343
#define sym_tuple_getitem _Py_uop_sym_tuple_getitem
364344
#define sym_tuple_length _Py_uop_sym_tuple_length
365345
#define sym_is_immortal _Py_uop_sym_is_immortal
346+
#define sym_new_truthiness _Py_uop_sym_new_truthiness
366347

367348
static int
368349
optimize_to_bool(
@@ -376,7 +357,7 @@ optimize_to_bool(
376357
*result_ptr = value;
377358
return 1;
378359
}
379-
int truthiness = sym_truthiness(value);
360+
int truthiness = sym_truthiness(ctx, value);
380361
if (truthiness >= 0) {
381362
PyObject *load = truthiness ? Py_True : Py_False;
382363
REPLACE_OP(this_instr, _POP_TOP_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)load);
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Failed to load comments.