Skip to content

Commit 97aca36

Browse files
committed
Optimize _CALL_ISINSTANCE for class tuples
1 parent 1d582e8 commit 97aca36

File tree

3 files changed

+162
-6
lines changed

3 files changed

+162
-6
lines changed

Lib/test/test_capi/test_opt.py

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2105,17 +2105,76 @@ def testfunc(n):
21052105
self.assertNotIn("_TO_BOOL_BOOL", uops)
21062106
self.assertIn("_GUARD_IS_TRUE_POP", uops)
21072107

2108-
def test_call_isinstance_tuple_of_classes(self):
2108+
def test_call_isinstance_tuple_of_classes_is_true(self):
21092109
def testfunc(n):
21102110
x = 0
21112111
for _ in range(n):
2112-
# A tuple of classes is currently not optimized,
2113-
# so this is only narrowed to bool:
21142112
y = isinstance(42, (int, str))
21152113
if y:
21162114
x += 1
21172115
return x
21182116

2117+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
2118+
self.assertEqual(res, TIER2_THRESHOLD)
2119+
self.assertIsNotNone(ex)
2120+
uops = get_opnames(ex)
2121+
self.assertNotIn("_CALL_ISINSTANCE", uops)
2122+
self.assertNotIn("_TO_BOOL_BOOL", uops)
2123+
self.assertNotIn("_GUARD_IS_TRUE_POP", uops)
2124+
self.assertIn("_BUILD_TUPLE", uops)
2125+
self.assertIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops)
2126+
2127+
def test_call_isinstance_tuple_of_classes_is_false(self):
2128+
def testfunc(n):
2129+
x = 0
2130+
for _ in range(n):
2131+
y = isinstance(42, (bool, str))
2132+
if not y:
2133+
x += 1
2134+
return x
2135+
2136+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
2137+
self.assertEqual(res, TIER2_THRESHOLD)
2138+
self.assertIsNotNone(ex)
2139+
uops = get_opnames(ex)
2140+
self.assertNotIn("_CALL_ISINSTANCE", uops)
2141+
self.assertNotIn("_TO_BOOL_BOOL", uops)
2142+
self.assertNotIn("_GUARD_IS_FALSE_POP", uops)
2143+
self.assertIn("_BUILD_TUPLE", uops)
2144+
self.assertIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops)
2145+
2146+
def test_call_isinstance_tuple_of_classes_true_unknown(self):
2147+
def testfunc(n):
2148+
x = 0
2149+
for _ in range(n):
2150+
# One of the classes is unknown, but we can still
2151+
# narrow to True
2152+
y = isinstance(42, (eval('str'), int))
2153+
if y:
2154+
x += 1
2155+
return x
2156+
2157+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
2158+
self.assertEqual(res, TIER2_THRESHOLD)
2159+
self.assertIsNotNone(ex)
2160+
uops = get_opnames(ex)
2161+
self.assertNotIn("_CALL_ISINSTANCE", uops)
2162+
self.assertNotIn("_TO_BOOL_BOOL", uops)
2163+
self.assertNotIn("_GUARD_IS_TRUE_POP", uops)
2164+
self.assertIn("_BUILD_TUPLE", uops)
2165+
self.assertIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops)
2166+
2167+
def test_call_isinstance_tuple_of_classes_unknown_not_narrowed(self):
2168+
def testfunc(n):
2169+
x = 0
2170+
for _ in range(n):
2171+
# One of the classes is unknown, so we can't narrow
2172+
# to True or False, only bool
2173+
y = isinstance(42, (str, eval('int')))
2174+
if y:
2175+
x += 1
2176+
return x
2177+
21192178
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
21202179
self.assertEqual(res, TIER2_THRESHOLD)
21212180
self.assertIsNotNone(ex)
@@ -2124,6 +2183,27 @@ def testfunc(n):
21242183
self.assertNotIn("_TO_BOOL_BOOL", uops)
21252184
self.assertIn("_GUARD_IS_TRUE_POP", uops)
21262185

