Skip to content

Commit c3fb398

Browse files
authored
Merge pull request numba#7211 from sklam/misc/rel0.54.0rc2
RC2 patches for release0.54 branch
2 parents 84cee19 + a5c8b70 commit c3fb398

File tree

6 files changed

+194
-42
lines changed

6 files changed

+194
-42
lines changed

CHANGE_LOG

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,9 @@ Pull-Requests:
273273
* PR `#7162 <https://github.com/numba/numba/pull/7162>`_: CUDA: Fix linkage of device functions when compiling for debug (`Graham Markall <https://github.com/gmarkall>`_)
274274
* PR `#7163 <https://github.com/numba/numba/pull/7163>`_: Split legalization pass to consider IR and features separately. (`stuartarchibald <https://github.com/stuartarchibald>`_)
275275
* PR `#7165 <https://github.com/numba/numba/pull/7165>`_: Fix use of np.clip where out is not provided. (`stuartarchibald <https://github.com/stuartarchibald>`_)
276+
* PR `#7196 <https://github.com/numba/numba/pull/7196>`_: Fixes for lineinfo emission. (`stuartarchibald <https://github.com/stuartarchibald>`_)
277+
* PR `#7209 <https://github.com/numba/numba/pull/7209>`_: Clamp numpy (`esc <https://github.com/esc>`_)
278+
* PR `#7216 <https://github.com/numba/numba/pull/7216>`_: Update CHANGE_LOG for 0.54.0rc2. (`stuartarchibald <https://github.com/stuartarchibald>`_)
276279

277280
Authors:
278281

numba/core/lowering.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1407,7 +1407,11 @@ def decref(self, typ, val):
14071407
if not self.context.enable_nrt:
14081408
return
14091409

1410-
self.context.nrt.decref(self.builder, typ, val)
1410+
# do not associate decref with "use", it creates "jumpy" line info as
1411+
# the decrefs are usually where the ir.Del nodes are, which is at the
1412+
# end of the block.
1413+
with debuginfo.suspend_emission(self.builder):
1414+
self.context.nrt.decref(self.builder, typ, val)
14111415

14121416

14131417
def _lit_or_omitted(value):

numba/core/typed_passes.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -843,13 +843,14 @@ def _strip_phi_nodes(self, func_ir):
843843
for target, rhs in exporters[label]:
844844
# If RHS is undefined
845845
if rhs is ir.UNDEFINED:
846-
# Put in a NULL initializer
847-
rhs = ir.Expr.null(loc=target.loc)
846+
# Put in a NULL initializer, set the location to be in what
847+
# will eventually materialize as the prologue.
848+
rhs = ir.Expr.null(loc=func_ir.loc)
848849

849850
assign = ir.Assign(
850851
target=target,
851852
value=rhs,
852-
loc=target.loc
853+
loc=rhs.loc
853854
)
854855
# Insert at the earliest possible location; i.e. after the
855856
# last assignment to rhs

numba/tests/support.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from contextlib import contextmanager
2323
import uuid
2424
import importlib
25+
import types as pytypes
2526

2627
import numpy as np
2728

@@ -113,6 +114,13 @@
113114

114115
needs_blas = unittest.skipUnless(has_blas, "BLAS needs SciPy 1.0+")
115116

117+
# Decorate a test with @needs_subprocess to ensure it doesn't run unless the
118+
# `SUBPROC_TEST` environment variable is set. Use this in conjunction with:
119+
# TestCase::subprocess_test_runner which will execute a given test in subprocess
120+
# with this environment variable set.
121+
_exec_cond = os.environ.get('SUBPROC_TEST', None) == '1'
122+
needs_subprocess = unittest.skipUnless(_exec_cond, "needs subprocess harness")
123+
116124

