Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions lldb/packages/Python/lldbsuite/test/cpu_feature.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""
Platform-agnostic helper to query for CPU features.
"""

import re


class CPUFeature:
def __init__(self, linux_cpu_info_flag: str = None, darwin_sysctl_key: str = None):
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)
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):
if not self.cpu_info_flag:
return "Unspecified cpuinfo flag", False

cmd = "cat /proc/cpuinfo"
err, retcode, output = cmd_runner(cmd)
if err.Fail() or retcode != 0:
return output, False

# FIXME: simple substring match, e.g., test for 'sme' will be true if
# 'sme2' or 'smefa64' is present
return None, (self.cpu_info_flag in output)

def _is_supported_darwin(self, cmd_runner):
if not self.sysctl_key:
return "Unspecified sysctl key", False

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")


class AArch64:
FPMR = CPUFeature("fpmr")
GCS = CPUFeature("gcs")
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")


class Loong:
LASX = CPUFeature("lasx")
LSX = CPUFeature("lsx")
24 changes: 7 additions & 17 deletions lldb/packages/Python/lldbsuite/test/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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]):
Expand Down
66 changes: 21 additions & 45 deletions lldb/packages/Python/lldbsuite/test/lldbtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -1360,39 +1328,47 @@ 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.AArch64.SVE)

def isAArch64SME(self):
return self.isAArch64() and "sme" in self.getCPUInfo()
return self.isAArch64() and self.isSupported(cpu_feature.AArch64.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.AArch64.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
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that cpuinfo here is just the contents of /proc/cpuinfo as a string (not a collection of flags). So testing with a prefix/substring will always be trivially true (and is nonsensical), e.g., sme will automatically be true if sme2 or smefa64 is present. I did not change the behavior of this.

One way to fix this would be to parse and merge the CPU flags in cpuinfo and use exact match on that set of flags.

return (
self.isAArch64()
and self.isSupported(cpu_feature.AArch64.SME)
and self.isSupported(cpu_feature.AArch64.SME_FA64)
)

def isAArch64MTE(self):
return self.isAArch64() and "mte" in self.getCPUInfo()
return self.isAArch64() and self.isSupported(cpu_feature.AArch64.MTE)

def isAArch64MTEStoreOnly(self):
return self.isAArch64() and "mtestoreonly" in self.getCPUInfo()
return self.isAArch64() and self.isSupported(cpu_feature.AArch64.MTE_STORE_ONLY)

def isAArch64GCS(self):
return self.isAArch64() and "gcs" in self.getCPUInfo()
return self.isAArch64() and self.isSupported(cpu_feature.AArch64.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.AArch64.PTR_AUTH)

def isAArch64FPMR(self):
return self.isAArch64() and "fpmr" in self.getCPUInfo()
return self.isAArch64() and self.isSupported(cpu_feature.AArch64.FPMR)

def isAArch64Windows(self):
"""Returns true if the architecture is AArch64 and platform windows."""
Expand All @@ -1407,10 +1383,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.Loong.LSX)

def isLoongArchLASX(self):
return self.isLoongArch() and "lasx" in self.getCPUInfo()
return self.isLoongArch() and self.isSupported(cpu_feature.Loong.LASX)

def isRISCV(self):
"""Returns true if the architecture is RISCV64 or RISCV32."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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.AArch64.SME)
@skipUnlessFeature(cpu_feature.AArch64.SME2)
# thread_set_state/thread_get_state only avail in macOS 15.4+
@skipIf(macos_version=["<", "15.4"])
def test(self):
Expand Down
Loading