Skip to content
Open
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
14 changes: 13 additions & 1 deletion dace/config_schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ required:
description: >
Specify the default data types to use in generating code.
If "Python", Python's semantics will be followed (i.e., `float` and `int`
are represented using 64 bits). If the property is set to "C", C's semantcs will be
are represented using 64 bits). If the property is set to "C", C's semantics will be
used (`float` and `int` are represented using 32bits).

unique_functions:
Expand Down Expand Up @@ -251,6 +251,18 @@ required:
If set, specifies additional arguments to the initial invocation
of ``cmake``.

lineinfo:
type: str
default: "inspect"
title: Add line info
description: >
Wether or not to add line info from the parsed code in the
generated SDFG. Valid options are `inspect` and `none`.
"inspect": During parsing, inspect the python call stack and
automatically add line info from the parsed source code in the
resulting SDFG.
"none": Do not save any line info in the resulting SDFG.

#############################################
# CPU compiler
cpu:
Expand Down
128 changes: 75 additions & 53 deletions dace/sdfg/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import itertools
import warnings
import sympy
from typing import (TYPE_CHECKING, Any, AnyStr, Callable, Dict, Iterable, Iterator, List, Optional, Set, Tuple, Type,
Union, overload)
from typing import (TYPE_CHECKING, Any, AnyStr, Callable, Dict, Iterable, Iterator, List, Literal, Optional, Set, Tuple,
Type, Union, overload)

import dace
from dace.frontend.python import astutils
Expand Down Expand Up @@ -39,20 +39,43 @@
NodeT = Union[nd.Node, 'ControlFlowBlock']
EdgeT = Union[MultiConnectorEdge[mm.Memlet], Edge['dace.sdfg.InterstateEdge']]
GraphT = Union['ControlFlowRegion', 'SDFGState']
ConfigurableDebugInfo = Literal["config"] | dtypes.DebugInfo | None


def _getdebuginfo(old_dinfo=None) -> dtypes.DebugInfo:
""" Returns a DebugInfo object for the position that called this function.
def _get_debug_info(
debug_info: ConfigurableDebugInfo,
default_lineinfo: dtypes.DebugInfo | None,
) -> dtypes.DebugInfo | None:
"""Returns a DebugInfo from the stack, if configured.

:param old_dinfo: Another DebugInfo object that will override the
return value of this function
:return: DebugInfo containing line number and calling file.
If lineinfo is configured in the config, this function inspects the python
stacktrace and returns a DebugInfo object for the position that called this
function. `default_lineinfo` has precedence, if given.
"""
if old_dinfo is not None:
return old_dinfo

caller = inspect.getframeinfo(inspect.stack()[2][0], context=0)
return dtypes.DebugInfo(caller.lineno, 0, caller.lineno, 0, caller.filename)
if isinstance(debug_info, dtypes.DebugInfo):
warnings.warn(
"Passing debug_info of type DebugInfo is deprecated. Pass `'config'` and set `compiler.lineinfo` in your DaCe config.",
DeprecationWarning,
stacklevel=3)
return debug_info

if debug_info is None:
warnings.warn(
"Passing debug_info=None is deprecated. Pass `'config'` and set `compiler.lineinfo` in your DaCe config.",
DeprecationWarning,
stacklevel=3,
)

do_inspect = dace.Config.get("compiler", "lineinfo") == "inspect" if debug_info == "config" else False
if (debug_info is None or do_inspect) and default_lineinfo is not None:
return default_lineinfo

if do_inspect:
caller = inspect.getframeinfo(inspect.stack()[2][0], context=0)
return dtypes.DebugInfo(caller.lineno, 0, caller.lineno, 0, caller.filename)

return None


def _make_iterators(ndrange):
Expand Down Expand Up @@ -1617,7 +1640,7 @@ def symbols_defined_at(self, node: nd.Node) -> Dict[str, dtypes.typeclass]:

# Dynamic SDFG creation API
##############################
def add_read(self, array_or_stream_name: str, debuginfo: Optional[dtypes.DebugInfo] = None) -> nd.AccessNode:
def add_read(self, array_or_stream_name: str, debuginfo: ConfigurableDebugInfo = "config") -> nd.AccessNode:
"""
Adds an access node to this SDFG state (alias of ``add_access``).

