<commit_before>
/*  Z80 CPU Simulator.

    Copyright (C) 2017 <NAME>.
    <EMAIL>

    Published under the MIT license.
*/

#ifndef Z80_H
#define Z80_H

#include <cassert>
#include <climits>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <utility>

namespace z80 {

#if UINT_FAST8_MAX < UINT_MAX
typedef unsigned fast_u8;
#else
typedef uint_fast8_t fast_u8;
#endif

#if UINT_FAST16_MAX < UINT_MAX
typedef unsigned fast_u16;
#else
typedef uint_fast16_t fast_u16;
#endif

typedef uint_least8_t least_u8;
typedef uint_least16_t least_u16;

typedef uint_fast32_t size_type;

static const fast_u8 mask8 = 0xff;
static const fast_u8 sign8_mask = 0x80;
static const fast_u16 mask16 = 0xffff;

static inline void unused(...) {}

static inline bool get_sign8(fast_u8 n) {
    return (n & sign8_mask) != 0;
}

static inline fast_u8 add8(fast_u8 a, fast_u8 b) {
    return (a + b) & mask8;
}

static inline fast_u8 sub8(fast_u8 a, fast_u8 b) {
    return (a - b) & mask8;
}

static inline fast_u8 inc8(fast_u8 n) {
    return add8(n, 1);
}

static inline fast_u8 dec8(fast_u8 n) {
    return sub8(n, 1);
}

static inline fast_u8 rol8(fast_u8 n) {
    return ((n << 1) | (n >> 7)) & mask8;
}

static inline fast_u8 ror8(fast_u8 n) {
    return ((n >> 1) | (n << 7)) & mask8;
}

static inline fast_u8 neg8(fast_u8 n) {
    return ((n ^ mask8) + 1) & mask8;
}

static inline fast_u8 abs8(fast_u8 n) {
    return !get_sign8(n) ? n : neg8(n);
}

static inline int sign_extend8(fast_u8 n) {
    auto a = static_cast<int>(abs8(n));
    return !get_sign8(n) ? a : -a;
}

static inline fast_u8 get_low8(fast_u16 n) {
    return n & mask8;
}

static inline fast_u8 get_high8(fast_u16 n) {
    return (n >> 8) & mask8;
}

static inline fast_u16 make16(fast_u8 hi, fast_u8 lo) {
    return (static_cast<fast_u16>(hi) << 8) | lo;
}

static inline fast_u16 add16(fast_u16 a, fast_u16 b) {
    return (a + b) & mask16;
}

static inline fast_u16 sub16(fast_u16 a, fast_u16 b) {
    return (a - b) & mask16;
}

static inline fast_u16 inc16(fast_u16 n) {
    return add16(n, 1);
}

static inline fast_u16 dec16(fast_u16 n) {
    return sub16(n, 1);
}

enum class reg { b, c, d, e, h, l, at_hl, a };

enum class regp { bc, de, hl, sp };
enum class regp2 { bc, de, hl, af };
enum class index_regp { hl, ix, iy };

enum class instruction_prefix { none, cb, ed };

enum class alu { add, adc, sub, sbc, and_a, xor_a, or_a, cp };
enum class rot { rlc, rrc, rl, rr, sla, sra, sll, srl };
enum class block_ld { ldi, ldd, ldir, lddr };

enum condition { nz, z, nc, c, po, pe, p, m };

template<typename D>
class instructions_decoder {
public:
    instructions_decoder() {}

    index_regp get_index_rp_kind() const { return state.index_rp; }
    index_regp get_next_index_rp_kind() const { return state.next_index_rp; }

    fast_u8 read_disp_or_null(bool may_need_disp = true) {
        if(get_index_rp_kind() == index_regp::hl || !may_need_disp)
            return 0;
        fast_u8 d = (*this)->on_disp_read();
        (*this)->on_5t_exec_cycle();
        return d;
    }

    fast_u8 read_disp_or_null(reg r) {
        return read_disp_or_null(r == reg::at_hl);
    }

    fast_u8 read_disp_or_null(reg r1, reg r2) {
        return read_disp_or_null(r1 == reg::at_hl || r2 == reg::at_hl);
    }

    instruction_prefix get_prefix() const { return state.prefix; }
    void set_prefix(instruction_prefix p) { state.prefix = p; }

    void on_ed_prefix() {
        // TODO: Should we reset the index register pair here?
        set_prefix(instruction_prefix::ed);
    }

    void on_cb_prefix() {
        set_prefix(instruction_prefix::cb);
        state.next_index_rp = state.index_rp;
    }

    void on_prefix_reset() {
        set_prefix(instruction_prefix::none);
    }

    void on_set_next_index_rp(index_regp irp) {
        // TODO: Should we reset the prefix here?
        state.next_index_rp = irp;
    }

    unsigned decode_int_mode(fast_u8 y) {
        y &= 3;
        return y < 2 ? 0 : y - 1;
    }

