OXYGENHUB
◆ main enhanced-x16-emulator / src / debug_ui / debug_ui_insn_tooltip.h
7.5 KiB · Objective-C · last changed chore(): initial commit 25 days ago source blame history raw
1
// Commander X16 Emulator — shared instruction tooltip for the debugger panels.
2
//
3
// Both the Source and Disassembly panels want the same answer to "what does
4
// this instruction do, and what will it do right now": a description from
5
// insn_info.c, the flags it touches, and — when the instruction is the one at
6
// the PC — a prediction of its effect.
7
//
8
// The prediction is strictly read-only. It re-decodes at the address with
9
// disasm() and reads operands through debug_ui_read6502(), which is the
10
// side-effect-free debug path, so hovering can never perturb VERA/VIA state.
11
//
12
// Header-only, like debug_ui_widgets.h, so it needs no CMake entry.
13
#ifndef DEBUG_UI_INSN_TOOLTIP_H
14
#define DEBUG_UI_INSN_TOOLTIP_H
15
16
#include "imgui.h"
17
#include "debug_ui_bridge.h"
18
19
#include <stdint.h>
20
#include <ctype.h>
21
22
extern "C" {
23
#include "insn_info.h"
24
#include "disasm.h"
25
#include "dbg_info.h"
26
}
27
28
// Body of the instruction tooltip (no Begin/EndTooltip — the caller owns that,
29
// so it can combine this with other content).
30
//
31
//   mnemonic  : the instruction's mnemonic, as disasm.c spells it
32
//   addr/bank : where the instruction lives
33
//   at_pc     : predict the outcome (only meaningful when this IS the PC)
34
static inline void
35
dbgui_instruction_tooltip_body(const char *mnemonic, uint16_t addr, uint8_t bank, bool at_pc)
36
{
37
    const insn_info_t *insn = insn_info_lookup(mnemonic);
38
    if (!insn) {
39
        return;
40
    }
41
42
    ImGui::Text("%s - %s", insn->mnemonic, insn->title);
43
    ImGui::TextDisabled("%s", insn->summary);
44
45
    char flags[32];
46
    insn_info_flag_text(insn->flags_set, flags, sizeof flags);
47
    if (flags[0]) {
48
        ImGui::Separator();
49
        ImGui::TextDisabled("affects: %s", flags);
50
    }
51
52
    if (!at_pc) {
53
        return;
54
    }
55
56
    char          dis[128];
57
    int32_t       eff     = -1;
58
    const int16_t x16bank = (int16_t)DEBUG_UI_CURRENT_BANK;
59
    const int     nbytes  = disasm(addr, bank, nullptr, dis, sizeof dis, x16bank,
60
                                   regs.status, &eff);
61
62
    ImGui::Separator();
63
    ImGui::TextColored(ImVec4(0.55f, 0.85f, 1.0f, 1.0f), "At the PC: %s", dis);
64
65
    const bool acc16 = regs.is65c816 && !regs.e && !(regs.status & FLAG_MEMORY_WIDTH);
66
    const bool idx16 = regs.is65c816 && !regs.e && !(regs.status & FLAG_INDEX_WIDTH);
67
    const int  w     = acc16 ? 4 : 2;
68
69
    uint16_t memval   = 0;
70
    bool     have_mem = false;
71
    if (eff >= 0) {
72
        uint8_t lo = debug_ui_read6502((uint16_t)eff, 0, x16bank);
73
        uint8_t hi = debug_ui_read6502((uint16_t)(eff + 1), 0, x16bank);
74
        memval     = acc16 ? (uint16_t)(lo | (hi << 8)) : lo;
75
        have_mem   = true;
76
        ImGui::TextDisabled("operand $%04X = $%0*X", (unsigned)eff, w, memval);
77
    }
78
79
    const ImVec4 will(0.45f, 0.95f, 0.45f, 1.0f);
80
    const ImVec4 wont(0.95f, 0.70f, 0.30f, 1.0f);
81
    const char   m0 = (char)tolower((unsigned char)mnemonic[0]);
82
    const char   m1 = mnemonic[1] ? (char)tolower((unsigned char)mnemonic[1]) : '\0';
83
    const char   m2 = mnemonic[1] && mnemonic[2] ? (char)tolower((unsigned char)mnemonic[2]) : '\0';
84
85
    switch (insn->kind) {
86
    case INSN_KIND_BRANCH:
87
        if (insn->branch_flag) {
88
            const uint8_t f     = insn->branch_flag;
89
            const bool    set   = (regs.status & f) != 0;
90
            const bool    taken = (set == insn->branch_when_set);
91
            const char   *fname = f == INSN_FLAG_Z   ? "Z"
92
                                  : f == INSN_FLAG_C ? "C"
93
                                  : f == INSN_FLAG_N ? "N"
94
                                                     : "V";
95
            // disasm() reports no effective address for relative branches, so
96
            // work the target out from the displacement.
97
            uint16_t target = (uint16_t)(eff >= 0 ? eff : 0);
98
            if (eff < 0 && nbytes >= 2) {
99
                const int8_t d = (int8_t)debug_ui_read6502((uint16_t)(addr + 1), bank, x16bank);
100
                target = (uint16_t)(addr + nbytes + d);
101
            }
102
            ImGui::Text("tests %s (currently %d), branches when %s", fname, set ? 1 : 0,
103
                        insn->branch_when_set ? "set" : "clear");
104
            if (taken)
105
                ImGui::TextColored(will, "WILL branch to $%04X", (unsigned)target);
106
            else
107
                ImGui::TextColored(wont, "will NOT branch - continues at $%04X",
108
                                   (unsigned)((addr + nbytes) & 0xFFFF));
109
            const char *tn = nullptr;
110
            if (taken && dbg_info_addr_to_label(target, &tn) && tn)
111
                ImGui::TextDisabled("target: %s", tn);
112
            ImGui::TextDisabled("A=$%02X X=$%02X Y=$%02X P=$%02X", regs.a, regs.xl, regs.yl,
113
                                regs.status);
114
        }
115
        break;
116
117
    case INSN_KIND_LOAD:
118
        if (have_mem) {
119
            const char *reg = m2 == 'a' ? "A" : (m2 == 'x' ? "X" : "Y");
120
            ImGui::TextColored(will, "%s will become $%0*X", reg, w, memval);
121
            ImGui::TextDisabled("then Z=%d N=%d", memval == 0 ? 1 : 0,
122
                                (memval & (acc16 ? 0x8000 : 0x80)) ? 1 : 0);
123
        }
124
        break;
125
126
    case INSN_KIND_STORE:
127
        if (eff >= 0) {
128
            const uint16_t v = m2 == 'a'   ? (acc16 ? regs.c : regs.a)
129
                               : m2 == 'x' ? (idx16 ? regs.x : regs.xl)
130
                               : m2 == 'y' ? (idx16 ? regs.y : regs.yl)
131
                                           : 0; // stz
132
            ImGui::TextColored(will, "$%04X will become $%0*X", (unsigned)eff, w, v);
133
            if (have_mem && v == memval)
134
                ImGui::TextDisabled("(no change - already that value)");
135
        }
136
        break;
137
138
    case INSN_KIND_COMPARE:
139
        if (have_mem) {
140
            const uint16_t r = m1 == 'm'   ? (acc16 ? regs.c : regs.a)
141
                               : m2 == 'x' ? (idx16 ? regs.x : regs.xl)
142
                                           : (idx16 ? regs.y : regs.yl);
143
            ImGui::Text("compares $%0*X with $%0*X", w, r, w, memval);
144
            ImGui::TextColored(will, "result: %s (Z=%d C=%d)",
145
                               r == memval ? "equal" : (r > memval ? "greater" : "less"),
146
                               r == memval ? 1 : 0, r >= memval ? 1 : 0);
147
        }
148
        break;
149
150
    case INSN_KIND_JUMP:
151
    case INSN_KIND_RETURN:
152
        if (eff >= 0) {
153
            ImGui::TextColored(will, "transfers control to $%04X", (unsigned)eff);
154
            const char *nm = nullptr;
155
            if (dbg_info_addr_to_label((uint16_t)eff, &nm) && nm)
156
                ImGui::TextDisabled("target: %s", nm);
157
        }
158
        break;
159
160
    case INSN_KIND_INCDEC:
161
        if (have_mem) {
162
            const bool     up = (m0 == 'i');
163
            const unsigned nv = (unsigned)((up ? memval + 1 : memval - 1) &
164
                                           (acc16 ? 0xFFFF : 0xFF));
165
            ImGui::TextColored(will, "$%04X: $%0*X -> $%0*X", (unsigned)eff, w, memval, w, nv);
166
        }
167
        break;
168
169
    default:
170
        break;
171
    }
172
173
    ImGui::TextDisabled("%d byte%s", nbytes, nbytes == 1 ? "" : "s");
174
}
175
176
// Extract the leading mnemonic from a disassembled line ("lda $26" -> "lda").
177
// Returns false when the line doesn't start with one.
178
static inline bool
179
dbgui_mnemonic_of(const char *disasm_line, char *out, size_t outsz)
180
{
181
    if (!disasm_line || !out || outsz == 0) {
182
        return false;
183
    }
184
    while (*disasm_line == ' ' || *disasm_line == '\t') {
185
        disasm_line++;
186
    }
187
    size_t n = 0;
188
    while (disasm_line[n] && !isspace((unsigned char)disasm_line[n]) && n + 1 < outsz) {
189
        out[n] = disasm_line[n];
190
        n++;
191
    }
192
    out[n] = '\0';
193
    return n > 0;
194
}
195
196
#endif