Skip to content

Commit 492f052

Browse files
authored
Merge branch 'master' into MovingBubblesFresh-clean
2 parents 6a0c621 + 1139cc4 commit 492f052

7 files changed

Lines changed: 172 additions & 18 deletions

File tree

.github/workflows/fp-stability.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ jobs:
101101
run: ~/.local/verrou/bin/valgrind --version
102102

103103
- name: Build MFC (debug, serial)
104+
# FFLAGS=-fno-inline prevents gfortran from inlining small functions into
105+
# their callers. Without it, DWARF debug info attributes inlined ops to
106+
# the caller's line (often a do-loop header), making Verrou dd_line point
107+
# to loop boundaries instead of the actual arithmetic.
108+
env:
109+
FFLAGS: "-fno-inline"
104110
run: ./mfc.sh build -t pre_process simulation --no-mpi --debug -j"$(nproc)"
105111

106112
- name: Run FP Stability Suite

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,5 @@ cce_*/
115115
cce_*.log
116116
run_cce_*.sh
117117
.ffmt_cache/
118-
**/.ffmt_cache/
118+
# FP-stability log artifacts (generated by ./mfc.sh fp-stability)
119+
fp-stability-logs/

.typos.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ tru = "tru" # typo for "true" in "when_tru" - tests dependency keys
3131
PNGs = "PNGs"
3232

3333
[files]
34-
extend-exclude = ["docs/documentation/references*", "docs/references.bib", "tests/", "toolchain/cce_simulation_workgroup_256.sh", "build-docs/", "build/", "build_test/"]
34+
extend-exclude = ["docs/documentation/references*", "docs/references.bib", "tests/", "toolchain/cce_simulation_workgroup_256.sh", "build-docs/", "build/", "build_test/", "fp-stability-logs/"]

src/common/include/2dHardcodedIC.fpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,10 +291,10 @@
291291
& - patch_icpp(1)%x_centroid)**2.0 - (y_cc(j) - patch_icpp(1)%y_centroid)**2.0)))**1.4
292292
q_prim_vf(eqn_idx%mom%beg + 0)%sf(i, j, &
293293
& 0) = patch_icpp(1)%vel(1) + (y_cc(j) - patch_icpp(1)%y_centroid)*(5.0/(2.0*pi))*exp(1.0*(1.0 - (x_cc(i) &
294-
& - patch_icpp(1) %x_centroid)**2.0 - (y_cc(j) - patch_icpp(1)%y_centroid)**2.0))
294+
& - patch_icpp(1)%x_centroid)**2.0 - (y_cc(j) - patch_icpp(1)%y_centroid)**2.0))
295295
q_prim_vf(eqn_idx%mom%beg + 1)%sf(i, j, &
296296
& 0) = patch_icpp(1)%vel(2) - (x_cc(i) - patch_icpp(1)%x_centroid)*(5.0/(2.0*pi))*exp(1.0*(1.0 - (x_cc(i) &
297-
& - patch_icpp(1) %x_centroid)**2.0 - (y_cc(j) - patch_icpp(1)%y_centroid)**2.0))
297+
& - patch_icpp(1)%x_centroid)**2.0 - (y_cc(j) - patch_icpp(1)%y_centroid)**2.0))
298298
end if
299299
case (281) ! Acoustic pulse
300300
! This is patch is hard-coded for test suite optimization used in the 2D_acoustic_pulse case: This analytic patch uses

toolchain/bootstrap/python.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,3 +408,20 @@ if ! cmp "$(pwd)/toolchain/pyproject.toml" "$(pwd)/build/pyproject.toml" > /dev/
408408

409409
fi # end of USE_UV=0 (pip) block
410410
fi
411+
412+
413+
# Apply patches to installed packages.
414+
# fypp: always emit a resync linemarker after single-line $: macro calls so
415+
# that the compiler attributes the following Fortran statement to the correct
416+
# source line rather than the call-site line (off-by-1 in backtraces).
417+
FYPP_PY="$(python3 -c "import fypp; print(fypp.__file__)" 2>/dev/null)"
418+
FYPP_PATCH="$(pwd)/toolchain/patches/fypp-linemarker-resync.patch"
419+
if [ -n "$FYPP_PY" ] && [ -f "$FYPP_PATCH" ]; then
420+
if ! grep -q "Always emit a resync marker" "$FYPP_PY" 2>/dev/null; then
421+
if patch -p1 --forward --silent "$FYPP_PY" < "$FYPP_PATCH" 2>/dev/null; then
422+
ok "(venv) Applied$MAGENTA fypp$COLOR_RESET linemarker-resync patch."
423+
else
424+
warn "(venv) Failed to apply$MAGENTA fypp$COLOR_RESET linemarker-resync patch (fypp version may have changed)."
425+
fi
426+
fi
427+
fi

toolchain/mfc/fp_stability.py

Lines changed: 119 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,57 @@
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

83134
def _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

578634
def _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

743806
def _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

766843
def _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

774869
def _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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--- a/fypp.py
2+
+++ b/fypp.py
3+
@@ -1842,11 +1842,16 @@ class _Renderer:
4+
if self._linenums:
5+
# Last line was folded, but no linenums were generated for
6+
# the continuation lines -> current line position is not
7+
# in sync with the one calculated from the last line number
8+
unsync = (
9+
len(foldedlines) and len(foldedlines[-1]) > 1
10+
and not self._contlinenums)
11+
# Eval directive in source consists of more than one line
12+
multiline = span[1] - span[0] > 1
13+
- if unsync or multiline:
14+
+ # Always emit a resync marker after a $: call. Without this,
15+
+ # single-line $: calls that expand to multi-line #if/#endif
16+
+ # blocks (e.g. GPU_PARALLEL_LOOP) cause the compiler to
17+
+ # attribute the next Fortran statement to the call-site line
18+
+ # rather than the following source line, producing off-by-1
19+
+ # errors in backtraces and debugger line info.
20+
+ if unsync or multiline or True:
21+
# For inline eval directives span[0] == span[1]
22+
# -> next line is span[0] + 1 and not span[1] as for
23+
# line eval directives
24+
nextline = max(span[1], span[0] + 1)
25+
trailing += self._linenumdir(nextline, fname)

0 commit comments

Comments
 (0)