    void decode_unprefixed() {
        fast_u8 op = (*this)->on_fetch();
        fast_u8 y = get_y_part(op);
        fast_u8 z = get_z_part(op);
        fast_u8 p = get_p_part(op);

        switch(op & x_mask) {
        case 0100: {
            // LD r[y], r[z] or HALT (in place of LD (HL), (HL))
            // LD r, r              f(4)
            // LD r, (HL)           f(4)           r(3)
            // LD r, (i+d)     f(4) f(4) r(3) e(5) r(3)
            // LD (HL), r           f(4)           w(3)
            // LD (i+d), r     f(4) f(4) r(3) e(5) w(3)
            // HALT                 f(4)
            auto rd = static_cast<reg>(y);
            auto rs = static_cast<reg>(z);
            if(rd == reg::at_hl && rs == reg::at_hl)
                assert(0);  // TODO: return (*this)->on_halt();
            return (*this)->on_ld_r_r(rd, rs, read_disp_or_null(rd, rs)); }
        case 0200: {
            // alu[y] r[z]
            // alu r            f(4)
            // alu (HL)         f(4)           r(3)
            // alu (i+d)   f(4) f(4) r(3) e(5) r(3)
            auto k = static_cast<alu>(y);
            auto r = static_cast<reg>(z);
            return (*this)->on_alu_r(k, r, read_disp_or_null(r)); }
        }
        switch(op & (x_mask | z_mask)) {
        case 0004: {
            // INC r[y]
            // INC r            f(4)
            // INC (HL)         f(4)           r(4) w(3)
            // INC (i+d)   f(4) f(4) r(3) e(5) r(4) w(3)
            auto r = static_cast<reg>(y);
            return (*this)->on_inc_r(r, read_disp_or_null(r)); }
        case 0005: {
            // DEC r[y]
            // DEC r            f(4)
            // DEC (HL)         f(4)           r(4) w(3)
            // DEC (i+d)   f(4) f(4) r(3) e(5) r(4) w(3)
            auto r = static_cast<reg>(y);
            return (*this)->on_dec_r(r, read_disp_or_null(r)); }
        case 0006: {
            // LD r[y], n
            // LD r, n              f(4)      r(3)
            // LD (HL), n           f(4)      r(3) w(3)
            // LD (i+d), n     f(4) f(4) r(3) r(5) w(3)
            auto r = static_cast<reg>(y);
            fast_u8 d, n;
            if(r != reg::at_hl || get_index_rp_kind() == index_regp::hl) {
                d = 0;
                n = (*this)->on_3t_imm8_read();
            } else {
                d = (*this)->on_disp_read();
                n = (*this)->on_5t_imm8_read();
            }
            return (*this)->on_ld_r_n(r, d, n); }
        case 0300: {
            // RET cc[y]  f(5) + r(3) r(3)
            (*this)->on_5t_fetch_cycle();
            auto cc = static_cast<condition>(y);
            return (*this)->on_ret_cc(cc); }
        case 0302: {
            // JP cc[y], nn  f(4) r(3) r(3)
            auto cc = static_cast<condition>(y);
            return (*this)->on_jp_cc_nn(cc, (*this)->on_3t_3t_imm16_read()); }
        case 0304: {
            // CALL cc[y], nn
            // cc met:      f(4) r(3) r(4) w(3) w(3)
            // cc not met:  f(4) r(3) r(3)
            auto cc = static_cast<condition>(y);
            bool cc_met = (*this)->check_condition(cc);
            fast_u16 nn = c ? (*this)->on_3t_4t_imm16_read() :
                              (*this)->on_3t_3t_imm16_read();
            return (*this)->on_call_cc_nn(cc_met, cc, nn); }
        case 0306: {
            // alu[y] n  f(4) r(3)
            auto k = static_cast<alu>(y);
            return (*this)->on_alu_n(k, (*this)->on_3t_imm8_read()); }
        case 0307:
            // RST y*8  f(5) w(3) w(3)
            (*this)->on_5t_fetch_cycle();
            return (*this)->on_rst(y * 8);
        }
        if((op & (x_mask | z_mask | (y_mask - 0030))) == 0040) {
            // JR cc[y-4], d  f(4) r(3) + e(5)
            auto cc = static_cast<condition>((op & (y_mask - 0040)) >> 3);
            return (*this)->on_jr_cc(cc, (*this)->on_disp_read());
        }
        switch(op & (x_mask | z_mask | q_mask)) {
        case 0001: {
            // LD rp[p], nn
            // LD rr, nn        f(4) r(3) r(3)
            // LD i, nn    f(4) f(4) r(3) r(3)
            auto rp = static_cast<regp>(p);
            return (*this)->on_ld_rp_nn(rp, (*this)->on_3t_3t_imm16_read()); }
        case 0003: {
            // INC rp[p]
            // INC rr           f(6)
            // INC i       f(4) f(6)
            (*this)->on_6t_fetch_cycle();
            auto rp = static_cast<regp>(p);
            return (*this)->on_inc_rp(rp); }
        case 0011: {
            // ADD HL, rp[p]
            // ADD HL, rr           f(4) e(4) e(3)
            // ADD i, rr       f(4) f(4) e(4) e(3)
            auto rp = static_cast<regp>(p);
            return (*this)->on_add_irp_rp(rp); }
        case 0013: {
            // DEC rp[p]
            // DEC rr           f(6)
            // DEC i       f(4) f(6)
            (*this)->on_6t_fetch_cycle();
            auto rp = static_cast<regp>(p);
            return (*this)->on_dec_rp(rp); }
        case 0301: {
            // POP rp2[p]
            // POP rr           f(4) r(3) r(3)
            // POP i       f(4) f(4) r(3) r(3)
            auto rp = static_cast<regp2>(p);
            return (*this)->on_pop_rp(rp); }
        case 0305: {
            // PUSH rp2[p]
            // PUSH rr          f(5) w(3) w(3)
            // PUSH i      f(4) f(5) w(3) w(3)
            (*this)->on_5t_fetch_cycle();
            auto rp = static_cast<regp2>(p);
            return (*this)->on_push_rp(rp); }
        }
        switch(op & (x_mask | z_mask | q_mask | (p_mask - 1))) {
        case 0002: {
            // LD (rp[p]), A  f(4) w(3)
            auto rp = static_cast<regp>(p);
            return (*this)->on_ld_at_rp_a(rp); }
        case 0012: {
            // LD A, (rp[p])  f(4) r(3)
            auto rp = static_cast<regp>(p);
            return (*this)->on_ld_a_at_rp(rp); }
        }
        switch(op) {
        case 0x00:
            // NOP  f(4)
            return (*this)->on_nop();
        case 0x08:
            // EX AF, AF'  f(4)
            return (*this)->on_ex_af_alt_af();
        case 0x0f:
            // RRCA  f(4)
            return (*this)->on_rrca();
        case 0x10:
            // DJNZ  f(5) r(3) + e(5)
            (*this)->on_5t_fetch_cycle();
            return (*this)->on_djnz((*this)->on_disp_read());
        case 0x18:
            // JR d  f(4) r(3) e(5)
            return (*this)->on_jr((*this)->on_disp_read());
        case 0x1f:
            // RRA  f(4)
            return (*this)->on_rra();
        case 0x22:
            // LD (nn), HL          f(4) r(3) r(3) w(3) w(3)
            // LD (nn), i      f(4) f(4) r(3) r(3) w(3) w(3)
            return (*this)->on_ld_at_nn_irp((*this)->on_3t_3t_imm16_read());
        case 0x2a:
            // LD HL, (nn)          f(4) r(3) r(3) r(3) r(3)
            // LD i, (nn)      f(4) f(4) r(3) r(3) r(3) r(3)
            return (*this)->on_ld_irp_at_nn((*this)->on_3t_3t_imm16_read());
        case 0x2f:
            // CPL  f(4)
            return (*this)->on_cpl();
        case 0x32:
            // LD (nn), A  f(4) r(3) r(3) w(3)
            return (*this)->on_ld_at_nn_a((*this)->on_3t_3t_imm16_read());
        case 0x37:
            // SCF  f(4)
            return (*this)->on_scf();
        case 0x3a:
            // LD A, (nn)  f(4) r(3) r(3) r(3)
            return (*this)->on_ld_a_at_nn((*this)->on_3t_3t_imm16_read());
        case 0x3f:
            // CCF  f(4)
            return (*this)->on_ccf();
        case 0xc3:
            // JP nn  f(4) r(3) r(3)
            return (*this)->on_jp_nn((*this)->on_3t_3t_imm16_read());
        case 0xc9:
            // RET  f(4) r(3) r(3)
            return (*this)->on_ret();
        case 0xcb:
            // CB prefix.
            return (*this)->on_cb_prefix();
        case 0xcd:
            // CALL nn  f(4) r(3) r(4) w(3) w(3)
            return (*this)->on_call_nn((*this)->on_3t_4t_imm16_read());
        case 0xd3:
            // OUT (n), A  f(4) r(3) o(4)
            return (*this)->on_out_n_a((*this)->on_3t_imm8_read());
        case 0xd9:
            // EXX  f(4)
            return (*this)->on_exx();
        case 0xe3:
            // EX (SP), irp
            // EX (SP), HL          f(4) r(3) r(4) w(3) w(5)
            // EX (SP), i      f(4) f(4) r(3) r(4) w(3) w(5)
            return (*this)->on_ex_at_sp_irp();
        case 0xe9:
            // JP HL            f(4)
            // JP i        f(4) f(4)
            return (*this)->on_jp_irp();
        case 0xeb:
            // EX DE, HL  f(4)
            return (*this)->on_ex_de_hl();
        case 0xed:
            // ED prefix.
            return (*this)->on_ed_prefix();
        case 0xf3:
            // DI  f(4)
            return (*this)->on_di();
        case 0xf9:
            // LD SP, HL        f(6)
            // LD SP, i    f(4) f(6)
            (*this)->on_6t_fetch_cycle();
            return (*this)->on_ld_sp_irp();
        case 0xfb:
            // EI  f(4)
            return (*this)->on_ei();
        case 0xfd:
            // FD prefix (IY-indexed instructions).
            return (*this)->on_set_next_index_rp(index_regp::iy);
        }

        // TODO
        std::fprintf(stderr, "Unknown opcode 0x%02x at 0x%04x.\n",
                     static_cast<unsigned>(op),
                     static_cast<unsigned>((*this)->get_last_read_addr()));
        std::abort();
    }

    void decode_cb_prefixed() {
        prefix_reset_guard guard(this);

        fast_u8 d = 0;
        index_regp irp = get_index_rp_kind();
        if(irp != index_regp::hl)
            d = (*this)->on_disp_read();

        fast_u8 op = (*this)->on_fetch();
        if(irp != index_regp::hl)
            (*this)->on_5t_fetch_cycle();

        fast_u8 y = get_y_part(op);
        fast_u8 z = get_z_part(op);

        auto r = static_cast<reg>(z);

        switch(op & x_mask) {
        case 0000: {
            // rot[y] r[z]
            // rot r                f(4)      f(4)
            // rot (HL)             f(4)      f(4) r(4) w(3)
            // rot (i+d)       f(4) f(4) r(3) f(5) r(4) w(3)
            auto k = static_cast<rot>(y);
            return (*this)->on_rot(k, r, d); }
        case 0100: {
            // BIT y, r[z]
            // BIT b, r             f(4)      f(4)
            // BIT b, (HL)          f(4)      f(4) r(4)
            // BIT b, (i+d)    f(4) f(4) r(3) f(5) r(4)
            auto b = static_cast<unsigned>(y);
            return (*this)->on_bit(b, r, d); }
        case 0200: {
            // RES y, r[z]
            // RES b, r             f(4)      f(4)
            // RES b, (HL)          f(4)      f(4) r(4) w(3)
            // RES b, (i+d)    f(4) f(4) r(3) f(5) r(4) w(3)
            auto b = static_cast<unsigned>(y);
            return (*this)->on_res(b, r, d); }
        case 0300: {
            // SET y, r[z]
            // SET b, r             f(4)      f(4)
            // SET b, (HL)          f(4)      f(4) r(4) w(3)
            // SET b, (i+d)    f(4) f(4) r(3) f(5) r(4) w(3)
            auto b = static_cast<unsigned>(y);
            return (*this)->on_set(b, r, d); }
        }

        std::fprintf(stderr, "Unknown CB-prefixed opcode 0x%02x at 0x%04x.\n",
                     static_cast<unsigned>(op),
                     static_cast<unsigned>((*this)->get_last_read_addr()));
        std::abort();
    }

    void decode_ed_prefixed() {
        prefix_reset_guard guard(this);
        fast_u8 op = (*this)->on_fetch();
        fast_u8 y = get_y_part(op);
        fast_u8 p = get_p_part(op);

        switch(op & (x_mask | z_mask)) {
        case 0100: {
            // IN r[y], (C)  f(4) f(4) i(4)
            // IN (C)        f(4) f(4) i(4)
            auto r = static_cast<reg>(y);
            return (*this)->on_in_r_c(r); }
        case 0102: {
            // ADC HL, rp[p]  f(4) f(4) e(4) e(3)
            // SBC HL, rp[p]  f(4) f(4) e(4) e(3)
            auto rp = static_cast<regp>(p);
            return op & q_mask ? (*this)->on_adc_hl_rp(rp) :
                                 (*this)->on_sbc_hl_rp(rp); }
        case 0103: {
            // LD rp[p], (nn)  f(4) f(4) r(3) r(3) r(3) r(3)
            // LD (nn), rp[p]  f(4) f(4) r(3) r(3) w(3) w(3)
            auto rp = static_cast<regp>(p);
            fast_u16 nn = (*this)->on_3t_3t_imm16_read();
            return op & q_mask ? (*this)->on_ld_rp_at_nn(rp, nn) :
                                 (*this)->on_ld_at_nn_rp(nn, rp); }
        case 0106: {
            // IM im[y]  f(4) f(4)
            return (*this)->on_im(decode_int_mode(y)); }
        case 0200: {
            // LDI, LDD, LDIR, LDDR  f(4) f(4) r(3) w(5) + e(5)
            if(y < 4)
                return (*this)->on_noni_ed(op);
            auto k = static_cast<block_ld>(y - 4);
            return (*this)->on_block_ld(k); }
        }
        switch(op) {
        case 0x47: {
            // LD I, A  f(4) f(5)
            (*this)->on_5t_fetch_cycle();
            return (*this)->on_ld_i_a(); }
        }

        std::fprintf(stderr, "Unknown ED-prefixed opcode 0x%02x at 0x%04x.\n",
                     static_cast<unsigned>(op),
                     static_cast<unsigned>((*this)->get_last_read_addr()));
        std::abort();
    }

