Skip to content

Commit d45bc5b

Browse files
committed
fp-stability: de-duplicate helpers from the review additions (no behavior change)
Cleanup pass over the ~680 lines this branch added to fp_stability.py. Extracts shared helpers that had accreted across the six feature commits, with identical behavior (69 toolchain tests + ruff + precheck green; emitted console/summary text unchanged): - _resolve_source / _read_source_lines: the 'abs-path-or-glob-under-src(-then-tree)' + readlines block was repeated in _read_source_line, _macro_context, _statement_at, _get_source_context. A search_whole_tree flag preserves the one difference (only _get_source_context fell back to the whole tree). - _blank_result(name): the 15-field result dict was written verbatim twice. _find_dd_tool(verrou_bin, tool): merges _find_dd_sym/_find_dd_line. _setup_dd_run: shared dd_run.sh/dd_cmp.py setup + threshold-default for dd_sym and dd_line. _capture_gen_source: shared nearest --gen-source capture for confirmation and disambiguation. _more_md: the '…and N more' truncation footer used in three summary sections.
1 parent 84bec6d commit d45bc5b

1 file changed

Lines changed: 113 additions & 131 deletions

File tree

toolchain/mfc/fp_stability.py

Lines changed: 113 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -159,20 +159,34 @@ def _stability_pass(max_dev: float, ref_scale: float, floor: float) -> bool:
159159
)
160160

161161

162-
def _read_source_line(fname: str, lineno: int) -> str:
163-
"""Return the raw source line at lineno (1-based), or '' if unavailable."""
162+
def _resolve_source(fname: str, search_whole_tree: bool = False) -> str:
163+
"""Resolve a (possibly bare) source filename to an existing path, or '' if not
164+
found. An absolute existing path is used as-is; otherwise the basename is
165+
located recursively under src/ (then the whole tree if `search_whole_tree`)."""
164166
if os.path.isabs(fname) and os.path.isfile(fname):
165-
candidates = [fname]
166-
else:
167-
candidates = glob.glob(os.path.join(MFC_ROOT_DIR, "src", "**", os.path.basename(fname)), recursive=True)
168-
if not candidates:
169-
return ""
167+
return fname
168+
candidates = glob.glob(os.path.join(MFC_ROOT_DIR, "src", "**", os.path.basename(fname)), recursive=True)
169+
if not candidates and search_whole_tree:
170+
candidates = glob.glob(os.path.join(MFC_ROOT_DIR, "**", os.path.basename(fname)), recursive=True)
171+
return candidates[0] if candidates else ""
172+
173+
174+
def _read_source_lines(fname: str, search_whole_tree: bool = False) -> list:
175+
"""Resolve `fname` and return its lines (with newlines), or [] if unreadable."""
176+
path = _resolve_source(fname, search_whole_tree)
177+
if not path:
178+
return []
170179
try:
171-
with open(candidates[0]) as fh:
172-
lines = fh.readlines()
173-
return lines[lineno - 1] if 0 < lineno <= len(lines) else ""
180+
with open(path) as fh:
181+
return fh.readlines()
174182
except OSError:
175-
return ""
183+
return []
184+
185+
186+
def _read_source_line(fname: str, lineno: int) -> str:
187+
"""Return the raw source line at lineno (1-based), or '' if unavailable."""
188+
lines = _read_source_lines(fname)
189+
return lines[lineno - 1] if 0 < lineno <= len(lines) else ""
176190

177191

178192
def _macro_context_in_lines(lines: list, lineno: int) -> str:
@@ -199,16 +213,8 @@ def _macro_context_in_lines(lines: list, lineno: int) -> str:
199213

200214
def _macro_context(fname: str, lineno: int) -> str:
201215
"""File-backed wrapper around _macro_context_in_lines; '' path safe."""
202-
if os.path.isabs(fname) and os.path.isfile(fname):
203-
candidates = [fname]
204-
else:
205-
candidates = glob.glob(os.path.join(MFC_ROOT_DIR, "src", "**", os.path.basename(fname)), recursive=True)
206-
if not candidates:
207-
return None
208-
try:
209-
with open(candidates[0]) as fh:
210-
lines = fh.readlines()
211-
except OSError:
216+
lines = _read_source_lines(fname)
217+
if not lines:
212218
return None
213219
return _macro_context_in_lines(lines, lineno)
214220

