// Copyright 2020 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "Core/PowerPC/Expression.h" #include #include #include #include #include #include #include #include // https://github.com/zserge/expr/ is a C program and sorta valid C++. // When included in a C++ program, it's treated as a C++ code, and it may cause // issues: may already be included, if so, including may // not do anything. is obligated to put its functions in the global // namespace, while may or may not. The C code we're interpreting as // C++ won't call functions by their qualified names. The code may work anyway // if puts its functions in the global namespace, or if the functions // are actually macros that expand inline, both of which are common. // NetBSD 10.0 i386 is an exception, and we need `using` there. using std::isinf; using std::isnan; #include #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" #include "Core/Core.h" #include "Core/Debugger/Debugger_SymbolMap.h" #include "Core/PowerPC/MMU.h" #include "Core/PowerPC/PowerPC.h" #include "Core/System.h" template static T HostRead(const Core::CPUThreadGuard& guard, u32 address); template static void HostWrite(const Core::CPUThreadGuard& guard, T var, u32 address); template <> u8 HostRead(const Core::CPUThreadGuard& guard, u32 address) { return PowerPC::MMU::HostRead_U8(guard, address); } template <> u16 HostRead(const Core::CPUThreadGuard& guard, u32 address) { return PowerPC::MMU::HostRead_U16(guard, address); } template <> u32 HostRead(const Core::CPUThreadGuard& guard, u32 address) { return PowerPC::MMU::HostRead_U32(guard, address); } template <> u64 HostRead(const Core::CPUThreadGuard& guard, u32 address) { return PowerPC::MMU::HostRead_U64(guard, address); } template <> void HostWrite(const Core::CPUThreadGuard& guard, u8 var, u32 address) { PowerPC::MMU::HostWrite_U8(guard, var, address); } template <> void HostWrite(const Core::CPUThreadGuard& guard, u16 var, u32 address) { PowerPC::MMU::HostWrite_U16(guard, var, address); } template <> void HostWrite(const Core::CPUThreadGuard& guard, u32 var, u32 address) { PowerPC::MMU::HostWrite_U32(guard, var, address); } template <> void HostWrite(const Core::CPUThreadGuard& guard, u64 var, u32 address) { PowerPC::MMU::HostWrite_U64(guard, var, address); } template static double HostReadFunc(expr_func* f, vec_expr_t* args, void* c) { if (vec_len(args) != 1) return 0; const u32 address = static_cast(expr_eval(&vec_nth(args, 0))); Core::CPUThreadGuard guard(Core::System::GetInstance()); return std::bit_cast(HostRead(guard, address)); } template static double HostWriteFunc(expr_func* f, vec_expr_t* args, void* c) { if (vec_len(args) != 2) return 0; const T var = static_cast(expr_eval(&vec_nth(args, 0))); const u32 address = static_cast(expr_eval(&vec_nth(args, 1))); Core::CPUThreadGuard guard(Core::System::GetInstance()); HostWrite(guard, std::bit_cast(var), address); return var; } template static double CastFunc(expr_func* f, vec_expr_t* args, void* c) { if (vec_len(args) != 1) return 0; return std::bit_cast(static_cast(expr_eval(&vec_nth(args, 0)))); } static double CallstackFunc(expr_func* f, vec_expr_t* args, void* c) { if (vec_len(args) != 1) return 0; std::vector stack; { Core::CPUThreadGuard guard(Core::System::GetInstance()); const bool success = Dolphin_Debugger::GetCallstack(guard, stack); if (!success) return 0; } double num = expr_eval(&vec_nth(args, 0)); if (!std::isnan(num)) { u32 address = static_cast(num); return std::any_of(stack.begin(), stack.end(), [address](const auto& s) { return s.vAddress == address; }); } const char* cstr = expr_get_str(&vec_nth(args, 0)); if (cstr != nullptr) { return std::any_of(stack.begin(), stack.end(), [cstr](const auto& s) { return s.Name.find(cstr) != std::string::npos; }); } return 0; } static std::optional ReadStringArg(const Core::CPUThreadGuard& guard, expr* e) { double num = expr_eval(e); if (!std::isnan(num)) { u32 address = static_cast(num); return PowerPC::MMU::HostGetString(guard, address); } const char* cstr = expr_get_str(e); if (cstr != nullptr) { return std::string(cstr); } return std::nullopt; } static double StreqFunc(expr_func* f, vec_expr_t* args, void* c) { if (vec_len(args) != 2) return 0; std::array strs; Core::CPUThreadGuard guard(Core::System::GetInstance()); for (int i = 0; i < 2; i++) { std::optional arg = ReadStringArg(guard, &vec_nth(args, i)); if (arg == std::nullopt) return 0; strs[i] = std::move(*arg); } return strs[0] == strs[1]; } static std::array g_expr_funcs{{ // For internal storage and comparisons, everything is auto-converted to Double. // If u64 ints are added, this could produce incorrect results. {"read_u8", HostReadFunc}, {"read_s8", HostReadFunc}, {"read_u16", HostReadFunc}, {"read_s16", HostReadFunc}, {"read_u32", HostReadFunc}, {"read_s32", HostReadFunc}, {"read_f32", HostReadFunc}, {"read_f64", HostReadFunc}, {"write_u8", HostWriteFunc}, {"write_u16", HostWriteFunc}, {"write_u32", HostWriteFunc}, {"write_f32", HostWriteFunc}, {"write_f64", HostWriteFunc}, {"u8", CastFunc}, {"s8", CastFunc}, {"u16", CastFunc}, {"s16", CastFunc}, {"u32", CastFunc}, {"s32", CastFunc}, {"callstack", CallstackFunc}, {"streq", StreqFunc}, {}, }}; void ExprDeleter::operator()(expr* expression) const { expr_destroy(expression, nullptr); } void ExprVarListDeleter::operator()(expr_var_list* vars) const { // Free list elements expr_destroy(nullptr, vars); // Free list object delete vars; } Expression::Expression(std::string_view text, ExprPointer ex, ExprVarListPointer vars) : m_text(text), m_expr(std::move(ex)), m_vars(std::move(vars)) { using LookupKV = std::pair; static constexpr auto sorted_lookup = []() consteval { using enum Expression::VarBindingType; auto unsorted_lookup = std::to_array({ {"r0", {GPR, 0}}, {"r1", {GPR, 1}}, {"r2", {GPR, 2}}, {"r3", {GPR, 3}}, {"r4", {GPR, 4}}, {"r5", {GPR, 5}}, {"r6", {GPR, 6}}, {"r7", {GPR, 7}}, {"r8", {GPR, 8}}, {"r9", {GPR, 9}}, {"r10", {GPR, 10}}, {"r11", {GPR, 11}}, {"r12", {GPR, 12}}, {"r13", {GPR, 13}}, {"r14", {GPR, 14}}, {"r15", {GPR, 15}}, {"r16", {GPR, 16}}, {"r17", {GPR, 17}}, {"r18", {GPR, 18}}, {"r19", {GPR, 19}}, {"r20", {GPR, 20}}, {"r21", {GPR, 21}}, {"r22", {GPR, 22}}, {"r23", {GPR, 23}}, {"r24", {GPR, 24}}, {"r25", {GPR, 25}}, {"r26", {GPR, 26}}, {"r27", {GPR, 27}}, {"r28", {GPR, 28}}, {"r29", {GPR, 29}}, {"r30", {GPR, 30}}, {"r31", {GPR, 31}}, {"f0", {FPR, 0}}, {"f1", {FPR, 1}}, {"f2", {FPR, 2}}, {"f3", {FPR, 3}}, {"f4", {FPR, 4}}, {"f5", {FPR, 5}}, {"f6", {FPR, 6}}, {"f7", {FPR, 7}}, {"f8", {FPR, 8}}, {"f9", {FPR, 9}}, {"f10", {FPR, 10}}, {"f11", {FPR, 11}}, {"f12", {FPR, 12}}, {"f13", {FPR, 13}}, {"f14", {FPR, 14}}, {"f15", {FPR, 15}}, {"f16", {FPR, 16}}, {"f17", {FPR, 17}}, {"f18", {FPR, 18}}, {"f19", {FPR, 19}}, {"f20", {FPR, 20}}, {"f21", {FPR, 21}}, {"f22", {FPR, 22}}, {"f23", {FPR, 23}}, {"f24", {FPR, 24}}, {"f25", {FPR, 25}}, {"f26", {FPR, 26}}, {"f27", {FPR, 27}}, {"f28", {FPR, 28}}, {"f29", {FPR, 29}}, {"f30", {FPR, 30}}, {"f31", {FPR, 31}}, {"pc", {PCtr}}, {"msr", {MSR}}, {"xer", {SPR, SPR_XER}}, {"lr", {SPR, SPR_LR}}, {"ctr", {SPR, SPR_CTR}}, {"dsisr", {SPR, SPR_DSISR}}, {"dar", {SPR, SPR_DAR}}, {"dec", {SPR, SPR_DEC}}, {"sdr1", {SPR, SPR_SDR}}, {"srr0", {SPR, SPR_SRR0}}, {"srr1", {SPR, SPR_SRR1}}, {"tbl", {SPR, SPR_TL}}, {"tbu", {SPR, SPR_TU}}, {"pvr", {SPR, SPR_PVR}}, {"sprg0", {SPR, SPR_SPRG0}}, {"sprg1", {SPR, SPR_SPRG1}}, {"sprg2", {SPR, SPR_SPRG2}}, {"sprg3", {SPR, SPR_SPRG3}}, {"ear", {SPR, SPR_EAR}}, {"ibat0u", {SPR, SPR_IBAT0U}}, {"ibat0l", {SPR, SPR_IBAT0L}}, {"ibat1u", {SPR, SPR_IBAT1U}}, {"ibat1l", {SPR, SPR_IBAT1L}}, {"ibat2u", {SPR, SPR_IBAT2U}}, {"ibat2l", {SPR, SPR_IBAT2L}}, {"ibat3u", {SPR, SPR_IBAT3U}}, {"ibat3l", {SPR, SPR_IBAT3L}}, {"ibat4u", {SPR, SPR_IBAT4U}}, {"ibat4l", {SPR, SPR_IBAT4L}}, {"ibat5u", {SPR, SPR_IBAT5U}}, {"ibat5l", {SPR, SPR_IBAT5L}}, {"ibat6u", {SPR, SPR_IBAT6U}}, {"ibat6l", {SPR, SPR_IBAT6L}}, {"ibat7u", {SPR, SPR_IBAT7U}}, {"ibat7l", {SPR, SPR_IBAT7L}}, {"dbat0u", {SPR, SPR_DBAT0U}}, {"dbat0l", {SPR, SPR_DBAT0L}}, {"dbat1u", {SPR, SPR_DBAT1U}}, {"dbat1l", {SPR, SPR_DBAT1L}}, {"dbat2u", {SPR, SPR_DBAT2U}}, {"dbat2l", {SPR, SPR_DBAT2L}}, {"dbat3u", {SPR, SPR_DBAT3U}}, {"dbat3l", {SPR, SPR_DBAT3L}}, {"dbat4u", {SPR, SPR_DBAT4U}}, {"dbat4l", {SPR, SPR_DBAT4L}}, {"dbat5u", {SPR, SPR_DBAT5U}}, {"dbat5l", {SPR, SPR_DBAT5L}}, {"dbat6u", {SPR, SPR_DBAT6U}}, {"dbat6l", {SPR, SPR_DBAT6L}}, {"dbat7u", {SPR, SPR_DBAT7U}}, {"dbat7l", {SPR, SPR_DBAT7L}}, {"gqr0", {SPR, SPR_GQR0 + 0}}, {"gqr1", {SPR, SPR_GQR0 + 1}}, {"gqr2", {SPR, SPR_GQR0 + 2}}, {"gqr3", {SPR, SPR_GQR0 + 3}}, {"gqr4", {SPR, SPR_GQR0 + 4}}, {"gqr5", {SPR, SPR_GQR0 + 5}}, {"gqr6", {SPR, SPR_GQR0 + 6}}, {"gqr7", {SPR, SPR_GQR0 + 7}}, {"hid0", {SPR, SPR_HID0}}, {"hid1", {SPR, SPR_HID1}}, {"hid2", {SPR, SPR_HID2}}, {"hid4", {SPR, SPR_HID4}}, {"iabr", {SPR, SPR_IABR}}, {"dabr", {SPR, SPR_DABR}}, {"wpar", {SPR, SPR_WPAR}}, {"dmau", {SPR, SPR_DMAU}}, {"dmal", {SPR, SPR_DMAL}}, {"ecid_u", {SPR, SPR_ECID_U}}, {"ecid_m", {SPR, SPR_ECID_M}}, {"ecid_l", {SPR, SPR_ECID_L}}, {"usia", {SPR, SPR_USIA}}, {"sia", {SPR, SPR_SIA}}, {"l2cr", {SPR, SPR_L2CR}}, {"ictc", {SPR, SPR_ICTC}}, {"mmcr0", {SPR, SPR_MMCR0}}, {"mmcr1", {SPR, SPR_MMCR1}}, {"pmc1", {SPR, SPR_PMC1}}, {"pmc2", {SPR, SPR_PMC2}}, {"pmc3", {SPR, SPR_PMC3}}, {"pmc4", {SPR, SPR_PMC4}}, {"thrm1", {SPR, SPR_THRM1}}, {"thrm2", {SPR, SPR_THRM2}}, {"thrm3", {SPR, SPR_THRM3}}, }); std::ranges::sort(unsorted_lookup, {}, &LookupKV::first); return unsorted_lookup; } (); static_assert(std::ranges::adjacent_find(sorted_lookup, {}, &LookupKV::first) == sorted_lookup.end(), "Expression: Sorted lookup should not contain duplicate keys."); for (auto* v = m_vars->head; v != nullptr; v = v->next) { const auto iter = std::ranges::lower_bound(sorted_lookup, v->name, {}, &LookupKV::first); if (iter != sorted_lookup.end() && iter->first == v->name) m_binds.emplace_back(iter->second); else m_binds.emplace_back(); } } std::optional Expression::TryParse(std::string_view text) { ExprVarListPointer vars{new expr_var_list{}}; ExprPointer ex{expr_create(text.data(), text.length(), vars.get(), g_expr_funcs.data())}; if (!ex) return std::nullopt; return Expression{text, std::move(ex), std::move(vars)}; } double Expression::Evaluate(Core::System& system) const { SynchronizeBindings(system, SynchronizeDirection::From); double result = expr_eval(m_expr.get()); SynchronizeBindings(system, SynchronizeDirection::To); Reporting(result); return result; } void Expression::SynchronizeBindings(Core::System& system, SynchronizeDirection dir) const { auto& ppc_state = system.GetPPCState(); auto bind = m_binds.begin(); for (auto* v = m_vars->head; v != nullptr; v = v->next, ++bind) { switch (bind->type) { case VarBindingType::Zero: if (dir == SynchronizeDirection::From) v->value = 0; break; case VarBindingType::GPR: if (dir == SynchronizeDirection::From) v->value = static_cast(ppc_state.gpr[bind->index]); else ppc_state.gpr[bind->index] = static_cast(static_cast(v->value)); break; case VarBindingType::FPR: if (dir == SynchronizeDirection::From) v->value = ppc_state.ps[bind->index].PS0AsDouble(); else ppc_state.ps[bind->index].SetPS0(v->value); break; case VarBindingType::SPR: if (dir == SynchronizeDirection::From) v->value = static_cast(ppc_state.spr[bind->index]); else ppc_state.spr[bind->index] = static_cast(static_cast(v->value)); break; case VarBindingType::PCtr: if (dir == SynchronizeDirection::From) v->value = static_cast(ppc_state.pc); break; case VarBindingType::MSR: if (dir == SynchronizeDirection::From) v->value = static_cast(ppc_state.msr.Hex); else ppc_state.msr.Hex = static_cast(static_cast(v->value)); break; } } } void Expression::Reporting(const double result) const { bool is_nan = std::isnan(result); std::string message; for (auto* v = m_vars->head; v != nullptr; v = v->next) { if (std::isnan(v->value)) is_nan = true; fmt::format_to(std::back_inserter(message), " {}={}", v->name, v->value); } if (is_nan) { message.append("\nBreakpoint condition encountered a NaN"); Core::DisplayMessage("Breakpoint condition has encountered a NaN.", 2000); } if (result != 0.0 || is_nan) NOTICE_LOG_FMT(MEMMAP, "Breakpoint condition returned: {}. Vars:{}", result, message); } std::string Expression::GetText() const { return m_text; }