Skip to content

Commit bfab808

Browse files
authored
[libunwind] Add support for the AArch64 "Vector Granule" (VG) register (llvm#153565)
The vector granule (AArch64 DWARF register 46) is a pseudo-register that contains the available size in bits of SVE vector registers in the current call frame, divided by 64. The vector granule can be used in DWARF expressions to describe SVE/SME stack frame layouts (e.g., the location of SVE callee-saves). The first time VG is evaluated (if not already set), it is initialized to the result of evaluating a "CNTD" instruction (this assumes SVE is available). To support SME, the value of VG can change per call frame; this is currently handled like any other callee-save and is intended to support the unwind information implemented in llvm#152283. This limits how VG is used in the CFI information of functions with "streaming-mode changes" (mode changes that change the SVE vector length), to make the unwinder's job easier.
1 parent 9039b59 commit bfab808

File tree

4 files changed

+102
-4
lines changed

4 files changed

+102
-4
lines changed

libunwind/include/__libunwind_config.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,11 @@
7373
# define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_PPC
7474
# elif defined(__aarch64__)
7575
# define _LIBUNWIND_TARGET_AARCH64 1
76-
# define _LIBUNWIND_CONTEXT_SIZE 66
76+
#define _LIBUNWIND_CONTEXT_SIZE 67
7777
# if defined(__SEH__)
7878
# define _LIBUNWIND_CURSOR_SIZE 164
7979
# else
80-
# define _LIBUNWIND_CURSOR_SIZE 78
80+
#define _LIBUNWIND_CURSOR_SIZE 79
8181
# endif
8282
# define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_ARM64
8383
# elif defined(__arm__)

libunwind/include/libunwind.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,7 @@ enum {
532532
UNW_AARCH64_X31 = 31,
533533
UNW_AARCH64_SP = 31,
534534
UNW_AARCH64_PC = 32,
535+
UNW_AARCH64_VG = 46,
535536

536537
// reserved block
537538
UNW_AARCH64_RA_SIGN_STATE = 34,

libunwind/src/Registers.hpp

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1851,6 +1851,8 @@ class _LIBUNWIND_HIDDEN Registers_arm64 {
18511851
void setFP(uint64_t value) { _registers.__fp = value; }
18521852

18531853
private:
1854+
uint64_t lazyGetVG() const;
1855+
18541856
struct GPRs {
18551857
uint64_t __x[29]; // x0-x28
18561858
uint64_t __fp; // Frame pointer x29
@@ -1860,12 +1862,22 @@ class _LIBUNWIND_HIDDEN Registers_arm64 {
18601862
uint64_t __ra_sign_state; // RA sign state register
18611863
};
18621864

1863-
GPRs _registers;
1864-
double _vectorHalfRegisters[32];
1865+
struct Misc {
1866+
mutable uint64_t __vg = 0; // Vector Granule
1867+
};
1868+
1869+
GPRs _registers;
18651870
// Currently only the lower double in 128-bit vectore registers
18661871
// is perserved during unwinding. We could define new register
18671872
// numbers (> 96) which mean whole vector registers, then this
18681873
// struct would need to change to contain whole vector registers.
1874+
double _vectorHalfRegisters[32];
1875+
1876+
// Miscellaneous/virtual registers. These are stored below the GPRs and FPRs
1877+
// as they do not correspond to physical registers, so do not need to be
1878+
// saved/restored in UnwindRegistersRestore.S and UnwindRegistersSave.S, and
1879+
// we don't want to modify the existing offsets for GPRs and FPRs.
1880+
Misc _misc_registers;
18691881
};
18701882

18711883
inline Registers_arm64::Registers_arm64(const void *registers) {
@@ -1895,11 +1907,27 @@ inline bool Registers_arm64::validRegister(int regNum) const {
18951907
return false;
18961908
if (regNum == UNW_AARCH64_RA_SIGN_STATE)
18971909
return true;
1910+
if (regNum == UNW_AARCH64_VG)
1911+
return true;
18981912
if ((regNum > 32) && (regNum < 64))
18991913
return false;
19001914
return true;
19011915
}
19021916

1917+
inline uint64_t Registers_arm64::lazyGetVG() const {
1918+
if (!_misc_registers.__vg) {
1919+
#if defined(__aarch64__)
1920+
register uint64_t vg asm("x0");
1921+
asm(".inst 0x04e0e3e0" // CNTD x0
1922+
: "=r"(vg));
1923+
_misc_registers.__vg = vg;
1924+
#else
1925+
_LIBUNWIND_ABORT("arm64 VG undefined");
1926+
#endif
1927+
}
1928+
return _misc_registers.__vg;
1929+
}
1930+
19031931
inline uint64_t Registers_arm64::getRegister(int regNum) const {
19041932
if (regNum == UNW_REG_IP || regNum == UNW_AARCH64_PC)
19051933
return _registers.__pc;
@@ -1911,6 +1939,8 @@ inline uint64_t Registers_arm64::getRegister(int regNum) const {
19111939
return _registers.__fp;
19121940
if (regNum == UNW_AARCH64_LR)
19131941
return _registers.__lr;
1942+
if (regNum == UNW_AARCH64_VG)
1943+
return lazyGetVG();
19141944
if ((regNum >= 0) && (regNum < 29))
19151945
return _registers.__x[regNum];
19161946
_LIBUNWIND_ABORT("unsupported arm64 register");
@@ -1927,6 +1957,8 @@ inline void Registers_arm64::setRegister(int regNum, uint64_t value) {
19271957
_registers.__fp = value;
19281958
else if (regNum == UNW_AARCH64_LR)
19291959
_registers.__lr = value;
1960+
else if (regNum == UNW_AARCH64_VG)
1961+
_misc_registers.__vg = value;
19301962
else if ((regNum >= 0) && (regNum < 29))
19311963
_registers.__x[regNum] = value;
19321964
else
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// REQUIRES: linux && target={{aarch64-.+}}
10+
11+
#include <libunwind.h>
12+
#include <stdlib.h>
13+
#include <string.h>
14+
15+
// Basic test of VG (Vector Granule) unwinding. This is meant to mimic SVE/SME
16+
// unwind info without requiring those features for this test.
17+
18+
#define VG_REGNUM 46
19+
20+
__attribute__((noinline)) void baz() {
21+
// The previous value of VG is 2
22+
asm(".cfi_escape 0x16, 0x2e, 0x01, 0x32");
23+
24+
unw_context_t context;
25+
unw_cursor_t cursor;
26+
unw_getcontext(&context);
27+
unw_init_local(&cursor, &context);
28+
29+
// Note: At this point VG is not defined (until we unw_step).
30+
31+
uint16_t expected_vgs[]{/*qux*/ 2, /*bar*/ 2, /*foo*/ 8, /*main*/ 2};
32+
for (uint16_t expected_vg : expected_vgs) {
33+
unw_step(&cursor);
34+
unw_word_t vg;
35+
unw_get_reg(&cursor, VG_REGNUM, &vg);
36+
if (vg != expected_vg)
37+
exit(1);
38+
}
39+
exit(0);
40+
}
41+
42+
__attribute__((noinline)) void qux() { baz(); }
43+
44+
__attribute__((noinline)) void bar() {
45+
// The previous value of VG is 8
46+
asm(".cfi_escape 0x16, 0x2e, 0x01, 0x38");
47+
// The previous value of W21 is VG (used to force an evaluation of VG).
48+
asm(".cfi_escape 0x16, 0x15, 0x03, 0x92, 0x2e, 0x00");
49+
50+
// smstop sm
51+
qux();
52+
// smstart sm
53+
}
54+
__attribute__((noinline)) void foo() {
55+
// The previous value of VG is 2
56+
asm(".cfi_escape 0x16, 0x2e, 0x01, 0x32");
57+
// The previous value of W21 is VG (used to force an evaluation of VG).
58+
asm(".cfi_escape 0x16, 0x15, 0x03, 0x92, 0x2e, 0x00");
59+
60+
// smstart sm
61+
bar();
62+
// smstop sm
63+
}
64+
65+
int main() { foo(); }

0 commit comments

Comments
 (0)