    void on_decode() {
        state.index_rp = state.next_index_rp;
        state.next_index_rp = index_regp::hl;

        switch(get_prefix()) {
        case instruction_prefix::none:
            return decode_unprefixed();
        case instruction_prefix::cb:
            return decode_cb_prefixed();
        case instruction_prefix::ed:
            return decode_ed_prefixed();
        }
        assert(0);
    }

    void decode() { (*this)->on_decode(); }

protected:
    class prefix_reset_guard {
    public:
        prefix_reset_guard(instructions_decoder *decoder)
            : decoder(decoder)
        {}

        ~prefix_reset_guard() {
            (*decoder)->on_prefix_reset();
        }

    private:
        instructions_decoder *decoder;
    };

    D *operator -> () { return static_cast<D*>(this); }

    static const fast_u8 x_mask = 0300;

    static const fast_u8 y_mask = 0070;
    fast_u8 get_y_part(fast_u8 op) { return (op & y_mask) >> 3; }

    static const fast_u8 z_mask = 0007;
    fast_u8 get_z_part(fast_u8 op) { return op & z_mask; }

    static const fast_u8 p_mask = 0060;
    fast_u8 get_p_part(fast_u8 op) { return (op & p_mask) >> 4; }

    static const fast_u8 q_mask = 0010;

private:
    struct decoder_state {
        decoder_state()
            : index_rp(index_regp::hl), next_index_rp(index_regp::hl),
              prefix(instruction_prefix::none)
        {}

        index_regp index_rp;
        index_regp next_index_rp;
        instruction_prefix prefix;
    };

    decoder_state state;
};

const char *get_reg_name(reg r);
const char *get_reg_name(regp rp, index_regp irp = index_regp::hl);
const char *get_reg_name(regp2 rp, index_regp irp = index_regp::hl);
const char *get_reg_name(index_regp irp);
const char *get_mnemonic(alu k);
const char *get_mnemonic(rot k);
const char *get_mnemonic(block_ld k);
bool is_two_operand_alu_instr(alu k);
const char *get_index_reg_name(index_regp irp);
const char *get_condition_name(condition cc);

class disassembler_base {
public:
    disassembler_base() {}
    virtual ~disassembler_base() {}

    virtual void on_output(const char *out) = 0;

    void on_format(const char *fmt) {
        on_format_impl(fmt, /* args= */ nullptr);
    }

    template<typename... types>
    void on_format(const char *fmt, const types &... args) {
        const void *ptrs[] = { static_cast<const void*>(&args)... };
        on_format_impl(fmt, ptrs);
    }

    virtual void on_format_impl(const char *fmt, const void *args[]);
};

template<typename D>
class disassembler : public instructions_decoder<D>,
                     public disassembler_base {
public:
    typedef instructions_decoder<D> decoder;

    disassembler() {}

    fast_u8 on_fetch() { return (*this)->on_read_next_byte(); }
    void on_5t_fetch_cycle() {}
    void on_6t_fetch_cycle() {}

    // 'call cc, nn' instructions require this function to disambiguate between
    // read cycles of various lengths. This disambiguation does not affect
    // disassembling so we just return false here.
    bool check_condition(condition cc) {
        unused(cc);
        return false;
    }

    fast_u8 on_3t_imm8_read() { return (*this)->on_read_next_byte(); }
    fast_u8 on_5t_imm8_read() { return (*this)->on_read_next_byte(); }

    fast_u16 on_3t_3t_imm16_read() {
        fast_u8 lo = (*this)->on_read_next_byte();
        fast_u8 hi = (*this)->on_read_next_byte();
        return make16(hi, lo); }
    fast_u16 on_3t_4t_imm16_read() {
        fast_u8 lo = (*this)->on_read_next_byte();
        fast_u8 hi = (*this)->on_read_next_byte();
        return make16(hi, lo); }

    fast_u8 on_disp_read() { return (*this)->on_read_next_byte(); }

    void on_3t_exec_cycle() {}
    void on_4t_exec_cycle() {}
    void on_5t_exec_cycle() {}

    void on_ed_prefix() { decoder::on_ed_prefix();
                          (*this)->on_format("noni 0xed"); }

    void on_noni_ed(fast_u8 op) {
        (*this)->on_format("noni N, N", 0xed, op); }

    void on_add_irp_rp(regp rp) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("add P, P", regp::hl, irp, rp, irp); }
    void on_adc_hl_rp(regp rp) {
        (*this)->on_format("adc hl, P", rp, index_regp::hl); }
    void on_alu_n(alu k, fast_u8 n) {
        (*this)->on_format("A N", k, n); }
    void on_alu_r(alu k, reg r, fast_u8 d) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("A R", k, r, irp, d); }
    void on_block_ld(block_ld k) {
        (*this)->on_format("L", k); }
    void on_bit(unsigned b, reg r, fast_u8 d) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("bit U, R", b, r, irp, d); }
    void on_call_nn(fast_u16 nn) {
        (*this)->on_format("call W", nn); }
    void on_call_cc_nn(bool cc_met, condition cc, fast_u16 nn) {
        unused(cc_met);
        (*this)->on_format("call C, W", cc, nn); }
    void on_ccf() {
        (*this)->on_format("ccf"); }
    void on_cpl() {
        (*this)->on_format("cpl"); }
    void on_dec_r(reg r, fast_u8 d) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("dec R", r, irp, d); }
    void on_dec_rp(regp rp) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("dec P", rp, irp); }
    void on_di() {
        (*this)->on_format("di"); }
    void on_djnz(fast_u8 d) {
        (*this)->on_format("djnz D", sign_extend8(d) + 2); }
    void on_ei() {
        (*this)->on_format("ei"); }
    void on_ex_af_alt_af() {
        (*this)->on_format("ex af, af'"); }
    void on_ex_de_hl() {
        (*this)->on_format("ex de, hl"); }
    void on_ex_at_sp_irp() {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("ex (sp), P", regp::hl, irp); }
    void on_exx() {
        (*this)->on_format("exx"); }
    void on_im(unsigned mode) {
        (*this)->on_format("im U", mode); }
    void on_in_r_c(reg r) {
        if(r == reg::at_hl)
            (*this)->on_format("in (c)");
        else
            (*this)->on_format("in R, (c)", r, index_regp::hl, 0); }
    void on_inc_r(reg r, fast_u8 d) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("inc R", r, irp, d); }
    void on_inc_rp(regp rp) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("inc P", rp, irp); }
    void on_jp_cc_nn(condition cc, fast_u16 nn) {
        (*this)->on_format("jp C, W", cc, nn); }
    void on_jp_irp() {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("jp (P)", regp::hl, irp); }
    void on_jp_nn(fast_u16 nn) {
        (*this)->on_format("jp W", nn); }
    void on_jr(fast_u8 d) {
        (*this)->on_format("jr D", sign_extend8(d) + 2); }
    void on_jr_cc(condition cc, fast_u8 d) {
        (*this)->on_format("jr C, D", cc, sign_extend8(d) + 2); }
    void on_ld_i_a() {
        (*this)->on_format("ld i, a"); }
    void on_ld_r_r(reg rd, reg rs, fast_u8 d) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("ld R, R", rd, irp, d, rs, irp, d); }
    void on_ld_r_n(reg r, fast_u8 d, fast_u8 n) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("ld R, N", r, irp, d, n); }
    void on_ld_rp_nn(regp rp, fast_u16 nn) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("ld P, W", rp, irp, nn); }
    void on_ld_irp_at_nn(fast_u16 nn) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("ld P, (W)", regp::hl, irp, nn); }
    void on_ld_at_nn_irp(fast_u16 nn) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("ld (W), P", nn, regp::hl, irp); }
    void on_ld_rp_at_nn(regp rp, fast_u16 nn) {
        (*this)->on_format("ld P, (W)", rp, index_regp::hl, nn); }
    void on_ld_at_nn_rp(fast_u16 nn, regp rp) {
        (*this)->on_format("ld (W), P", nn, rp, index_regp::hl); }
    void on_ld_a_at_nn(fast_u16 nn) {
        (*this)->on_format("ld a, (W)", nn); }
    void on_ld_at_nn_a(fast_u16 nn) {
        (*this)->on_format("ld (W), a", nn); }
    void on_ld_a_at_rp(regp rp) {
        (*this)->on_format("ld a, (P)", rp, index_regp::hl); }
    void on_ld_at_rp_a(regp rp) {
        (*this)->on_format("ld (P), a", rp, index_regp::hl); }
    void on_ld_sp_irp() {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("ld sp, P", regp::hl, irp); }
    void on_nop() {
        (*this)->on_format("nop"); }
    void on_out_n_a(fast_u8 n) {
        (*this)->on_format("out (N), a", n); }
    void on_pop_rp(regp2 rp) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("pop G", rp, irp); }
    void on_push_rp(regp2 rp) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("push G", rp, irp); }
    void on_res(unsigned b, reg r, fast_u8 d) {
        index_regp irp = (*this)->get_index_rp_kind();
        if(irp == index_regp::hl || r == reg::at_hl)
            (*this)->on_format("res U, R", b, r, irp, d);
        else
            (*this)->on_format("res U, R, R", b, reg::at_hl, irp, d,
                               r, index_regp::hl, /* d= */ 0); }
    void on_ret() {
        (*this)->on_format("ret"); }
    void on_ret_cc(condition cc) {
        (*this)->on_format("ret C", cc); }
    void on_rot(rot k, reg r, fast_u8 d) {
        index_regp irp = (*this)->get_index_rp_kind();
        if(irp == index_regp::hl || r == reg::at_hl)
            (*this)->on_format("O R", k, r, irp, d);
        else
            (*this)->on_format("O R, R", k, reg::at_hl, irp, d,
                               r, index_regp::hl, /* d= */ 0); }
    void on_rra() {
        (*this)->on_format("rra"); }
    void on_rrca() {
        (*this)->on_format("rrca"); }
    void on_rst(fast_u16 nn) {
        (*this)->on_format("rst W", nn); }
    void on_scf() {
        (*this)->on_format("scf"); }
    void on_set(unsigned b, reg r, fast_u8 d) {
        index_regp irp = (*this)->get_index_rp_kind();
        if(irp == index_regp::hl || r == reg::at_hl)
            (*this)->on_format("set U, R", b, r, irp, d);
        else
            (*this)->on_format("set U, R, R", b, reg::at_hl, irp, d,
                               r, index_regp::hl, /* d= */ 0); }
    void on_sbc_hl_rp(regp rp) {
        (*this)->on_format("sbc hl, P", rp, index_regp::hl); }

    void disassemble() { (*this)->decode(); }

