Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
328212f
adapted work depth analysis to control flow regions
alexanderfluck Nov 13, 2025
ffcc756
Merge branch 'main' into fix-work-depth-analysis
alexanderfluck Nov 22, 2025
bf039d6
Merge remote-tracking branch 'upstream/main' into fix-work-depth-anal…
alexanderfluck Jan 7, 2026
2be952a
use work depth from other branch
alexanderfluck Feb 16, 2026
fc5151e
fix all errors
alexanderfluck Mar 17, 2026
88b68a7
Merge remote-tracking branch 'upstream/main' into fix-work-depth-anal…
alexanderfluck Mar 18, 2026
942a957
reapply changes after merge
alexanderfluck Mar 18, 2026
79b5dc5
fix operational intensity simulation
alexanderfluck Mar 18, 2026
3d39b06
Merge remote-tracking branch 'upstream/main' into fix-work-depth-anal…
alexanderfluck Apr 1, 2026
2f243f0
Merge remote-tracking branch 'upstream/main' into fix-work-depth-anal…
alexanderfluck Apr 27, 2026
e906254
Add tests for inlined control flow regions
alexanderfluck Apr 27, 2026
07176f2
Merge remote-tracking branch 'upstream/main' into fix-operational-int…
alexanderfluck Apr 28, 2026
09801b0
Merge remote-tracking branch 'upstream/main' into fix-work-depth-anal…
alexanderfluck Apr 28, 2026
84af34c
add back accidentally removed test
alexanderfluck Apr 28, 2026
12ae756
add back accidentally removed test
alexanderfluck Apr 28, 2026
e6882ba
Merge branch 'fix-operational-intensity-simulation' into modernize-sd…
alexanderfluck Apr 28, 2026
5b9bc37
move static symbol mapping to helpers
alexanderfluck Apr 28, 2026
e52fdcb
fixes
alexanderfluck Apr 28, 2026
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
2 changes: 1 addition & 1 deletion dace/sdfg/performance_evaluation/assumptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ def parse_assumptions(assumptions, array_symbols):
for sym, assum in condensed_assumptions.items():
i = 0
for g in assum.greater:
replacement_symbol = sp.Symbol(f'_p_{sym}', positive=True, integer=True)
replacement_symbol = sp.Symbol(f'_p_{sym}', nonnegative=True, integer=True)
all_subs[i][0].update({sp.Symbol(sym): replacement_symbol + g})
all_subs[i][1].update({replacement_symbol: sp.Symbol(sym) - g})
i += 1
Expand Down
175 changes: 175 additions & 0 deletions dace/sdfg/performance_evaluation/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
from collections import deque
from typing import List, Dict, Set, Tuple, Optional, Union
import networkx as nx
import re
import sympy as sp
from dace.sdfg.state import ControlFlowRegion
from dace.symbolic import pystr_to_symbolic

NodeT = str
EdgeT = Tuple[NodeT, NodeT]
Expand Down Expand Up @@ -335,3 +339,174 @@ def find_loop_guards_tails_exits(sdfg_nx: nx.DiGraph):
# remove artificial end node
sdfg_nx.remove_node(artificial_end_node)
return nodes_oNodes_exits

def get_legacy_loop_body(cfr, guard, tail, exits):
"""
Get all nodes in a legacy loop body.
A node is in the loop body if:
- It's reachable from guard
- It can reach tail
- It's not an exit node
"""
# Forward reachability from guard
forward_reachable = set()
queue = deque([guard])
while queue:
node = queue.popleft()
if node in forward_reachable:
continue
forward_reachable.add(node)
for edge in cfr.out_edges(node):
queue.append(edge.dst)

# Backward reachability to tail
backward_reachable = set()
queue = deque([tail])
while queue:
node = queue.popleft()
if node in backward_reachable:
continue
backward_reachable.add(node)
for edge in cfr.in_edges(node):
queue.append(edge.src)

# Loop body = (forward AND backward) - exits
loop_body = (forward_reachable & backward_reachable) - set(exits)

return loop_body

def get_legacy_loop_ranges(cfr: ControlFlowRegion) -> Dict[SDFGState, Tuple[sp.Expr, sp.Expr, sp.Expr, sp.Symbol]]:
"""
Builds a map from loop guard states to their loop variable and iteration
range, harvesting the annotations set by propagate_states /
_annotate_loop_ranges.

Must be called AFTER propagate_states has been run on the SDFG.

:param cfr: The ControlFlowRegion to inspect (only its direct nodes are
checked, not descendants, since control_flow_region_work_depth
is called recursively anyway).
:return: A dict mapping each legacy loop guard SDFGState to a tuple
(loop_var, start, stop, stride)
"""
#propagate_states(cfr)
result: Dict[SDFGState, Tuple[sp.Symbol, sp.Expr, sp.Expr, sp.Expr]] = {}

for node in cfr.nodes():
if not getattr(node, 'is_loop_guard', False):
continue

itvar_str: str = node.itvar
loop_var: sp.Symbol = sp.Symbol(itvar_str)

