| //===-- RegisterFlagsDetector_arm64.cpp -----------------------------------===// | |
| // | |
| // 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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "RegisterFlagsDetector_arm64.h" | |
| #include "lldb/lldb-private-types.h" | |
| // This file is built on all systems because it is used by native processes and | |
| // core files, so we manually define the needed HWCAP values here. | |
| // These values are the same for Linux and FreeBSD. | |
| #define HWCAP_FPHP (1ULL << 9) | |
| #define HWCAP_ASIMDHP (1ULL << 10) | |
| #define HWCAP_DIT (1ULL << 24) | |
| #define HWCAP_SSBS (1ULL << 28) | |
| #define HWCAP_GCS (1ULL << 32) | |
| #define HWCAP2_BTI (1ULL << 17) | |
| #define HWCAP2_MTE (1ULL << 18) | |
| #define HWCAP2_AFP (1ULL << 20) | |
| #define HWCAP2_SME (1ULL << 23) | |
| #define HWCAP2_EBF16 (1ULL << 32) | |
| #define HWCAP2_FPMR (1ULL << 48) | |
| #define HWCAP2_POE (1ULL << 63) | |
| #define HWCAP3_MTE_STORE_ONLY (1ULL << 1) | |
| using namespace lldb_private; | |
| Arm64RegisterFlagsDetector::Fields | |
| Arm64RegisterFlagsDetector::DetectPOREL0Fields(uint64_t hwcap, uint64_t hwcap2, | |
| uint64_t hwcap3) { | |
| (void)hwcap; | |
| (void)hwcap3; | |
| if (!(hwcap2 & HWCAP2_POE)) | |
| return {}; | |
| static const FieldEnum por_el0_perm_enum("por_el0_perm_enum", | |
| { | |
| {0b0000, "No Access"}, | |
| {0b0001, "Read"}, | |
| {0b0010, "Execute"}, | |
| {0b0011, "Read, Execute"}, | |
| {0b0100, "Write"}, | |
| {0b0101, "Write, Read"}, | |
| {0b0110, "Write, Execute"}, | |
| {0b0111, "Read, Write, Execute"}, | |
| }); | |
| return { | |
| {"Perm15", 60, 63, &por_el0_perm_enum}, | |
| {"Perm14", 56, 59, &por_el0_perm_enum}, | |
| {"Perm13", 52, 55, &por_el0_perm_enum}, | |
| {"Perm12", 48, 51, &por_el0_perm_enum}, | |
| {"Perm11", 44, 47, &por_el0_perm_enum}, | |
| {"Perm10", 40, 43, &por_el0_perm_enum}, | |
| {"Perm9", 36, 39, &por_el0_perm_enum}, | |
| {"Perm8", 32, 35, &por_el0_perm_enum}, | |
| {"Perm7", 28, 31, &por_el0_perm_enum}, | |
| {"Perm6", 24, 27, &por_el0_perm_enum}, | |
| {"Perm5", 20, 23, &por_el0_perm_enum}, | |
| {"Perm4", 16, 19, &por_el0_perm_enum}, | |
| {"Perm3", 12, 15, &por_el0_perm_enum}, | |
| {"Perm2", 8, 11, &por_el0_perm_enum}, | |
| {"Perm1", 4, 7, &por_el0_perm_enum}, | |
| {"Perm0", 0, 3, &por_el0_perm_enum}, | |
| }; | |
| } | |
| Arm64RegisterFlagsDetector::Fields | |
| Arm64RegisterFlagsDetector::DetectFPMRFields(uint64_t hwcap, uint64_t hwcap2, | |
| uint64_t hwcap3) { | |
| (void)hwcap; | |
| (void)hwcap3; | |
| if (!(hwcap2 & HWCAP2_FPMR)) | |
| return {}; | |
| static const FieldEnum fp8_format_enum("fp8_format_enum", { | |
| {0, "FP8_E5M2"}, | |
| {1, "FP8_E4M3"}, | |
| }); | |
| return { | |
| {"LSCALE2", 32, 37}, | |
| {"NSCALE", 24, 31}, | |
| {"LSCALE", 16, 22}, | |
| {"OSC", 15}, | |
| {"OSM", 14}, | |
| {"F8D", 6, 8, &fp8_format_enum}, | |
| {"F8S2", 3, 5, &fp8_format_enum}, | |
| {"F8S1", 0, 2, &fp8_format_enum}, | |
| }; | |
| } | |
| Arm64RegisterFlagsDetector::Fields | |
| Arm64RegisterFlagsDetector::DetectGCSFeatureFields(uint64_t hwcap, | |
| uint64_t hwcap2, | |
| uint64_t hwcap3) { | |
| (void)hwcap2; | |
| (void)hwcap3; | |
| if (!(hwcap & HWCAP_GCS)) | |
| return {}; | |
| return { | |
| {"PUSH", 2}, | |
| {"WRITE", 1}, | |
| {"ENABLE", 0}, | |
| }; | |
| } | |
| Arm64RegisterFlagsDetector::Fields | |
| Arm64RegisterFlagsDetector::DetectSVCRFields(uint64_t hwcap, uint64_t hwcap2, | |
| uint64_t hwcap3) { | |
| (void)hwcap; | |
| (void)hwcap3; | |
| if (!(hwcap2 & HWCAP2_SME)) | |
| return {}; | |
| // Represents the pseudo register that lldb-server builds, which itself | |
| // matches the architectural register SCVR. The fields match SVCR in the Arm | |
| // manual. | |
| return { | |
| {"ZA", 1}, | |
| {"SM", 0}, | |
| }; | |
| } | |
| Arm64RegisterFlagsDetector::Fields | |
| Arm64RegisterFlagsDetector::DetectMTECtrlFields(uint64_t hwcap, uint64_t hwcap2, | |
| uint64_t hwcap3) { | |
| (void)hwcap; | |
| if (!(hwcap2 & HWCAP2_MTE)) | |
| return {}; | |
| // Represents the contents of NT_ARM_TAGGED_ADDR_CTRL and the value passed | |
| // to prctl(PR_TAGGED_ADDR_CTRL...). Fields are derived from the defines | |
| // used to build the value. | |
| std::vector<RegisterFlags::Field> fields; | |
| fields.reserve(4); | |
| if (hwcap3 & HWCAP3_MTE_STORE_ONLY) | |
| fields.push_back({"STORE_ONLY", 19}); | |
| static const FieldEnum tcf_enum( | |
| "tcf_enum", | |
| {{0, "TCF_NONE"}, {1, "TCF_SYNC"}, {2, "TCF_ASYNC"}, {3, "TCF_ASYMM"}}); | |
| fields.insert( | |
| std::end(fields), | |
| {{"TAGS", 3, 18}, // 16 bit bitfield shifted up by PR_MTE_TAG_SHIFT. | |
| {"TCF", 1, 2, &tcf_enum}, | |
| {"TAGGED_ADDR_ENABLE", 0}}); | |
| return fields; | |
| } | |
| Arm64RegisterFlagsDetector::Fields | |
| Arm64RegisterFlagsDetector::DetectFPCRFields(uint64_t hwcap, uint64_t hwcap2, | |
| uint64_t hwcap3) { | |
| (void)hwcap3; | |
| static const FieldEnum rmode_enum( | |
| "rmode_enum", {{0, "RN"}, {1, "RP"}, {2, "RM"}, {3, "RZ"}}); | |
| std::vector<RegisterFlags::Field> fpcr_fields{ | |
| {"AHP", 26}, {"DN", 25}, {"FZ", 24}, {"RMode", 22, 23, &rmode_enum}, | |
| // Bits 21-20 are "Stride" which is unused in AArch64 state. | |
| }; | |
| // FEAT_FP16 is indicated by the presence of FPHP (floating point half | |
| // precision) and ASIMDHP (Advanced SIMD half precision) features. | |
| if ((hwcap & HWCAP_FPHP) && (hwcap & HWCAP_ASIMDHP)) | |
| fpcr_fields.push_back({"FZ16", 19}); | |
| // Bits 18-16 are "Len" which is unused in AArch64 state. | |
| fpcr_fields.push_back({"IDE", 15}); | |
| // Bit 14 is unused. | |
| if (hwcap2 & HWCAP2_EBF16) | |
| fpcr_fields.push_back({"EBF", 13}); | |
| fpcr_fields.push_back({"IXE", 12}); | |
| fpcr_fields.push_back({"UFE", 11}); | |
| fpcr_fields.push_back({"OFE", 10}); | |
| fpcr_fields.push_back({"DZE", 9}); | |
| fpcr_fields.push_back({"IOE", 8}); | |
| // Bits 7-3 reserved. | |
| if (hwcap2 & HWCAP2_AFP) { | |
| fpcr_fields.push_back({"NEP", 2}); | |
| fpcr_fields.push_back({"AH", 1}); | |
| fpcr_fields.push_back({"FIZ", 0}); | |
| } | |
| return fpcr_fields; | |
| } | |
| Arm64RegisterFlagsDetector::Fields | |
| Arm64RegisterFlagsDetector::DetectFPSRFields(uint64_t hwcap, uint64_t hwcap2, | |
| uint64_t hwcap3) { | |
| // fpsr's contents are constant. | |
| (void)hwcap; | |
| (void)hwcap2; | |
| (void)hwcap3; | |
| return { | |
| // Bits 31-28 are N/Z/C/V, only used by AArch32. | |
| {"QC", 27}, | |
| // Bits 26-8 reserved. | |
| {"IDC", 7}, | |
| // Bits 6-5 reserved. | |
| {"IXC", 4}, | |
| {"UFC", 3}, | |
| {"OFC", 2}, | |
| {"DZC", 1}, | |
| {"IOC", 0}, | |
| }; | |
| } | |
| Arm64RegisterFlagsDetector::Fields | |
| Arm64RegisterFlagsDetector::DetectCPSRFields(uint64_t hwcap, uint64_t hwcap2, | |
| uint64_t hwcap3) { | |
| (void)hwcap3; | |
| // The fields here are a combination of the Arm manual's SPSR_EL1, | |
| // plus a few changes where Linux has decided not to make use of them at all, | |
| // or at least not from userspace. | |
| // Status bits that are always present. | |
| std::vector<RegisterFlags::Field> cpsr_fields{ | |
| {"N", 31}, {"Z", 30}, {"C", 29}, {"V", 28}, | |
| // Bits 27-26 reserved. | |
| }; | |
| if (hwcap2 & HWCAP2_MTE) | |
| cpsr_fields.push_back({"TCO", 25}); | |
| if (hwcap & HWCAP_DIT) | |
| cpsr_fields.push_back({"DIT", 24}); | |
| // UAO and PAN are bits 23 and 22 and have no meaning for userspace so | |
| // are treated as reserved by the kernels. | |
| cpsr_fields.push_back({"SS", 21}); | |
| cpsr_fields.push_back({"IL", 20}); | |
| // Bits 19-14 reserved. | |
| // Bit 13, ALLINT, requires FEAT_NMI that isn't relevant to userspace, and we | |
| // can't detect either, don't show this field. | |
| if (hwcap & HWCAP_SSBS) | |
| cpsr_fields.push_back({"SSBS", 12}); | |
| if (hwcap2 & HWCAP2_BTI) | |
| cpsr_fields.push_back({"BTYPE", 10, 11}); | |
| cpsr_fields.push_back({"D", 9}); | |
| cpsr_fields.push_back({"A", 8}); | |
| cpsr_fields.push_back({"I", 7}); | |
| cpsr_fields.push_back({"F", 6}); | |
| // Bit 5 reserved | |
| // Called "M" in the ARMARM. | |
| cpsr_fields.push_back({"nRW", 4}); | |
| // This is a 4 bit field M[3:0] in the ARMARM, we split it into parts. | |
| cpsr_fields.push_back({"EL", 2, 3}); | |
| // Bit 1 is unused and expected to be 0. | |
| cpsr_fields.push_back({"SP", 0}); | |
| return cpsr_fields; | |
| } | |
| void Arm64RegisterFlagsDetector::DetectFields(uint64_t hwcap, uint64_t hwcap2, | |
| uint64_t hwcap3) { | |
| for (auto ® : m_registers) | |
| reg.m_flags.SetFields(reg.m_detector(hwcap, hwcap2, hwcap3)); | |
| m_has_detected = true; | |
| } | |
| void Arm64RegisterFlagsDetector::UpdateRegisterInfo( | |
| const RegisterInfo *reg_info, uint32_t num_regs) { | |
| assert(m_has_detected && | |
| "Must call DetectFields before updating register info."); | |
| // Register names will not be duplicated, so we do not want to compare against | |
| // one if it has already been found. Each time we find one, we erase it from | |
| // this list. | |
| std::vector<std::pair<llvm::StringRef, const RegisterFlags *>> | |
| search_registers; | |
| for (const auto ® : m_registers) { | |
| // It is possible that a register is all extension dependent fields, and | |
| // none of them are present. | |
| if (reg.m_flags.GetFields().size()) | |
| search_registers.push_back({reg.m_name, ®.m_flags}); | |
| } | |
| // Walk register information while there are registers we know need | |
| // to be updated. Example: | |
| // Register information: [a, b, c, d] | |
| // To be patched: [b, c] | |
| // * a != b, a != c, do nothing and move on. | |
| // * b == b, patch b, new patch list is [c], move on. | |
| // * c == c, patch c, patch list is empty, exit early without looking at d. | |
| for (uint32_t idx = 0; idx < num_regs && search_registers.size(); | |
| ++idx, ++reg_info) { | |
| auto reg_it = std::find_if( | |
| search_registers.cbegin(), search_registers.cend(), | |
| [reg_info](auto reg) { return reg.first == reg_info->name; }); | |
| if (reg_it != search_registers.end()) { | |
| // Attach the field information. | |
| reg_info->flags_type = reg_it->second; | |
| // We do not expect to see this name again so don't look for it again. | |
| search_registers.erase(reg_it); | |
| } | |
| } | |
| // We do not assert that search_registers is empty here, because it may | |
| // contain registers from optional extensions that are not present on the | |
| // current target. | |
| } |