protected:
    D *operator -> () { return static_cast<D*>(this); }
};

class processor_base {
public:
    processor_base() {}

protected:
    static const unsigned sf_bit = 7;
    static const unsigned zf_bit = 6;
    static const unsigned yf_bit = 5;
    static const unsigned hf_bit = 4;
    static const unsigned xf_bit = 3;
    static const unsigned pf_bit = 2;
    static const unsigned nf_bit = 1;
    static const unsigned cf_bit = 0;

    static const fast_u8 sf_mask = 1 << sf_bit;
    static const fast_u8 zf_mask = 1 << zf_bit;
    static const fast_u8 yf_mask = 1 << yf_bit;
    static const fast_u8 hf_mask = 1 << hf_bit;
    static const fast_u8 xf_mask = 1 << xf_bit;
    static const fast_u8 pf_mask = 1 << pf_bit;
    static const fast_u8 nf_mask = 1 << nf_bit;
    static const fast_u8 cf_mask = 1 << cf_bit;

    template<typename T>
    fast_u8 zf_ari(T n) {
        return (n == 0 ? 1u : 0u) << zf_bit;
    }

    template<typename T>
    fast_u8 hf_ari(T r, T a, T b) {
        return (r ^ a ^ b) & hf_mask;
    }

    fast_u8 hf_dec(fast_u8 n) {
        return (n & 0xf) == 0xf ? hf_mask : 0;
    }

    fast_u8 hf_inc(fast_u8 n) {
        return (n & 0xf) == 0x0 ? hf_mask : 0;
    }

    template<typename T>
    fast_u8 pf_ari(T r, T a, T b) {
        fast_u16 x = r ^ a ^ b;
        return ((x >> 6) ^ (x >> 5)) & pf_mask;
    }

    bool pf_log4(fast_u8 n) {
        return 0x9669 & (1 << (n & 0xf));
    }

    fast_u8 pf_log(fast_u8 n) {
        bool lo = pf_log4(n);
        bool hi = pf_log4(n >> 4);
        return lo == hi ? pf_mask : 0;
    }

    fast_u8 pf_dec(fast_u8 n) {
        return n == 0x7f ? pf_mask : 0;
    }

    fast_u8 pf_inc(fast_u8 n) {
        return n == 0x80 ? pf_mask : 0;
    }

    fast_u8 cf_ari(bool c) {
        return c ? cf_mask : 0;
    }

    struct processor_state {
        processor_state()
            : last_read_addr(0), interrupt_disabled(false),
              bc(0), de(0), hl(0), af(0), ix(0), iy(0),
              alt_bc(0), alt_de(0), alt_hl(0), alt_af(0),
              pc(0), sp(0), ir(0), memptr(0),
              iff1(false), iff2(false), halted(false), int_mode(0)
        {}

        void ex_af_alt_af() {
            std::swap(af, alt_af);
        }

        void ex_de_hl() {
            std::swap(de, hl);
        }

        void exx() {
            std::swap(bc, alt_bc);
            std::swap(de, alt_de);
            std::swap(hl, alt_hl);
        }

        fast_u16 last_read_addr;
        bool interrupt_disabled;
        fast_u16 bc, de, hl, af, ix, iy;
        fast_u16 alt_bc, alt_de, alt_hl, alt_af;
        fast_u16 pc, sp, ir, memptr;
        bool iff1, iff2, halted;
        unsigned int_mode;
    } state;
};