2186+
def test_call_isinstance_empty_tuple(self):
2187+
def testfunc(n):
2188+
x = 0
2189+
for _ in range(n):
2190+
y = isinstance(42, ())
2191+
if not y:
2192+
x += 1
2193+
return x
2194+
2195+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
2196+
self.assertEqual(res, TIER2_THRESHOLD)
2197+
self.assertIsNotNone(ex)
2198+
uops = get_opnames(ex)
2199+
self.assertNotIn("_CALL_ISINSTANCE", uops)
2200+
self.assertNotIn("_TO_BOOL_BOOL", uops)
2201+
self.assertNotIn("_GUARD_IS_FALSE_POP", uops)
2202+
self.assertNotIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops)
2203+
self.assertNotIn("_POP_CALL_LOAD_CONST_INLINE_BORROW", uops)
2204+
self.assertNotIn("_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW", uops)
2205+
self.assertNotIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops)
2206+
21272207
def test_call_isinstance_metaclass(self):
21282208
class EvenNumberMeta(type):
21292209
def __instancecheck__(self, number):

Python/optimizer_bytecodes.c

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,9 @@ dummy_func(void) {
938938
}
939939

940940
op(_CALL_ISINSTANCE, (unused, unused, instance, cls -- res)) {
941+
// The below define is equivalent to PyObject_TypeCheck(inst, cls)
942+
#define sym_IS_SUBTYPE(inst, cls) ((inst) == (cls) || PyType_IsSubtype(inst, cls))
943+
941944
// the result is always a bool, but sometimes we can
942945
// narrow it down to True or False
943946
res = sym_new_type(ctx, &PyBool_Type);
@@ -947,14 +950,55 @@ dummy_func(void) {
947950
// isinstance(inst, cls) where both inst and cls have
948951
// known types, meaning we can deduce either True or False
949952

950-
// The below check is equivalent to PyObject_TypeCheck(inst, cls)
951953
PyObject *out = Py_False;
952-
if (inst_type == cls_o || PyType_IsSubtype(inst_type, cls_o)) {
954+
if (sym_IS_SUBTYPE(inst_type, cls_o)) {
953955
out = Py_True;
954956
}
955957
sym_set_const(res, out);
956958
REPLACE_OP(this_instr, _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)out);
957959
}
960+
else if (inst_type && sym_matches_type(cls, &PyTuple_Type)) {
961+
// isinstance(inst, tup) where inst has a known type and tup is a tuple.
962+
// We can deduce True if inst is an instance of at least one of
963+
// the items in the tuple.
964+
// We can deduce False if all items in the tuple have known types and
965+
// inst is not an instance of any of them.
966+
967+
int length = sym_tuple_length(cls);
968+
bool all_items_known = true;
969+
PyObject *out = NULL;
970+
if (length >= 0) {
971+
// We cannot do anything about tuples with unknown (length == -1)
972+
973+
for (int i = 0; i < length; i++) {
974+
JitOptSymbol *item = sym_tuple_getitem(ctx, cls, i);
975+
if (!sym_has_type(item)) {
976+
// There is an unknown item in the tuple,
977+
// we can no longer deduce False.
978+
all_items_known = false;
979+
continue;
980+
}
981+
PyTypeObject *cls_o = (PyTypeObject *)sym_get_const(ctx, item);
982+
if (cls_o &&
983+
sym_matches_type(item, &PyType_Type) &&
984+
sym_IS_SUBTYPE(inst_type, cls_o))
985+
{
986+
out = Py_True;
987+
break;
988+
}
989+
}
990+
if (!out && all_items_known) {
991+
// We haven't deduced True, but all items in the tuple are known
992+
// so we can deduce False
993+
out = Py_False;
994+
}
995+
if (out) {
996+
sym_set_const(res, out);
997+
REPLACE_OP(this_instr, _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)out);
998+
}
999+
}
1000+
}
1001+
#undef sym_IS_SUBTYPE
9581002
}
9591003

9601004
op(_GUARD_IS_TRUE_POP, (flag -- )) {

Python/optimizer_cases.c.h

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

0 commit comments

Comments
 (0)