Skip to content

Commit 200b1bf

Browse files
jiel-nvgmarkall
andauthored
Local variable debug info deduplication (#222)
This PR improves the debug info for local variables. * Add a CUDA native lowering pass to the lowering pipeline, so that we can customize the numba cuda IR w/o affecting upstream numba. * Switch the llvm intrinsic call "llvm.dbg.declare" to "llvm.dbg.value" in order to provide more accurate value tracking. * Eliminate the user variable aliases (the .$1 variations) in DILocalVariable. * Merge DILocalVariable nodes with same name and type but different lines by using cache. * Merge the BooleanLiteral type into boolean type and merge the IntegerLiteral type into integer type. * Add three unit tests. Fixes nvbug#5027648, nvbug#5009771, nvbug#5120628. --------- Co-authored-by: Graham Markall <[email protected]>
1 parent ffc9c70 commit 200b1bf

File tree

4 files changed

+195
-3
lines changed

4 files changed

+195
-3
lines changed

numba_cuda/numba/cuda/compiler.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from numba.cuda.cudadrv import nvvm
4141
from numba.cuda.descriptor import cuda_target
4242
from numba.cuda.target import CUDACABICallConv
43+
from numba.cuda import lowering
4344

4445

4546
def _nvvm_options_type(x):
@@ -163,6 +164,18 @@ def run_pass(self, state):
163164
return True
164165

165166

167+
@register_pass(mutates_CFG=True, analysis_only=False)
168+
class CUDANativeLowering(NativeLowering):
169+
"""Lowering pass for a CUDA native function IR described solely in terms of
170+
Numba's standard `numba.core.ir` nodes."""
171+
172+
_name = "cuda_native_lowering"
173+
174+
@property
175+
def lowering_class(self):
176+
return lowering.CUDALower
177+
178+
166179
class CUDABytecodeInterpreter(Interpreter):
167180
# Based on the superclass implementation, but names the resulting variable
168181
# "$bool<N>" instead of "bool<N>" - see Numba PR #9888:
@@ -251,7 +264,7 @@ def define_cuda_lowering_pipeline(self, state):
251264

252265
# lower
253266
pm.add_pass(CreateLibrary, "create library")
254-
pm.add_pass(NativeLowering, "native lowering")
267+
pm.add_pass(CUDANativeLowering, "cuda native lowering")
255268
pm.add_pass(CUDABackend, "cuda backend")
256269

257270
pm.finalize()

numba_cuda/numba/cuda/debuginfo.py

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
from llvmlite import ir
2-
from numba.core import types
2+
from numba.core import types, cgutils
33
from numba.core.debuginfo import DIBuilder
44
from numba.cuda.types import GridGroup
55

66
_BYTE_SIZE = 8
77

88

99
class CUDADIBuilder(DIBuilder):
10+
def __init__(self, module, filepath, cgctx, directives_only):
11+
super().__init__(module, filepath, cgctx, directives_only)
12+
# Cache for local variable metadata type and line deduplication
13+
self._vartypelinemap = {}
14+
1015
def _var_type(self, lltype, size, datamodel=None):
1116
is_bool = False
17+
is_int_literal = False
1218
is_grid_group = False
1319

1420
if isinstance(lltype, ir.IntType):
@@ -20,15 +26,23 @@ def _var_type(self, lltype, size, datamodel=None):
2026
name = str(datamodel.fe_type)
2127
if isinstance(datamodel.fe_type, types.Boolean):
2228
is_bool = True
29+
if isinstance(datamodel.fe_type, types.BooleanLiteral):
30+
name = "bool"
31+
elif isinstance(datamodel.fe_type, types.Integer):
32+
if isinstance(datamodel.fe_type, types.IntegerLiteral):
33+
name = f"int{_BYTE_SIZE * size}"
34+
is_int_literal = True
2335
elif isinstance(datamodel.fe_type, GridGroup):
2436
is_grid_group = True
2537

26-
if is_bool or is_grid_group:
38+
if is_bool or is_int_literal or is_grid_group:
2739
m = self.module
2840
bitsize = _BYTE_SIZE * size
2941
# Boolean type workaround until upstream Numba is fixed
3042
if is_bool:
3143
ditok = "DW_ATE_boolean"
44+
elif is_int_literal:
45+
ditok = "DW_ATE_signed"
3246
# GridGroup type should use numba.cuda implementation
3347
elif is_grid_group:
3448
ditok = "DW_ATE_unsigned"
@@ -44,3 +58,79 @@ def _var_type(self, lltype, size, datamodel=None):
4458

4559
# For other cases, use upstream Numba implementation
4660
return super()._var_type(lltype, size, datamodel=datamodel)
61+
62+
def mark_variable(
63+
self,
64+
builder,
65+
allocavalue,
66+
name,
67+
lltype,
68+
size,
69+
line,
70+
datamodel=None,
71+
argidx=None,
72+
):
73+
if name.startswith("$") or "." in name:
74+
# Do not emit llvm.dbg.declare on user variable alias
75+
return
76+
else:
77+
int_type = (ir.IntType,)
78+
real_type = ir.FloatType, ir.DoubleType
79+
if isinstance(lltype, int_type + real_type):
80+
# Start with scalar variable, swtiching llvm.dbg.declare
81+
# to llvm.dbg.value
82+
return
83+
else:
84+
return super().mark_variable(
85+
builder,
86+
allocavalue,
87+
name,
88+
lltype,
89+
size,
90+
line,
91+
datamodel,
92+
argidx,
93+
)
94+
95+
def update_variable(
96+
self,
97+
builder,
98+
value,
99+
name,
100+
lltype,
101+
size,
102+
line,
103+
datamodel=None,
104+
argidx=None,
105+
):
106+
m = self.module
107+
fnty = ir.FunctionType(ir.VoidType(), [ir.MetaDataType()] * 3)
108+
decl = cgutils.get_or_insert_function(m, fnty, "llvm.dbg.value")
109+
110+
mdtype = self._var_type(lltype, size, datamodel)
111+
index = name.find(".")
112+
if index >= 0:
113+
name = name[:index]
114+
# Merge DILocalVariable nodes with same name and type but different
115+
# lines. Use the cached [(name, type) -> line] info to deduplicate
116+
# metadata. Use the lltype as part of key.
117+
key = (name, lltype)
118+
if key in self._vartypelinemap:
119+
line = self._vartypelinemap[key]
120+
else:
121+
self._vartypelinemap[key] = line
122+
arg_index = 0 if argidx is None else argidx
123+
mdlocalvar = m.add_debug_info(
124+
"DILocalVariable",
125+
{
126+
"name": name,
127+
"arg": arg_index,
128+
"scope": self.subprograms[-1],
129+
"file": self.difile,
130+
"line": line,
131+
"type": mdtype,
132+
},
133+
)
134+
mdexpr = m.add_debug_info("DIExpression", {})
135+
136+
return builder.call(decl, [value, mdlocalvar, mdexpr])

numba_cuda/numba/cuda/lowering.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from numba.core.lowering import Lower
2+
from llvmlite import ir
3+
4+
5+
class CUDALower(Lower):
6+
def storevar(self, value, name, argidx=None):
7+
"""
8+
Store the value into the given variable.
9+
"""
10+
super().storevar(value, name, argidx)
11+
12+
# Emit llvm.dbg.value instead of llvm.dbg.declare for local scalar
13+
# variables immediately after a store instruction.
14+
if (
15+
self.context.enable_debuginfo
16+
# Conditions used to elide stores in parent method
17+
and (
18+
name not in self._singly_assigned_vars
19+
or self._disable_sroa_like_opt
20+
)
21+
# No emission of debuginfo for internal names
22+
and not name.startswith("$")
23+
):
24+
# Emit debug value for user variable
25+
fetype = self.typeof(name)
26+
lltype = self.context.get_value_type(fetype)
27+
int_type = (ir.IntType,)
28+
real_type = ir.FloatType, ir.DoubleType
29+
if isinstance(lltype, int_type + real_type):
30+
# Emit debug value for scalar variable
31+
sizeof = self.context.get_abi_sizeof(lltype)
32+
datamodel = self.context.data_model_manager[fetype]
33+
line = self.loc.line if argidx is None else self.defn_loc.line
34+
self.debuginfo.update_variable(
35+
self.builder,
36+
value,
37+
name,
38+
lltype,
39+
sizeof,
40+
line,
41+
datamodel,
42+
argidx,
43+
)

numba_cuda/numba/cuda/tests/cudapy/test_debuginfo.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,52 @@ def test_kernel_args_types_dump(self):
310310
with captured_stdout():
311311
self._test_kernel_args_types()
312312

313+
def test_llvm_dbg_value(self):
314+
sig = (types.int32, types.int32)
315+
316+
@cuda.jit("void(int32, int32)", debug=True, opt=False)
317+
def f(x, y):
318+
z = x # noqa: F841
319+
z = 100 # noqa: F841
320+
z = y # noqa: F841
321+
z = True # noqa: F841
322+
323+
llvm_ir = f.inspect_llvm(sig)
324+
# Verify the call to llvm.dbg.declare is replaced by llvm.dbg.value
325+
pat1 = r'call void @"llvm.dbg.declare"'
326+
match = re.compile(pat1).search(llvm_ir)
327+
self.assertIsNone(match, msg=llvm_ir)
328+
pat2 = r'call void @"llvm.dbg.value"'
329+
match = re.compile(pat2).search(llvm_ir)
330+
self.assertIsNotNone(match, msg=llvm_ir)
331+
332+
def test_no_user_var_alias(self):
333+
sig = (types.int32, types.int32)
334+
335+
@cuda.jit("void(int32, int32)", debug=True, opt=False)
336+
def f(x, y):
337+
z = x # noqa: F841
338+
z = y # noqa: F841
339+
340+
llvm_ir = f.inspect_llvm(sig)
341+
pat = r'!DILocalVariable.*name:\s+"z\$1".*'
342+
match = re.compile(pat).search(llvm_ir)
343+
self.assertIsNone(match, msg=llvm_ir)
344+
345+
def test_no_literal_type(self):
346+
sig = (types.int32,)
347+
348+
@cuda.jit("void(int32)", debug=True, opt=False)
349+
def f(x):
350+
z = x # noqa: F841
351+
z = 100 # noqa: F841
352+
z = True # noqa: F841
353+
354+
llvm_ir = f.inspect_llvm(sig)
355+
pat = r'!DIBasicType.*name:\s+"Literal.*'
356+
match = re.compile(pat).search(llvm_ir)
357+
self.assertIsNone(match, msg=llvm_ir)
358+
313359

314360
if __name__ == "__main__":
315361
unittest.main()

0 commit comments

Comments
 (0)