template<typename D>
class processor : public instructions_decoder<D>,
                  public processor_base {
public:
    typedef instructions_decoder<D> decoder;

    processor() {}

    fast_u8 get_b() const { return get_high8(state.bc); }
    void set_b(fast_u8 b) { state.bc = make16(b, get_c()); }

    fast_u8 on_get_b() const { return get_b(); }
    void on_set_b(fast_u8 b) { set_b(b); }

    fast_u8 get_c() const { return get_low8(state.bc); }
    void set_c(fast_u8 c) { state.bc = make16(get_b(), c); }

    fast_u8 on_get_c() const { return get_c(); }
    void on_set_c(fast_u8 c) { set_c(c); }

    fast_u8 get_d() const { return get_high8(state.de); }
    void set_d(fast_u8 d) { state.de = make16(d, get_e()); }

    fast_u8 on_get_d() const { return get_d(); }
    void on_set_d(fast_u8 d) { set_d(d); }

    fast_u8 get_e() const { return get_low8(state.de); }
    void set_e(fast_u8 e) { state.de = make16(get_d(), e); }

    fast_u8 on_get_e() const { return get_e(); }
    void on_set_e(fast_u8 e) { set_e(e); }

    fast_u8 get_h() const { return get_high8(state.hl); }
    void set_h(fast_u8 h) { state.hl = make16(h, get_l()); }

    fast_u8 on_get_h() const { return get_h(); }
    void on_set_h(fast_u8 h) { set_h(h); }

    fast_u8 get_l() const { return get_low8(state.hl); }
    void set_l(fast_u8 l) { state.hl = make16(get_h(), l); }

    fast_u8 on_get_l() const { return get_l(); }
    void on_set_l(fast_u8 l) { set_l(l); }

    fast_u8 get_a() const { return get_high8(state.af); }
    void set_a(fast_u8 a) { state.af = make16(a, get_f()); }

    fast_u8 on_get_a() const { return get_a(); }
    void on_set_a(fast_u8 a) { set_a(a); }

    fast_u8 get_f() const { return get_low8(state.af); }
    void set_f(fast_u8 f) { state.af = make16(get_a(), f); }

    fast_u8 on_get_f() const { return get_f(); }
    void on_set_f(fast_u8 f) { set_f(f); }

    fast_u8 get_ixh() const { return get_high8(state.ix); }
    void set_ixh(fast_u8 ixh) { state.ix = make16(ixh, get_ixl()); }

    fast_u8 on_get_ixh() const { return get_ixh(); }
    void on_set_ixh(fast_u8 ixh) { set_ixh(ixh); }

    fast_u8 get_ixl() const { return get_low8(state.ix); }
    void set_ixl(fast_u8 ixl) { state.ix = make16(get_ixh(), ixl); }

    fast_u8 on_get_ixl() const { return get_ixl(); }
    void on_set_ixl(fast_u8 ixl) { set_ixl(ixl); }

    fast_u8 get_iyh() const { return get_high8(state.iy); }
    void set_iyh(fast_u8 iyh) { state.iy = make16(iyh, get_iyl()); }

    fast_u8 on_get_iyh() const { return get_iyh(); }
    void on_set_iyh(fast_u8 iyh) { set_iyh(iyh); }

    fast_u8 get_iyl() const { return get_low8(state.iy); }
    void set_iyl(fast_u8 iyl) { state.iy = make16(get_iyh(), iyl); }

    fast_u8 on_get_iyl() const { return get_iyl(); }
    void on_set_iyl(fast_u8 iyl) { set_iyl(iyl); }

    fast_u8 get_i() const { return get_high8(state.ir); }
    void set_i(fast_u8 i) { state.ir = make16(i, get_r_reg()); }

    fast_u8 on_get_i() const { return get_i(); }
    void on_set_i(fast_u8 i) { set_i(i); }

    void set_i_on_ld(fast_u8 i) { (*this)->on_set_i(i); }

    fast_u8 get_r_reg() const { return get_low8(state.ir); }
    void set_r_reg(fast_u8 r) { state.ir = make16(get_i(), r); }

    fast_u8 on_get_r_reg() const { return get_r_reg(); }
    void on_set_r_reg(fast_u8 r) { set_r_reg(r); }

    void on_inc_r_reg() {
        // TODO: Consider splitting R into R[7] and R[6:0].
        fast_u8 r = (*this)->on_get_r_reg();
        r = (r & 0x80) | (inc8(r) & 0x7f);
        (*this)->set_r_reg(r);
    }

    fast_u16 get_af() const { return state.af; }
    void set_af(fast_u16 af) { state.af = af; }

    fast_u16 on_get_af() {
        // Always get the low byte first.
        fast_u8 f = (*this)->on_get_f();
        fast_u8 a = (*this)->on_get_a();
        return make16(a, f); }
    void on_set_af(fast_u16 af) {
        // Always set the low byte first.
        (*this)->on_set_f(get_low8(af));
        (*this)->on_set_a(get_high8(af)); }

    fast_u16 get_hl() const { return state.hl; }
    void set_hl(fast_u16 hl) { state.hl = hl; }

    fast_u16 on_get_hl() {
        // Always get the low byte first.
        fast_u8 l = (*this)->on_get_l();
        fast_u8 h = (*this)->on_get_h();
        return make16(h, l); }
    void on_set_hl(fast_u16 hl) {<commit_msg>Support getting and setting the alternative AF.<commit_after>
/*  Z80 CPU Simulator.

    Copyright (C) 2017 <NAME>.
    <EMAIL>

    Published under the MIT license.
*/

#ifndef Z80_H
#define Z80_H

#include <cassert>
#include <climits>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <utility>

namespace z80 {

#if UINT_FAST8_MAX < UINT_MAX
typedef unsigned fast_u8;
#else
typedef uint_fast8_t fast_u8;
#endif

#if UINT_FAST16_MAX < UINT_MAX
typedef unsigned fast_u16;
#else
typedef uint_fast16_t fast_u16;
#endif

typedef uint_least8_t least_u8;
typedef uint_least16_t least_u16;

typedef uint_fast32_t size_type;

static const fast_u8 mask8 = 0xff;
static const fast_u8 sign8_mask = 0x80;
static const fast_u16 mask16 = 0xffff;

static inline void unused(...) {}

static inline bool get_sign8(fast_u8 n) {
    return (n & sign8_mask) != 0;
}

static inline fast_u8 add8(fast_u8 a, fast_u8 b) {
    return (a + b) & mask8;
}

static inline fast_u8 sub8(fast_u8 a, fast_u8 b) {
    return (a - b) & mask8;
}

static inline fast_u8 inc8(fast_u8 n) {
    return add8(n, 1);
}

static inline fast_u8 dec8(fast_u8 n) {
    return sub8(n, 1);
}

static inline fast_u8 rol8(fast_u8 n) {
    return ((n << 1) | (n >> 7)) & mask8;
}

static inline fast_u8 ror8(fast_u8 n) {
    return ((n >> 1) | (n << 7)) & mask8;
}

static inline fast_u8 neg8(fast_u8 n) {
    return ((n ^ mask8) + 1) & mask8;
}

static inline fast_u8 abs8(fast_u8 n) {
    return !get_sign8(n) ? n : neg8(n);
}

static inline int sign_extend8(fast_u8 n) {
    auto a = static_cast<int>(abs8(n));
    return !get_sign8(n) ? a : -a;
}

static inline fast_u8 get_low8(fast_u16 n) {
    return n & mask8;
}

static inline fast_u8 get_high8(fast_u16 n) {
    return (n >> 8) & mask8;
}

static inline fast_u16 make16(fast_u8 hi, fast_u8 lo) {
    return (static_cast<fast_u16>(hi) << 8) | lo;
}

static inline fast_u16 add16(fast_u16 a, fast_u16 b) {
    return (a + b) & mask16;
}

static inline fast_u16 sub16(fast_u16 a, fast_u16 b) {
    return (a - b) & mask16;
}

static inline fast_u16 inc16(fast_u16 n) {
    return add16(n, 1);
}

static inline fast_u16 dec16(fast_u16 n) {
    return sub16(n, 1);
}

enum class reg { b, c, d, e, h, l, at_hl, a };

enum class regp { bc, de, hl, sp };
enum class regp2 { bc, de, hl, af };
enum class index_regp { hl, ix, iy };

enum class instruction_prefix { none, cb, ed };

enum class alu { add, adc, sub, sbc, and_a, xor_a, or_a, cp };
enum class rot { rlc, rrc, rl, rr, sla, sra, sll, srl };
enum class block_ld { ldi, ldd, ldir, lddr };

enum condition { nz, z, nc, c, po, pe, p, m };

template<typename D>
class instructions_decoder {
public:
    instructions_decoder() {}

    index_regp get_index_rp_kind() const { return state.index_rp; }
    index_regp get_next_index_rp_kind() const { return state.next_index_rp; }

    fast_u8 read_disp_or_null(bool may_need_disp = true) {
        if(get_index_rp_kind() == index_regp::hl || !may_need_disp)
            return 0;
        fast_u8 d = (*this)->on_disp_read();
        (*this)->on_5t_exec_cycle();
        return d;
    }

    fast_u8 read_disp_or_null(reg r) {
        return read_disp_or_null(r == reg::at_hl);
    }

    fast_u8 read_disp_or_null(reg r1, reg r2) {
        return read_disp_or_null(r1 == reg::at_hl || r2 == reg::at_hl);
    }

    instruction_prefix get_prefix() const { return state.prefix; }
    void set_prefix(instruction_prefix p) { state.prefix = p; }

    void on_ed_prefix() {
        // TODO: Should we reset the index register pair here?
        set_prefix(instruction_prefix::ed);
    }

    void on_cb_prefix() {
        set_prefix(instruction_prefix::cb);
        state.next_index_rp = state.index_rp;
    }

    void on_prefix_reset() {
        set_prefix(instruction_prefix::none);
    }

    void on_set_next_index_rp(index_regp irp) {
        // TODO: Should we reset the prefix here?
        state.next_index_rp = irp;
    }

    unsigned decode_int_mode(fast_u8 y) {
        y &= 3;
        return y < 2 ? 0 : y - 1;
    }

    void decode_unprefixed() {
        fast_u8 op = (*this)->on_fetch();
        fast_u8 y = get_y_part(op);
        fast_u8 z = get_z_part(op);
        fast_u8 p = get_p_part(op);

        switch(op & x_mask) {
        case 0100: {
            // LD r[y], r[z] or HALT (in place of LD (HL), (HL))
            // LD r, r              f(4)
            // LD r, (HL)           f(4)           r(3)
            // LD r, (i+d)     f(4) f(4) r(3) e(5) r(3)
            // LD (HL), r           f(4)           w(3)
            // LD (i+d), r     f(4) f(4) r(3) e(5) w(3)
            // HALT                 f(4)
            auto rd = static_cast<reg>(y);
            auto rs = static_cast<reg>(z);
            if(rd == reg::at_hl && rs == reg::at_hl)
                assert(0);  // TODO: return (*this)->on_halt();
            return (*this)->on_ld_r_r(rd, rs, read_disp_or_null(rd, rs)); }
        case 0200: {
            // alu[y] r[z]
            // alu r            f(4)
            // alu (HL)         f(4)           r(3)
            // alu (i+d)   f(4) f(4) r(3) e(5) r(3)
            auto k = static_cast<alu>(y);
            auto r = static_cast<reg>(z);
            return (*this)->on_alu_r(k, r, read_disp_or_null(r)); }
        }
        switch(op & (x_mask | z_mask)) {
        case 0004: {
            // INC r[y]
            // INC r            f(4)
            // INC (HL)         f(4)           r(4) w(3)
            // INC (i+d)   f(4) f(4) r(3) e(5) r(4) w(3)
            auto r = static_cast<reg>(y);
            return (*this)->on_inc_r(r, read_disp_or_null(r)); }
        case 0005: {
            // DEC r[y]
            // DEC r            f(4)
            // DEC (HL)         f(4)           r(4) w(3)
            // DEC (i+d)   f(4) f(4) r(3) e(5) r(4) w(3)
            auto r = static_cast<reg>(y);
            return (*this)->on_dec_r(r, read_disp_or_null(r)); }
        case 0006: {
            // LD r[y], n
            // LD r, n              f(4)      r(3)
            // LD (HL), n           f(4)      r(3) w(3)
            // LD (i+d), n     f(4) f(4) r(3) r(5) w(3)
            auto r = static_cast<reg>(y);
            fast_u8 d, n;
            if(r != reg::at_hl || get_index_rp_kind() == index_regp::hl) {
                d = 0;
                n = (*this)->on_3t_imm8_read();
            } else {
                d = (*this)->on_disp_read();
                n = (*this)->on_5t_imm8_read();
            }
            return (*this)->on_ld_r_n(r, d, n); }
        case 0300: {
            // RET cc[y]  f(5) + r(3) r(3)
            (*this)->on_5t_fetch_cycle();
            auto cc = static_cast<condition>(y);
            return (*this)->on_ret_cc(cc); }
        case 0302: {
            // JP cc[y], nn  f(4) r(3) r(3)
            auto cc = static_cast<condition>(y);
            return (*this)->on_jp_cc_nn(cc, (*this)->on_3t_3t_imm16_read()); }
        case 0304: {
            // CALL cc[y], nn
            // cc met:      f(4) r(3) r(4) w(3) w(3)
            // cc not met:  f(4) r(3) r(3)
            auto cc = static_cast<condition>(y);
            bool cc_met = (*this)->check_condition(cc);
            fast_u16 nn = c ? (*this)->on_3t_4t_imm16_read() :
                              (*this)->on_3t_3t_imm16_read();
            return (*this)->on_call_cc_nn(cc_met, cc, nn); }
        case 0306: {
            // alu[y] n  f(4) r(3)
            auto k = static_cast<alu>(y);
            return (*this)->on_alu_n(k, (*this)->on_3t_imm8_read()); }
        case 0307:
            // RST y*8  f(5) w(3) w(3)
            (*this)->on_5t_fetch_cycle();
            return (*this)->on_rst(y * 8);
        }
        if((op & (x_mask | z_mask | (y_mask - 0030))) == 0040) {
            // JR cc[y-4], d  f(4) r(3) + e(5)
            auto cc = static_cast<condition>((op & (y_mask - 0040)) >> 3);
            return (*this)->on_jr_cc(cc, (*this)->on_disp_read());
        }
        switch(op & (x_mask | z_mask | q_mask)) {
        case 0001: {
            // LD rp[p], nn
            // LD rr, nn        f(4) r(3) r(3)
            // LD i, nn    f(4) f(4) r(3) r(3)
            auto rp = static_cast<regp>(p);
            return (*this)->on_ld_rp_nn(rp, (*this)->on_3t_3t_imm16_read()); }
        case 0003: {
            // INC rp[p]
            // INC rr           f(6)
            // INC i       f(4) f(6)
            (*this)->on_6t_fetch_cycle();
            auto rp = static_cast<regp>(p);
            return (*this)->on_inc_rp(rp); }
        case 0011: {
            // ADD HL, rp[p]
            // ADD HL, rr           f(4) e(4) e(3)
            // ADD i, rr       f(4) f(4) e(4) e(3)
            auto rp = static_cast<regp>(p);
            return (*this)->on_add_irp_rp(rp); }
        case 0013: {
            // DEC rp[p]
            // DEC rr           f(6)
            // DEC i       f(4) f(6)
            (*this)->on_6t_fetch_cycle();
            auto rp = static_cast<regp>(p);
            return (*this)->on_dec_rp(rp); }
        case 0301: {
            // POP rp2[p]
            // POP rr           f(4) r(3) r(3)
            // POP i       f(4) f(4) r(3) r(3)
            auto rp = static_cast<regp2>(p);
            return (*this)->on_pop_rp(rp); }
        case 0305: {
            // PUSH rp2[p]
            // PUSH rr          f(5) w(3) w(3)
            // PUSH i      f(4) f(5) w(3) w(3)
            (*this)->on_5t_fetch_cycle();
            auto rp = static_cast<regp2>(p);
            return (*this)->on_push_rp(rp); }
        }
        switch(op & (x_mask | z_mask | q_mask | (p_mask - 1))) {
        case 0002: {
            // LD (rp[p]), A  f(4) w(3)
            auto rp = static_cast<regp>(p);
            return (*this)->on_ld_at_rp_a(rp); }
        case 0012: {
            // LD A, (rp[p])  f(4) r(3)
            auto rp = static_cast<regp>(p);
            return (*this)->on_ld_a_at_rp(rp); }
        }
        switch(op) {
        case 0x00:
            // NOP  f(4)
            return (*this)->on_nop();
        case 0x08:
            // EX AF, AF'  f(4)
            return (*this)->on_ex_af_alt_af();
        case 0x0f:
            // RRCA  f(4)
            return (*this)->on_rrca();
        case 0x10:
            // DJNZ  f(5) r(3) + e(5)
            (*this)->on_5t_fetch_cycle();
            return (*this)->on_djnz((*this)->on_disp_read());
        case 0x18:
            // JR d  f(4) r(3) e(5)
            return (*this)->on_jr((*this)->on_disp_read());
        case 0x1f:
            // RRA  f(4)
            return (*this)->on_rra();
        case 0x22:
            // LD (nn), HL          f(4) r(3) r(3) w(3) w(3)
            // LD (nn), i      f(4) f(4) r(3) r(3) w(3) w(3)
            return (*this)->on_ld_at_nn_irp((*this)->on_3t_3t_imm16_read());
        case 0x2a:
            // LD HL, (nn)          f(4) r(3) r(3) r(3) r(3)
            // LD i, (nn)      f(4) f(4) r(3) r(3) r(3) r(3)
            return (*this)->on_ld_irp_at_nn((*this)->on_3t_3t_imm16_read());
        case 0x2f:
            // CPL  f(4)
            return (*this)->on_cpl();
        case 0x32:
            // LD (nn), A  f(4) r(3) r(3) w(3)
            return (*this)->on_ld_at_nn_a((*this)->on_3t_3t_imm16_read());
        case 0x37:
            // SCF  f(4)
            return (*this)->on_scf();
        case 0x3a:
            // LD A, (nn)  f(4) r(3) r(3) r(3)
            return (*this)->on_ld_a_at_nn((*this)->on_3t_3t_imm16_read());
        case 0x3f:
            // CCF  f(4)
            return (*this)->on_ccf();
        case 0xc3:
            // JP nn  f(4) r(3) r(3)
            return (*this)->on_jp_nn((*this)->on_3t_3t_imm16_read());
        case 0xc9:
            // RET  f(4) r(3) r(3)
            return (*this)->on_ret();
        case 0xcb:
            // CB prefix.
            return (*this)->on_cb_prefix();
        case 0xcd:
            // CALL nn  f(4) r(3) r(4) w(3) w(3)
            return (*this)->on_call_nn((*this)->on_3t_4t_imm16_read());
        case 0xd3:
            // OUT (n), A  f(4) r(3) o(4)
            return (*this)->on_out_n_a((*this)->on_3t_imm8_read());
        case 0xd9:
            // EXX  f(4)
            return (*this)->on_exx();
        case 0xe3:
            // EX (SP), irp
            // EX (SP), HL          f(4) r(3) r(4) w(3) w(5)
            // EX (SP), i      f(4) f(4) r(3) r(4) w(3) w(5)
            return (*this)->on_ex_at_sp_irp();
        case 0xe9:
            // JP HL            f(4)
            // JP i        f(4) f(4)
            return (*this)->on_jp_irp();
        case 0xeb:
            // EX DE, HL  f(4)
            return (*this)->on_ex_de_hl();
        case 0xed:
            // ED prefix.
            return (*this)->on_ed_prefix();
        case 0xf3:
            // DI  f(4)
            return (*this)->on_di();
        case 0xf9:
            // LD SP, HL        f(6)
            // LD SP, i    f(4) f(6)
            (*this)->on_6t_fetch_cycle();
            return (*this)->on_ld_sp_irp();
        case 0xfb:
            // EI  f(4)
            return (*this)->on_ei();
        case 0xfd:
            // FD prefix (IY-indexed instructions).
            return (*this)->on_set_next_index_rp(index_regp::iy);
        }

        // TODO
        std::fprintf(stderr, "Unknown opcode 0x%02x at 0x%04x.\n",
                     static_cast<unsigned>(op),
                     static_cast<unsigned>((*this)->get_last_read_addr()));
        std::abort();
    }

    void decode_cb_prefixed() {
        prefix_reset_guard guard(this);

        fast_u8 d = 0;
        index_regp irp = get_index_rp_kind();
        if(irp != index_regp::hl)
            d = (*this)->on_disp_read();

        fast_u8 op = (*this)->on_fetch();
        if(irp != index_regp::hl)
            (*this)->on_5t_fetch_cycle();

        fast_u8 y = get_y_part(op);
        fast_u8 z = get_z_part(op);

        auto r = static_cast<reg>(z);

        switch(op & x_mask) {
        case 0000: {
            // rot[y] r[z]
            // rot r                f(4)      f(4)
            // rot (HL)             f(4)      f(4) r(4) w(3)
            // rot (i+d)       f(4) f(4) r(3) f(5) r(4) w(3)
            auto k = static_cast<rot>(y);
            return (*this)->on_rot(k, r, d); }
        case 0100: {
            // BIT y, r[z]
            // BIT b, r             f(4)      f(4)
            // BIT b, (HL)          f(4)      f(4) r(4)
            // BIT b, (i+d)    f(4) f(4) r(3) f(5) r(4)
            auto b = static_cast<unsigned>(y);
            return (*this)->on_bit(b, r, d); }
        case 0200: {
            // RES y, r[z]
            // RES b, r             f(4)      f(4)
            // RES b, (HL)          f(4)      f(4) r(4) w(3)
            // RES b, (i+d)    f(4) f(4) r(3) f(5) r(4) w(3)
            auto b = static_cast<unsigned>(y);
            return (*this)->on_res(b, r, d); }
        case 0300: {
            // SET y, r[z]
            // SET b, r             f(4)      f(4)
            // SET b, (HL)          f(4)      f(4) r(4) w(3)
            // SET b, (i+d)    f(4) f(4) r(3) f(5) r(4) w(3)
            auto b = static_cast<unsigned>(y);
            return (*this)->on_set(b, r, d); }
        }

        std::fprintf(stderr, "Unknown CB-prefixed opcode 0x%02x at 0x%04x.\n",
                     static_cast<unsigned>(op),
                     static_cast<unsigned>((*this)->get_last_read_addr()));
        std::abort();
    }

    void decode_ed_prefixed() {
        prefix_reset_guard guard(this);
        fast_u8 op = (*this)->on_fetch();
        fast_u8 y = get_y_part(op);
        fast_u8 p = get_p_part(op);

        switch(op & (x_mask | z_mask)) {
        case 0100: {
            // IN r[y], (C)  f(4) f(4) i(4)
            // IN (C)        f(4) f(4) i(4)
            auto r = static_cast<reg>(y);
            return (*this)->on_in_r_c(r); }
        case 0102: {
            // ADC HL, rp[p]  f(4) f(4) e(4) e(3)
            // SBC HL, rp[p]  f(4) f(4) e(4) e(3)
            auto rp = static_cast<regp>(p);
            return op & q_mask ? (*this)->on_adc_hl_rp(rp) :
                                 (*this)->on_sbc_hl_rp(rp); }
        case 0103: {
            // LD rp[p], (nn)  f(4) f(4) r(3) r(3) r(3) r(3)
            // LD (nn), rp[p]  f(4) f(4) r(3) r(3) w(3) w(3)
            auto rp = static_cast<regp>(p);
            fast_u16 nn = (*this)->on_3t_3t_imm16_read();
            return op & q_mask ? (*this)->on_ld_rp_at_nn(rp, nn) :
                                 (*this)->on_ld_at_nn_rp(nn, rp); }
        case 0106: {
            // IM im[y]  f(4) f(4)
            return (*this)->on_im(decode_int_mode(y)); }
        case 0200: {
            // LDI, LDD, LDIR, LDDR  f(4) f(4) r(3) w(5) + e(5)
            if(y < 4)
                return (*this)->on_noni_ed(op);
            auto k = static_cast<block_ld>(y - 4);
            return (*this)->on_block_ld(k); }
        }
        switch(op) {
        case 0x47: {
            // LD I, A  f(4) f(5)
            (*this)->on_5t_fetch_cycle();
            return (*this)->on_ld_i_a(); }
        }

        std::fprintf(stderr, "Unknown ED-prefixed opcode 0x%02x at 0x%04x.\n",
                     static_cast<unsigned>(op),
                     static_cast<unsigned>((*this)->get_last_read_addr()));
        std::abort();
    }

    void on_decode() {
        state.index_rp = state.next_index_rp;
        state.next_index_rp = index_regp::hl;

        switch(get_prefix()) {
        case instruction_prefix::none:
            return decode_unprefixed();
        case instruction_prefix::cb:
            return decode_cb_prefixed();
        case instruction_prefix::ed:
            return decode_ed_prefixed();
        }
        assert(0);
    }

    void decode() { (*this)->on_decode(); }

protected:
    class prefix_reset_guard {
    public:
        prefix_reset_guard(instructions_decoder *decoder)
            : decoder(decoder)
        {}

        ~prefix_reset_guard() {
            (*decoder)->on_prefix_reset();
        }

    private:
        instructions_decoder *decoder;
    };

    D *operator -> () { return static_cast<D*>(this); }

    static const fast_u8 x_mask = 0300;

    static const fast_u8 y_mask = 0070;
    fast_u8 get_y_part(fast_u8 op) { return (op & y_mask) >> 3; }

    static const fast_u8 z_mask = 0007;
    fast_u8 get_z_part(fast_u8 op) { return op & z_mask; }

    static const fast_u8 p_mask = 0060;
    fast_u8 get_p_part(fast_u8 op) { return (op & p_mask) >> 4; }

    static const fast_u8 q_mask = 0010;

private:
    struct decoder_state {
        decoder_state()
            : index_rp(index_regp::hl), next_index_rp(index_regp::hl),
              prefix(instruction_prefix::none)
        {}

        index_regp index_rp;
        index_regp next_index_rp;
        instruction_prefix prefix;
    };

    decoder_state state;
};

const char *get_reg_name(reg r);
const char *get_reg_name(regp rp, index_regp irp = index_regp::hl);
const char *get_reg_name(regp2 rp, index_regp irp = index_regp::hl);
const char *get_reg_name(index_regp irp);
const char *get_mnemonic(alu k);
const char *get_mnemonic(rot k);
const char *get_mnemonic(block_ld k);
bool is_two_operand_alu_instr(alu k);
const char *get_index_reg_name(index_regp irp);
const char *get_condition_name(condition cc);

class disassembler_base {
public:
    disassembler_base() {}
    virtual ~disassembler_base() {}

    virtual void on_output(const char *out) = 0;

    void on_format(const char *fmt) {
        on_format_impl(fmt, /* args= */ nullptr);
    }

    template<typename... types>
    void on_format(const char *fmt, const types &... args) {
        const void *ptrs[] = { static_cast<const void*>(&args)... };
        on_format_impl(fmt, ptrs);
    }

    virtual void on_format_impl(const char *fmt, const void *args[]);
};

template<typename D>
class disassembler : public instructions_decoder<D>,
                     public disassembler_base {
public:
    typedef instructions_decoder<D> decoder;

    disassembler() {}

    fast_u8 on_fetch() { return (*this)->on_read_next_byte(); }
    void on_5t_fetch_cycle() {}
    void on_6t_fetch_cycle() {}

    // 'call cc, nn' instructions require this function to disambiguate between
    // read cycles of various lengths. This disambiguation does not affect
    // disassembling so we just return false here.
    bool check_condition(condition cc) {
        unused(cc);
        return false;
    }

    fast_u8 on_3t_imm8_read() { return (*this)->on_read_next_byte(); }
    fast_u8 on_5t_imm8_read() { return (*this)->on_read_next_byte(); }

    fast_u16 on_3t_3t_imm16_read() {
        fast_u8 lo = (*this)->on_read_next_byte();
        fast_u8 hi = (*this)->on_read_next_byte();
        return make16(hi, lo); }
    fast_u16 on_3t_4t_imm16_read() {
        fast_u8 lo = (*this)->on_read_next_byte();
        fast_u8 hi = (*this)->on_read_next_byte();
        return make16(hi, lo); }

    fast_u8 on_disp_read() { return (*this)->on_read_next_byte(); }

    void on_3t_exec_cycle() {}
    void on_4t_exec_cycle() {}
    void on_5t_exec_cycle() {}

    void on_ed_prefix() { decoder::on_ed_prefix();
                          (*this)->on_format("noni 0xed"); }

    void on_noni_ed(fast_u8 op) {
        (*this)->on_format("noni N, N", 0xed, op); }

    void on_add_irp_rp(regp rp) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("add P, P", regp::hl, irp, rp, irp); }
    void on_adc_hl_rp(regp rp) {
        (*this)->on_format("adc hl, P", rp, index_regp::hl); }
    void on_alu_n(alu k, fast_u8 n) {
        (*this)->on_format("A N", k, n); }
    void on_alu_r(alu k, reg r, fast_u8 d) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("A R", k, r, irp, d); }
    void on_block_ld(block_ld k) {
        (*this)->on_format("L", k); }
    void on_bit(unsigned b, reg r, fast_u8 d) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("bit U, R", b, r, irp, d); }
    void on_call_nn(fast_u16 nn) {
        (*this)->on_format("call W", nn); }
    void on_call_cc_nn(bool cc_met, condition cc, fast_u16 nn) {
        unused(cc_met);
        (*this)->on_format("call C, W", cc, nn); }
    void on_ccf() {
        (*this)->on_format("ccf"); }
    void on_cpl() {
        (*this)->on_format("cpl"); }
    void on_dec_r(reg r, fast_u8 d) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("dec R", r, irp, d); }
    void on_dec_rp(regp rp) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("dec P", rp, irp); }
    void on_di() {
        (*this)->on_format("di"); }
    void on_djnz(fast_u8 d) {
        (*this)->on_format("djnz D", sign_extend8(d) + 2); }
    void on_ei() {
        (*this)->on_format("ei"); }
    void on_ex_af_alt_af() {
        (*this)->on_format("ex af, af'"); }
    void on_ex_de_hl() {
        (*this)->on_format("ex de, hl"); }
    void on_ex_at_sp_irp() {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("ex (sp), P", regp::hl, irp); }
    void on_exx() {
        (*this)->on_format("exx"); }
    void on_im(unsigned mode) {
        (*this)->on_format("im U", mode); }
    void on_in_r_c(reg r) {
        if(r == reg::at_hl)
            (*this)->on_format("in (c)");
        else
            (*this)->on_format("in R, (c)", r, index_regp::hl, 0); }
    void on_inc_r(reg r, fast_u8 d) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("inc R", r, irp, d); }
    void on_inc_rp(regp rp) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("inc P", rp, irp); }
    void on_jp_cc_nn(condition cc, fast_u16 nn) {
        (*this)->on_format("jp C, W", cc, nn); }
    void on_jp_irp() {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("jp (P)", regp::hl, irp); }
    void on_jp_nn(fast_u16 nn) {
        (*this)->on_format("jp W", nn); }
    void on_jr(fast_u8 d) {
        (*this)->on_format("jr D", sign_extend8(d) + 2); }
    void on_jr_cc(condition cc, fast_u8 d) {
        (*this)->on_format("jr C, D", cc, sign_extend8(d) + 2); }
    void on_ld_i_a() {
        (*this)->on_format("ld i, a"); }
    void on_ld_r_r(reg rd, reg rs, fast_u8 d) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("ld R, R", rd, irp, d, rs, irp, d); }
    void on_ld_r_n(reg r, fast_u8 d, fast_u8 n) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("ld R, N", r, irp, d, n); }
    void on_ld_rp_nn(regp rp, fast_u16 nn) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("ld P, W", rp, irp, nn); }
    void on_ld_irp_at_nn(fast_u16 nn) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("ld P, (W)", regp::hl, irp, nn); }
    void on_ld_at_nn_irp(fast_u16 nn) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("ld (W), P", nn, regp::hl, irp); }
    void on_ld_rp_at_nn(regp rp, fast_u16 nn) {
        (*this)->on_format("ld P, (W)", rp, index_regp::hl, nn); }
    void on_ld_at_nn_rp(fast_u16 nn, regp rp) {
        (*this)->on_format("ld (W), P", nn, rp, index_regp::hl); }
    void on_ld_a_at_nn(fast_u16 nn) {
        (*this)->on_format("ld a, (W)", nn); }
    void on_ld_at_nn_a(fast_u16 nn) {
        (*this)->on_format("ld (W), a", nn); }
    void on_ld_a_at_rp(regp rp) {
        (*this)->on_format("ld a, (P)", rp, index_regp::hl); }
    void on_ld_at_rp_a(regp rp) {
        (*this)->on_format("ld (P), a", rp, index_regp::hl); }
    void on_ld_sp_irp() {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("ld sp, P", regp::hl, irp); }
    void on_nop() {
        (*this)->on_format("nop"); }
    void on_out_n_a(fast_u8 n) {
        (*this)->on_format("out (N), a", n); }
    void on_pop_rp(regp2 rp) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("pop G", rp, irp); }
    void on_push_rp(regp2 rp) {
        index_regp irp = (*this)->get_index_rp_kind();
        (*this)->on_format("push G", rp, irp); }
    void on_res(unsigned b, reg r, fast_u8 d) {
        index_regp irp = (*this)->get_index_rp_kind();
        if(irp == index_regp::hl || r == reg::at_hl)
            (*this)->on_format("res U, R", b, r, irp, d);
        else
            (*this)->on_format("res U, R, R", b, reg::at_hl, irp, d,
                               r, index_regp::hl, /* d= */ 0); }
    void on_ret() {
        (*this)->on_format("ret"); }
    void on_ret_cc(condition cc) {
        (*this)->on_format("ret C", cc); }
    void on_rot(rot k, reg r, fast_u8 d) {
        index_regp irp = (*this)->get_index_rp_kind();
        if(irp == index_regp::hl || r == reg::at_hl)
            (*this)->on_format("O R", k, r, irp, d);
        else
            (*this)->on_format("O R, R", k, reg::at_hl, irp, d,
                               r, index_regp::hl, /* d= */ 0); }
    void on_rra() {
        (*this)->on_format("rra"); }
    void on_rrca() {
        (*this)->on_format("rrca"); }
    void on_rst(fast_u16 nn) {
        (*this)->on_format("rst W", nn); }
    void on_scf() {
        (*this)->on_format("scf"); }
    void on_set(unsigned b, reg r, fast_u8 d) {
        index_regp irp = (*this)->get_index_rp_kind();
        if(irp == index_regp::hl || r == reg::at_hl)
            (*this)->on_format("set U, R", b, r, irp, d);
        else
            (*this)->on_format("set U, R, R", b, reg::at_hl, irp, d,
                               r, index_regp::hl, /* d= */ 0); }
    void on_sbc_hl_rp(regp rp) {
        (*this)->on_format("sbc hl, P", rp, index_regp::hl); }

    void disassemble() { (*this)->decode(); }

