https://github.com/yln created https://github.com/llvm/llvm-project/pull/153914
This addresses limitations in our testing infrastructure for checking CPU features. Before this * `getCPUInfo()` was Linux-only, and the * `@skipUnlessFeature` decorator was Darwin-only and did not consider the remote (on device) testing use case. Introduce `CPUFeature` class to as an abstraction to hide the platform-specific implementations to check for CPU features. Unify local (on host) and remote (on device) test execution by always going through `test.Base.run_platform_command()` which uses LLDB's `platform shell <cmd>` command. Potential future cleanups: I think `@skipUnlessFeature` decorator could be used in place of code like this: ``` if not self.isAArch64SME(): self.skipTest("SME must be present.") if not self.isAArch64SME2(): self.skipTest("SME2 must be present.") ``` >From fd3eff77a288062c86fcad3f32bf27e7cba6de76 Mon Sep 17 00:00:00 2001 From: Julian Lettner <jlett...@apple.com> Date: Fri, 15 Aug 2025 16:19:37 -0700 Subject: [PATCH 1/2] [LLDB] Add CPU feature check support to Darwin Add new `CPUFeature` class as an abstraction to hide the platform-specific implementations to check for CPU features. Unify local (on host) and remote (on device) test execution by always going through `test.Base.run_platform_command()` which uses LLDB's `platform shell <cmd>` command. --- .../Python/lldbsuite/test/cpu_feature.py | 55 ++++++++++++++++ .../Python/lldbsuite/test/lldbtest.py | 62 +++++-------------- .../register_command/TestRegisters.py | 20 +----- 3 files changed, 73 insertions(+), 64 deletions(-) create mode 100644 lldb/packages/Python/lldbsuite/test/cpu_feature.py diff --git a/lldb/packages/Python/lldbsuite/test/cpu_feature.py b/lldb/packages/Python/lldbsuite/test/cpu_feature.py new file mode 100644 index 0000000000000..541e1e0b7520b --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/cpu_feature.py @@ -0,0 +1,55 @@ +""" +Platform-agnostic helper to query for CPU features. +""" + +import re + + +class CPUFeature: + def __init__(self, linux_cpu_info_flag: str, darwin_sysctl_key: str): + self.cpu_info_flag = linux_cpu_info_flag + self.sysctl_key = darwin_sysctl_key + + def is_supported(self, triple, cmd_runner): + if re.match(".*-.*-linux", triple): + err_msg, res = self._is_supported_linux(cmd_runner) + elif re.match(".*-apple-.*", triple): + err_msg, res = self._is_supported_darwin(cmd_runner) + else: + err_msg, res = None, False + + if err_msg: + print(f"CPU feature check failed: {err_msg}") + + return res + + def _is_supported_linux(self, cmd_runner): + cmd = "cat /proc/cpuinfo" + err, retcode, output = cmd_runner(cmd) + if err.Fail() or retcode != 0: + err_msg = f"cat /proc/cpuinfo failed: {output}" + return err_msg, False + + return None, (self.cpu_info_flag in output) + + def _is_supported_darwin(self, cmd_runner): + cmd = f"sysctl -n {self.sysctl_key}" + err, retcode, output = cmd_runner(cmd) + if err.Fail() or retcode != 0: + return output, False + + return None, (output.strip() == "1") + + +# List of CPU features +FPMR = CPUFeature("fpmr", "???") +GCS = CPUFeature("gcs", "???") +LASX = CPUFeature("lasx", "???") +LSX = CPUFeature("lsx", "???") +MTE = CPUFeature("mte", "???") +MTE_STORE_ONLY = CPUFeature("mtestoreonly", "???") +PTR_AUTH = CPUFeature("paca", "hw.optional.arm.FEAT_PAuth2") +SME = CPUFeature("sme", "hw.optional.arm.FEAT_SME") +SME_FA64 = CPUFeature("smefa64", "???") +SME2 = CPUFeature("sme2", "hw.optional.arm.FEAT_SME2") +SVE = CPUFeature("sve", "???") diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py index 0fc85fcc4d2d6..d9cf2689b3d25 100644 --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -48,6 +48,7 @@ # LLDB modules import lldb from . import configuration +from . import cpu_feature from . import decorators from . import lldbplatformutil from . import lldbtest_config @@ -1315,39 +1316,6 @@ def isPPC64le(self): return True return False - def getCPUInfo(self): - triple = self.dbg.GetSelectedPlatform().GetTriple() - - # TODO other platforms, please implement this function - if not re.match(".*-.*-linux", triple): - return "" - - # Need to do something different for non-Linux/Android targets - cpuinfo_path = self.getBuildArtifact("cpuinfo") - if configuration.lldb_platform_name: - self.runCmd( - 'platform get-file "/proc/cpuinfo" ' + cpuinfo_path, check=False - ) - if not self.res.Succeeded(): - if self.TraceOn(): - print( - 'Failed to get /proc/cpuinfo from remote: "{}"'.format( - self.res.GetOutput().strip() - ) - ) - print("All cpuinfo feature checks will fail.") - return "" - else: - cpuinfo_path = "/proc/cpuinfo" - - try: - with open(cpuinfo_path, "r") as f: - cpuinfo = f.read() - except: - return "" - - return cpuinfo - def isAArch64(self): """Returns true if the architecture is AArch64.""" arch = self.getArchitecture().lower() @@ -1360,39 +1328,43 @@ def isARM(self): self.getArchitecture().lower().startswith("arm") ) + def isSupported(self, cpu_feature: cpu_feature.CPUFeature): + triple = self.dbg.GetSelectedPlatform().GetTriple() + cmd_runner = self.run_platform_command + return cpu_feature.is_supported(triple, cmd_runner) + def isAArch64SVE(self): - return self.isAArch64() and "sve" in self.getCPUInfo() + return self.isAArch64() and self.isSupported(cpu_feature.SVE) def isAArch64SME(self): - return self.isAArch64() and "sme" in self.getCPUInfo() + return self.isAArch64() and self.isSupported(cpu_feature.SME) def isAArch64SME2(self): # If you have sme2, you also have sme. - return self.isAArch64() and "sme2" in self.getCPUInfo() + return self.isAArch64() and self.isSupported(cpu_feature.SME2) def isAArch64SMEFA64(self): # smefa64 allows the use of the full A64 instruction set in streaming # mode. This is required by certain test programs to setup register # state. - cpuinfo = self.getCPUInfo() - return self.isAArch64() and "sme" in cpuinfo and "smefa64" in cpuinfo + return self.isAArch64() and self.isSupported(cpu_feature.SME) and self.isSupported(cpu_feature.SME_FA64) def isAArch64MTE(self): - return self.isAArch64() and "mte" in self.getCPUInfo() + return self.isAArch64() and self.isSupported(cpu_feature.MTE) def isAArch64MTEStoreOnly(self): - return self.isAArch64() and "mtestoreonly" in self.getCPUInfo() + return self.isAArch64() and self.isSupported(cpu_feature.MTE_STORE_ONLY) def isAArch64GCS(self): - return self.isAArch64() and "gcs" in self.getCPUInfo() + return self.isAArch64() and self.isSupported(cpu_feature.GCS) def isAArch64PAuth(self): if self.getArchitecture() == "arm64e": return True - return self.isAArch64() and "paca" in self.getCPUInfo() + return self.isAArch64() and self.isSupported(cpu_feature.PTR_AUTH) def isAArch64FPMR(self): - return self.isAArch64() and "fpmr" in self.getCPUInfo() + return self.isAArch64() and self.isSupported(cpu_feature.FPMR) def isAArch64Windows(self): """Returns true if the architecture is AArch64 and platform windows.""" @@ -1407,10 +1379,10 @@ def isLoongArch(self): return arch in ["loongarch64", "loongarch32"] def isLoongArchLSX(self): - return self.isLoongArch() and "lsx" in self.getCPUInfo() + return self.isLoongArch() and self.isSupported(cpu_feature.LSX) def isLoongArchLASX(self): - return self.isLoongArch() and "lasx" in self.getCPUInfo() + return self.isLoongArch() and self.isSupported(cpu_feature.LASX) def isRISCV(self): """Returns true if the architecture is RISCV64 or RISCV32.""" diff --git a/lldb/test/API/commands/register/register/register_command/TestRegisters.py b/lldb/test/API/commands/register/register/register_command/TestRegisters.py index 0134139892794..29d090a279070 100644 --- a/lldb/test/API/commands/register/register/register_command/TestRegisters.py +++ b/lldb/test/API/commands/register/register/register_command/TestRegisters.py @@ -21,24 +21,6 @@ def tearDown(self): self.dbg.GetSelectedTarget().GetProcess().Destroy() TestBase.tearDown(self) - # on macOS, detect if the current machine is arm64 and supports SME - def get_sme_available(self): - if self.getArchitecture() != "arm64": - return None - try: - sysctl_output = subprocess.check_output( - ["sysctl", "hw.optional.arm.FEAT_SME"] - ).decode("utf-8") - except subprocess.CalledProcessError: - return None - m = re.match(r"hw\.optional\.arm\.FEAT_SME: (\w+)", sysctl_output) - if m: - if int(m.group(1)) == 1: - return True - else: - return False - return None - @skipIfiOSSimulator @skipIf(archs=no_match(["amd64", "arm$", "i386", "x86_64"])) @expectedFailureAll(oslist=["freebsd", "netbsd"], bugnumber="llvm.org/pr48371") @@ -51,7 +33,7 @@ def test_register_commands(self): self.log_enable("registers") error_str_matched = False - if self.get_sme_available() and self.platformIsDarwin(): + if self.isAArch64SME() and self.platformIsDarwin(): # On Darwin AArch64 SME machines, we will have unavailable # registers when not in Streaming SVE Mode/SME, so # `register read -a` will report that some registers >From dd5f0b829fb008076daa51122fadd7715cb57921 Mon Sep 17 00:00:00 2001 From: Julian Lettner <jlett...@apple.com> Date: Fri, 15 Aug 2025 19:11:31 -0700 Subject: [PATCH 2/2] [LLDB] Use new CPU feature check for @skipUnlessFeature decorator --- .../Python/lldbsuite/test/cpu_feature.py | 3 +++ .../Python/lldbsuite/test/decorators.py | 24 ++++++------------- .../sme-registers/TestSMERegistersDarwin.py | 6 ++--- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/lldb/packages/Python/lldbsuite/test/cpu_feature.py b/lldb/packages/Python/lldbsuite/test/cpu_feature.py index 541e1e0b7520b..9c0a4102ea944 100644 --- a/lldb/packages/Python/lldbsuite/test/cpu_feature.py +++ b/lldb/packages/Python/lldbsuite/test/cpu_feature.py @@ -10,6 +10,9 @@ def __init__(self, linux_cpu_info_flag: str, darwin_sysctl_key: str): self.cpu_info_flag = linux_cpu_info_flag self.sysctl_key = darwin_sysctl_key + def __str__(self): + return self.cpu_info_flag + def is_supported(self, triple, cmd_runner): if re.match(".*-.*-linux", triple): err_msg, res = self._is_supported_linux(cmd_runner) diff --git a/lldb/packages/Python/lldbsuite/test/decorators.py b/lldb/packages/Python/lldbsuite/test/decorators.py index bd10bcc3d6ce0..bc400cd3b8f12 100644 --- a/lldb/packages/Python/lldbsuite/test/decorators.py +++ b/lldb/packages/Python/lldbsuite/test/decorators.py @@ -23,6 +23,7 @@ from lldbsuite.support import temp_file from lldbsuite.test import lldbplatform from lldbsuite.test import lldbplatformutil +from lldbsuite.test.cpu_feature import CPUFeature class DecorateMode: @@ -1127,24 +1128,13 @@ def skipIfLLVMTargetMissing(target): return unittest.skipIf(not found, "requires " + target) -# Call sysctl on darwin to see if a specified hardware feature is available on this machine. -def skipUnlessFeature(feature): - def is_feature_enabled(): - if platform.system() == "Darwin": - try: - output = subprocess.check_output( - ["/usr/sbin/sysctl", feature], stderr=subprocess.DEVNULL - ).decode("utf-8") - # If 'feature: 1' was output, then this feature is available and - # the test should not be skipped. - if re.match(r"%s: 1\s*" % feature, output): - return None - else: - return "%s is not supported on this system." % feature - except subprocess.CalledProcessError: - return "%s is not supported on this system." % feature +def skipUnlessFeature(cpu_feature: CPUFeature): + def hasFeature(test_case): + if not test_case.isSupported(cpu_feature): + return f"Unsupported CPU feature: {cpu_feature}" + return None - return skipTestIfFn(is_feature_enabled) + return skipTestIfFn(hasFeature) def skipIfBuildType(types: list[str]): diff --git a/lldb/test/API/macosx/sme-registers/TestSMERegistersDarwin.py b/lldb/test/API/macosx/sme-registers/TestSMERegistersDarwin.py index 6f9d055cef506..28bebb3ffeb6e 100644 --- a/lldb/test/API/macosx/sme-registers/TestSMERegistersDarwin.py +++ b/lldb/test/API/macosx/sme-registers/TestSMERegistersDarwin.py @@ -1,6 +1,7 @@ import lldb from lldbsuite.test.lldbtest import * from lldbsuite.test.decorators import * +import lldbsuite.test.cpu_feature as cpu_feature import lldbsuite.test.lldbutil as lldbutil import os @@ -9,10 +10,9 @@ class TestSMERegistersDarwin(TestBase): NO_DEBUG_INFO_TESTCASE = True mydir = TestBase.compute_mydir(__file__) - @skipIfRemote @skipUnlessDarwin - @skipUnlessFeature("hw.optional.arm.FEAT_SME") - @skipUnlessFeature("hw.optional.arm.FEAT_SME2") + @skipUnlessFeature(cpu_feature.SME) + @skipUnlessFeature(cpu_feature.SME2) # thread_set_state/thread_get_state only avail in macOS 15.4+ @skipIf(macos_version=["<", "15.4"]) def test(self): _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits