Skip to content

Commit 8620ea3

Browse files
authored
Merge branch 'main' into zstd-unsharable-de-compressor
2 parents 1c3071d + 8d490b3 commit 8620ea3

File tree

13 files changed

+253
-44
lines changed

13 files changed

+253
-44
lines changed

Include/internal/pycore_opcode_metadata.h

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

Lib/test/test_capi/test_opt.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1959,6 +1959,121 @@ def testfunc(n):
19591959
self.assertNotIn("_GUARD_THIRD_NULL", uops)
19601960
self.assertNotIn("_GUARD_CALLABLE_ISINSTANCE", uops)
19611961

1962+
def test_call_isinstance_is_true(self):
1963+
def testfunc(n):
1964+
x = 0
1965+
for _ in range(n):
1966+
y = isinstance(42, int)
1967+
if y:
1968+
x += 1
1969+
return x
1970+
1971+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
1972+
self.assertEqual(res, TIER2_THRESHOLD)
1973+
self.assertIsNotNone(ex)
1974+
uops = get_opnames(ex)
1975+
self.assertIn("_CALL_ISINSTANCE", uops)
1976+
self.assertNotIn("_TO_BOOL_BOOL", uops)
1977+
self.assertNotIn("_GUARD_IS_TRUE_POP", uops)
1978+
1979+
def test_call_isinstance_is_false(self):
1980+
def testfunc(n):
1981+
x = 0
1982+
for _ in range(n):
1983+
y = isinstance(42, str)
1984+
if not y:
1985+
x += 1
1986+
return x
1987+
1988+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
1989+
self.assertEqual(res, TIER2_THRESHOLD)
1990+
self.assertIsNotNone(ex)
1991+
uops = get_opnames(ex)
1992+
self.assertIn("_CALL_ISINSTANCE", uops)
1993+
self.assertNotIn("_TO_BOOL_BOOL", uops)
1994+
self.assertNotIn("_GUARD_IS_FALSE_POP", uops)
1995+
1996+
def test_call_isinstance_subclass(self):
1997+
def testfunc(n):
1998+
x = 0
1999+
for _ in range(n):
2000+
y = isinstance(True, int)
2001+
if y:
2002+
x += 1
2003+
return x
2004+
2005+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
2006+
self.assertEqual(res, TIER2_THRESHOLD)
2007+
self.assertIsNotNone(ex)
2008+
uops = get_opnames(ex)
2009+
self.assertIn("_CALL_ISINSTANCE", uops)
2010+
self.assertNotIn("_TO_BOOL_BOOL", uops)
2011+
self.assertNotIn("_GUARD_IS_TRUE_POP", uops)
2012+
2013+
def test_call_isinstance_unknown_object(self):
2014+
def testfunc(n):
2015+
x = 0
2016+
for _ in range(n):
2017+
# The optimizer doesn't know the return type here:
2018+
bar = eval("42")
2019+
# This will only narrow to bool:
2020+
y = isinstance(bar, int)
2021+
if y:
2022+
x += 1
2023+
return x
2024+
2025+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
2026+
self.assertEqual(res, TIER2_THRESHOLD)
2027+
self.assertIsNotNone(ex)
2028+
uops = get_opnames(ex)
2029+
self.assertIn("_CALL_ISINSTANCE", uops)
2030+
self.assertNotIn("_TO_BOOL_BOOL", uops)
2031+
self.assertIn("_GUARD_IS_TRUE_POP", uops)
2032+
2033+
def test_call_isinstance_tuple_of_classes(self):
2034+
def testfunc(n):
2035+
x = 0
2036+
for _ in range(n):
2037+
# A tuple of classes is currently not optimized,
2038+
# so this is only narrowed to bool:
2039+
y = isinstance(42, (int, str))
2040+
if y:
2041+
x += 1
2042+
return x
2043+
2044+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
2045+
self.assertEqual(res, TIER2_THRESHOLD)
2046+
self.assertIsNotNone(ex)
2047+
uops = get_opnames(ex)
2048+
self.assertIn("_CALL_ISINSTANCE", uops)
2049+
self.assertNotIn("_TO_BOOL_BOOL", uops)
2050+
self.assertIn("_GUARD_IS_TRUE_POP", uops)
2051+
2052+
def test_call_isinstance_metaclass(self):
2053+
class EvenNumberMeta(type):
2054+
def __instancecheck__(self, number):
2055+
return number % 2 == 0
2056+
2057+
class EvenNumber(metaclass=EvenNumberMeta):
2058+
pass
2059+
2060+
def testfunc(n):
2061+
x = 0
2062+
for _ in range(n):
2063+
# Only narrowed to bool
2064+
y = isinstance(42, EvenNumber)
2065+
if y:
2066+
x += 1
2067+
return x
2068+
2069+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
2070+
self.assertEqual(res, TIER2_THRESHOLD)
2071+
self.assertIsNotNone(ex)
2072+
uops = get_opnames(ex)
2073+
self.assertIn("_CALL_ISINSTANCE", uops)
2074+
self.assertNotIn("_TO_BOOL_BOOL", uops)
2075+
self.assertIn("_GUARD_IS_TRUE_POP", uops)
2076+
19622077

19632078
def global_identity(x):
19642079
return x

Lib/test/test_interpreters/test_api.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,59 @@ def test_created_with_capi(self):
647647
self.interp_exists(interpid))
648648

649649

