Skip to content

Commit 4204e16

Browse files
ylnmahesh-attarde
authored andcommitted
[lldb][test] Unify test infrastructure for checking CPU features (llvm#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 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.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.") ```
1 parent dde9f3b commit 4204e16

File tree

5 files changed

+107
-84
lines changed

5 files changed

+107
-84
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""
2+
Platform-agnostic helper to query for CPU features.
3+
"""
4+
5+
import re
6+
7+
8+
class CPUFeature:
9+
def __init__(self, linux_cpu_info_flag: str = None, darwin_sysctl_key: str = None):
10+
self.cpu_info_flag = linux_cpu_info_flag
11+
self.sysctl_key = darwin_sysctl_key
12+
13+
def __str__(self):
14+
for arch_class in ALL_ARCHS:
15+
for feat_var in dir(arch_class):
16+
if self == getattr(arch_class, feat_var):
17+
return f"{arch_class.__name__}.{feat_var}"
18+
raise AssertionError("unreachable")
19+
20+
def is_supported(self, triple, cmd_runner):
21+
if re.match(".*-.*-linux", triple):
22+
err_msg, res = self._is_supported_linux(cmd_runner)
23+
elif re.match(".*-apple-.*", triple):
24+
err_msg, res = self._is_supported_darwin(cmd_runner)
25+
else:
26+
err_msg, res = None, False
27+
28+
if err_msg:
29+
print(f"CPU feature check failed: {err_msg}")
30+
31+
return res
32+
33+
def _is_supported_linux(self, cmd_runner):
34+
if not self.cpu_info_flag:
35+
return f"Unspecified cpuinfo flag for {self}", False
36+
37+
cmd = "cat /proc/cpuinfo"
38+
err, retcode, output = cmd_runner(cmd)
39+
if err.Fail() or retcode != 0:
40+
return output, False
41+
42+
# FIXME: simple substring match, e.g., test for 'sme' will be true if
43+
# 'sme2' or 'smefa64' is present
44+
return None, (self.cpu_info_flag in output)
45+
46+
def _is_supported_darwin(self, cmd_runner):
47+
if not self.sysctl_key:
48+
return f"Unspecified sysctl key for {self}", False
49+
50+
cmd = f"sysctl -n {self.sysctl_key}"
51+
err, retcode, output = cmd_runner(cmd)
52+
if err.Fail() or retcode != 0:
53+
return output, False
54+
55+
return None, (output.strip() == "1")
56+
57+
58+
class AArch64:
59+
FPMR = CPUFeature("fpmr")
60+
GCS = CPUFeature("gcs")
61+
MTE = CPUFeature("mte")
62+
MTE_STORE_ONLY = CPUFeature("mtestoreonly")
63+
PTR_AUTH = CPUFeature("paca", "hw.optional.arm.FEAT_PAuth2")
64+
SME = CPUFeature("sme", "hw.optional.arm.FEAT_SME")
65+
SME_FA64 = CPUFeature("smefa64")
66+
SME2 = CPUFeature("sme2", "hw.optional.arm.FEAT_SME2")
67+
SVE = CPUFeature("sve")
68+
69+
70+
class Loong:
71+
LASX = CPUFeature("lasx")
72+
LSX = CPUFeature("lsx")
73+
74+
75+
ALL_ARCHS = [AArch64, Loong]

lldb/packages/Python/lldbsuite/test/decorators.py

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from lldbsuite.support import temp_file
2828
from lldbsuite.test import lldbplatform
2929
from lldbsuite.test import lldbplatformutil
30+
from lldbsuite.test.cpu_feature import CPUFeature
3031

3132

3233
class DecorateMode:
@@ -1131,24 +1132,13 @@ def skipIfLLVMTargetMissing(target):
11311132
return unittest.skipIf(not found, "requires " + target)
11321133

11331134

1134-
# Call sysctl on darwin to see if a specified hardware feature is available on this machine.
1135-
def skipUnlessFeature(feature):
1136-
def is_feature_enabled():
1137-
if platform.system() == "Darwin":
1138-
try:
1139-
output = subprocess.check_output(
1140-
["/usr/sbin/sysctl", feature], stderr=subprocess.DEVNULL
1141-
).decode("utf-8")
1142-
# If 'feature: 1' was output, then this feature is available and
1143-
# the test should not be skipped.
1144-
if re.match(r"%s: 1\s*" % feature, output):
1145-
return None
1146-
else:
1147-
return "%s is not supported on this system." % feature
1148-
except subprocess.CalledProcessError:
1149-
return "%s is not supported on this system." % feature
1135+
def skipUnlessFeature(cpu_feature: CPUFeature):
1136+
def hasFeature(test_case):
1137+
if not test_case.isSupported(cpu_feature):
1138+
return f"Unsupported CPU feature: {cpu_feature}"
1139+
return None
11501140

1151-
return skipTestIfFn(is_feature_enabled)
1141+
return skipTestIfFn(hasFeature)
11521142

11531143

11541144
def skipIfBuildType(types: list[str]):

lldb/packages/Python/lldbsuite/test/lldbtest.py

Lines changed: 21 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
# LLDB modules
4949
import lldb
5050
from . import configuration
51+
from . import cpu_feature
5152
from . import decorators
5253
from . import lldbplatformutil
5354
from . import lldbtest_config
@@ -1315,39 +1316,6 @@ def isPPC64le(self):
13151316
return True
13161317
return False
13171318

1318-
def getCPUInfo(self):
1319-
triple = self.dbg.GetSelectedPlatform().GetTriple()
1320-
1321-
# TODO other platforms, please implement this function
1322-
if not re.match(".*-.*-linux", triple):
1323-
return ""
1324-
1325-
# Need to do something different for non-Linux/Android targets
1326-
cpuinfo_path = self.getBuildArtifact("cpuinfo")
1327-
if configuration.lldb_platform_name:
1328-
self.runCmd(
1329-
'platform get-file "/proc/cpuinfo" ' + cpuinfo_path, check=False
1330-
)
1331-
if not self.res.Succeeded():
1332-
if self.TraceOn():
1333-
print(
1334-
'Failed to get /proc/cpuinfo from remote: "{}"'.format(
1335-
self.res.GetOutput().strip()
1336-
)
1337-
)
1338-
print("All cpuinfo feature checks will fail.")
1339-
return ""
1340-
else:
1341-
cpuinfo_path = "/proc/cpuinfo"
1342-
1343-
try:
1344-
with open(cpuinfo_path, "r") as f:
1345-
cpuinfo = f.read()
1346-
except:
1347-
return ""
1348-
1349-
return cpuinfo
1350-
13511319
def isAArch64(self):
13521320
"""Returns true if the architecture is AArch64."""
13531321
arch = self.getArchitecture().lower()
@@ -1360,39 +1328,47 @@ def isARM(self):
13601328
self.getArchitecture().lower().startswith("arm")
13611329
)
13621330

1331+
def isSupported(self, cpu_feature: cpu_feature.CPUFeature):
1332+
triple = self.dbg.GetSelectedPlatform().GetTriple()
1333+
cmd_runner = self.run_platform_command
1334+
return cpu_feature.is_supported(triple, cmd_runner)
1335+
13631336
def isAArch64SVE(self):
1364-
return self.isAArch64() and "sve" in self.getCPUInfo()
1337+
return self.isAArch64() and self.isSupported(cpu_feature.AArch64.SVE)
13651338

13661339
def isAArch64SME(self):
1367-
return self.isAArch64() and "sme" in self.getCPUInfo()
1340+
return self.isAArch64() and self.isSupported(cpu_feature.AArch64.SME)
13681341

13691342
def isAArch64SME2(self):
13701343
# If you have sme2, you also have sme.
1371-
return self.isAArch64() and "sme2" in self.getCPUInfo()
1344+
return self.isAArch64() and self.isSupported(cpu_feature.AArch64.SME2)
13721345

13731346
def isAArch64SMEFA64(self):
13741347
# smefa64 allows the use of the full A64 instruction set in streaming
13751348
# mode. This is required by certain test programs to setup register
13761349
# state.
1377-
cpuinfo = self.getCPUInfo()
1378-
return self.isAArch64() and "sme" in cpuinfo and "smefa64" in cpuinfo
1350+
return (
1351+
self.isAArch64()
1352+
and self.isSupported(cpu_feature.AArch64.SME)
1353+
and self.isSupported(cpu_feature.AArch64.SME_FA64)
1354+
)
13791355

13801356
def isAArch64MTE(self):
1381-
return self.isAArch64() and "mte" in self.getCPUInfo()
1357+
return self.isAArch64() and self.isSupported(cpu_feature.AArch64.MTE)
13821358

13831359
def isAArch64MTEStoreOnly(self):
1384-
return self.isAArch64() and "mtestoreonly" in self.getCPUInfo()
1360+
return self.isAArch64() and self.isSupported(cpu_feature.AArch64.MTE_STORE_ONLY)
13851361

13861362
def isAArch64GCS(self):
1387-
return self.isAArch64() and "gcs" in self.getCPUInfo()
1363+
return self.isAArch64() and self.isSupported(cpu_feature.AArch64.GCS)
13881364

13891365
def isAArch64PAuth(self):
13901366
if self.getArchitecture() == "arm64e":
13911367
return True
1392-
return self.isAArch64() and "paca" in self.getCPUInfo()
1368+
return self.isAArch64() and self.isSupported(cpu_feature.AArch64.PTR_AUTH)
13931369

13941370
def isAArch64FPMR(self):
1395-
return self.isAArch64() and "fpmr" in self.getCPUInfo()
1371+
return self.isAArch64() and self.isSupported(cpu_feature.AArch64.FPMR)
13961372

13971373
def isAArch64Windows(self):
13981374
"""Returns true if the architecture is AArch64 and platform windows."""
@@ -1407,10 +1383,10 @@ def isLoongArch(self):
14071383
return arch in ["loongarch64", "loongarch32"]
14081384

14091385
def isLoongArchLSX(self):
1410-
return self.isLoongArch() and "lsx" in self.getCPUInfo()
1386+
return self.isLoongArch() and self.isSupported(cpu_feature.Loong.LSX)
14111387

14121388
def isLoongArchLASX(self):
1413-
return self.isLoongArch() and "lasx" in self.getCPUInfo()
1389+
return self.isLoongArch() and self.isSupported(cpu_feature.Loong.LASX)
14141390

14151391
def isRISCV(self):
14161392
"""Returns true if the architecture is RISCV64 or RISCV32."""

lldb/test/API/commands/register/register/register_command/TestRegisters.py

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,6 @@ def tearDown(self):
2121
self.dbg.GetSelectedTarget().GetProcess().Destroy()
2222
TestBase.tearDown(self)
2323

24-
# on macOS, detect if the current machine is arm64 and supports SME
25-
def get_sme_available(self):
26-
if self.getArchitecture() != "arm64":
27-
return None
28-
try:
29-
sysctl_output = subprocess.check_output(
30-
["sysctl", "hw.optional.arm.FEAT_SME"]
31-
).decode("utf-8")
32-
except subprocess.CalledProcessError:
33-
return None
34-
m = re.match(r"hw\.optional\.arm\.FEAT_SME: (\w+)", sysctl_output)
35-
if m:
36-
if int(m.group(1)) == 1:
37-
return True
38-
else:
39-
return False
40-
return None
41-
4224
@skipIfiOSSimulator
4325
@skipIf(archs=no_match(["amd64", "arm$", "i386", "x86_64"]))
4426
@expectedFailureAll(oslist=["freebsd", "netbsd"], bugnumber="llvm.org/pr48371")
@@ -51,7 +33,7 @@ def test_register_commands(self):
5133
self.log_enable("registers")
5234

5335
error_str_matched = False
54-
if self.get_sme_available() and self.platformIsDarwin():
36+
if self.isAArch64SME() and self.platformIsDarwin():
5537
# On Darwin AArch64 SME machines, we will have unavailable
5638
# registers when not in Streaming SVE Mode/SME, so
5739
# `register read -a` will report that some registers

lldb/test/API/macosx/sme-registers/TestSMERegistersDarwin.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import lldb
22
from lldbsuite.test.lldbtest import *
33
from lldbsuite.test.decorators import *
4+
import lldbsuite.test.cpu_feature as cpu_feature
45
import lldbsuite.test.lldbutil as lldbutil
56
import os
67

@@ -9,10 +10,9 @@ class TestSMERegistersDarwin(TestBase):
910
NO_DEBUG_INFO_TESTCASE = True
1011
mydir = TestBase.compute_mydir(__file__)
1112

12-
@skipIfRemote
1313
@skipUnlessDarwin
14-
@skipUnlessFeature("hw.optional.arm.FEAT_SME")
15-
@skipUnlessFeature("hw.optional.arm.FEAT_SME2")
14+
@skipUnlessFeature(cpu_feature.AArch64.SME)
15+
@skipUnlessFeature(cpu_feature.AArch64.SME2)
1616
# thread_set_state/thread_get_state only avail in macOS 15.4+
1717
@skipIf(macos_version=["<", "15.4"])
1818
def test(self):

0 commit comments

Comments
 (0)