96 lines
3.7 KiB
Python
Executable File
96 lines
3.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Determines which clang-tidy checks are "fast enough" to run in clangd.
|
|
# This runs clangd --check --check-tidy-time and parses the output.
|
|
# This program outputs a header fragment specifying which checks are fast:
|
|
# FAST(bugprone-argument-comment, 5)
|
|
# SLOW(misc-const-correctness, 200)
|
|
# If given the old header fragment as input, we lean to preserve its choices.
|
|
#
|
|
# This is not deterministic or hermetic, but should be run occasionally to
|
|
# update the list of allowed checks. From llvm-project:
|
|
# clang-tools-extra/clangd/TidyFastChecks.py --clangd=build-opt/bin/clangd
|
|
# Be sure to use an optimized, no-asserts, tidy-enabled build of clangd!
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
# Checks faster than FAST_THRESHOLD are fast, slower than SLOW_THRESHOLD slow.
|
|
# If a check is in between, we stick with our previous decision. This avoids
|
|
# enabling/disabling checks between releases due to random measurement jitter.
|
|
FAST_THRESHOLD = 8 # percent
|
|
SLOW_THRESHOLD = 15
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--target', help='X-macro output file. '
|
|
'If it exists, existing contents will be used for hysteresis',
|
|
default='clang-tools-extra/clangd/TidyFastChecks.inc')
|
|
parser.add_argument('--source', help='Source file to benchmark tidy checks',
|
|
default='clang/lib/Sema/Sema.cpp')
|
|
parser.add_argument('--clangd', help='clangd binary to invoke',
|
|
default='build/bin/clangd')
|
|
parser.add_argument('--checks', help='check glob to run', default='*')
|
|
parser.add_argument('--verbose', help='log clangd output', action='store_true')
|
|
args = parser.parse_args()
|
|
|
|
# Use the preprocessor to extract the list of previously-fast checks.
|
|
def read_old_fast(path):
|
|
text = subprocess.check_output(["cpp",
|
|
"-P", # Omit GNU line markers
|
|
"-nostdinc", # Don't include stdc-predef.h
|
|
"-DFAST(C,T)=C", # Print fast checks only
|
|
path])
|
|
for line in text.splitlines():
|
|
if line.strip():
|
|
yield line.strip().decode('utf-8')
|
|
old_fast = list(read_old_fast(args.target)) if os.path.exists(args.target) else []
|
|
print(f"Old fast checks: {old_fast}", file=sys.stderr)
|
|
|
|
# Runs clangd --check --check-tidy-time.
|
|
# Yields (check, percent-overhead) pairs.
|
|
def measure():
|
|
process = subprocess.Popen([args.clangd,
|
|
"--check=" + args.source,
|
|
"--check-locations=0", # Skip useless slow steps.
|
|
"--check-tidy-time=" + args.checks],
|
|
stderr=subprocess.PIPE)
|
|
recording = False
|
|
for line in iter(process.stderr.readline, b""):
|
|
if args.verbose:
|
|
print("clangd> ", line, file=sys.stderr)
|
|
if not recording:
|
|
if b'Timing AST build with individual clang-tidy checks' in line:
|
|
recording = True
|
|
continue
|
|
if b'Finished individual clang-tidy checks' in line:
|
|
return
|
|
match = re.search(rb'(\S+) = (\S+)%', line)
|
|
if match:
|
|
yield (match.group(1).decode('utf-8'), float(match.group(2)))
|
|
|
|
with open(args.target, 'w', buffering=1) as target:
|
|
# Produce an includable X-macros fragment with our decisions.
|
|
print(f"""// This file is generated, do not edit it directly!
|
|
// Deltas are percentage regression in parsing {args.source}
|
|
#ifndef FAST
|
|
#define FAST(CHECK, DELTA)
|
|
#endif
|
|
#ifndef SLOW
|
|
#define SLOW(CHECK, DELTA)
|
|
#endif
|
|
""", file=target)
|
|
|
|
for check, time in measure():
|
|
threshold = SLOW_THRESHOLD if check in old_fast else FAST_THRESHOLD
|
|
decision = "FAST" if time <= threshold else "SLOW"
|
|
print(f"{decision} {check} {time}% <= {threshold}%", file=sys.stderr)
|
|
print(f"{decision}({check}, {time})", file=target)
|
|
|
|
print("""
|
|
#undef FAST
|
|
#undef SLOW
|
|
""", file=target)
|