{
 "number": 35608,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35608",
 "title": "contrib: Skip llvm-cov rendering for deterministic fuzz inputs",
 "author": "HowHsu",
 "author_association": "CONTRIBUTOR",
 "created_at": "2026-06-26T11:55:11Z",
 "updated_at": "2026-09-03T13:39:53Z",
 "age_days": 83,
 "draft": false,
 "labels": [
  "Scripts and tools"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "c237c9f2ff53ea193bb3b8825cf92ac14444a14e",
 "head_ref": "det-fuzz-cov-profdata-fastpath",
 "head_repo": "HowHsu/bitcoin",
 "head_history": [
  {
   "t": "2026-06-26T12:16:41Z",
   "sha": "87ecdf2cf0bd3aa222b744a59ceaa62c2240b725"
  },
  {
   "t": "2026-09-03T13:38:25Z",
   "sha": "c237c9f2ff53ea193bb3b8825cf92ac14444a14e"
  }
 ],
 "additions": 71,
 "deletions": 36,
 "changed_files": 1,
 "commit_count": 1,
 "size_bucket": "M",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "stale_ack": [
     {
      "login": "jeanpablojp",
      "url": "https://github.com/bitcoin/bitcoin/pull/35608#issuecomment-5340967884"
     }
    ]
   },
   "conflicts": [
    {
     "number": 35809,
     "title": "contrib: add deterministic fuzz coverage mode",
     "author": "HowHsu"
    }
   ]
  }
 },
 "acks_parsed": {},
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 1,
  "changes_requested": 0,
  "distinct_reviewers": [
   "jeanpablojp",
   "maflcko"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-03T13:39:53Z",
  "last_reviewer_activity": "2026-09-02T09:57:02Z",
  "last_reviewer": "maflcko",
  "author_silent_days": 14,
  "waiting_on_author_days": 0,
  "days_since_update": 14
 },
 "refs": {
  "mentioned": [
   35809
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 35809,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "contrib: add deterministic fuzz coverage mode"
   }
  ],
  "conflicts": [
   35809
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "contrib/devtools/deterministic-fuzz-coverage/src/main.rs"
 ],
 "body": "## Summary\n\n  `deterministic-fuzz-coverage` rendered a full `llvm-cov show` report for every\n  fuzz input on both runs and diffed the two reports, even though almost all\n  inputs are deterministic. `llvm-cov show` is by far the most expensive step, so\n  the vast majority of that work produced two identical reports that were\n  immediately discarded.\n\n  Compare the merged `.profdata` first: identical counters are a sufficient\n  condition for deterministic coverage, so when the two profdata files match the\n  `llvm-cov show` rendering is skipped entirely. Only when they differ does it\n  fall back to rendering and diffing the reports to confirm and locate the\n  difference.\n\n  The pass/fail result is unchanged: identical profdata implies identical\n  counters, which implies identical `llvm-cov show` output (the report is a\n  deterministic function of the profdata and the binary). Any profdata difference\n  falls back to the exact same `llvm-cov` diff as before.\n\n  ## Benchmark\n\n  standalone coverage build (`-DBUILD_FOR_FUZZING=ON` +\n  `-fprofile-instr-generate -fcoverage-mapping`, no sanitizer), par=4,\n  qa-assets corpora:\n\n  | target            | inputs | before | after | speedup |\n  |-------------------|-------:|-------:|------:|--------:|\n  | addition_overflow |    112 |    32s |   10s |    3.2x |\n  | process_message   |   2386 |   719s |  247s |    2.9x |\n\n  On `process_message` every input is deterministic, so all 2386 take the\n  profdata fast path and zero `llvm-cov show` reports are rendered, down from\n  2*2386 + 2 = 4774.",
 "commits": [
  {
   "sha": "c237c9f2ff53ea193bb3b8825cf92ac14444a14e",
   "date": "2026-09-03T13:36:03Z",
   "message": "contrib: Skip llvm-cov rendering for deterministic fuzz inputs\n\ndeterministic-fuzz-coverage rendered a full `llvm-cov show` report for\nevery input on both runs and diffed the two reports. `llvm-cov show`\nis by far the most expensive step, so almost all of that work produced\ntwo identical reports that were immediately thrown away.\n\nCompare the merged profdata first instead: identical counters are a\nsufficient condition for deterministic coverage, so when the two\nprofdata files match we skip rendering entirely. Only when they differ\ndo we fall back to rendering and diffing the `llvm-cov show` reports to\nrender and locate the difference.\n\nCo-authored-by: maflcko <6399679+maflcko@users.noreply.github.com>"
  }
 ],
 "timeline": [
  {
   "t": "2026-06-26T12:16:41Z",
   "kind": "force_push",
   "who": "HowHsu",
   "commit": "87ecdf2cf0bd3aa222b744a59ceaa62c2240b725"
  },
  {
   "t": "2026-08-19T10:39:52Z",
   "kind": "comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "text": "tACK 87ecdf2cf0bd3aa222b744a59ceaa62c2240b725\n\nRan both versions on qa-assets with the coverage build left over from my #35809 review with par=4. `addition_overflow` went from 5m49s to 31s and `process_message` from 1h50m to 25m48s, same PASS on both, and the new flow rendered zero reports. Identical `.profdata` would mean an identical report anyway; if they differ it falls back to the usual render and diff."
  },
  {
   "t": "2026-09-01T19:24:30Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "contrib/devtools/deterministic-fuzz-coverage/src/main.rs",
   "commit": "87ecdf2cf0bd3aa222b744a59ceaa62c2240b725",
   "in_reply_to": null,
   "text": "Nit: There's now a lot of `'a'` and `'b'`. Maybe `check_diff` can just accept the `&Path` and then do everything itself. This would de-dupl a bunch of code. Something like this (untested):\n\n```diff\n contrib/devtools/deterministic-fuzz-coverage/src/main.rs | 48 +++++++++++++++++++++---------------------------\n 1 file changed, 21 insertions(+), 27 deletions(-)\n\ndiff --git a/contrib/devtools/deterministic-fuzz-coverage/src/main.rs b/contrib/devtools/deterministic-fuzz-coverage/src/main.rs\nindex d536cf1..5859d7e 100644\n--- a/contrib/devtools/deterministic-fuzz-coverage/src/main.rs\n+++ b/contrib/devtools/deterministic-fuzz-coverage/src/main.rs\n@@ -216,7 +216,18 @@ fn deterministic_coverage(\n         };\n-    let check_diff = |a: &Path, b: &Path, err: &str| -> AppResult {\n+    let check_diff = |entry: &Path, thread_id: usize, err: &str| -> AppResult {\n+        let profdata_base = run_profile('a', entry, thread_id)?;\n+        let profdata_repeat = run_profile('b', entry, thread_id)?;\n+        // Fast path: identical counters mean the coverage is deterministic, so\n+        // skip the expensive llvm-cov rendering entirely.\n+        if profdata_eq(&profdata_base, &profdata_repeat)? {\n+            return Ok(());\n+        }\n+        // Slow path: the profiles differ, so render both into human-readable\n+        // reports and diff them to confirm and locate the difference.\n+        let cov_txt_base = render_cov(&profdata_base, 'a', thread_id)?;\n+        let cov_txt_repeat = render_cov(&profdata_repeat, 'b', thread_id)?;\n         let same = Command::new(GIT)\n             .args([\"--no-pager\", \"diff\", \"--no-index\"])\n-            .arg(a)\n-            .arg(b)\n+            .arg(&cov_txt_base)\n+            .arg(&cov_txt_repeat)\n             .status()\n@@ -249,16 +260,5 @@ The coverage was not deterministic between runs.\n         }\n-        let profdata_base = run_profile('a', &entry, thread_id)?;\n-        let profdata_repeat = run_profile('b', &entry, thread_id)?;\n-        // Fast path: identical counters mean the coverage is deterministic, so\n-        // skip the expensive llvm-cov rendering entirely.\n-        if profdata_eq(&profdata_base, &profdata_repeat)? {\n-            return Ok(());\n-        }\n-        // Slow path: the profiles differ, so render both into human-readable\n-        // reports and diff them to confirm and locate the difference.\n-        let cov_txt_base = render_cov(&profdata_base, 'a', thread_id)?;\n-        let cov_txt_repeat = render_cov(&profdata_repeat, 'b', thread_id)?;\n         check_diff(\n-            &cov_txt_base,\n-            &cov_txt_repeat,\n+            &entry,\n+            thread_id,\n             &format!(\"The fuzz target input was {}.\", entry.display()),\n@@ -297,13 +297,7 @@ The coverage was not deterministic between runs.\n         }\n-        let profdata_base = run_profile('a', &corpus_dir, 0)?;\n-        let profdata_repeat = run_profile('b', &corpus_dir, 0)?;\n-        if !profdata_eq(&profdata_base, &profdata_repeat)? {\n-            let cov_txt_base = render_cov(&profdata_base, 'a', 0)?;\n-            let cov_txt_repeat = render_cov(&profdata_repeat, 'b', 0)?;\n-            check_diff(\n-                &cov_txt_base,\n-                &cov_txt_repeat,\n-                &format!(\"All fuzz inputs in {} were used.\", corpus_dir.display()),\n-            )?;\n-        }\n+        check_diff(\n+            &corpus_dir,\n+            0,\n+            &format!(\"All fuzz inputs in {} were used.\", corpus_dir.display()),\n+        )?;\n     }\n```\n\nPossibly could also inline `profdata_eq` and only keep a small comment as it is only called once, to save some more lines?"
  },
  {
   "t": "2026-09-01T19:24:44Z",
   "kind": "review",
   "who": "maflcko",
   "assoc": "MEMBER",
   "state": "APPROVED",
   "commit": "87ecdf2cf0bd3aa222b744a59ceaa62c2240b725",
   "text": "lgtm, left a nit to slim this down a bit"
  },
  {
   "t": "2026-09-02T09:04:25Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "contrib/devtools/deterministic-fuzz-coverage/src/main.rs",
   "commit": "87ecdf2cf0bd3aa222b744a59ceaa62c2240b725",
   "in_reply_to": 3907610391,
   "text": "Small nit: A build with `-fprofile-instr-generate` but no `-fcoverage-mapping` now prints `Coverage test passed` where master dies with `llvm-cov show failed`. Determinism is still checked either way, only the broken build slips by. Under your version, worth one `run_profile` and `render_cov` before the loop?"
  },
  {
   "t": "2026-09-02T09:57:02Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "contrib/devtools/deterministic-fuzz-coverage/src/main.rs",
   "commit": "87ecdf2cf0bd3aa222b744a59ceaa62c2240b725",
   "in_reply_to": 3907610391,
   "text": "This is a dev-only script. If there is a \"broken build\" (because the dev ignored the docs), I think it is fine to not hold their hands with extra logic here. If needed, the error message can be adjusted, but other than that, I'd say the logic shouldn't be modified."
  },
  {
   "t": "2026-09-03T13:38:25Z",
   "kind": "force_push",
   "who": "HowHsu",
   "commit": "c237c9f2ff53ea193bb3b8825cf92ac14444a14e"
  },
  {
   "t": "2026-09-03T13:39:53Z",
   "kind": "review_comment",
   "who": "HowHsu",
   "assoc": "CONTRIBUTOR",
   "path": "contrib/devtools/deterministic-fuzz-coverage/src/main.rs",
   "commit": "87ecdf2cf0bd3aa222b744a59ceaa62c2240b725",
   "in_reply_to": 3907610391,
   "text": "Updated, thanks. Kept the `profdata_eq`, since I feel it is very good for beginners (like me) to understand the code logic."
  }
 ],
 "labels_log": [
  {
   "t": "2026-06-26T11:55:14Z",
   "action": "labeled",
   "label": "Scripts and tools",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-26T12:17:19Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-06-26T13:18:05Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 7104,
 "text_tokens_estimate": 1776,
 "changed_paths": [
  "contrib/devtools/deterministic-fuzz-coverage/src/main.rs"
 ],
 "files": [
  {
   "path": "contrib/devtools/deterministic-fuzz-coverage/src/main.rs",
   "add": 71,
   "del": 36
  }
 ],
 "test_lines": 107,
 "git": {
  "head": "c237c9f2ff53ea193bb3b8825cf92ac14444a14e",
  "head_matches_backup": true,
  "base": "633044f14362108b782b37408385b6e024eaa41b",
  "commits": [
   {
    "sha": "c237c9f2ff",
    "subject": "contrib: Skip llvm-cov rendering for deterministic fuzz inputs",
    "files": 1,
    "add": 71,
    "del": 36
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "d10bb7af6efc3b14",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}