Skip to content

Commit d2e339f

Browse files
committed
Bail when encountering an unknown class
1 parent 467bcb9 commit d2e339f

File tree

3 files changed

+86
-24
lines changed

3 files changed

+86
-24
lines changed

Lib/test/test_capi/test_opt.py

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2143,13 +2143,13 @@ def testfunc(n):
21432143
self.assertIn("_BUILD_TUPLE", uops)
21442144
self.assertIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops)
21452145

2146-
def test_call_isinstance_tuple_of_classes_true_unknown(self):
2146+
def test_call_isinstance_tuple_of_classes_true_unknown_1(self):
21472147
def testfunc(n):
21482148
x = 0
21492149
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))
2150+
# One of the classes is unknown, but it comes
2151+
# after a known class, so we can narrow to True
2152+
y = isinstance(42, (int, eval('str')))
21532153
if y:
21542154
x += 1
21552155
return x
@@ -2160,11 +2160,30 @@ def testfunc(n):
21602160
uops = get_opnames(ex)
21612161
self.assertNotIn("_CALL_ISINSTANCE", uops)
21622162
self.assertNotIn("_TO_BOOL_BOOL", uops)
2163-
self.assertNotIn("_GUARD_IS_TRUE_POP", uops)
2163+
self.assertNotIn("_GUARD_IS_FALSE_POP", uops)
21642164
self.assertIn("_BUILD_TUPLE", uops)
21652165
self.assertIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops)
21662166

2167-
def test_call_isinstance_tuple_of_classes_unknown_not_narrowed(self):
2167+
def test_call_isinstance_tuple_of_classes_true_unknown_2(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, (eval('str'), int))
2174+
if y:
2175+
x += 1
2176+
return x
2177+
2178+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
2179+
self.assertEqual(res, TIER2_THRESHOLD)
2180+
self.assertIsNotNone(ex)
2181+
uops = get_opnames(ex)
2182+
self.assertIn("_CALL_ISINSTANCE", uops)
2183+
self.assertNotIn("_TO_BOOL_BOOL", uops)
2184+
self.assertIn("_GUARD_IS_TRUE_POP", uops)
2185+
2186+
def test_call_isinstance_tuple_of_classes_true_unknown_3(self):
21682187
def testfunc(n):
21692188
x = 0
21702189
for _ in range(n):
@@ -2183,6 +2202,25 @@ def testfunc(n):
21832202
self.assertNotIn("_TO_BOOL_BOOL", uops)
21842203
self.assertIn("_GUARD_IS_TRUE_POP", uops)
21852204

2205+
def test_call_isinstance_tuple_of_classes_true_unknown_4(self):
2206+
def testfunc(n):
2207+
x = 0
2208+
for _ in range(n):
2209+
# One of the classes is unknown, so we can't narrow
2210+
# to True or False, only bool
2211+
y = isinstance(42, (eval('int'), str))
2212+
if y:
2213+
x += 1
2214+
return x
2215+
2216+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
2217+
self.assertEqual(res, TIER2_THRESHOLD)
2218+
self.assertIsNotNone(ex)
2219+
uops = get_opnames(ex)
2220+
self.assertIn("_CALL_ISINSTANCE", uops)
2221+
self.assertNotIn("_TO_BOOL_BOOL", uops)
2222+
self.assertIn("_GUARD_IS_TRUE_POP", uops)
2223+
21862224
def test_call_isinstance_empty_tuple(self):
21872225
def testfunc(n):
21882226
x = 0
@@ -2248,6 +2286,36 @@ def testfunc(n):
22482286
self.assertNotIn("_TO_BOOL_BOOL", uops)
22492287
self.assertIn("_GUARD_IS_TRUE_POP", uops)
22502288

2289+
def test_call_isinstance_tuple_metaclass(self):
2290+
calls = 0
2291+
2292+
class Meta(type):
2293+
def __instancecheck__(self, _):
2294+
nonlocal calls
2295+
calls += 1
2296+
return False
2297+
2298+
class Unknown(metaclass=Meta):
2299+
pass
2300+
2301+
def testfunc(n):
2302+
x = 0
2303+
for _ in range(n):
2304+
# Only narrowed to bool
2305+
y = isinstance(42, (Unknown, int))
2306+
if y:
2307+
x += 1
2308+
return x, calls
2309+
2310+
(res, calls), ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
2311+
self.assertEqual(res, TIER2_THRESHOLD)
2312+
self.assertEqual(calls, TIER2_THRESHOLD)
2313+
self.assertIsNotNone(ex)
2314+
uops = get_opnames(ex)
2315+
self.assertIn("_CALL_ISINSTANCE", uops)
2316+
self.assertNotIn("_TO_BOOL_BOOL", uops)
2317+
self.assertIn("_GUARD_IS_TRUE_POP", uops)
2318+
22512319
def test_set_type_version_sets_type(self):
22522320
class C:
22532321
A = 1

Python/optimizer_bytecodes.c

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -938,9 +938,6 @@ 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-
944941
// the result is always a bool, but sometimes we can
945942
// narrow it down to True or False
946943
res = sym_new_type(ctx, &PyBool_Type);
@@ -951,7 +948,7 @@ dummy_func(void) {
951948
// known types, meaning we can deduce either True or False
952949

953950
PyObject *out = Py_False;
954-
if (sym_IS_SUBTYPE(inst_type, cls_o)) {
951+
if (inst_type == cls_o || PyType_IsSubtype(inst_type, cls_o)) {
955952
out = Py_True;
956953
}
957954
sym_set_const(res, out);
@@ -972,23 +969,24 @@ dummy_func(void) {
972969
for (int i = 0; i < length; i++) {
973970
JitOptSymbol *item = sym_tuple_getitem(ctx, cls, i);
974971
if (!sym_has_type(item)) {
975-
// There is an unknown item in the tuple,
976-
// we can no longer deduce False.
972+
// There is an unknown item in the tuple.
973+
// It could potentially define its own __instancecheck__
974+
// method so we can only deduce bool.
977975
all_items_known = false;
978-
continue;
976+
break;
979977
}
980978
PyTypeObject *cls_o = (PyTypeObject *)sym_get_const(ctx, item);
981979
if (cls_o &&
982980
sym_matches_type(item, &PyType_Type) &&
983-
sym_IS_SUBTYPE(inst_type, cls_o))
981+
(inst_type == cls_o || PyType_IsSubtype(inst_type, cls_o)))
984982
{
985983
out = Py_True;
986984
break;
987985
}
988986
}
989-
if (!out && all_items_known) {
987+
if (out == NULL && all_items_known) {
990988
// We haven't deduced True, but all items in the tuple are known
991-
// so we can deduce False
989+
// so we can deduce False.
992990
out = Py_False;
993991
}
994992
if (out) {
@@ -997,7 +995,6 @@ dummy_func(void) {
997995
}
998996
}
999997
}
1000-
#undef sym_IS_SUBTYPE
1001998
}
1002999

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

Python/optimizer_cases.c.h

Lines changed: 4 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)