Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion numba/core/lowering.py
Original file line number Diff line number Diff line change
Expand Up @@ -1407,7 +1407,11 @@ def decref(self, typ, val):
if not self.context.enable_nrt:
return

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


def _lit_or_omitted(value):
Expand Down
7 changes: 4 additions & 3 deletions numba/core/typed_passes.py
Original file line number Diff line number Diff line change
Expand Up @@ -843,13 +843,14 @@ def _strip_phi_nodes(self, func_ir):
for target, rhs in exporters[label]:
# If RHS is undefined
if rhs is ir.UNDEFINED:
# Put in a NULL initializer
rhs = ir.Expr.null(loc=target.loc)
# Put in a NULL initializer, set the location to be in what
# will eventually materialize as the prologue.
rhs = ir.Expr.null(loc=func_ir.loc)

assign = ir.Assign(
>
value=rhs,
loc=target.loc
loc=rhs.loc
)
# Insert at the earliest possible location; i.e. after the
# last assignment to rhs
Expand Down
144 changes: 130 additions & 14 deletions numba/tests/test_debuginfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,25 @@ def _get_metadata(self, fn, sig):
metadata.append(line)
return metadata

def _subprocess_test_runner(self, test_name):
# Runs a named unit test, test_name (str), from this class in a
# subprocess with `NUMBA_OPT` set to 0 so as to get the most unoptimized
# llvm IR available. It then checks the test executed without error.
themod = self.__module__
thecls = type(self).__name__
injected_method = f'{themod}.{thecls}.{test_name}'
cmd = [sys.executable, '-m', 'numba.runtests', injected_method]
env_copy = os.environ.copy()
env_copy['SUBPROC_TEST'] = '1'
env_copy['NUMBA_OPT'] = '0'
status = subprocess.run(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, timeout=60,
env=env_copy, universal_newlines=True)
self.assertEqual(status.returncode, 0)
self.assertIn('OK', status.stderr)
self.assertTrue('FAIL' not in status.stderr)
self.assertTrue('ERROR' not in status.stderr)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a duplication of

def runner(self):
themod = self.__module__
test_clazz_name = self.id().split('.')[-1].split('_')[-1]
the_test = f'{themod}.{test_clazz_name}'
cmd = [sys.executable, '-m', 'numba.runtests', the_test]
env_copy = os.environ.copy()
env_copy['SUBPROC_TEST'] = '1'
status = subprocess.run(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, timeout=self._TIMEOUT,
env=env_copy, universal_newlines=True)
self.assertEqual(status.returncode, 0, msg=status.stderr)
self.assertIn('OK', status.stderr)
self.assertTrue('FAIL' not in status.stderr)
self.assertTrue('ERROR' not in status.stderr)

we should dedup here or in a immediate follow up PR


def test_DW_LANG(self):

@njit(debug=True)
Expand Down Expand Up @@ -263,24 +282,121 @@ def test_DILocation_entry_blk(self):
# as jitting literally anything at any point in the lifetime of the
# process ends up with a codegen at opt 3. This is not amenable to this
# test!
themod = self.__module__
thecls = type(self).__name__
injected_method = f'{themod}.{thecls}.test_DILocation_entry_blk_impl'
cmd = [sys.executable, '-m', 'numba.runtests', injected_method]
env_copy = os.environ.copy()
env_copy['SUBPROC_TEST'] = '1'
# This test relies on the CFG not being simplified as it checks the jump
# from the entry block to the first basic block. Force OPT as 0, if set
# via the env var the targetmachine and various pass managers all end up
# at OPT 0 and the IR is minimally transformed prior to lowering to ELF.
env_copy['NUMBA_OPT'] = '0'
status = subprocess.run(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, timeout=60,
env=env_copy, universal_newlines=True)
self.assertEqual(status.returncode, 0)
self.assertIn('OK', status.stderr)
self.assertTrue('FAIL' not in status.stderr)
self.assertTrue('ERROR' not in status.stderr)
self._subprocess_test_runner('test_DILocation_entry_blk_impl')