117125
def ignore_internal_warnings():
118126
"""Use in testing within a ` warnings.catch_warnings` block to filter out
@@ -502,6 +510,46 @@ def run_nullary_func(self, pyfunc, flags):
502510
self.assertPreciseEqual(got, expected)
503511
return got, expected
504512

513+
def subprocess_test_runner(self, test_module, test_class=None,
514+
test_name=None, envvars=None, timeout=60):
515+
"""
516+
Runs named unit test(s) as specified in the arguments as:
517+
test_module.test_class.test_name. test_module must always be supplied
518+
and if no further refinement is made with test_class and test_name then
519+
all tests in the module will be run. The tests will be run in a
520+
subprocess with environment variables specified in `envvars`.
521+
If given, envvars must be a map of form:
522+
environment variable name (str) -> value (str)
523+
It is most convenient to use this method in conjunction with
524+
@needs_subprocess as the decorator will cause the decorated test to be
525+
skipped unless the `SUBPROC_TEST` environment variable is set
526+
(this special environment variable is set by this method such that the
527+
specified test(s) will not be skipped in the subprocess).
528+
529+
530+
Following execution in the subprocess this method will check the test(s)
531+
executed without error. The timeout kwarg can be used to allow more time
532+
for longer running tests, it defaults to 60 seconds.
533+
"""
534+
themod = self.__module__
535+
thecls = type(self).__name__
536+
parts = (test_module, test_class, test_name)
537+
fully_qualified_test = '.'.join(x for x in parts if x is not None)
538+
cmd = [sys.executable, '-m', 'numba.runtests', fully_qualified_test]
539+
env_copy = os.environ.copy()
540+
env_copy['SUBPROC_TEST'] = '1'
541+
envvars = pytypes.MappingProxyType({} if envvars is None else envvars)
542+
env_copy.update(envvars)
543+
status = subprocess.run(cmd, stdout=subprocess.PIPE,
544+
stderr=subprocess.PIPE, timeout=timeout,
545+
env=env_copy, universal_newlines=True)
546+
streams = (f'\ncaptured stdout: {status.stdout}\n'
547+
f'captured stderr: {status.stderr}')
548+
self.assertEqual(status.returncode, 0, streams)
549+
self.assertIn('OK', status.stderr)
550+
self.assertNotIn('FAIL', status.stderr)
551+
self.assertNotIn('ERROR', status.stderr)
552+
505553

506554
class SerialMixin(object):
507555
"""Mixin to mark test for serial execution.

numba/tests/test_debuginfo.py

Lines changed: 126 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import inspect
2-
import os
32
import re
4-
import subprocess
5-
import sys
63

7-
from numba.tests.support import TestCase, override_config
4+
from numba.tests.support import TestCase, override_config, needs_subprocess
85
from numba import jit, njit
96
from numba.core import types
107
import unittest
118
import llvmlite.binding as llvm
129

10+
#NOTE: These tests are potentially sensitive to changes in SSA or lowering
11+
# behaviour and may need updating should changes be made to the corresponding
12+
# algorithms.
13+
1314

