mirror of
https://github.com/mlaux/gb6.git
synced 2026-10-25 07:02:33 +00:00
678 lines
22 KiB
C
678 lines
22 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "compiler.h"
|
|
#include "emitters.h"
|
|
#include "branches.h"
|
|
#include "flags.h"
|
|
#include "interop.h"
|
|
#include "cb_prefix.h"
|
|
#include "reg_loads.h"
|
|
#include "mem_loads.h"
|
|
#include "alu.h"
|
|
#include "stack.h"
|
|
#include "instructions.h"
|
|
#include "timing.h"
|
|
|
|
// helper for reading GB memory during compilation
|
|
#define READ_BYTE(off) (ctx->read(ctx->dmg, src_address + (off)))
|
|
|
|
uint16_t m68k_offsets[256];
|
|
|
|
int pending_cycles;
|
|
|
|
// GB offsets that are targets of backward jumps within the block; pending
|
|
// cycles need to be flushed before these so loop heads sit at pending == 0
|
|
uint8_t flush_at[256];
|
|
|
|
// SM83 instruction lengths for the branch-target pre-scan (CB handled as 2)
|
|
const uint8_t insn_length[256] = {
|
|
1, 3, 1, 1, 1, 1, 2, 1, 3, 1, 1, 1, 1, 1, 2, 1, // 0x00
|
|
2, 3, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1, // 0x10
|
|
2, 3, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1, // 0x20
|
|
2, 3, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1, // 0x30
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0
|
|
1, 1, 3, 3, 3, 1, 2, 1, 1, 1, 3, 2, 3, 3, 2, 1, // 0xc0
|
|
1, 1, 3, 1, 3, 1, 2, 1, 1, 1, 3, 1, 3, 1, 2, 1, // 0xd0
|
|
2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 1, 1, 2, 1, // 0xe0
|
|
2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 1, 1, 2, 1, // 0xf0
|
|
};
|
|
|
|
void defer_cycles(int n)
|
|
{
|
|
pending_cycles += n;
|
|
}
|
|
|
|
void flush_cycles(struct code_block *block)
|
|
{
|
|
emit_add_cycles(block, pending_cycles);
|
|
pending_cycles = 0;
|
|
}
|
|
|
|
// mark targets of backward jr within the block so the main loop flushes
|
|
// pending cycles there. spurious marks (offsets the main loop never lands
|
|
// on, or code past an exit) are harmless
|
|
static void scan_branch_targets(
|
|
uint16_t src_address,
|
|
struct compile_ctx *ctx
|
|
) {
|
|
uint16_t off = 0;
|
|
|
|
while (off < 256) {
|
|
uint8_t op = READ_BYTE(off);
|
|
uint16_t next = off + insn_length[op];
|
|
|
|
if (op == 0x18 || op == 0x20 || op == 0x28
|
|
|| op == 0x30 || op == 0x38) {
|
|
int16_t target = (int16_t) next + (int8_t) READ_BYTE(off + 1);
|
|
if (target >= 0 && target < off) {
|
|
flush_at[target] = 1;
|
|
}
|
|
}
|
|
off = next;
|
|
}
|
|
}
|
|
|
|
// Reconstruct BC from split format (0x00BB00CC) into D1.w as 0xBBCC
|
|
void compile_join_bc(struct code_block *block, int dreg)
|
|
{
|
|
emit_move_l_dn_dn(block, REG_68K_D_BC, dreg); // D1 = 0x00BB00CC
|
|
emit_lsr_l_imm_dn(block, 8, dreg); // D1 = 0x0000BB00
|
|
emit_move_b_dn_dn(block, REG_68K_D_BC, dreg); // D1 = 0x0000BBCC
|
|
}
|
|
|
|
// Reconstruct DE from split format (0x00DD00EE) into D1.w as 0xDDEE
|
|
void compile_join_de(struct code_block *block, int dreg)
|
|
{
|
|
emit_move_l_dn_dn(block, REG_68K_D_DE, dreg); // D1 = 0x00DD00EE
|
|
emit_lsr_l_imm_dn(block, 8, dreg); // D1 = 0x0000DD00
|
|
emit_move_b_dn_dn(block, REG_68K_D_DE, dreg); // D1 = 0x0000DDEE
|
|
}
|
|
|
|
static void compile_ld_imm16_split(
|
|
struct code_block *block,
|
|
uint8_t reg,
|
|
struct compile_ctx *ctx,
|
|
uint16_t src_address,
|
|
uint16_t *src_ptr
|
|
) {
|
|
uint8_t lobyte = READ_BYTE(*src_ptr);
|
|
uint8_t hibyte = READ_BYTE(*src_ptr + 1);
|
|
uint32_t split = ((uint32_t) hibyte << 16) | lobyte;
|
|
*src_ptr += 2;
|
|
if (split < 0x80) {
|
|
emit_moveq_dn(block, reg, split);
|
|
} else {
|
|
emit_move_l_dn(block, reg, split);
|
|
}
|
|
}
|
|
|
|
static void compile_ld_imm16_contiguous(
|
|
struct code_block *block,
|
|
uint8_t reg,
|
|
struct compile_ctx *ctx,
|
|
uint16_t src_address,
|
|
uint16_t *src_ptr
|
|
) {
|
|
uint8_t lobyte = READ_BYTE(*src_ptr);
|
|
uint8_t hibyte = READ_BYTE(*src_ptr + 1);
|
|
*src_ptr += 2;
|
|
emit_movea_w_imm16(block, reg, hibyte << 8 | lobyte);
|
|
}
|
|
|
|
struct code_block *compile_block(uint16_t src_address, struct compile_ctx *ctx)
|
|
{
|
|
struct code_block *block;
|
|
uint16_t src_ptr = 0;
|
|
uint8_t op;
|
|
int done = 0;
|
|
size_t k;
|
|
|
|
#ifdef DEBUG_COMPILE
|
|
printf("compile_block: src_address=0x%04x\n", src_address);
|
|
printf(" first bytes: %02x %02x %02x\n",
|
|
READ_BYTE(0), READ_BYTE(1), READ_BYTE(2));
|
|
#endif
|
|
|
|
if (ctx->alloc) {
|
|
block = ctx->alloc(sizeof *block);
|
|
} else {
|
|
block = malloc(sizeof *block);
|
|
}
|
|
if (!block) {
|
|
return NULL;
|
|
}
|
|
|
|
block->length = 0;
|
|
block->count = 0;
|
|
block->src_address = src_address;
|
|
block->error = 0;
|
|
block->failed_opcode = 0;
|
|
block->failed_address = 0;
|
|
memset(m68k_offsets, 0, sizeof m68k_offsets);
|
|
|
|
pending_cycles = 0;
|
|
memset(flush_at, 0, sizeof flush_at);
|
|
scan_branch_targets(src_address, ctx);
|
|
|
|
// set everything to illegal instruction so it's easy to catch weird branches
|
|
for (k = 0; k < sizeof block->code; k += 2) {
|
|
block->code[k] = 0x4a;
|
|
block->code[k + 1] = 0xfc;
|
|
}
|
|
|
|
while (!done) {
|
|
size_t before = block->length;
|
|
// detect overflow of code block and chain to next block
|
|
// longest instruction is around 150 bytes, exit sequence is 26 bytes
|
|
if (block->length > sizeof(block->code) - 200 || src_ptr >= 256) {
|
|
flush_cycles(block);
|
|
emit_moveq_dn(block, REG_68K_D_NEXT_PC, 0);
|
|
emit_move_w_dn(block, REG_68K_D_NEXT_PC, src_address + src_ptr);
|
|
emit_block_exit(block, (uint16_t) (src_address + src_ptr));
|
|
break;
|
|
}
|
|
|
|
if (flush_at[src_ptr]) {
|
|
flush_cycles(block);
|
|
}
|
|
m68k_offsets[src_ptr] = block->length;
|
|
block->count++;
|
|
op = READ_BYTE(src_ptr);
|
|
src_ptr++;
|
|
|
|
if (op != 0xcb) {
|
|
defer_cycles(instructions[op].cycles);
|
|
}
|
|
|
|
switch (op) {
|
|
case 0x00: // nop
|
|
// emit nothing
|
|
break;
|
|
|
|
case 0x02: // ld (bc), a
|
|
compile_join_bc(block, 1);
|
|
compile_call_dmg_write_a(block);
|
|
break;
|
|
|
|
case 0x0a: // ld a, (bc)
|
|
compile_join_bc(block, 1);
|
|
compile_call_dmg_read_a(block);
|
|
break;
|
|
|
|
case 0x12: // ld (de), a
|
|
compile_join_de(block, 1);
|
|
compile_call_dmg_write_a(block);
|
|
break;
|
|
|
|
case 0x1a: // ld a, (de)
|
|
compile_join_de(block, 1);
|
|
compile_call_dmg_read_a(block);
|
|
break;
|
|
|
|
case 0x01: // ld bc, imm16
|
|
compile_ld_imm16_split(block, REG_68K_D_BC, ctx, src_address, &src_ptr);
|
|
break;
|
|
case 0x11: // ld de, imm16
|
|
compile_ld_imm16_split(block, REG_68K_D_DE, ctx, src_address, &src_ptr);
|
|
break;
|
|
case 0x21: // ld hl, imm16
|
|
compile_ld_imm16_contiguous(block, REG_68K_A_HL, ctx, src_address, &src_ptr);
|
|
break;
|
|
case 0x31: // ld sp, imm16
|
|
compile_ld_sp_imm16(ctx, block, src_address, &src_ptr);
|
|
break;
|
|
|
|
case 0x32: // ld (hl-), a
|
|
compile_call_dmg_write_hl_a(block); // dmg_write(dmg, HL, A)
|
|
emit_subq_w_an(block, REG_68K_A_HL, 1); // HL--
|
|
break;
|
|
|
|
case 0x23: // inc hl
|
|
emit_addq_w_an(block, REG_68K_A_HL, 1);
|
|
break;
|
|
|
|
case 0x2a: // ld a, (hl+)
|
|
compile_call_dmg_read_hl_a(block); // dmg_read(dmg, HL) into A
|
|
emit_addq_w_an(block, REG_68K_A_HL, 1); // HL++
|
|
break;
|
|
|
|
case 0x3a: // ld a, (hl-)
|
|
compile_call_dmg_read_hl_a(block); // dmg_read(dmg, HL) into A
|
|
emit_subq_w_an(block, REG_68K_A_HL, 1); // HL--
|
|
break;
|
|
|
|
case 0x2b: // dec hl
|
|
emit_subq_w_an(block, REG_68K_A_HL, 1);
|
|
break;
|
|
|
|
case 0x3b: // dec sp
|
|
emit_subq_l_an(block, REG_68K_A_SP, 1);
|
|
emit_subq_w_disp_an(block, 1, JIT_CTX_GB_SP, REG_68K_A_CTX);
|
|
break;
|
|
|
|
case 0x33: // inc sp
|
|
emit_addq_l_an(block, REG_68K_A_SP, 1);
|
|
emit_addq_w_disp_an(block, 1, JIT_CTX_GB_SP, REG_68K_A_CTX);
|
|
break;
|
|
|
|
case 0x03: // inc bc
|
|
emit_ext_w_dn(block, REG_68K_D_BC);
|
|
emit_addq_l_dn(block, REG_68K_D_BC, 1);
|
|
break;
|
|
|
|
|
|
case 0x06: // ld b, imm8
|
|
emit_swap(block, REG_68K_D_BC);
|
|
emit_move_b_dn(block, REG_68K_D_BC, READ_BYTE(src_ptr++));
|
|
emit_swap(block, REG_68K_D_BC);
|
|
break;
|
|
|
|
case 0x07: // rlca - rotate A left, old bit 7 to carry and bit 0
|
|
emit_rol_b_imm(block, 1, REG_68K_D_A);
|
|
emit_move_sr_dn(block, REG_68K_D_FLAGS);
|
|
emit_andi_b_dn(block, REG_68K_D_FLAGS, 0xfb); // Z always 0
|
|
break;
|
|
|
|
case 0x0b: // dec bc
|
|
emit_ext_w_dn(block, REG_68K_D_BC);
|
|
emit_subq_l_dn(block, REG_68K_D_BC, 1);
|
|
break;
|
|
|
|
case 0x0e: // ld c, imm8
|
|
emit_move_b_dn(block, REG_68K_D_BC, READ_BYTE(src_ptr++));
|
|
break;
|
|
|
|
case 0x0f: // rrca - rotate A right, old bit 0 to carry and bit 7
|
|
emit_ror_b_imm(block, 1, REG_68K_D_A);
|
|
emit_move_sr_dn(block, REG_68K_D_FLAGS);
|
|
emit_andi_b_dn(block, REG_68K_D_FLAGS, 0xfb); // Z always 0
|
|
break;
|
|
|
|
case 0x13: // inc de
|
|
emit_ext_w_dn(block, REG_68K_D_DE);
|
|
emit_addq_l_dn(block, REG_68K_D_DE, 1);
|
|
break;
|
|
|
|
case 0x1b: // dec de
|
|
emit_ext_w_dn(block, REG_68K_D_DE);
|
|
emit_subq_l_dn(block, REG_68K_D_DE, 1);
|
|
break;
|
|
|
|
case 0x16: // ld d, imm8
|
|
emit_swap(block, REG_68K_D_DE);
|
|
emit_move_b_dn(block, REG_68K_D_DE, READ_BYTE(src_ptr++));
|
|
emit_swap(block, REG_68K_D_DE);
|
|
break;
|
|
|
|
case 0x09: // add hl, bc
|
|
case 0x19: // add hl, de
|
|
// use add.w so flags are set
|
|
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_0);
|
|
if (op == 0x09) {
|
|
compile_join_bc(block, 1); // D1.w = BC
|
|
} else {
|
|
compile_join_de(block, 1); // D1.w = DE
|
|
}
|
|
emit_add_w_dn_dn(block, REG_68K_D_SCRATCH_1, REG_68K_D_SCRATCH_0); // D3 += D1, sets C
|
|
emit_movea_w_dn_an(block, REG_68K_D_SCRATCH_0, REG_68K_A_HL); // HL = D3
|
|
compile_set_c_flag(block);
|
|
break;
|
|
|
|
case 0x29: // add hl, hl
|
|
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_0); // D3.w = HL
|
|
emit_add_w_dn_dn(block, REG_68K_D_SCRATCH_0, REG_68K_D_SCRATCH_0);
|
|
emit_movea_w_dn_an(block, REG_68K_D_SCRATCH_0, REG_68K_A_HL); // HL = D3
|
|
compile_set_c_flag(block);
|
|
break;
|
|
|
|
case 0x39: // add hl, sp
|
|
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_0); // D0.w = HL
|
|
// read gb_sp from context, not A3 - might be a native pointer
|
|
emit_move_w_disp_an_dn(block, JIT_CTX_GB_SP, REG_68K_A_CTX, REG_68K_D_SCRATCH_1);
|
|
emit_add_w_dn_dn(block, REG_68K_D_SCRATCH_1, REG_68K_D_SCRATCH_0); // D0 += D1, sets C
|
|
emit_movea_w_dn_an(block, REG_68K_D_SCRATCH_0, REG_68K_A_HL); // HL = D0
|
|
compile_set_c_flag(block);
|
|
break;
|
|
|
|
case 0x1e: // ld e, imm8
|
|
emit_move_b_dn(block, REG_68K_D_DE, READ_BYTE(src_ptr++));
|
|
break;
|
|
|
|
case 0x26: // ld h, imm8
|
|
// move.w a0, d5
|
|
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
|
|
// rol.w #8, d5
|
|
emit_rol_w_8(block, REG_68K_D_SCRATCH_1);
|
|
emit_move_b_dn(block, REG_68K_D_SCRATCH_1, READ_BYTE(src_ptr++));
|
|
// ror.w #8, d5
|
|
emit_ror_w_8(block, REG_68K_D_SCRATCH_1);
|
|
// movea.w d5, a0
|
|
emit_movea_w_dn_an(block, REG_68K_D_SCRATCH_1, REG_68K_A_HL);
|
|
break;
|
|
|
|
|
|
case 0x2e: // ld l, imm8
|
|
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_SCRATCH_1);
|
|
emit_move_b_dn(block, REG_68K_D_SCRATCH_1, READ_BYTE(src_ptr++));
|
|
emit_movea_w_dn_an(block, REG_68K_D_SCRATCH_1, REG_68K_A_HL);
|
|
break;
|
|
|
|
case 0x3e: // ld a, imm8
|
|
emit_moveq_dn(block, REG_68K_D_A, READ_BYTE(src_ptr++));
|
|
break;
|
|
|
|
case 0x10: // stop - CGB speed switch or end of execution
|
|
src_ptr++; // skip the padding byte (0x00) after STOP opcode
|
|
compile_call_stop(block, src_address + src_ptr);
|
|
done = 1;
|
|
break;
|
|
|
|
case 0x18: // jr disp8
|
|
compile_jr(block, ctx, &src_ptr, src_address);
|
|
// always ends the block to avoid compiling following data
|
|
// as code
|
|
done = 1;
|
|
break;
|
|
|
|
case 0x20: // jr nz, disp8
|
|
compile_jr_cond(block, ctx, &src_ptr, src_address, 2, 0);
|
|
break;
|
|
case 0x28: // jr z, disp8
|
|
compile_jr_cond(block, ctx, &src_ptr, src_address, 2, 1);
|
|
break;
|
|
case 0x30: // jr nc, disp8
|
|
compile_jr_cond(block, ctx, &src_ptr, src_address, 0, 0);
|
|
break;
|
|
case 0x38: // jr c, disp8
|
|
compile_jr_cond(block, ctx, &src_ptr, src_address, 0, 1);
|
|
break;
|
|
|
|
case 0x22: // ld (hl+), a
|
|
compile_call_dmg_write_hl_a(block);
|
|
emit_addq_w_an(block, REG_68K_A_HL, 1);
|
|
break;
|
|
|
|
case 0xcb: // CB prefix
|
|
{
|
|
uint8_t cb_op = READ_BYTE(src_ptr++);
|
|
defer_cycles(instructions[0x100 + cb_op].cycles);
|
|
if (!compile_cb_insn(block, cb_op)) {
|
|
block->error = 1;
|
|
block->failed_opcode = 0xcb00 | cb_op;
|
|
block->failed_address = src_address + src_ptr - 2;
|
|
flush_cycles(block);
|
|
emit_move_l_dn(block, REG_68K_D_NEXT_PC, 0xffffffff);
|
|
emit_dispatch_jump(block);
|
|
done = 1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0xc0: // ret nz
|
|
compile_ret_cond(block, 2, 0);
|
|
break;
|
|
|
|
case 0xc2: // jp nz, imm16
|
|
compile_jp_cond(block, ctx, &src_ptr, src_address, 2, 0);
|
|
break;
|
|
|
|
case 0xc3: // jp imm16
|
|
{
|
|
uint16_t target = READ_BYTE(src_ptr) | (READ_BYTE(src_ptr + 1) << 8);
|
|
src_ptr += 2;
|
|
flush_cycles(block);
|
|
emit_moveq_dn(block, REG_68K_D_NEXT_PC, 0);
|
|
emit_move_w_dn(block, REG_68K_D_NEXT_PC, target);
|
|
emit_block_exit(block, target);
|
|
done = 1;
|
|
}
|
|
break;
|
|
|
|
case 0xc8: // ret z
|
|
compile_ret_cond(block, 2, 1);
|
|
break;
|
|
|
|
case 0xc9: // ret
|
|
compile_ret(block);
|
|
done = 1;
|
|
break;
|
|
|
|
case 0xca: // jp z, imm16
|
|
compile_jp_cond(block, ctx, &src_ptr, src_address, 2, 1);
|
|
break;
|
|
|
|
case 0xc4: // call nz, imm16
|
|
compile_call_cond(block, ctx, &src_ptr, src_address, 2, 0);
|
|
break;
|
|
|
|
case 0xcc: // call z, imm16
|
|
compile_call_cond(block, ctx, &src_ptr, src_address, 2, 1);
|
|
break;
|
|
|
|
case 0xcd: // call imm16
|
|
compile_call_imm16(block, ctx, &src_ptr, src_address);
|
|
done = 1;
|
|
break;
|
|
|
|
case 0xd4: // call nc, imm16
|
|
compile_call_cond(block, ctx, &src_ptr, src_address, 0, 0);
|
|
break;
|
|
|
|
case 0xd0: // ret nc
|
|
compile_ret_cond(block, 0, 0);
|
|
break;
|
|
|
|
case 0xd2: // jp nc, imm16
|
|
compile_jp_cond(block, ctx, &src_ptr, src_address, 0, 0);
|
|
break;
|
|
|
|
case 0xd8: // ret c
|
|
compile_ret_cond(block, 0, 1);
|
|
break;
|
|
|
|
case 0xda: // jp c, imm16
|
|
compile_jp_cond(block, ctx, &src_ptr, src_address, 0, 1);
|
|
break;
|
|
|
|
case 0xdc: // call c, imm16
|
|
compile_call_cond(block, ctx, &src_ptr, src_address, 0, 1);
|
|
break;
|
|
|
|
case 0xe0: // ld ($ff00 + u8), a
|
|
compile_ldh_u8_a(block, ctx, READ_BYTE(src_ptr++));
|
|
break;
|
|
|
|
case 0xe9: // jp (hl)
|
|
flush_cycles(block);
|
|
// d3 can end up with a host pointer from an inlined memory access,
|
|
// so clear upper 16 bits
|
|
emit_moveq_dn(block, REG_68K_D_NEXT_PC, 0);
|
|
emit_move_w_an_dn(block, REG_68K_A_HL, REG_68K_D_NEXT_PC);
|
|
emit_dispatch_jump(block);
|
|
done = 1;
|
|
break;
|
|
|
|
case 0x17: // rla - rotate A left through carry
|
|
{
|
|
// Save old carry (bit 0 of D7) - already in position for bit 0 of A
|
|
emit_move_b_dn_dn(block, REG_68K_D_FLAGS, REG_68K_D_SCRATCH_1);
|
|
emit_andi_b_dn(block, REG_68K_D_SCRATCH_1, 0x01); // isolate C flag
|
|
|
|
// Shift left - bit 7 goes to 68k C flag, 0 goes to bit 0
|
|
emit_lsl_b_imm_dn(block, 1, REG_68K_D_A);
|
|
|
|
emit_move_sr_dn(block, REG_68K_D_FLAGS);
|
|
emit_andi_b_dn(block, REG_68K_D_FLAGS, 0xfb); // Z always 0
|
|
|
|
// OR old carry into bit 0
|
|
emit_or_b_dn_dn(block, REG_68K_D_SCRATCH_1, REG_68K_D_A);
|
|
|
|
break;
|
|
}
|
|
|
|
case 0x1f: // rra
|
|
{
|
|
// Save old carry (bit 0 of D7), shift to bit 7 position
|
|
emit_move_b_dn_dn(block, REG_68K_D_FLAGS, REG_68K_D_SCRATCH_1);
|
|
emit_andi_b_dn(block, REG_68K_D_SCRATCH_1, 0x01); // isolate C flag
|
|
emit_ror_b_imm(block, 1, REG_68K_D_SCRATCH_1); // 0x01 -> 0x80
|
|
|
|
// Shift right - bit 0 goes to 68k C flag, 0 goes to bit 7
|
|
emit_lsr_b_imm_dn(block, 1, REG_68K_D_A);
|
|
|
|
emit_move_sr_dn(block, REG_68K_D_FLAGS);
|
|
emit_andi_b_dn(block, REG_68K_D_FLAGS, 0xfb); // Z always 0
|
|
|
|
// OR old carry into bit 7
|
|
emit_or_b_dn_dn(block, REG_68K_D_SCRATCH_1, REG_68K_D_A);
|
|
|
|
break;
|
|
}
|
|
|
|
case 0xc7: // rst nn
|
|
compile_rst_n(block, 0x00, src_address + src_ptr);
|
|
done = 1;
|
|
break;
|
|
case 0xcf:
|
|
compile_rst_n(block, 0x08, src_address + src_ptr);
|
|
done = 1;
|
|
break;
|
|
case 0xd7:
|
|
compile_rst_n(block, 0x10, src_address + src_ptr);
|
|
done = 1;
|
|
break;
|
|
case 0xdf:
|
|
compile_rst_n(block, 0x18, src_address + src_ptr);
|
|
done = 1;
|
|
break;
|
|
case 0xe7:
|
|
compile_rst_n(block, 0x20, src_address + src_ptr);
|
|
done = 1;
|
|
break;
|
|
case 0xef:
|
|
compile_rst_n(block, 0x28, src_address + src_ptr);
|
|
done = 1;
|
|
break;
|
|
case 0xf7:
|
|
compile_rst_n(block, 0x30, src_address + src_ptr);
|
|
done = 1;
|
|
break;
|
|
case 0xff:
|
|
compile_rst_n(block, 0x38, src_address + src_ptr);
|
|
done = 1;
|
|
break;
|
|
|
|
case 0xe2: // ld ($ff00 + c), a
|
|
compile_ldh_c_a(block);
|
|
break;
|
|
|
|
case 0xf0: // ld a, ($ff00 + u8)
|
|
if (compile_ldh_a_u8(block, ctx, src_address, &src_ptr)) {
|
|
done = 1;
|
|
}
|
|
break;
|
|
|
|
case 0xf2: // ld a, ($ff00 + c)
|
|
compile_ldh_a_c(block);
|
|
break;
|
|
|
|
case 0xd9: // reti
|
|
compile_call_ei_di(block, 1);
|
|
compile_ret(block);
|
|
done = 1;
|
|
break;
|
|
|
|
case 0xf3: // di
|
|
compile_call_ei_di(block, 0);
|
|
break;
|
|
|
|
case 0xfb: // ei
|
|
compile_call_ei_di(block, 1);
|
|
break;
|
|
|
|
case 0x36: // ld (hl), u8
|
|
compile_call_dmg_write_hl_imm(block, READ_BYTE(src_ptr++));
|
|
break;
|
|
|
|
case 0xea: // ld (u16), a
|
|
compile_ld_u16_a(block, ctx, src_address, &src_ptr);
|
|
break;
|
|
|
|
case 0xfa: // ld a, (u16)
|
|
compile_ld_a_u16(block, ctx, src_address, &src_ptr);
|
|
break;
|
|
|
|
case 0x76:
|
|
compile_halt(block, src_address + src_ptr);
|
|
done = 1;
|
|
break;
|
|
|
|
case 0x05: // dec b
|
|
case 0x3d: // dec a
|
|
// counted empty delay loop: dec r; jr nz, -3 (OAM DMA waits)
|
|
if (READ_BYTE(src_ptr) == 0x20
|
|
&& (int8_t) READ_BYTE(src_ptr + 1) == -3) {
|
|
uint16_t loop_pc = src_address + src_ptr - 1;
|
|
if (ctx->cache_store) {
|
|
ctx->cache_store(loop_pc, ctx->current_bank,
|
|
(void *) (block->code + m68k_offsets[src_ptr - 1]));
|
|
}
|
|
compile_delay_loop(block, op, loop_pc,
|
|
src_address + src_ptr + 2);
|
|
src_ptr += 2;
|
|
break;
|
|
}
|
|
compile_alu_op(block, op, ctx, src_address, &src_ptr);
|
|
break;
|
|
|
|
default:
|
|
// 8-bit ALU ops
|
|
if (compile_alu_op(block, op, ctx, src_address, &src_ptr)) {
|
|
break;
|
|
}
|
|
// stack operations (push, pop, ld sp)
|
|
if (compile_stack_op(block, op, ctx, src_address, &src_ptr)) {
|
|
break;
|
|
}
|
|
// register loads: 0x40-0x7f (except 0x76 HALT)
|
|
if (op >= 0x40 && op <= 0x7f) {
|
|
if (compile_reg_load(block, op)) {
|
|
break;
|
|
}
|
|
}
|
|
// unknown opcode - set error info and halt
|
|
block->error = 1;
|
|
block->failed_opcode = op;
|
|
block->failed_address = src_address + src_ptr - 1;
|
|
flush_cycles(block);
|
|
emit_move_l_dn(block, REG_68K_D_NEXT_PC, 0xffffffff);
|
|
emit_dispatch_jump(block);
|
|
done = 1;
|
|
break;
|
|
}
|
|
|
|
size_t emitted = block->length - before;
|
|
// the fused register LY wait is the biggest expected sequence
|
|
if (emitted > 176) {
|
|
fprintf(stderr, "warning: instruction %02x emitted %zu bytes\n", op, emitted);
|
|
}
|
|
}
|
|
|
|
block->end_address = src_address + src_ptr;
|
|
return block;
|
|
}
|
|
|
|
void block_free(struct code_block *block)
|
|
{
|
|
free(block);
|
|
}
|