Expand All @@ -1626,10 +1649,9 @@ def add_read(self, array_or_stream_name: str, debuginfo: Optional[dtypes.DebugIn
:return: An array access node.
:see: add_access
"""
debuginfo = _getdebuginfo(debuginfo or self._default_lineinfo)
return self.add_access(array_or_stream_name, debuginfo=debuginfo)

def add_write(self, array_or_stream_name: str, debuginfo: Optional[dtypes.DebugInfo] = None) -> nd.AccessNode:
def add_write(self, array_or_stream_name: str, debuginfo: ConfigurableDebugInfo = "config") -> nd.AccessNode:
"""
Adds an access node to this SDFG state (alias of ``add_access``).

Expand All @@ -1638,17 +1660,16 @@ def add_write(self, array_or_stream_name: str, debuginfo: Optional[dtypes.DebugI
:return: An array access node.
:see: add_access
"""
debuginfo = _getdebuginfo(debuginfo or self._default_lineinfo)
return self.add_access(array_or_stream_name, debuginfo=debuginfo)

def add_access(self, array_or_stream_name: str, debuginfo: Optional[dtypes.DebugInfo] = None) -> nd.AccessNode:
def add_access(self, array_or_stream_name: str, debuginfo: ConfigurableDebugInfo = "config") -> nd.AccessNode:
""" Adds an access node to this SDFG state.

:param array_or_stream_name: The name of the array/stream.
:param debuginfo: Source line information for this access node.
:return: An array access node.
"""
debuginfo = _getdebuginfo(debuginfo or self._default_lineinfo)
debuginfo = _get_debug_info(debuginfo, self._default_lineinfo)
node = nd.AccessNode(array_or_stream_name, debuginfo=debuginfo)
self.add_node(node)
return node
Expand All @@ -1666,10 +1687,10 @@ def add_tasklet(
code_exit: str = "",
location: dict = None,
side_effects: Optional[bool] = None,
debuginfo=None,
debuginfo: ConfigurableDebugInfo = "config",
):
""" Adds a tasklet to the SDFG state. """
debuginfo = _getdebuginfo(debuginfo or self._default_lineinfo)
debuginfo = _get_debug_info(debuginfo, self._default_lineinfo)

# Make dictionary of autodetect connector types from set
if isinstance(inputs, (set, collections.abc.KeysView)):
Expand Down Expand Up @@ -1715,7 +1736,7 @@ def add_nested_sdfg(
symbol_mapping: Dict[str, Any] = None,
name=None,
location: Optional[Dict[str, symbolic.SymbolicType]] = None,
debuginfo: Optional[dtypes.DebugInfo] = None,
debuginfo: ConfigurableDebugInfo = "config",
external_path: Optional[str] = None,
):
"""
Expand All @@ -1738,7 +1759,7 @@ def add_nested_sdfg(
"""
if name is None:
name = sdfg.label
debuginfo = _getdebuginfo(debuginfo or self._default_lineinfo)
debuginfo = _get_debug_info(debuginfo, self._default_lineinfo)

if sdfg is None and external_path is None:
raise ValueError('Neither an SDFG nor an external SDFG path has been provided')
Expand Down Expand Up @@ -1802,7 +1823,7 @@ def add_map(
ndrange: Union[Dict[str, Union[str, sbs.Subset]], List[Tuple[str, Union[str, sbs.Subset]]]],
schedule=dtypes.ScheduleType.Default,
unroll=False,
debuginfo=None,
debuginfo: ConfigurableDebugInfo = "config",
) -> Tuple[nd.MapEntry, nd.MapExit]:
""" Adds a map entry and map exit.

Expand All @@ -1814,21 +1835,23 @@ def add_map(

:return: (map_entry, map_exit) node 2-tuple
"""
debuginfo = _getdebuginfo(debuginfo or self._default_lineinfo)
debuginfo = _get_debug_info(debuginfo, self._default_lineinfo)
map = nd.Map(name, *_make_iterators(ndrange), schedule=schedule, unroll=unroll, debuginfo=debuginfo)
map_entry = nd.MapEntry(map)
map_exit = nd.MapExit(map)
self.add_nodes_from([map_entry, map_exit])
return map_entry, map_exit

def add_consume(self,
name,
elements: Tuple[str, str],
condition: str = None,
schedule=dtypes.ScheduleType.Default,
chunksize=1,
debuginfo=None,
language=dtypes.Language.Python) -> Tuple[nd.ConsumeEntry, nd.ConsumeExit]:
def add_consume(
self,
name,
elements: Tuple[str, str],
condition: str = None,
schedule=dtypes.ScheduleType.Default,
chunksize=1,
debuginfo: ConfigurableDebugInfo = "config",
language=dtypes.Language.Python,
) -> Tuple[nd.ConsumeEntry, nd.ConsumeExit]:
""" Adds consume entry and consume exit nodes.

:param name: Label
Expand All @@ -1850,7 +1873,7 @@ def add_consume(self,
"(PE_index, num_PEs)")
pe_tuple = (elements[0], SymbolicProperty.from_string(elements[1]))

debuginfo = _getdebuginfo(debuginfo or self._default_lineinfo)
debuginfo = _get_debug_info(debuginfo, self._default_lineinfo)
if condition is not None:
condition = CodeBlock(condition, language)
consume = nd.Consume(name, pe_tuple, condition, schedule, chunksize, debuginfo=debuginfo)
Expand All @@ -1860,24 +1883,23 @@ def add_consume(self,
self.add_nodes_from([entry, exit])
return entry, exit

def add_mapped_tasklet(self,
name: str,
map_ranges: Union[Dict[str, Union[str, sbs.Subset]], List[Tuple[str, Union[str,
sbs.Subset]]]],
inputs: Dict[str, mm.Memlet],
code: str,
outputs: Dict[str, mm.Memlet],
schedule=dtypes.ScheduleType.Default,
unroll_map=False,
location=None,
language=dtypes.Language.Python,
debuginfo=None,
external_edges=False,
input_nodes: Optional[Union[Dict[str, nd.AccessNode], List[nd.AccessNode],
Set[nd.AccessNode]]] = None,
output_nodes: Optional[Union[Dict[str, nd.AccessNode], List[nd.AccessNode],
Set[nd.AccessNode]]] = None,
propagate=True) -> Tuple[nd.Tasklet, nd.MapEntry, nd.MapExit]:
def add_mapped_tasklet(
self,
name: str,
map_ranges: Union[Dict[str, Union[str, sbs.Subset]], List[Tuple[str, Union[str, sbs.Subset]]]],
inputs: Dict[str, mm.Memlet],
code: str,
outputs: Dict[str, mm.Memlet],
schedule=dtypes.ScheduleType.Default,
unroll_map=False,
location=None,
language=dtypes.Language.Python,
debuginfo: ConfigurableDebugInfo = "config",
external_edges=False,
input_nodes: Optional[Union[Dict[str, nd.AccessNode], List[nd.AccessNode], Set[nd.AccessNode]]] = None,
output_nodes: Optional[Union[Dict[str, nd.AccessNode], List[nd.AccessNode], Set[nd.AccessNode]]] = None,
propagate=True,
) -> Tuple[nd.Tasklet, nd.MapEntry, nd.MapExit]:
""" Convenience function that adds a map entry, tasklet, map exit,
and the respective edges to external arrays.

Expand Down Expand Up @@ -1910,7 +1932,7 @@ def add_mapped_tasklet(self,
:return: tuple of (tasklet, map_entry, map_exit)
"""
map_name = name + "_map"
debuginfo = _getdebuginfo(debuginfo or self._default_lineinfo)
debuginfo = _get_debug_info(debuginfo, self._default_lineinfo)

# Create appropriate dictionaries from inputs
tinputs = {k: None for k, v in inputs.items()}
Expand Down Expand Up @@ -2030,7 +2052,7 @@ def add_reduce(
axes,
identity=None,
schedule=dtypes.ScheduleType.Default,
debuginfo=None,
debuginfo: ConfigurableDebugInfo = "config",
) -> 'dace.libraries.standard.Reduce':
""" Adds a reduction node.

Expand All @@ -2044,7 +2066,7 @@ def add_reduce(
:return: A Reduce node
"""
import dace.libraries.standard as stdlib # Avoid import loop
debuginfo = _getdebuginfo(debuginfo or self._default_lineinfo)
debuginfo = _get_debug_info(debuginfo, self._default_lineinfo)
result = stdlib.Reduce('Reduce', wcr, axes, identity, schedule=schedule, debuginfo=debuginfo)
self.add_node(result)
return result
Expand Down
68 changes: 68 additions & 0 deletions tests/python_frontend/debug_info_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from dace import SDFG, SDFGState
from dace import dtypes
from dace.config import Config, temporary_config

import pytest


@pytest.fixture()
def state() -> SDFGState:
"""Setup a simple SDFG for testing and return the state."""

sdfg = SDFG("tester")
sdfg.add_array("A", [10], dtypes.int64)
sdfg.add_array("B", [15], dtypes.int64)

return sdfg.add_state("start_here", is_start_block=True)


def test_config_compiler_lineinfo_none(state: SDFGState) -> None:
with temporary_config():
Config.set(*["compiler", "lineinfo"], value="none")
node = state.add_access("A")
assert node.debuginfo is None


def test_config_compiler_lineinfo_inspect(state: SDFGState) -> None:
# Ensure "inspect" is the default configuration
assert Config.get(*["compiler", "lineinfo"]) == "inspect"

# Add an access and expect debug info to be added (from this file)
node = state.add_access("A")
assert node.debuginfo is not None
assert node.debuginfo.filename == __file__


def test_using_default_lineinfo(state: SDFGState) -> None:
line = 42
filename = "my/test/path.py"
state._default_lineinfo = dtypes.DebugInfo(start_line=line, filename=filename)

read_A = state.add_read("A")
assert read_A.debuginfo is not None
assert read_A.debuginfo.filename == filename
assert read_A.debuginfo.start_line == line

with pytest.deprecated_call():
read_B = state.add_read("B", None)
assert read_B.debuginfo is not None
assert read_B.debuginfo.filename == filename
assert read_B.debuginfo.start_line == line


def test_passing_debug_info_warns(state: SDFGState) -> None:
with pytest.deprecated_call():
state.add_access("A", debuginfo=dtypes.DebugInfo(start_line=42))


def test_passing_None_warns(state: SDFGState) -> None:
with pytest.deprecated_call():
state.add_access("A", debuginfo=None)


if __name__ == "__main__":
test_config_compiler_lineinfo_none()
test_config_compiler_lineinfo_inspect()
test_using_default_lineinfo()
test_passing_debug_info_warns()
test_passing_None_warns()
Loading