protected:
    D *operator -> () { return static_cast<D*>(this); }
};

class processor_base {
public:
    processor_base() {}

protected:
    static const unsigned sf_bit = 7;
    static const unsigned zf_bit = 6;
    static const unsigned yf_bit = 5;
    static const unsigned hf_bit = 4;
    static const unsigned xf_bit = 3;
    static const unsigned pf_bit = 2;
    static const unsigned nf_bit = 1;
    static const unsigned cf_bit = 0;

    static const fast_u8 sf_mask = 1 << sf_bit;
    static const fast_u8 zf_mask = 1 << zf_bit;
    static const fast_u8 yf_mask = 1 << yf_bit;
    static const fast_u8 hf_mask = 1 << hf_bit;
    static const fast_u8 xf_mask = 1 << xf_bit;
    static const fast_u8 pf_mask = 1 << pf_bit;
    static const fast_u8 nf_mask = 1 << nf_bit;
    static const fast_u8 cf_mask = 1 << cf_bit;

    template<typename T>
    fast_u8 zf_ari(T n) {
        return (n == 0 ? 1u : 0u) << zf_bit;
    }

    template<typename T>
    fast_u8 hf_ari(T r, T a, T b) {
        return (r ^ a ^ b) & hf_mask;
    }

    fast_u8 hf_dec(fast_u8 n) {
        return (n & 0xf) == 0xf ? hf_mask : 0;
    }