1415
class TestDebugInfo(TestCase):
1516
"""
@@ -55,15 +56,12 @@ def bar(x):
5556
self._check(bar, sig=(types.int32,), expect=False)
5657

5758

58-
# some tests need clean environments to hide optimisation default states
59-
_exec_cond = os.environ.get('SUBPROC_TEST', None) == '1'
60-
needs_subprocess = unittest.skipUnless(_exec_cond, "needs subprocess harness")
61-
62-
6359
class TestDebugInfoEmission(TestCase):
6460
""" Tests that debug info is emitted correctly.
6561
"""
6662

63+
_NUMBA_OPT_0_ENV = {'NUMBA_OPT': '0'}
64+
6765
def _get_llvmir(self, fn, sig):
6866
with override_config('OPT', 0):
6967
fn.compile(sig)
@@ -78,6 +76,14 @@ def _get_metadata(self, fn, sig):
7876
metadata.append(line)
7977
return metadata
8078

79+
def _subprocess_test_runner(self, test_name):
80+
themod = self.__module__
81+
thecls = type(self).__name__
82+
self.subprocess_test_runner(test_module=themod,
83+
test_class=thecls,
84+
test_name=test_name,
85+
envvars=self._NUMBA_OPT_0_ENV)
86+
8187
def test_DW_LANG(self):
8288

8389
@njit(debug=True)
@@ -263,24 +269,121 @@ def test_DILocation_entry_blk(self):
263269
# as jitting literally anything at any point in the lifetime of the
264270
# process ends up with a codegen at opt 3. This is not amenable to this
265271
# test!
266-
themod = self.__module__
267-
thecls = type(self).__name__
268-
injected_method = f'{themod}.{thecls}.test_DILocation_entry_blk_impl'
269-
cmd = [sys.executable, '-m', 'numba.runtests', injected_method]
270-
env_copy = os.environ.copy()
271-
env_copy['SUBPROC_TEST'] = '1'
272272
# This test relies on the CFG not being simplified as it checks the jump
273273
# from the entry block to the first basic block. Force OPT as 0, if set
274274
# via the env var the targetmachine and various pass managers all end up
275275
# at OPT 0 and the IR is minimally transformed prior to lowering to ELF.
276-
env_copy['NUMBA_OPT'] = '0'
277-
status = subprocess.run(cmd, stdout=subprocess.PIPE,
278-
stderr=subprocess.PIPE, timeout=60,
279-
env=env_copy, universal_newlines=True)
280-
self.assertEqual(status.returncode, 0)
281-
self.assertIn('OK', status.stderr)
282-
self.assertTrue('FAIL' not in status.stderr)
283-
self.assertTrue('ERROR' not in status.stderr)
276+
self._subprocess_test_runner('test_DILocation_entry_blk_impl')
277+
278+
@needs_subprocess
279+
def test_DILocation_decref_impl(self):
280+
""" This tests that decref's generated from `ir.Del`s as variables go
281+
out of scope do not have debuginfo associated with them (the location of
282+
`ir.Del` is an implementation detail).
283+
"""
284+
285+
@njit(debug=True)
286+
def sink(*x):
287+
pass
288+
289+
# This function has many decrefs!
290+
@njit(debug=True)
291+
def foo(a):
292+
x = (a, a)
293+
if a[0] == 0:
294+
sink(x)
295+
return 12
296+
z = x[0][0]
297+
return z
298+
299+
sig = (types.float64[::1],)
300+
full_ir = self._get_llvmir(foo, sig=sig)
301+
302+
# make sure decref lines end with `meminfo.<number>)` without !dbg info.
303+
count = 0
304+
for line in full_ir.splitlines():
305+
line_stripped = line.strip()
306+
if line_stripped.startswith('call void @NRT_decref'):
307+
self.assertRegex(line, r'.*meminfo\.[0-9]+\)$')
308+
count += 1
309+
self.assertGreater(count, 0) # make sure there were some decrefs!
310+
311+
def test_DILocation_decref(self):
312+
# Test runner for test_DILocation_decref_impl, needs a subprocess
313+
# with opt=0 to preserve decrefs.
314+
self._subprocess_test_runner('test_DILocation_decref_impl')
315+
316+
def test_DILocation_undefined(self):
317+
""" Tests that DILocation information for undefined vars is associated
318+
with the line of the function definition (so it ends up in the prologue)
319+
"""
320+
@njit(debug=True)
321+
def foo(n):
322+
if n:
323+
if n > 0:
324+
c = 0
325+
return c
326+
else:
327+
# variable c is not defined in this branch
328+
c += 1
329+
return c
330+
331+
sig = (types.intp,)
332+
metadata = self._get_metadata(foo, sig=sig)
333+
pysrc, pysrc_line_start = inspect.getsourcelines(foo)
334+
# Looks for versions of variable "c" and captures the line number
335+
expr = r'.*!DILocalVariable\(name: "c\$?[0-9]?",.*line: ([0-9]+),.*'
336+
matcher = re.compile(expr)
337+
associated_lines = set()
338+
for md in metadata:
339+
match = matcher.match(md)
340+
if match:
341+
groups = match.groups()
342+
self.assertEqual(len(groups), 1)
343+
associated_lines.add(int(groups[0]))
344+
self.assertEqual(len(associated_lines), 3) # 3 versions of 'c'
345+
self.assertIn(pysrc_line_start, associated_lines)
346+
347+
def test_DILocation_versioned_variables(self):
348+
""" Tests that DILocation information for versions of variables matches
349+
up to their definition site."""
350+
# Note: there's still something wrong in the DI/SSA naming, the ret c is
351+
# associated with the logically first definition.
352+
353+
@njit(debug=True)
354+
def foo(n):
355+
if n:
356+
c = 5
357+
else:
358+
c = 1
359+
return c
360+
361+
sig = (types.intp,)
362+
metadata = self._get_metadata(foo, sig=sig)
363+
pysrc, pysrc_line_start = inspect.getsourcelines(foo)
364+
365+
# Looks for SSA versioned names i.e. <basename>$<version id> of the
366+
# variable 'c' and captures the line
367+
expr = r'.*!DILocalVariable\(name: "c\$[0-9]?",.*line: ([0-9]+),.*'
368+
matcher = re.compile(expr)
369+
associated_lines = set()
370+
for md in metadata:
371+
match = matcher.match(md)
372+
if match:
373+
groups = match.groups()
374+
self.assertEqual(len(groups), 1)
375+
associated_lines.add(int(groups[0]))
376+
self.assertEqual(len(associated_lines), 2) # 2 SSA versioned names 'c'
377+
378+
# Now find the `c = ` lines in the python source
379+
py_lines = set()
380+
for ix, pyln in enumerate(pysrc):
381+
if 'c = ' in pyln:
382+
py_lines.add(ix + pysrc_line_start)
383+
self.assertEqual(len(py_lines), 2) # 2 assignments to c
384+
385+
# check that the DILocation from the DI for `c` matches the python src
386+
self.assertEqual(associated_lines, py_lines)
284387

285388

286389
if __name__ == '__main__':

numba/tests/test_parfors.py

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@
4343
from numba.tests.support import (TestCase, captured_stdout, MemoryLeakMixin,
4444
override_env_config, linux_only, tag,
4545
skip_parfors_unsupported, _32bit, needs_blas,
46-
needs_lapack, disabled_test, skip_unless_scipy)
46+
needs_lapack, disabled_test, skip_unless_scipy,
47+
needs_subprocess)
4748
import cmath
4849
import unittest
4950

@@ -52,9 +53,8 @@
5253
# used to determine whether a test is skipped or not, such that if you want to
5354
# run any parfors test directly this environment variable can be set. The
5455
# subprocesses running the test classes set this environment variable as the new
55-
# process starts which enables the tests within the process.
56-
_exec_cond = os.environ.get('SUBPROC_TEST', None) == '1'
57-
needs_subprocess = unittest.skipUnless(_exec_cond, "needs subprocess harness")
56+
# process starts which enables the tests within the process. The decorator
57+
# @needs_subprocess is used to ensure the appropriate test skips are made.
5858

5959

6060
@skip_parfors_unsupported
@@ -73,17 +73,10 @@ class TestParforsRunner(TestCase):
7373
def runner(self):
7474
themod = self.__module__
7575
test_clazz_name = self.id().split('.')[-1].split('_')[-1]
76-
the_test = f'{themod}.{test_clazz_name}'
77-
cmd = [sys.executable, '-m', 'numba.runtests', the_test]
78-
env_copy = os.environ.copy()
79-
env_copy['SUBPROC_TEST'] = '1'
80-
status = subprocess.run(cmd, stdout=subprocess.PIPE,
81-
stderr=subprocess.PIPE, timeout=self._TIMEOUT,
82-
env=env_copy, universal_newlines=True)
83-
self.assertEqual(status.returncode, 0, msg=status.stderr)
84-
self.assertIn('OK', status.stderr)
85-
self.assertTrue('FAIL' not in status.stderr)
86-
self.assertTrue('ERROR' not in status.stderr)
76+
# don't specify a given test, it's an entire class that needs running
77+
self.subprocess_test_runner(test_module=themod,
78+
test_class=test_clazz_name,
79+
timeout=self._TIMEOUT)
8780

8881
def test_TestParforBasic(self):
8982
self.runner()

0 commit comments

Comments
 (0)