Skip to content
This repository was archived by the owner on Jan 25, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions numba/core/cgutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,13 +370,20 @@ def alloca_once(builder, ty, size=None, name='', zfill=False):
"""
if isinstance(size, int):
size = ir.Constant(intp_t, size)
# suspend dwarf emission else it links up python source lines with alloca in
# the entry block as well as their actual location and it makes the debug
# info "jump about".
ref = builder.debug_metadata
builder.debug_metadata = None
with builder.goto_entry_block():
ptr = builder.alloca(ty, size=size, name=name)
# Always zero-fill at init-site. This is safe.
builder.store(ptr.type.pointee(None), ptr)
# Also zero-fill at the use-site
if zfill:
builder.store(ptr.type.pointee(None), ptr)
# continue with dwarf emission
builder.debug_metadata = ref
return ptr


Expand Down
2 changes: 1 addition & 1 deletion numba/core/debuginfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ def _di_file(self):

def _di_compile_unit(self):
return self.module.add_debug_info('DICompileUnit', {
'language': ir.DIToken('DW_LANG_Python'),
'language': ir.DIToken('DW_LANG_C_plus_plus'),
'file': self.difile,
'producer': 'Numba',
'runtimeVersion': 0,
Expand Down
8 changes: 6 additions & 2 deletions numba/core/lowering.py
Original file line number Diff line number Diff line change
Expand Up @@ -1302,11 +1302,15 @@ def alloca_lltype(self, name, lltype):
aptr = cgutils.alloca_once(self.builder, lltype,
name=name, zfill=False)
if is_uservar:
# If it's an arg set the location as the function definition line
if name in self.func_ir.arg_names:
loc = self.loc.with_lineno(self.func_ir.loc.line)
else:
loc = self.loc
# Emit debug info for user variable
sizeof = self.context.get_abi_sizeof(lltype)
self.debuginfo.mark_variable(self.builder, aptr, name=name,
lltype=lltype, size=sizeof,
loc=self.loc)
lltype=lltype, size=sizeof, loc=loc)
return aptr

def incref(self, typ, val):
Expand Down
136 changes: 135 additions & 1 deletion numba/tests/test_debuginfo.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import re
import inspect

from numba.tests.support import TestCase, override_config
from numba import jit
from numba import jit, njit
from numba.core import types
import unittest
import llvmlite.binding as llvm


class TestDebugInfo(TestCase):
"""
These tests only checks the compiled assembly for debuginfo.
"""

def _getasm(self, fn, sig):
fn.compile(sig)
return fn.inspect_asm(sig)
Expand Down Expand Up @@ -49,5 +52,136 @@ def bar(x):
self._check(bar, sig=(types.int32,), expect=False)


class TestDebugInfoEmission(TestCase):
""" Tests that debug info is emitted correctly.
"""

def _get_llvmir(self, fn, sig):
with override_config('OPT', 0):
fn.compile(sig)
return fn.inspect_llvm(sig)

def _get_metadata(self, fn, sig):
ll = self._get_llvmir(fn, sig).splitlines()
meta_re = re.compile(r'![0-9]+ =.*')
metadata = []
for line in ll:
if meta_re.match(line):
metadata.append(line)
return metadata

def test_DW_LANG(self):

@njit(debug=True)
def foo():
pass

metadata = self._get_metadata(foo, sig=())
DICompileUnit = metadata[0]
self.assertEqual('!0', DICompileUnit[:2])
self.assertIn('!DICompileUnit(language: DW_LANG_C_plus_plus',
DICompileUnit)
self.assertIn('producer: "Numba"', DICompileUnit)

def test_DILocation(self):
""" Tests that DILocation information is reasonable.
"""

@njit(debug=True, error_model='numpy')
def foo(a):
b = a + 1.23
c = a * 2.34
d = b / c
print(d)
return d

# the above produces LLVM like:
# define function() {
# entry:
# alloca
# store 0 to alloca
# <arithmetic for doing the operations on b, c, d>
# setup for print
# branch
# other_labels:
# ... <elided>
# }
#
# The following checks that:
# * the alloca and store have no !dbg
# * the arithmetic occurs in the order defined and with !dbg
# * that the !dbg entries are monotonically increasing in value with
# source line number

metadata = self._get_metadata(foo, sig=(types.float64,))
full_ir = self._get_llvmir(foo, sig=(types.float64,))

llvm.initialize()
llvm.initialize_native_target()
llvm.initialize_native_asmprinter()

module = llvm.parse_assembly(full_ir)

name = foo.overloads[foo.signatures[0]].fndesc.mangled_name
funcs = [x for x in module.functions if x.name == name]
self.assertEqual(len(funcs), 1)
func = funcs[0]
blocks = [x for x in func.blocks]
self.assertGreater(len(blocks), 1)
block = blocks[0]

# Find non-call instr and check the sequence is as expected
instrs = [x for x in block.instructions if x.opcode != 'call']
op_seq = [x.opcode for x in instrs]
op_expect = ('fadd', 'fmul', 'fdiv')
self.assertIn(''.join(op_expect), ''.join(op_seq))

# Parse out metadata from end of each line, check it monotonically
# ascends with LLVM source line. Also store all the dbg references,
# these will be checked later.
line2dbg = set()
re_dbg_ref = re.compile(r'.*!dbg (![0-9]+).*$')
found = -1
for instr in instrs:
inst_as_str = str(instr)
matched = re_dbg_ref.match(inst_as_str)
if not matched:
# if there's no match, ensure it is one of alloca or store,
# it's important that the zero init/alloca instructions have
# no dbg data
accepted = ('alloca ', 'store ')
self.assertTrue(any([x in inst_as_str for x in accepted]))
continue
groups = matched.groups()
self.assertEqual(len(groups), 1)
dbg_val = groups[0]
int_dbg_val = int(dbg_val[1:])
if found >= 0:
self.assertTrue(int_dbg_val >= found)
found = int_dbg_val
# some lines will alias dbg info, this is fine, it's only used to
# make sure that the line numbers are correct WRT python
line2dbg.add(dbg_val)

pysrc, pysrc_line_start = inspect.getsourcelines(foo)

# build a map of dbg reference to DI* information
metadata_definition_map = dict()
meta_definition_split = re.compile(r'(![0-9]+) = (.*)')
for line in metadata:
matched = meta_definition_split.match(line)
if matched:
dbg_val, info = matched.groups()
metadata_definition_map[dbg_val] = info

# Pull out metadata entries referred to by the llvm line end !dbg
# check they match the python source, the +2 is for the @njit decorator
# and the function definition line.
pyln_range = range(pysrc_line_start + 2, pysrc_line_start + len(pysrc))
for (k, line_no) in zip(sorted(line2dbg), pyln_range):
dilocation_info = metadata_definition_map[k]
self.assertIn(f'line: {line_no}', dilocation_info)


if __name__ == '__main__':
unittest.main()