650+
def test_remaining_threads(self):
651+
r_interp, w_interp = self.pipe()
652+
653+
FINISHED = b'F'
654+
655+
# It's unlikely, but technically speaking, it's possible
656+
# that the thread could've finished before interp.close() is
657+
# reached, so this test might not properly exercise the case.
658+
# However, it's quite unlikely and I'm too lazy to deal with it.
659+
interp = interpreters.create()
660+
interp.exec(f"""if True:
661+
import os
662+
import threading
663+
import time
664+
665+
def task():
666+
time.sleep(1)
667+
os.write({w_interp}, {FINISHED!r})
668+
669+
threads = [threading.Thread(target=task) for _ in range(3)]
670+
for t in threads:
671+
t.start()
672+
""")
673+
interp.close()
674+
675+
self.assertEqual(os.read(r_interp, 1), FINISHED)
676+
677+
def test_remaining_daemon_threads(self):
678+
interp = _interpreters.create(
679+
types.SimpleNamespace(
680+
use_main_obmalloc=False,
681+
allow_fork=False,
682+
allow_exec=False,
683+
allow_threads=True,
684+
allow_daemon_threads=True,
685+
check_multi_interp_extensions=True,
686+
gil='own',
687+
)
688+
)
689+
_interpreters.exec(interp, f"""if True:
690+
import threading
691+
import time
692+
693+
def task():
694+
time.sleep(100)
695+
696+
threads = [threading.Thread(target=task, daemon=True) for _ in range(3)]
697+
for t in threads:
698+
t.start()
699+
""")
700+
_interpreters.destroy(interp)
701+
702+
650703
class TestInterpreterPrepareMain(TestBase):
651704

652705
def test_empty(self):
@@ -755,7 +808,10 @@ def script():
755808
spam.eggs()
756809
757810
interp = interpreters.create()
758-
interp.exec(script)
811+
try:
812+
interp.exec(script)
813+
finally:
814+
interp.close()
759815
""")
760816

761817
stdout, stderr = self.assert_python_failure(scriptfile)
@@ -764,7 +820,7 @@ def script():
764820
# File "{interpreters.__file__}", line 179, in exec
765821
self.assertEqual(stderr, dedent(f"""\
766822
Traceback (most recent call last):
767-
File "{scriptfile}", line 9, in <module>
823+
File "{scriptfile}", line 10, in <module>
768824
interp.exec(script)
769825
~~~~~~~~~~~^^^^^^^^
770826
{interpmod_line.strip()}

Lib/test/test_interpreters/test_lifecycle.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ def test_sys_path_0(self):
132132
'sub': sys.path[0],
133133
}}, indent=4), flush=True)
134134
""")
135+
interp.close()
135136
'''
136137
# <tmp>/
137138
# pkg/
@@ -172,7 +173,10 @@ def test_gh_109793(self):
172173
argv = [sys.executable, '-c', '''if True:
173174
from test.support import interpreters
174175
interp = interpreters.create()
175-
raise Exception
176+
try:
177+
raise Exception
178+
finally:
179+
interp.close()
176180
''']
177181
proc = subprocess.run(argv, capture_output=True, text=True)
178182
self.assertIn('Traceback', proc.stderr)

Lib/test/test_threading.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1689,10 +1689,7 @@ def f():
16891689
16901690
_testcapi.run_in_subinterp(%r)
16911691
""" % (subinterp_code,)
1692-
with test.support.SuppressCrashReport():
1693-
rc, out, err = assert_python_failure("-c", script)
1694-
self.assertIn("Fatal Python error: Py_EndInterpreter: "
1695-
"not the last thread", err.decode())
1692+
assert_python_ok("-c", script)
16961693

16971694
def _check_allowed(self, before_start='', *,
16981695
allowed=True,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a crash when using threads inside of a subinterpreter.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Narrow the return type and constant-evaluate ``CALL_ISINSTANCE`` for a
2+
subset of known values in the JIT. Patch by Tomas Roun

Modules/_io/bytesio.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,9 +607,9 @@ _io_BytesIO_readinto_impl(bytesio *self, Py_buffer *buffer)
607607
len = 0;
608608
}
609609

610-
memcpy(buffer->buf, PyBytes_AS_STRING(self->buf) + self->pos, len);
611610
assert(self->pos + len < PY_SSIZE_T_MAX);
612611
assert(len >= 0);
612+
memcpy(buffer->buf, PyBytes_AS_STRING(self->buf) + self->pos, len);
613613
self->pos += len;
614614

615615
return PyLong_FromSsize_t(len);

Modules/_remote_debugging_module.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1765,7 +1765,7 @@ get_async_stack_trace(PyObject* self, PyObject* args)
17651765

17661766
static PyMethodDef methods[] = {
17671767
{"get_stack_trace", get_stack_trace, METH_VARARGS,
1768-
"Get the Python stack from a given pod"},
1768+
"Get the Python stack from a given pid"},
17691769
{"get_async_stack_trace", get_async_stack_trace, METH_VARARGS,
17701770
"Get the asyncio stack from a given pid"},
17711771
{"get_all_awaited_by", get_all_awaited_by, METH_VARARGS,

Programs/_testembed.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,9 +1395,12 @@ static int test_audit_subinterpreter(void)
13951395
PySys_AddAuditHook(_audit_subinterpreter_hook, NULL);
13961396
_testembed_initialize();
13971397

1398-
Py_NewInterpreter();
1399-
Py_NewInterpreter();
1400-
Py_NewInterpreter();
1398+
PyThreadState *tstate = PyThreadState_Get();
1399+
for (int i = 0; i < 3; ++i)
1400+
{
1401+
Py_EndInterpreter(Py_NewInterpreter());
1402+
PyThreadState_Swap(tstate);
1403+
}
14011404

14021405
Py_Finalize();
14031406

0 commit comments

Comments
 (0)