# guard.ranges[itvar] is a subsets.Range with one entry: [(start, stop, stride)]
rng = node.ranges[itvar_str][0] # -> (start, stop, stride)
start = sp.sympify(rng[0])
stop = sp.sympify(rng[1])
stride = sp.sympify(rng[2])

result[node] = (loop_var, start, stop, stride)

return result

def subs_till_fixed_point(expr:sp.Expr, symbol_map:Dict[sp.Expr, sp.Expr]):
"""
Takes a sympy expression and a symbol mapping and applies the mapping to the expression until a fixed point is reached
Needs the guarantee that the symbol mapping does not have cyclic dependencies.

:param expr: Description
:param symbol_map: Description
:return: Description
"""
if not isinstance(expr, sp.Expr):
return expr
prev = None
curr = expr
while prev != curr:
prev = curr
curr = curr.subs(symbol_map)
return curr

def get_static_symbols(sdfg: SDFG):
"""
Returns a mapping of symbols that are assigned exactly at one point in the sdfg.

:param sdfg: The sdfg for which we want to find the static symbols and their corresponding assignment
:return: The mapping of the symbols to higher levels (iterated to a fixed point)
"""


patterns = [
"dace.complex128",
"dace.float64",
"dace.float32",
"dace.int64",
"dace.int32",
"dace.int16",
"dace.uint32",
"dace.uint16",
"dace.uint8",
"float",
"int"
]

type_regex = re.compile("|".join(map(re.escape, patterns)))
static_symbol_mapping:Dict[sp.Symbol, sp.Expr] = {sp.Symbol(a): sp.Symbol(a) for a in sdfg.arg_names}
non_static_symbols = set()
for node, containing_state in sdfg.all_nodes_recursive():
if isinstance(node, nodes.AccessNode):

if containing_state.in_degree(node) == 1:
edge = containing_state.in_edges(node)[0]
source = edge.src

if edge.data.volume == 1:
if isinstance(source, nodes.Tasklet):
tasklet = source
in_map = {}
out_map = {}
# Incoming edges: symbols feeding the tasklet
for e in containing_state.in_edges(tasklet):
if not isinstance(e.src, nodes.AccessNode):
continue
sym = str(e.src.data)
in_map[e.dst_conn] = sym
# Outgoing edges: symbols written by the tasklet
# Out edges should only be one, but for safety we iterate
for e in containing_state.out_edges(tasklet):
if not isinstance(e.dst, nodes.AccessNode):
continue
sym = sp.Symbol(e.dst.data)
out_map[e.src_conn] = sym
code = tasklet.code.as_string.strip()
# Expect a single assignment
lines = [l.strip() for l in code.splitlines() if l.strip()]
lhs, rhs = lines[0].split('=',1)
lhs = lhs.strip()
rhs = rhs.strip()
rhs = type_regex.sub("", rhs)
# Parse RHS using SymPy, with tasklet inputs substituted
lhs_sympy = pystr_to_symbolic(lhs)
lhs_sympy = lhs_sympy.subs(out_map)

if not lhs_sympy in static_symbol_mapping.keys():
try:
rhs_sympy = pystr_to_symbolic(rhs)
rhs_sympy = rhs_sympy.subs(in_map)
static_symbol_mapping[lhs_sympy] = rhs_sympy
except:
non_static_symbols.add(lhs_sympy)
else:
non_static_symbols.add(lhs_sympy)

elif isinstance(source, nodes.AccessNode):
data_sym = sp.Symbol(source.data)
nd_sym = sp.Symbol(node.data)
if not data_sym in static_symbol_mapping.keys():
static_symbol_mapping[data_sym] = nd_sym
else:
non_static_symbols.add(data_sym)

static_symbol_mapping = {k: v for (k, v) in static_symbol_mapping.items() if k not in non_static_symbols}
static_symbol_mapping = {str(k): subs_till_fixed_point(v, static_symbol_mapping) for k,v in static_symbol_mapping.items()}
return static_symbol_mapping
9 changes: 7 additions & 2 deletions dace/sdfg/performance_evaluation/op_in_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
""" Contains class CacheLineTracker which keeps track of all arrays of an SDFG and their cache line position
and class AccessStack which which corresponds to the stack used to compute the stack distance.
Further, provides a curve fitting method and plotting function. """

from __future__ import annotations
import warnings
from dace.data import Array
import sympy as sp
Expand All @@ -11,7 +11,6 @@
import numpy as np
from dace import symbol


class CacheLineTracker:
""" A CacheLineTracker maps data container accesses to the corresponding accessed cache line. """

Expand Down Expand Up @@ -129,6 +128,12 @@ def copy(self):
curr.next = Node(x)
curr = curr.next
return new_stack

def replace_self(self, other:AccessStack):
self.top = other.top
self.num_calls = other.num_calls
self.lengh = other.length
self.C = other.C


def plot(x, work_map, cache_misses, op_in_map, symbol_name, C, L, sympy_f, element, name):
Expand Down
Loading