Skip to content

Commit 8aba690

Browse files
committed
Improve performance hints for nogil + pxd (#6088)
In order to be called efficiently, these functions must have the exception specification set in the pxd file since Cython is unable to assume an implicit exception specification. This improves the quality of the messages to draw attention to this detail. Closes #6001
1 parent ae120d5 commit 8aba690

7 files changed

Lines changed: 66 additions & 3 deletions

File tree

Cython/Compiler/ExprNodes.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6495,7 +6495,8 @@ def generate_result_code(self, code):
64956495
PyrexTypes.write_noexcept_performance_hint(
64966496
self.pos, code.funcstate.scope,
64976497
function_name=perf_hint_entry.name if perf_hint_entry else None,
6498-
void_return=self.type.is_void, is_call=True)
6498+
void_return=self.type.is_void, is_call=True,
6499+
is_from_pxd=(perf_hint_entry and perf_hint_entry.defined_in_pxd))
64996500
code.globalstate.use_utility_code(
65006501
UtilityCode.load_cached("ErrOccurredWithGIL", "Exceptions.c"))
65016502
exc_checks.append("__Pyx_ErrOccurredWithGIL()")

Cython/Compiler/PyrexTypes.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5475,7 +5475,9 @@ def cap_length(s, max_len=63):
54755475
hash_prefix = hashlib.sha256(s.encode('ascii')).hexdigest()[:6]
54765476
return '%s__%s__etc' % (hash_prefix, s[:max_len-17])
54775477

5478-
def write_noexcept_performance_hint(pos, env, function_name=None, void_return=False, is_call=False):
5478+
def write_noexcept_performance_hint(pos, env,
5479+
function_name=None, void_return=False, is_call=False,
5480+
is_from_pxd=False):
54795481
if function_name:
54805482
# we need it escaped everywhere we use it
54815483
function_name = "'%s'" % function_name
@@ -5498,6 +5500,9 @@ def write_noexcept_performance_hint(pos, env, function_name=None, void_return=Fa
54985500
solutions.append(
54995501
"Use an 'int' return type on %s to allow an error code to be returned." %
55005502
the_function)
5503+
if is_from_pxd and not void_return:
5504+
solutions.append(
5505+
"Declare any exception value explicitly for functions in pxd files.")
55015506
if len(solutions) == 1:
55025507
msg = "%s %s" % (msg, solutions[0])
55035508
else:

Cython/Compiler/Symtab.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import builtins
1515

1616
from ..Utils import try_finally_contextmanager
17-
from .Errors import warning, error, InternalError
17+
from .Errors import warning, error, InternalError, performance_hint
1818
from .StringEncoding import EncodedString
1919
from . import Options, Naming
2020
from . import PyrexTypes
@@ -907,6 +907,17 @@ def declare_cfunction(self, name, type, pos,
907907
elif not in_pxd and entry.defined_in_pxd and type.compatible_signature_with(entry.type):
908908
# TODO: check that this was done by a signature optimisation and not a user error.
909909
#warning(pos, "Function signature does not match previous declaration", 1)
910+
911+
# Cython can't assume anything about cimported functions declared without
912+
# an exception value. This is a performance problem mainly for nogil functions.
913+
if entry.type.nogil and entry.type.exception_value is None and type.exception_value:
914+
performance_hint(
915+
entry.pos,
916+
f"No exception value declared for '{entry.name}' in pxd file.\n"
917+
"Users cimporting this function and calling it without the gil "
918+
f"will always require an exception check.\n"
919+
"Suggest adding an explicit exception value.",
920+
self)
910921
entry.type = type
911922
else:
912923
error(pos, "Function signature does not match previous declaration")

tests/compile/nogil_perf_hints.pyx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# mode: compile
2+
# tag: perf_hints
3+
4+
# Compile only to avoid needing to compile definitions of the functions
5+
# declared in another pxd file
6+
7+
from nogil_perf_hints_pxd cimport f_has_except_value, f_missing_except_value, f_noexcept
8+
9+
def test():
10+
with nogil:
11+
# should not generate performance hints
12+
f_has_except_value()
13+
f_noexcept()
14+
# should generate performance hints.
15+
# (Unfortunately it's difficult to check the extra information on the 2nd+ line of the hint)
16+
f_missing_except_value()
17+
18+
_PERFORMANCE_HINTS = """
19+
16:30: Exception check after calling 'f_missing_except_value' will always require the GIL to be acquired.
20+
"""
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
cdef int f_has_except_value() nogil except -1
2+
cdef int f_missing_except_value() nogil
3+
cdef int f_noexcept() nogil noexcept

tests/run/nogil.pxd

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
11
cdef void voidexceptnogil_in_pxd() nogil
2+
3+
# These definitions are unhelpful to people cimporting
4+
# them because the exception value isn't in the pxd.
5+
cdef int f_in_pxd1() nogil
6+
cdef int f_in_pxd2() nogil

tests/run/nogil.pyx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,26 @@ def test_performance_hint_nogil():
198198
voidexceptnogil_in_other_pxd()
199199

200200

201+
cdef int f_in_pxd1() nogil except -1:
202+
return 0
203+
204+
cdef int f_in_pxd2() nogil: # implicit except -1?
205+
return 0
206+
207+
def test_declared_in_pxd():
208+
"""
209+
>>> test_declared_in_pxd()
210+
"""
211+
with nogil:
212+
# no warnings here because we're in the same file as the declaration
213+
f_in_pxd1()
214+
f_in_pxd2()
215+
216+
201217
# Note that we're only able to check the first line of the performance hint
202218
_PERFORMANCE_HINTS = """
219+
5:18: No exception value declared for 'f_in_pxd1' in pxd file.
220+
6:18: No exception value declared for 'f_in_pxd2' in pxd file.
203221
20:9: Exception check after calling 'f' will always require the GIL to be acquired.
204222
24:5: Exception check on 'f' will always require the GIL to be acquired.
205223
34:5: Exception check on 'release_gil_in_nogil' will always require the GIL to be acquired.

0 commit comments

Comments
 (0)