llvm-project
119 строк · 3.8 Кб
1#!/usr/bin/env python3
2#
3# Determines which clang-tidy checks are "fast enough" to run in clangd.
4# This runs clangd --check --check-tidy-time and parses the output.
5# This program outputs a header fragment specifying which checks are fast:
6# FAST(bugprone-argument-comment, 5)
7# SLOW(misc-const-correctness, 200)
8# If given the old header fragment as input, we lean to preserve its choices.
9#
10# This is not deterministic or hermetic, but should be run occasionally to
11# update the list of allowed checks. From llvm-project:
12# clang-tools-extra/clangd/TidyFastChecks.py --clangd=build-opt/bin/clangd
13# Be sure to use an optimized, no-asserts, tidy-enabled build of clangd!
14
15import argparse
16import os
17import re
18import subprocess
19import sys
20
21# Checks faster than FAST_THRESHOLD are fast, slower than SLOW_THRESHOLD slow.
22# If a check is in between, we stick with our previous decision. This avoids
23# enabling/disabling checks between releases due to random measurement jitter.
24FAST_THRESHOLD = 8 # percent
25SLOW_THRESHOLD = 15
26
27parser = argparse.ArgumentParser()
28parser.add_argument(
29"--target",
30help="X-macro output file. "
31"If it exists, existing contents will be used for hysteresis",
32default="clang-tools-extra/clangd/TidyFastChecks.inc",
33)
34parser.add_argument(
35"--source",
36help="Source file to benchmark tidy checks",
37default="clang/lib/Sema/Sema.cpp",
38)
39parser.add_argument(
40"--clangd", help="clangd binary to invoke", default="build/bin/clangd"
41)
42parser.add_argument("--checks", help="check glob to run", default="*")
43parser.add_argument("--verbose", help="log clangd output", action="store_true")
44args = parser.parse_args()
45
46# Use the preprocessor to extract the list of previously-fast checks.
47def read_old_fast(path):
48text = subprocess.check_output(
49[
50"cpp",
51"-P", # Omit GNU line markers
52"-nostdinc", # Don't include stdc-predef.h
53"-DFAST(C,T)=C", # Print fast checks only
54path,
55]
56)
57for line in text.splitlines():
58if line.strip():
59yield line.strip().decode("utf-8")
60
61
62old_fast = list(read_old_fast(args.target)) if os.path.exists(args.target) else []
63print(f"Old fast checks: {old_fast}", file=sys.stderr)
64
65# Runs clangd --check --check-tidy-time.
66# Yields (check, percent-overhead) pairs.
67def measure():
68process = subprocess.Popen(
69[
70args.clangd,
71"--check=" + args.source,
72"--check-locations=0", # Skip useless slow steps.
73"--check-tidy-time=" + args.checks,
74],
75stderr=subprocess.PIPE,
76)
77recording = False
78for line in iter(process.stderr.readline, b""):
79if args.verbose:
80print("clangd> ", line, file=sys.stderr)
81if not recording:
82if b"Timing AST build with individual clang-tidy checks" in line:
83recording = True
84continue
85if b"Finished individual clang-tidy checks" in line:
86return
87match = re.search(rb"(\S+) = (\S+)%", line)
88if match:
89yield (match.group(1).decode("utf-8"), float(match.group(2)))
90
91
92with open(args.target, "w", buffering=1) as target:
93# Produce an includable X-macros fragment with our decisions.
94print(
95f"""// This file is generated, do not edit it directly!
96// Deltas are percentage regression in parsing {args.source}
97#ifndef FAST
98#define FAST(CHECK, DELTA)
99#endif
100#ifndef SLOW
101#define SLOW(CHECK, DELTA)
102#endif
103""",
104file=target,
105)
106
107for check, time in measure():
108threshold = SLOW_THRESHOLD if check in old_fast else FAST_THRESHOLD
109decision = "FAST" if time <= threshold else "SLOW"
110print(f"{decision} {check} {time}% <= {threshold}%", file=sys.stderr)
111print(f"{decision}({check}, {time})", file=target)
112
113print(
114"""
115#undef FAST
116#undef SLOW
117""",
118file=target,
119)
120