blob: 9f6b106a21fecb6848f3ceb229c3a6ad4f12c0ba [file] [log] [blame]
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// REQUIRES: target={{aarch64-.+}}
// UNSUPPORTED: target={{.*-windows.*}}
#include <libunwind.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/auxv.h>
// Basic test of unwinding with SME lazy saves. This tests libunwind disables ZA
// (and commits a lazy save of ZA) before resuming from unwinding.
// Note: This test requires SME (and is setup to pass on targets without SME).
static bool checkHasSME() {
constexpr int hwcap2_sme = (1 << 23);
unsigned long hwcap2 = getauxval(AT_HWCAP2);
return (hwcap2 & hwcap2_sme) != 0;
}
struct TPIDR2Block {
void *za_save_buffer;
uint64_t num_save_slices;
};
__attribute__((noinline)) void private_za() {
// Note: Lazy save active on entry to function.
unw_context_t context;
unw_cursor_t cursor;
unw_getcontext(&context);
unw_init_local(&cursor, &context);
unw_step(&cursor);
unw_resume(&cursor);
}
bool isZAOn() {
register uint64_t svcr asm("x20");
asm(".inst 0xd53b4254" : "=r"(svcr));
return (svcr & 0b10) != 0;
}
__attribute__((noinline)) void za_function_with_lazy_save() {
register uint64_t tmp asm("x8");
// SMSTART ZA (should zero ZA)
asm(".inst 0xd503457f");
// RDSVL x8, #1 (read streaming vector length)
asm(".inst 0x04bf5828" : "=r"(tmp));
// Allocate and fill ZA save buffer with 0xAA.
size_t buffer_size = tmp * tmp;
uint8_t *za_save_buffer = (uint8_t *)alloca(buffer_size);
memset(za_save_buffer, 0xAA, buffer_size);
TPIDR2Block block = {za_save_buffer, tmp};
tmp = reinterpret_cast<uint64_t>(&block);
// MRS TPIDR2_EL0, x8 (setup lazy save of ZA)
asm(".inst 0xd51bd0a8" ::"r"(tmp));
// ZA should be on before unwinding.
if (!isZAOn()) {
fprintf(stderr, __FILE__ ": fail (ZA not on before call)\n");
abort();
} else {
fprintf(stderr, __FILE__ ": pass (ZA on before call)\n");
}
private_za();
// ZA should be off after unwinding.
if (isZAOn()) {
fprintf(stderr, __FILE__ ": fail (ZA on after unwinding)\n");
abort();
} else {
fprintf(stderr, __FILE__ ": pass (ZA off after unwinding)\n");
}
// MRS x8, TPIDR2_EL0 (read TPIDR2_EL0)
asm(".inst 0xd53bd0a8" : "=r"(tmp));
// ZA should have been saved (TPIDR2_EL0 zero).
if (tmp != 0) {
fprintf(stderr, __FILE__ ": fail (TPIDR2_EL0 non-null after unwinding)\n");
abort();
} else {
fprintf(stderr, __FILE__ ": pass (TPIDR2_EL0 null after unwinding)\n");
}
// ZA (all zero) should have been saved to the buffer.
for (unsigned i = 0; i < buffer_size; ++i) {
if (za_save_buffer[i] != 0) {
fprintf(stderr,
__FILE__ ": fail (za_save_buffer non-zero after unwinding)\n");
abort();
}
}
fprintf(stderr, __FILE__ ": pass (za_save_buffer zero'd after unwinding)\n");
}
int main(int, char **) {
if (!checkHasSME()) {
fprintf(stderr, __FILE__ ": pass (no SME support)\n");
return 0; // Pass (SME is required for this test to run).
}
za_function_with_lazy_save();
return 0;
}