@needs_subprocess
def test_DILocation_decref_impl(self):
""" This tests that decref's generated from `ir.Del`s as variables go
out of scope do not have debuginfo associated with them (the location of
`ir.Del` is an implementation detail).
"""

@njit(debug=True)
def sink(*x):
pass

# This function has many decrefs!
@njit(debug=True)
def foo(a):
x = (a, a)
if a[0] == 0:
sink(x)
return 12
z = x[0][0]
return z

sig = (types.float64[::1],)
full_ir = self._get_llvmir(foo, sig=sig)

# make sure decref lines end with `meminfo.<number>)`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# make sure decref lines end with `meminfo.<number>)`.
# make sure decref lines end with `meminfo.<number>)` without dbg info.

count = 0
for line in full_ir.splitlines():
line_stripped = line.strip()
if line_stripped.startswith('call void @NRT_decref'):
self.assertRegex(line, r'.*meminfo\.[0-9]+\)$')
count += 1
self.assertTrue(count > 0) # make sure there were some decrefs!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.assertTrue(count > 0) # make sure there were some decrefs!
self.assertGreater(count, 0) # make sure there were some decrefs!


def test_DILocation_decref(self):
# Test runner for test_DILocation_decref_impl, needs a subprocess
# with opt=0 to preserve decrefs.
self._subprocess_test_runner('test_DILocation_decref_impl')

def test_DILocation_undefined(self):
""" Tests that DILocation information for undefined vars is associated
with the line of the function definition (so it ends up in the prologue)
"""
@njit(debug=True)
def foo(n):
if n:
if n > 0:
c = 0
return c
else:
# variable c is not defined in this branch
c += 1
return c

sig = (types.intp,)
metadata = self._get_metadata(foo, sig=sig)
pysrc, pysrc_line_start = inspect.getsourcelines(foo)
# Looks for versions of variable "c" and captures the line number
expr = r'.*!DILocalVariable\(name: "c\$?[0-9]?",.*line: ([0-9]+),.*'
matcher = re.compile(expr)
associated_lines = set()
for md in metadata:
match = matcher.match(md)
if match:
groups = match.groups()
self.assertEqual(len(groups), 1)
associated_lines.add(int(groups[0]))
self.assertEqual(len(associated_lines), 3) # 3 versions of 'c'
self.assertIn(pysrc_line_start, associated_lines)

def test_DILocation_versioned_variables(self):
""" Tests that DILocation information for versions of variables matches
up to their definition site."""
# Note: there's still something wrong in the DI/SSA naming, the ret c is
# associated with the logically first definition.

@njit(debug=True)
def foo(n):
if n:
c = 5
else:
c = 1
return c

sig = (types.intp,)
metadata = self._get_metadata(foo, sig=sig)
pysrc, pysrc_line_start = inspect.getsourcelines(foo)

# Looks for SSA versioned names i.e. <basename>$<version id> of the
# variable 'c' and captures the line
expr = r'.*!DILocalVariable\(name: "c\$[0-9]?",.*line: ([0-9]+),.*'
matcher = re.compile(expr)
associated_lines = set()
for md in metadata:
match = matcher.match(md)
if match:
groups = match.groups()
self.assertEqual(len(groups), 1)
associated_lines.add(int(groups[0]))
self.assertEqual(len(associated_lines), 2) # 2 SSA versioned names 'c'

# Now find the `c = ` lines in the python source
py_lines = set()
for ix, pyln in enumerate(pysrc):
if 'c = ' in pyln:
py_lines.add(ix + pysrc_line_start)
self.assertEqual(len(py_lines), 2) # 2 assignments to c

# check that the DILocation from the DI for `c` matches the python src
self.assertEqual(associated_lines, py_lines)


if __name__ == '__main__':
Expand Down