7979# Matches the first "at" frame in a Valgrind stack trace: "(file.fpp:LINE)".
8080_VGFRAME_RE = re .compile (r"\(([^):]+\.(?:fpp|f90|F90|c|cpp))\s*:(\d+)\)" )
8181
82+ # Lines that are clearly control-flow delimiters rather than arithmetic.
83+ # dd_line sometimes reports these when the responsible arithmetic is on the
84+ # preceding line but shares DWARF debug info with the delimiter (e.g. loop
85+ # boundaries in #:for-expanded code, or inlined functions at call sites).
86+ _CONTROL_FLOW_RE = re .compile (
87+ r"^\s*("
88+ r"end\s+(do|if|select|where|forall|subroutine|function|module|program|block)\b"
89+ r"|do\s+\w+\s*=\s*[\w,\s]+" # naked do-loop header (no arithmetic)
90+ r"|else(\s+if\s*\(.*\)\s*then)?\s*$" # else / else if (...) then
91+ r"|(recursive\s+|pure\s+|elemental\s+)*subroutine\s+\w+" # subroutine declaration
92+ r"|\$:END_GPU\w+" # fypp GPU macro closers
93+ r"|#:end\w*" # fypp directive closers (#:endfor, #:enddef, etc.)
94+ r"|\s*!\s*$" # comment-only lines
95+ r"|\s*$" # blank lines
96+ r")" ,
97+ re .IGNORECASE ,
98+ )
99+
100+
101+ def _read_source_line (fname : str , lineno : int ) -> str :
102+ """Return the raw source line at lineno (1-based), or '' if unavailable."""
103+ if os .path .isabs (fname ) and os .path .isfile (fname ):
104+ candidates = [fname ]
105+ else :
106+ candidates = glob .glob (os .path .join (MFC_ROOT_DIR , "src" , "**" , os .path .basename (fname )), recursive = True )
107+ if not candidates :
108+ return ""
109+ try :
110+ with open (candidates [0 ]) as fh :
111+ lines = fh .readlines ()
112+ return lines [lineno - 1 ] if 0 < lineno <= len (lines ) else ""
113+ except OSError :
114+ return ""
115+
116+
117+ def _is_arithmetic_loc (fname : str , start : int , end : int ) -> bool :
118+ """Return True if any line in [start, end] contains non-trivial arithmetic.
119+
120+ Filters out loop delimiters and fypp directive lines that dd_line sometimes
121+ reports when the responsible arithmetic shares DWARF info with its enclosing
122+ control-flow boundary (inlining, #:for template expansion, etc.).
123+ Returns True (keep) when uncertain so we never silently drop real hotspots.
124+ """
125+ for lineno in range (start , end + 1 ):
126+ line = _read_source_line (fname , lineno )
127+ if not line :
128+ return True # can't read — keep to be safe
129+ if not _CONTROL_FLOW_RE .match (line ):
130+ return True
131+ return False
132+
82133
83134def _get_source_context (fname : str , lineno : int , context : int = 2 ) -> str :
84135 """Return a annotated source snippet around lineno, or '' if file not found.
@@ -572,7 +623,12 @@ def _run_cancellation_check(case: dict, verrou_bin: str, sim_bin: str, work_dir:
572623 _run_simulation_verrou (verrou_bin , sim_bin , work_dir , run_dir , rounding_mode = "nearest" , extra_flags = flags )
573624 except MFCException :
574625 pass
575- return _parse_cancel_gen (gen_path )
626+ raw = _parse_cancel_gen (gen_path )
627+ filtered = [(f , ln ) for f , ln in raw if _is_arithmetic_loc (f , ln , ln )]
628+ skipped = len (raw ) - len (filtered )
629+ if skipped :
630+ cons .print (f" [dim]cancellation: filtered { skipped } control-flow boundary site(s)[/dim]" )
631+ return filtered
576632
577633
578634def _run_mca_samples (
@@ -683,8 +739,15 @@ def _write_dd_run_sh(path: str, verrou_bin: str, sim_bin: str, ic_dir: str):
683739 # test steps while letting the reference use nearest-rounding.
684740 ROUND="${{VERROU_ROUNDING_MODE:-float}}"
685741
742+ # verrou_dd_sym injects VERROU_EXCLUDE (symbols to exclude from perturbation).
743+ # verrou_dd_line injects VERROU_SOURCE (source lines to restrict perturbation to).
744+ # Forward them as valgrind flags when set.
745+ EXTRA=""
746+ [ -n "${{VERROU_EXCLUDE:-}}" ] && EXTRA="$EXTRA --exclude=$VERROU_EXCLUDE"
747+ [ -n "${{VERROU_SOURCE:-}}" ] && EXTRA="$EXTRA --source=$VERROU_SOURCE"
748+
686749 cd "$TMPDIR_RUN"
687- "$VERROU_BIN" --tool=verrou --error-limit=no --rounding-mode="$ROUND" "$SIM_BIN"
750+ "$VERROU_BIN" --tool=verrou --error-limit=no --rounding-mode="$ROUND" $EXTRA "$SIM_BIN"
688751 rc=$?
689752
690753 [ -d "$TMPDIR_RUN/D" ] && cp -a "$TMPDIR_RUN/D/." "$RUNDIR/"
@@ -741,10 +804,17 @@ def _dd_env(verrou_bin: str) -> dict:
741804
742805
743806def _parse_rddmin_locs (summary_path : str ) -> list :
744- """Extract [(rel_path, start_line, end_line)] from a dd_line rddmin_summary."""
807+ """Extract [(rel_path, start_line, end_line)] from a dd_line rddmin_summary.
808+
809+ Filters out locations whose source lines are pure control-flow delimiters
810+ (loop boundaries, fypp directive closers, blank/comment lines). These can
811+ appear when the responsible arithmetic shares DWARF debug info with an
812+ enclosing boundary due to inlining or #:for template expansion.
813+ """
745814 if not os .path .isfile (summary_path ):
746815 return []
747816 locs = []
817+ skipped = []
748818 with open (summary_path ) as fh :
749819 for line in fh :
750820 m = _LOC_RE .search (line )
@@ -759,16 +829,41 @@ def _parse_rddmin_locs(summary_path: str) -> list:
759829 rel = path
760830 except ValueError :
761831 rel = path
762- locs .append ((rel .replace ("\\ " , "/" ), start , end ))
832+ rel = rel .replace ("\\ " , "/" )
833+ if _is_arithmetic_loc (path , start , end ):
834+ locs .append ((rel , start , end ))
835+ else :
836+ skipped .append ((rel , start , end ))
837+ for rel , start , end in skipped :
838+ loc = f"{ rel } :{ start } " if start == end else f"{ rel } :{ start } -{ end } "
839+ cons .print (f" [dim]dd_line: skipped control-flow boundary { loc } [/dim]" )
763840 return locs
764841
765842
766843def _parse_rddmin_syms (summary_path : str ) -> list :
767- """Extract symbol/function names from a dd_sym rddmin_summary."""
844+ """Extract symbol/function names from a dd_sym rddmin_summary.
845+
846+ rddmin_summary format:
847+ ddmin0:\\ tFail Ratio: ...\\ tFail indexes: ...
848+ \\ t<funcname>\\ t<binary_path>
849+ ddmin1:\\ t...
850+ \\ t<funcname>\\ t<binary_path>
851+
852+ Lines starting with 'ddmin' are metadata; function names are on the
853+ indented (tab-prefixed) lines as the first tab-delimited field.
854+ """
768855 if not os .path .isfile (summary_path ):
769856 return []
857+ syms = []
770858 with open (summary_path ) as fh :
771- return [ln .strip () for ln in fh if ln .strip ()]
859+ for ln in fh :
860+ stripped = ln .strip ()
861+ if not stripped or stripped .startswith ("ddmin" ):
862+ continue
863+ sym = stripped .split ("\t " )[0 ].strip ()
864+ if sym :
865+ syms .append (sym )
866+ return syms
772867
773868
774869def _run_dd_tool (
@@ -821,7 +916,14 @@ def _run_dd_sym(case: dict, verrou_bin: str, sim_bin: str, work_dir: str, log_di
821916 return _parse_rddmin_syms (os .path .join (dd_dir , "dd.sym" , "rddmin_summary" ))
822917
823918
824- def _run_dd_line (case : dict , verrou_bin : str , sim_bin : str , work_dir : str , log_dir : str , threshold : float = None ) -> list :
919+ def _run_dd_line (
920+ case : dict ,
921+ verrou_bin : str ,
922+ sim_bin : str ,
923+ work_dir : str ,
924+ log_dir : str ,
925+ threshold : float = None ,
926+ ) -> list :
825927 """Run verrou_dd_line; return list of (rel_path, start_line, end_line) tuples."""
826928 dd_bin = _find_dd_line (verrou_bin )
827929 if not dd_bin :
@@ -833,12 +935,8 @@ def _run_dd_line(case: dict, verrou_bin: str, sim_bin: str, work_dir: str, log_d
833935 dd_run_sh = os .path .join (dd_dir , "dd_run.sh" )
834936 dd_cmp_py = os .path .join (dd_dir , "dd_cmp.py" )
835937 effective_threshold = threshold if threshold is not None else case ["threshold" ]
836- if not os .path .isfile (dd_run_sh ):
837- _write_dd_run_sh (dd_run_sh , verrou_bin , sim_bin , work_dir )
838- _write_dd_cmp_py (dd_cmp_py , case ["compare" ], effective_threshold )
839- else :
840- # dd_sym already wrote dd_cmp.py with its threshold; rewrite with ours if different
841- _write_dd_cmp_py (dd_cmp_py , case ["compare" ], effective_threshold )
938+ _write_dd_run_sh (dd_run_sh , verrou_bin , sim_bin , work_dir )
939+ _write_dd_cmp_py (dd_cmp_py , case ["compare" ], effective_threshold )
842940 _run_dd_tool (dd_bin , dd_dir , dd_run_sh , dd_cmp_py , _dd_env (verrou_bin ), "dd_line.log" , "dd.line" , "verrou_dd_line" )
843941 return _parse_rddmin_locs (os .path .join (dd_dir , "dd.line" , "rddmin_summary" ))
844942
@@ -954,7 +1052,14 @@ def _run_case(
9541052 cons .print (f" [bold yellow]dd_sym error[/bold yellow]: { exc } " )
9551053 if dd_threshold > 0 and run_dd_line :
9561054 try :
957- result ["dd_line_locs" ] = _run_dd_line (case , verrou_bin , sim_bin , work_dir , log_dir , threshold = dd_threshold )
1055+ result ["dd_line_locs" ] = _run_dd_line (
1056+ case ,
1057+ verrou_bin ,
1058+ sim_bin ,
1059+ work_dir ,
1060+ log_dir ,
1061+ threshold = dd_threshold ,
1062+ )
9581063 except Exception as exc :
9591064 cons .print (f" [bold yellow]dd_line error[/bold yellow]: { exc } " )
9601065
0 commit comments