11import inspect
2- import os
32import 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
85from numba import jit , njit
96from numba .core import types
107import unittest
118import 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
1415class 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-
6359class 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
286389if __name__ == '__main__' :
0 commit comments