@@ -241,17 +247,7 @@ def _statement_bounds_in_lines(lines: list, lineno: int) -> tuple:
241247
def _statement_at(fname: str, lineno: int) -> tuple:
242248
"""File-backed (start, end, text) for the logical statement at fname:lineno;
243249
text is the joined statement. Returns (lineno, lineno, '') if unreadable."""
244-
if os.path.isabs(fname) and os.path.isfile(fname):
245-
candidates = [fname]
246-
else:
247-
candidates = glob.glob(os.path.join(MFC_ROOT_DIR, "src", "**", os.path.basename(fname)), recursive=True)
248-
if not candidates:
249-
return lineno, lineno, ""
250-
try:
251-
with open(candidates[0]) as fh:
252-
lines = fh.readlines()
253-
except OSError:
254-
return lineno, lineno, ""
250+
lines = _read_source_lines(fname)
255251
if not 0 < lineno <= len(lines):
256252
return lineno, lineno, ""
257253
start, end = _statement_bounds_in_lines(lines, lineno)
@@ -283,18 +279,8 @@ def _get_source_context(fname: str, lineno: int, context: int = 2) -> str:
283279
fname may be a bare basename (e.g. 'm_weno.fpp') or a relative path.
284280
Searches recursively under MFC_ROOT_DIR/src/ first, then the whole tree.
285281
"""
286-
if os.path.isabs(fname) and os.path.isfile(fname):
287-
candidates = [fname]
288-
else:
289-
candidates = glob.glob(os.path.join(MFC_ROOT_DIR, "src", "**", os.path.basename(fname)), recursive=True)
290-
if not candidates:
291-
candidates = glob.glob(os.path.join(MFC_ROOT_DIR, "**", os.path.basename(fname)), recursive=True)
292-
if not candidates:
293-
return ""
294-
try:
295-
with open(candidates[0]) as fh:
296-
lines = fh.readlines()
297-
except OSError:
282+
lines = _read_source_lines(fname, search_whole_tree=True)
283+
if not lines:
298284
return ""
299285
start = max(0, lineno - context - 1)
300286
end = min(len(lines), lineno + context)
@@ -589,13 +575,10 @@ def _find_binary(name: str) -> str:
589575
return max(candidates, key=os.path.getmtime) if candidates else ""
590576

591577

592-
def _find_dd_sym(verrou_bin: str) -> str:
593-
c = os.path.join(os.path.dirname(verrou_bin), "verrou_dd_sym")
594-
return c if os.path.isfile(c) else ""
595-
596-
597-
def _find_dd_line(verrou_bin: str) -> str:
598-
c = os.path.join(os.path.dirname(verrou_bin), "verrou_dd_line")
578+
def _find_dd_tool(verrou_bin: str, tool: str) -> str:
579+
"""Path to a verrou_dd_* tool (e.g. 'verrou_dd_sym') next to the verrou binary,
580+
or '' if absent."""
581+
c = os.path.join(os.path.dirname(verrou_bin), tool)
599582
return c if os.path.isfile(c) else ""
600583

601584

@@ -1152,19 +1135,26 @@ def _run_dd_tool(
11521135
return summary_lines
11531136

11541137

1138+
def _setup_dd_run(case: dict, verrou_bin: str, sim_bin: str, work_dir: str, dd_dir: str, threshold: float):
1139+
"""Write dd_run.sh and dd_cmp.py for a verrou_dd_* run into dd_dir; return their
1140+
paths. The threshold falls back to _DD_FALLBACK_THRESHOLD when unset."""
1141+
os.makedirs(dd_dir, exist_ok=True)
1142+
dd_run_sh = os.path.join(dd_dir, "dd_run.sh")
1143+
dd_cmp_py = os.path.join(dd_dir, "dd_cmp.py")
1144+
_write_dd_run_sh(dd_run_sh, verrou_bin, sim_bin, work_dir)
1145+
_write_dd_cmp_py(dd_cmp_py, case["compare"], threshold if threshold is not None else _DD_FALLBACK_THRESHOLD)
1146+
return dd_run_sh, dd_cmp_py
1147+
1148+
11551149
def _run_dd_sym(case: dict, verrou_bin: str, sim_bin: str, work_dir: str, log_dir: str, threshold: float = None) -> list:
11561150
"""Run verrou_dd_sym; return list of responsible symbol names."""
1157-
dd_bin = _find_dd_sym(verrou_bin)
1151+
dd_bin = _find_dd_tool(verrou_bin, "verrou_dd_sym")
11581152
if not dd_bin:
11591153
cons.print(" [dim]verrou_dd_sym not found; skipping delta-debug[/dim]")
11601154
return []
11611155

11621156
dd_dir = os.path.join(log_dir, case["name"])
1163-
os.makedirs(dd_dir, exist_ok=True)
1164-
dd_run_sh = os.path.join(dd_dir, "dd_run.sh")
1165-
dd_cmp_py = os.path.join(dd_dir, "dd_cmp.py")
1166-
_write_dd_run_sh(dd_run_sh, verrou_bin, sim_bin, work_dir)
1167-
_write_dd_cmp_py(dd_cmp_py, case["compare"], threshold if threshold is not None else _DD_FALLBACK_THRESHOLD)
1157+
dd_run_sh, dd_cmp_py = _setup_dd_run(case, verrou_bin, sim_bin, work_dir, dd_dir, threshold)
11681158
_run_dd_tool(dd_bin, dd_dir, dd_run_sh, dd_cmp_py, _dd_env(verrou_bin), "dd_sym.log", "dd.sym", "verrou_dd_sym")
11691159
cons.print(f" [dim]dd_sym logs: {dd_dir}[/dim]")
11701160
return _parse_rddmin_syms(os.path.join(dd_dir, "dd.sym", "rddmin_summary"))
@@ -1179,18 +1169,13 @@ def _run_dd_line(
11791169
threshold: float = None,
11801170
) -> list:
11811171
"""Run verrou_dd_line; return [{path, start, end, macro}] location dicts."""
1182-
dd_bin = _find_dd_line(verrou_bin)
1172+
dd_bin = _find_dd_tool(verrou_bin, "verrou_dd_line")
11831173
if not dd_bin:
11841174
cons.print(" [dim]verrou_dd_line not found; skipping line-level debug[/dim]")
11851175
return []
11861176

11871177
dd_dir = os.path.join(log_dir, case["name"])
1188-
os.makedirs(dd_dir, exist_ok=True)
1189-
dd_run_sh = os.path.join(dd_dir, "dd_run.sh")
1190-
dd_cmp_py = os.path.join(dd_dir, "dd_cmp.py")
1191-
effective_threshold = threshold if threshold is not None else _DD_FALLBACK_THRESHOLD
1192-
_write_dd_run_sh(dd_run_sh, verrou_bin, sim_bin, work_dir)
1193-
_write_dd_cmp_py(dd_cmp_py, case["compare"], effective_threshold)
1178+
dd_run_sh, dd_cmp_py = _setup_dd_run(case, verrou_bin, sim_bin, work_dir, dd_dir, threshold)
11941179
_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")
11951180
return _parse_rddmin_locs(os.path.join(dd_dir, "dd.line", "rddmin_summary"))
11961181

@@ -1217,6 +1202,26 @@ def _source_perturb_dev(verrou_bin, sim_bin, work_dir, ref_dir, conf_dir, src_li
12171202
return _max_diff_np(ref_dir, run_dir, compare)
12181203

12191204

1205+
def _capture_gen_source(verrou_bin, sim_bin, work_dir, run_dir, gen_path):
1206+
"""Run nearest-rounding with --gen-source to capture the symbol-correct
1207+
executed source lines (FILE\\tLINE\\tSYMBOL); return them, or None on failure."""
1208+
try:
1209+
_run_simulation_verrou(
1210+
verrou_bin,
1211+
sim_bin,
1212+
work_dir,
1213+
run_dir,
1214+
rounding_mode="nearest",
1215+
extra_flags=[f"--gen-source={gen_path}"],
1216+
)
1217+
except MFCException:
1218+
return None
1219+
if not os.path.isfile(gen_path):
1220+
return None
1221+
with open(gen_path) as fh:
1222+
return fh.readlines()
1223+
1224+
12201225
def _run_confirmation(case, verrou_bin, sim_bin, work_dir, ref_dir, dd_line_locs, dd_threshold, float_proxy):
12211226
"""Positive control for dd_line: perturb ONLY the suspect lines and confirm
12221227
the instability reproduces, then rank each line by its individual share.
@@ -1237,22 +1242,9 @@ def _run_confirmation(case, verrou_bin, sim_bin, work_dir, ref_dir, dd_line_locs
12371242
return None, None, dd_line_locs
12381243
conf_dir = os.path.join(work_dir, "confirm")
12391244
os.makedirs(conf_dir, exist_ok=True)
1240-
gen_path = os.path.join(conf_dir, "gen_source.txt")
1241-
try:
1242-
_run_simulation_verrou(
1243-
verrou_bin,
1244-
sim_bin,
1245-
work_dir,
1246-
conf_dir,
1247-
rounding_mode="nearest",
1248-
extra_flags=[f"--gen-source={gen_path}"],
1249-
)
1250-
except MFCException:
1245+
gen_lines = _capture_gen_source(verrou_bin, sim_bin, work_dir, conf_dir, os.path.join(conf_dir, "gen_source.txt"))
1246+
if gen_lines is None:
12511247
return None, None, dd_line_locs
1252-
if not os.path.isfile(gen_path):
1253-
return None, None, dd_line_locs
1254-
with open(gen_path) as fh:
1255-
gen_lines = fh.readlines()
12561248
compare = case["compare"]
12571249

12581250
# whole-set positive control
@@ -1298,23 +1290,13 @@ def _disambiguate_instances(case, prec_sim_bin, verrou_bin, work_dir, hotspot_fi
12981290
prec_dir = os.path.join(work_dir, "precision")
12991291
ref_dir = os.path.join(prec_dir, "ref")
13001292
os.makedirs(ref_dir, exist_ok=True)
1301-
gen_path = os.path.join(prec_dir, "gen_source.txt")
13021293
try:
13031294
_run_simulation_verrou(verrou_bin, prec_sim_bin, work_dir, ref_dir, rounding_mode="nearest")
1304-
_run_simulation_verrou(
1305-
verrou_bin,
1306-
prec_sim_bin,
1307-
work_dir,
1308-
prec_dir,
1309-
rounding_mode="nearest",
1310-
extra_flags=[f"--gen-source={gen_path}"],
1311-
)
13121295
except MFCException:
13131296
return []
1314-
if not os.path.isfile(gen_path):
1297+
gen_lines = _capture_gen_source(verrou_bin, prec_sim_bin, work_dir, prec_dir, os.path.join(prec_dir, "gen_source.txt"))
1298+
if gen_lines is None:
13151299
return []
1316-
with open(gen_path) as fh:
1317-
gen_lines = fh.readlines()
13181300

13191301
f90_file = os.path.join(sidecar_dir, os.path.basename(hotspot_file) + ".f90")
13201302
compare = case["compare"]
@@ -1336,6 +1318,27 @@ def _disambiguate_instances(case, prec_sim_bin, verrou_bin, work_dir, hotspot_fi
13361318
return results
13371319

13381320

1321+
def _blank_result(name: str) -> dict:
1322+
"""A result dict with every field at its empty/unmeasured default."""
1323+
return {
1324+
"name": name,
1325+
"passed": False,
1326+
"max_dev": float("inf"),
1327+
"sig_bits": None,
1328+
"float_proxy": None,
1329+
"vprec": [],
1330+
"dd_sym_syms": [],
1331+
"dd_line_locs": [],
1332+
"dd_line_confirmed": None,
1333+
"dd_line_confirm_dev": None,
1334+
"cancellation_locs": [],
1335+
"cancellation_bits": {},
1336+
"mca_dev": None,
1337+
"mca_sigbits": None,
1338+
"float_max_locs": [],
1339+
}
1340+
1341+
13391342
def _run_case(
13401343
case: dict,
13411344
verrou_bin: str,
@@ -1362,23 +1365,7 @@ def _run_case(
13621365
cons.print(f" pass floor: >= {MIN_SIG_BITS} significant bits retained")
13631366

13641367
work_dir = tempfile.mkdtemp(prefix=f"mfc-fps-{name}-")
1365-
result = {
1366-
"name": name,
1367-
"passed": False,
1368-
"max_dev": float("inf"),
1369-
"sig_bits": None,
1370-
"float_proxy": None,
1371-
"vprec": [],
1372-
"dd_sym_syms": [],
1373-
"dd_line_locs": [],
1374-
"dd_line_confirmed": None,
1375-
"dd_line_confirm_dev": None,
1376-
"cancellation_locs": [],
1377-
"cancellation_bits": {},
1378-
"mca_dev": None,
1379-
"mca_sigbits": None,
1380-
"float_max_locs": [],
1381-
}
1368+
result = _blank_result(name)
13821369
try:
13831370
cons.print(" [dim]running pre_process...[/dim]")
13841371
_write_inp(case["sim"], "simulation", work_dir)
@@ -1615,6 +1602,14 @@ def _emit_github_annotations(results: list):
16151602
print(f"::notice title=FP cancellation [{r['name']}]::{n_cc - 3} more cancellation site(s) not annotated inline; see the step summary", flush=True)
16161603

16171604

1605+
def _more_md(total: int, shown: int, noun: str) -> str:
1606+
"""Markdown bullet noting `total - shown` further items elided from a list,
1607+
or '' when nothing was truncated."""
1608+
if total <= shown:
1609+
return ""
1610+
return f"- _…and {total - shown} more {noun}; see fp-stability-logs/_"
1611+
1612+
16181613
def _emit_github_summary(results: list, n_samples: int):
16191614
"""Write a markdown results table to GITHUB_STEP_SUMMARY.
16201615
@@ -1681,8 +1676,9 @@ def _emit_github_summary(results: list, n_samples: int):
16811676
for e in ordered[:15]:
16821677
lost = e["bits"] / math.log2(10)
16831678
md.append(f"- **≥ {lost:.0f} digits lost** (~{_digits_left(e['bits']):.0f} of 16 left) — `{e['where']}`" + (f" — `{e['text']}`" if e["text"] else ""))
1684-
if len(ordered) > 15:
1685-
md.append(f"- _…and {len(ordered) - 15} more statement(s); see fp-stability-logs/_")
1679+
footer = _more_md(len(ordered), 15, "statement(s)")
1680+
if footer:
1681+
md.append(footer)
16861682
md.append("")
16871683

16881684
# VPREC sweep — one column per bit level, ❌ where bits retained < floor
@@ -1747,8 +1743,9 @@ def _emit_github_summary(results: list, n_samples: int):
17471743
for line in snippet.splitlines():
17481744
md.append(f" {line}")
17491745
md.append(" ```")
1750-
if len(r["dd_line_locs"]) > 10:
1751-
md.append(f"- _…and {len(r['dd_line_locs']) - 10} more hotspot(s); see fp-stability-logs/_")
1746+
footer = _more_md(len(r["dd_line_locs"]), 10, "hotspot(s)")
1747+
if footer:
1748+
md.append(footer)
17521749
md.append("")
17531750
md.append("</details>\n")
17541751

@@ -1771,8 +1768,9 @@ def _emit_github_summary(results: list, n_samples: int):
17711768
md.append(f"**`{r['name']}`** — {len(r['float_max_locs'])} site(s)\n")
17721769
for fname, lineno in r["float_max_locs"][:10]:
17731770
md.append(f"- `{fname}:{lineno}`")
1774-
if len(r["float_max_locs"]) > 10:
1775-
md.append(f"- _…and {len(r['float_max_locs']) - 10} more site(s); see fp-stability-logs/_")
1771+
footer = _more_md(len(r["float_max_locs"]), 10, "site(s)")
1772+
if footer:
1773+
md.append(footer)
17761774
md.append("")
17771775

17781776
with open(summary_path, "a") as f:
@@ -1857,23 +1855,7 @@ def fp_stability():
18571855
)
18581856
except MFCException as exc:
18591857
cons.print(f" [bold red]ERROR[/bold red]: {exc}")
1860-
r = {
1861-
"name": case["name"],
1862-
"passed": False,
1863-
"max_dev": float("inf"),
1864-
"sig_bits": None,
1865-
"float_proxy": None,
1866-
"vprec": [],
1867-
"dd_sym_syms": [],
1868-
"dd_line_locs": [],
1869-
"dd_line_confirmed": None,
1870-
"dd_line_confirm_dev": None,
1871-
"cancellation_locs": [],
1872-
"cancellation_bits": {},
1873-
"mca_dev": None,
1874-
"mca_sigbits": None,
1875-
"float_max_locs": [],
1876-
}
1858+
r = _blank_result(case["name"])
18771859
results.append(r)
18781860

18791861
elapsed = time.time() - start

0 commit comments

Comments
 (0)