[Lldb-commits] [lldb] [lldb][AArch64][Linux] Add tests for SME only support (PR #165414)

Jason Molenda via lldb-commits lldb-commits at lists.llvm.org
Mon Nov 3 14:58:01 PST 2025


================
@@ -0,0 +1,553 @@
+"""
+Check handling of registers on an AArch64 Linux system that only has SME. As
+opposed to SVE and SME. Check register access and restoration after expression
+evaluation.
+"""
+
+from enum import Enum
+from pprint import pprint
+from functools import lru_cache
+from itertools import permutations
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+from itertools import cycle
+
+
+class Mode(Enum):
+    SIMD = 0
+    SSVE = 2
+
+    def __str__(self):
+        return "streaming" if self == Mode.SSVE else "simd"
+
+
+class ZA(Enum):
+    ON = 1
+    OFF = 2
+
+    def __str__(self):
+        return "on" if self == ZA.ON else "off"
+
+
+class ByteVector(object):
+    def __init__(self, data):
+        self.data = data
+
+    def __repr__(self):
+        return "{" + " ".join([f"0x{b:02x}" for b in self.data]) + "}"
+
+    def as_bytes(self):
+        return self.data
+
+
+class HexValue(object):
+    # Assume all values are 64-bit, but some display as 32-bit, like fpcr.
+    def __init__(self, value, repr_size=8):
+        self.value = value
+        # In bytes
+        self.repr_size = repr_size
+
+    def __repr__(self):
+        return f"0x{self.value:0{self.repr_size*2}x}"
+
+    def as_bytes(self):
+        data = []
+        v = self.value
+        # Little endian order.
+        for i in range(8):
+            data.append(v & 0xFF)
+            v >>= 8
+
+        return data
+
+
+class SVESIMDRegistersTestCase(TestBase):
+    def reg_names(self, prefix, count):
+        return [f"{prefix}{n}" for n in range(count)]
+
+    def expected_registers(self, svl_b, mode, za):
+        register_values = []
+
+        if mode == Mode.SIMD:
+            # In streaming mode we have real V registers and no Z registers.
+
+            # V regs are {N <7 0s> N <7 0s>} because we set the bottom element to N
+            # where N is 1 + the register index.
+            v_values = [
+                ByteVector([n + 1] + [0] * 7 + [n + 1] + [0] * 7) for n in range(32)
+            ]
+
+            # Z regs are {N <7 0s> N <7 0s> <16 more 0s}. First half overlaps a V
+            # register, the second half we fake 0s for as there is no real Z register
+            # in non-streaming mode.
+            z_values = [
+                ByteVector([n + 1] + [0] * 7 + [n + 1] + [0] * 7 + [0] * (svl_b - 16))
+                for n in range(32)
+            ]
+
+            # P regs are {<4 0s>}, we fake the value.
+            p_values = [ByteVector([0] * (svl_b // 8)) for _ in range(16)]
+        else:
+            # In streaming mode, Z registers are real and V are the bottom 128
+            # bits of the Z registers.
+
+            # Streaming SVE registers have their elements set to their number plus 1.
+            # So z0 has elements of 0x01, z1 is 0x02 and so on.
+            v_values = [ByteVector([n + 1] * 16) for n in range(32)]
+
+            z_values = [ByteVector([n + 1] * svl_b) for n in range(32)]
+
+            # P registers have all emlements set to the same value and that value
+            # cycles between 0xff, 0x55, 0x11, 0x01 and 0x00.
+            p_values = []
+            for i, v in zip(range(16), cycle([0xFF, 0x55, 0x11, 0x01, 0x00])):
+                p_values.append(ByteVector([v] * (svl_b // 8)))
+
+        # Would use strict=True here but it requires Python 3.10.
+        register_values += list(zip(self.reg_names("v", 32), v_values))
+        register_values += [
+            ("fpsr", HexValue(0x50000015, repr_size=4)),
+            ("fpcr", HexValue(0x05551505, repr_size=4)),
+        ]
+        register_values += list(zip(self.reg_names("z", 32), z_values))
+        register_values += list(zip(self.reg_names("p", 16), p_values))
+
+        # ffr is all 0s. In SIMD mode we're faking the value, in streaming mode,
+        # use of ffr is illegal so the kernel tells us it's 0s.
+        register_values += [("ffr", ByteVector([0] * (svl_b // 8)))]
+
+        svcr_value = 1 if mode == Mode.SSVE else 0
+        if za == ZA.ON:
+            svcr_value += 2
+
+        register_values += [
+            ("svcr", HexValue(svcr_value)),
+            # SVG is the streaming vector length in granules.
+            ("svg", HexValue(svl_b // 8)),
+        ]
+
+        # ZA and ZTO may be enabled or disabled regardless of streaming mode.
+        # ZA is a square of vector length * vector length.
+        # ZT0 is 512 bits regardless of streaming vector length.
+        if za == ZA.ON:
+            register_values += [("za", ByteVector(list(range(1, svl_b + 1)) * svl_b))]
+            if self.isAArch64SME2():
+                register_values += [("zt0", ByteVector(list(range(1, (512 // 8) + 1))))]
+        else:
+            # ZA is fake.
+            register_values += [("za", ByteVector([0x0] * (svl_b * svl_b)))]
+            # ZT0 is also fake.
+            if self.isAArch64SME2():
+                register_values += [("zt0", ByteVector([0x00] * (512 // 8)))]
+
+        return dict(register_values)
+
+    def check_expected_regs_fn(self, expected_registers):
+        def check_expected_regs():
+            self.expect(
+                f'register read {" ".join(expected_registers.keys())}',
+                substrs=[f"{n} = {v}" for n, v in expected_registers.items()],
+            )
+
+        return check_expected_regs
+
+    def skip_if_not_sme_only(self):
+        if self.isAArch64SVE():
+            self.skipTest("SVE must not be present outside of streaming mode.")
+
+        if not self.isAArch64SME():
+            self.skipTest("SSVE registers must be supported.")
+
+    def setup_test(self, mode, za, svl):
+        self.build()
+        self.line = line_number("main.c", "// Set a break point here.")
+
+        exe = self.getBuildArtifact("a.out")
+        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
+
+        self.runCmd(f"settings set target.run-args {mode} {za} {svl}")
+
+        lldbutil.run_break_set_by_file_and_line(
+            self, "main.c", self.line, num_expected_locations=1
+        )
+        self.runCmd("run", RUN_SUCCEEDED)
+
+        self.expect(
+            "thread backtrace",
+            STOPPED_DUE_TO_BREAKPOINT,
+            substrs=["stop reason = breakpoint 1."],
+        )
+
+    def write_expected_reg_data(self, reg_data):
+        # Write expected register values into program memory so it can be
+        # verified in-process.
+        # This must be done via. memory write instead of expressions because
----------------
jasonmolenda wrote:

silly thing, but no period after via.

https://github.com/llvm/llvm-project/pull/165414


More information about the lldb-commits mailing list