    fast_u8 hf_inc(fast_u8 n) {
        return (n & 0xf) == 0x0 ? hf_mask : 0;
    }

    template<typename T>
    fast_u8 pf_ari(T r, T a, T b) {
        fast_u16 x = r ^ a ^ b;
        return ((x >> 6) ^ (x >> 5)) & pf_mask;
    }

    bool pf_log4(fast_u8 n) {
        return 0x9669 & (1 << (n & 0xf));
    }

    fast_u8 pf_log(fast_u8 n) {
        bool lo = pf_log4(n);
        bool hi = pf_log4(n >> 4);
        return lo == hi ? pf_mask : 0;
    }

    fast_u8 pf_dec(fast_u8 n) {
        return n == 0x7f ? pf_mask : 0;
    }

    fast_u8 pf_inc(fast_u8 n) {
        return n == 0x80 ? pf_mask : 0;
    }

    fast_u8 cf_ari(bool c) {
        return c ? cf_mask : 0;
    }

    struct processor_state {
        processor_state()
            : last_read_addr(0), interrupt_disabled(false),
              bc(0), de(0), hl(0), af(0), ix(0), iy(0),
              alt_bc(0), alt_de(0), alt_hl(0), alt_af(0),
              pc(0), sp(0), ir(0), memptr(0),
              iff1(false), iff2(false), halted(false), int_mode(0)
        {}

