[llvm] cb4e752 - [reland] [lit] [compiler-rt] Add llvm-lit global command cache to speed up test config (#196152)
via llvm-commits
llvm-commits at lists.llvm.org
Wed May 6 14:02:00 PDT 2026
Author: Andrew Haberlandt
Date: 2026-05-06T21:01:56Z
New Revision: cb4e752a6dcb9700d86f715d9682dd8a47d61376
URL: https://github.com/llvm/llvm-project/commit/cb4e752a6dcb9700d86f715d9682dd8a47d61376
DIFF: https://github.com/llvm/llvm-project/commit/cb4e752a6dcb9700d86f715d9682dd8a47d61376.diff
LOG: [reland] [lit] [compiler-rt] Add llvm-lit global command cache to speed up test config (#196152)
Re-lands #195888
Fixes two issues:
- `date -Ins` is not available on older macOS versions (I think
pre-15.4). This caused the new `test_cache` test to fail. Switched to
just using `date` + a sleep (with a comment explaining why). Even if the
sleep is too long/short, the test should still pass.
- `functools.cache` is not available on Python 3.8. I've moved the
`_memoize` helper out of TestRunner.py into util.py, and switched to it
instead. I had to make a small change to the memoize helper to support
arbitrary args/kwargs.
Added:
llvm/utils/lit/tests/unit/Util.py
Modified:
compiler-rt/test/lit.common.cfg.py
llvm/utils/lit/lit/LitConfig.py
llvm/utils/lit/lit/TestRunner.py
llvm/utils/lit/lit/util.py
Removed:
################################################################################
diff --git a/compiler-rt/test/lit.common.cfg.py b/compiler-rt/test/lit.common.cfg.py
index fef8f7ab297cc..12446b4708d20 100644
--- a/compiler-rt/test/lit.common.cfg.py
+++ b/compiler-rt/test/lit.common.cfg.py
@@ -20,20 +20,8 @@ def get_path_from_clang(args, allow_failure):
f"--target={config.target_triple}",
*args,
]
- path = None
- try:
- result = subprocess.run(
- clang_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True
- )
- path = result.stdout.decode().strip()
- except subprocess.CalledProcessError as e:
- msg = f"Failed to run {clang_cmd}\nrc:{e.returncode}\nstdout:{e.stdout}\ne.stderr{e.stderr}"
- if allow_failure:
- lit_config.warning(msg)
- else:
- lit_config.fatal(msg)
- return path, clang_cmd
-
+ path = lit_config.run_command_cached(clang_cmd, allow_failure, text=True)
+ return path.strip(), clang_cmd
def find_compiler_libdir():
"""
@@ -592,10 +580,12 @@ def get_ios_commands_dir():
# There is no simulator-specific sw_vers/sysctl, so we use the host OS version
os_detection_prefix = []
- darwin_os_version = subprocess.check_output(
- os_detection_prefix + ["sw_vers", "-productVersion"], universal_newlines=True
+ darwin_os_version = lit_config.run_command_cached(
+ os_detection_prefix + ["sw_vers", "-productVersion"],
+ universal_newlines=True,
+ text=True,
)
- darwin_os_version = tuple(int(x) for x in darwin_os_version.split("."))
+ darwin_os_version = tuple(int(x) for x in darwin_os_version.strip().split("."))
if len(darwin_os_version) == 2:
darwin_os_version = (darwin_os_version[0], darwin_os_version[1], 0)
@@ -608,17 +598,15 @@ def get_ios_commands_dir():
config.darwin_os_version = darwin_os_version
# Detect x86_64h
- try:
- output = subprocess.check_output(
- os_detection_prefix + ["sysctl", "hw.cpusubtype"]
- )
+ output = lit_config.run_command_cached(
+ os_detection_prefix + ["sysctl", "hw.cpusubtype"], text=True, allow_failure=True
+ )
+ if output:
output_re = re.match("^hw.cpusubtype: ([0-9]+)$", output)
if output_re:
cpu_subtype = int(output_re.group(1))
if cpu_subtype == 8: # x86_64h
config.available_features.add("x86_64h")
- except:
- pass
# 32-bit iOS simulator is deprecated and removed in latest Xcode.
if config.apple_platform == "iossim":
@@ -955,18 +943,11 @@ def is_windows_lto_supported():
if lit.util.which("log"):
# Querying the log can only done by a privileged user so
# so check if we can query the log.
- exit_code = -1
- with open("/dev/null", "r") as f:
- # Run a `log show` command the should finish fairly quickly and produce very little output.
- exit_code = subprocess.call(
- ["log", "show", "--last", "1m", "--predicate", "1 == 0"],
- stdout=f,
- stderr=f,
- )
- if exit_code == 0:
+ res = lit_config.run_command_cached(
+ ["log", "show", "--last", "1m", "--predicate", "1 == 0"], allow_failure=True
+ )
+ if res is not None:
config.available_features.add("darwin_log_cmd")
- else:
- lit_config.warning("log command found but cannot queried")
else:
lit_config.warning("log command not found. Some tests will be skipped.")
elif config.android:
diff --git a/llvm/utils/lit/lit/LitConfig.py b/llvm/utils/lit/lit/LitConfig.py
index 3b0dfaa981d4a..4be2a0f6d8121 100644
--- a/llvm/utils/lit/lit/LitConfig.py
+++ b/llvm/utils/lit/lit/LitConfig.py
@@ -263,6 +263,22 @@ def fatal(self, message):
self._write_message("fatal", message)
sys.exit(2)
+ def run_command_cached(self, cmd, allow_failure=False, **kwargs):
+ """
+ Run a command with subprocess.run, with a cache global to this llvm-lit invocation
+ If allow_failure is True, lit_config.fatal will be invoked if the command fails.
+ All additional kwargs are passed to subprocess.run
+ """
+ if type(cmd) is list:
+ cmd = tuple(cmd)
+ return lit.util.runCommandCached(self, cmd, allow_failure, **kwargs)
+ elif type(cmd) is str:
+ return lit.util.runCommandCached(self, cmd, allow_failure, **kwargs)
+ else:
+ raise ValueError(
+ f"runCommandCached expected list or str, got {type(cmd)}: {cmd}"
+ )
+
@enum.unique
class DiagnosticLevel(enum.IntEnum):
diff --git a/llvm/utils/lit/lit/TestRunner.py b/llvm/utils/lit/lit/TestRunner.py
index dbaace6d6c60c..743f7bcccd533 100644
--- a/llvm/utils/lit/lit/TestRunner.py
+++ b/llvm/utils/lit/lit/TestRunner.py
@@ -1065,22 +1065,10 @@ def regex_escape(s):
return substitutions
-def _memoize(f):
- cache = {} # Intentionally unbounded, see applySubstitutions()
-
- def memoized(x):
- if x not in cache:
- cache[x] = f(x)
- return cache[x]
-
- return memoized
-
-
- at _memoize
+ at lit.util.memoize # Intentionally unbounded: see applySubstitutions
def _caching_re_compile(r):
return re.compile(r)
-
class ExpandableScriptDirective(object):
"""
Common interface for lit directives for which any lit substitutions must be
diff --git a/llvm/utils/lit/lit/util.py b/llvm/utils/lit/lit/util.py
index a800f1f6e1419..152655e81c391 100644
--- a/llvm/utils/lit/lit/util.py
+++ b/llvm/utils/lit/lit/util.py
@@ -13,7 +13,6 @@
import sys
import threading
-
def pythonize_bool(value):
if value is None:
return False
@@ -441,3 +440,41 @@ def killProcessAndChildren(pid):
psutilProc.kill()
except psutil.NoSuchProcess:
pass
+
+
+def memoize(f):
+ cache = {} # Unbounded
+
+ def make_key(args, kwargs):
+ return args, tuple(kwargs.items())
+
+ def memoized(*args, **kwargs):
+ key = make_key(args, kwargs)
+ if key not in cache:
+ cache[key] = f(*args, **kwargs)
+ return cache[key]
+
+ return memoized
+
+
+ at memoize
+def runCommandCached(lit_config, cmd, allow_failure, **kwargs):
+ """
+ Run a command with subprocess.run, with a cache global to this llvm-lit invocation
+ If allow_failure is True, lit_config.fatal will be invoked if the command fails.
+ All additional kwargs are passed to subprocess.run
+ """
+ try:
+ result = subprocess.run(
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, **kwargs
+ )
+ return result.stdout
+ except FileNotFoundError as e:
+ msg = f"Failed to run {cmd}: {e}"
+ except subprocess.CalledProcessError as e:
+ msg = f"Failed to run {cmd}\nrc:{e.returncode}\nstdout:{e.stdout}\ne.stderr{e.stderr}"
+
+ if not allow_failure:
+ lit_config.fatal(msg)
+
+ return None
diff --git a/llvm/utils/lit/tests/unit/Util.py b/llvm/utils/lit/tests/unit/Util.py
new file mode 100644
index 0000000000000..794950f44ef19
--- /dev/null
+++ b/llvm/utils/lit/tests/unit/Util.py
@@ -0,0 +1,93 @@
+# RUN: %{python} %s
+# UNSUPPORTED: system-windows
+
+import unittest
+import platform
+import time
+
+from lit.util import runCommandCached
+from lit.LitConfig import LitConfig
+
+
+class TestCommandCache(unittest.TestCase):
+ @staticmethod
+ def _lit_config():
+ return LitConfig(
+ progname="lit",
+ path=[],
+ diagnostic_level="note",
+ useValgrind=False,
+ valgrindLeakCheck=False,
+ valgrindArgs=[],
+ noExecute=False,
+ debug=False,
+ isWindows=(platform.system() == "Windows"),
+ order="smart",
+ params={},
+ )
+
+ def test_basic(self):
+ lit_config = self._lit_config()
+
+ self.assertEqual(lit_config.run_command_cached(["echo", "-n", "hi"]), b"hi")
+ self.assertNotEqual(lit_config.run_command_cached("ls"), None)
+
+ # Test that arguments (e.g. text=True) get forwarded to subprocess.run
+ self.assertEqual(
+ lit_config.run_command_cached(["echo", "-n", "hi"], text=True), "hi"
+ )
+
+ # shell=True is not implied
+ self.assertEqual(
+ lit_config.run_command_cached("ls -al", allow_failure=True), None
+ )
+ self.assertNotEqual(
+ lit_config.run_command_cached("ls -al", allow_failure=True, shell=True),
+ None,
+ )
+
+ self.assertEqual(
+ lit_config.run_command_cached("exit 0", shell=True, allow_failure=True), b""
+ )
+
+ def test_fatal(self):
+ lit_config = self._lit_config()
+
+ # Test fatal errors
+ fatal_counter = 0
+
+ def wrap_fatal(msg):
+ nonlocal fatal_counter
+ fatal_counter += 1
+
+ lit_config.fatal = wrap_fatal
+ lit_config.run_command_cached(["asdfghjkl"])
+ self.assertEqual(fatal_counter, 1)
+
+ self.assertEqual(
+ lit_config.run_command_cached(["asdfghjkl"], allow_failure=True), None
+ )
+ self.assertEqual(
+ lit_config.run_command_cached("exit 1", shell=True, allow_failure=True),
+ None,
+ )
+ self.assertEqual(fatal_counter, 1)
+
+ def test_cache(self):
+ lit_config = self._lit_config()
+
+ date = lit_config.run_command_cached("date", shell=True)
+ self.assertNotEqual(date, None)
+
+ # Ideally, we'd just use `date -Ins` and not need to sleep,
+ # but nanosecond support was only added in macOS 15.4-ish.
+ # (Note that even if this sleep is too short/too long the
+ # test should still pass.)
+ time.sleep(2)
+
+ # Second time should be cached, i.e. equal to the first
+ self.assertEqual(lit_config.run_command_cached("date", shell=True), date)
+
+
+if __name__ == "__main__":
+ unittest.main()
More information about the llvm-commits
mailing list