        void ex_af_alt_af() {
            std::swap(af, alt_af);
        }

        void ex_de_hl() {
            std::swap(de, hl);
        }

        void exx() {
            std::swap(bc, alt_bc);
            std::swap(de, alt_de);
            std::swap(hl, alt_hl);
        }

        fast_u16 last_read_addr;
        bool interrupt_disabled;
        fast_u16 bc, de, hl, af, ix, iy;
        fast_u16 alt_bc, alt_de, alt_hl, alt_af;
        fast_u16 pc, sp, ir, memptr;
        bool iff1, iff2, halted;
        unsigned int_mode;
    } state;
};

template<typename D>
class processor : public instructions_decoder<D>,
                  public processor_base {
public:
    typedef instructions_decoder<D> decoder;

    processor() {}

    fast_u8 get_b() const { return get_high8(state.bc); }
    void set_b(fast_u8 b) { state.bc = make16(b, get_c()); }

    fast_u8 on_get_b() const { return get_b(); }
    void on_set_b(fast_u8 b) { set_b(b); }

    fast_u8 get_c() const { return get_low8(state.bc); }
    void set_c(fast_u8 c) { state.bc = make16(get_b(), c); }

    fast_u8 on_get_c() const { return get_c(); }
    void on_set_c(fast_u8 c) { set_c(c); }

    fast_u8 get_d() const { return get_high8(state.de); }
    void set_d(fast_u8 d) { state.de = make16(d, get_e()); }

    fast_u8 on_get_d() const { return get_d(); }
    void on_set_d(fast_u8 d) { set_d(d); }

    fast_u8 get_e() const { return get_low8(state.de); }
    void set_e(fast_u8 e) { state.de = make16(get_d(), e); }

    fast_u8 on_get_e() const { return get_e(); }
    void on_set_e(fast_u8 e) { set_e(e); }

    fast_u8 get_h() const { return get_high8(state.hl); }
    void set_h(fast_u8 h) { state.hl = make16(h, get_l()); }

    fast_u8 on_get_h() const { return get_h(); }
    void on_set_h(fast_u8 h) { set_h(h); }

    fast_u8 get_l() const { return get_low8(state.hl); }
    void set_l(fast_u8 l) { state.hl = make16(get_h(), l); }

    fast_u8 on_get_l() const { return get_l(); }
    void on_set_l(fast_u8 l) { set_l(l); }

    fast_u8 get_a() const { return get_high8(state.af); }
    void set_a(fast_u8 a) { state.af = make16(a, get_f()); }

    fast_u8 on_get_a() const { return get_a(); }
    void on_set_a(fast_u8 a) { set_a(a); }

    fast_u8 get_f() const { return get_low8(state.af); }
    void set_f(fast_u8 f) { state.af = make16(get_a(), f); }

    fast_u8 on_get_f() const { return get_f(); }
    void on_set_f(fast_u8 f) { set_f(f); }

    fast_u8 get_ixh() const { return get_high8(state.ix); }
    void set_ixh(fast_u8 ixh) { state.ix = make16(ixh, get_ixl()); }

    fast_u8 on_get_ixh() const { return get_ixh(); }
    void on_set_ixh(fast_u8 ixh) { set_ixh(ixh); }

    fast_u8 get_ixl() const { return get_low8(state.ix); }
    void set_ixl(fast_u8 ixl) { state.ix = make16(get_ixh(), ixl); }

    fast_u8 on_get_ixl() const { return get_ixl(); }
    void on_set_ixl(fast_u8 ixl) { set_ixl(ixl); }

    fast_u8 get_iyh() const { return get_high8(state.iy); }
    void set_iyh(fast_u8 iyh) { state.iy = make16(iyh, get_iyl()); }

    fast_u8 on_get_iyh() const { return get_iyh(); }
    void on_set_iyh(fast_u8 iyh) { set_iyh(iyh); }

    fast_u8 get_iyl() const { return get_low8(state.iy); }
    void set_iyl(fast_u8 iyl) { state.iy = make16(get_iyh(), iyl); }

    fast_u8 on_get_iyl() const { return get_iyl(); }
    void on_set_iyl(fast_u8 iyl) { set_iyl(iyl); }

    fast_u8 get_i() const { return get_high8(state.ir); }
    void set_i(fast_u8 i) { state.ir = make16(i, get_r_reg()); }

    fast_u8 on_get_i() const { return get_i(); }
    void on_set_i(fast_u8 i) { set_i(i); }

    void set_i_on_ld(fast_u8 i) { (*this)->on_set_i(i); }

    fast_u8 get_r_reg() const { return get_low8(state.ir); }
    void set_r_reg(fast_u8 r) { state.ir = make16(get_i(), r); }

    fast_u8 on_get_r_reg() const { return get_r_reg(); }
    void on_set_r_reg(fast_u8 r) { set_r_reg(r); }

    void on_inc_r_reg() {
        // TODO: Consider splitting R into R[7] and R[6:0].
        fast_u8 r = (*this)->on_get_r_reg();
        r = (r & 0x80) | (inc8(r) & 0x7f);
        (*this)->set_r_reg(r);
    }

    fast_u16 get_af() const { return state.af; }
    void set_af(fast_u16 af) { state.af = af; }

    fast_u16 get_alt_af() const { return state.alt_af; }
    void set_alt_af(fast_u16 af) { state.alt_af = af; }

    fast_u16 on_get_af() {
        // Always get the low byte first.
        fast_u8 f = (*this)->on_get_f();
        fast_u8 a = (*this)->on_get_a();
        return make16(a, f); }
    void on_set_af(fast_u16 af) {
        // Always set the low byte first.
        (*this)->on_set_f(get_low8(af));
        (*this)->on_set_a(get_high8(af)); }

    fast_u16 get_hl() const { return state.hl; }
    void set_hl(fast_u16 hl) { state.hl = hl; }

    fast_u16 on_get_hl() {
        // Always get the low byte first.
        fast_u8 l = (*this)->on_get_l();
        fast_u8 h = (*this)->on_get_h();
        return make16(h, l); }
    void on_set_hl(fast_u16 hl) {
{'fraction_non_alphanumeric': 0.16120226430274992, 'fraction_numerical': 0.03134967385994655, 'mean_word_length': 1.9826448405896469, 'pattern_counts': {'":': 0, '<': 181, '<?xml version=': 0, '>': 559, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 18, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5650445', 'n_tokens_mistral': 28283, 'n_tokens_neox': 27173, 'n_words': 5952}