mirror of
https://github.com/mlaux/gb6.git
synced 2026-10-24 01:02:15 +00:00
317 lines
10 KiB
C
317 lines
10 KiB
C
#include <stdint.h>
|
|
#include "compiler.h"
|
|
#include "branches.h"
|
|
#include "emitters.h"
|
|
#include "stack.h"
|
|
|
|
// helper for reading GB memory during compilation
|
|
#define READ_BYTE(off) (ctx->read(ctx->dmg, src_address + (off)))
|
|
|
|
void compile_jr(
|
|
struct code_block *block,
|
|
struct compile_ctx *ctx,
|
|
uint16_t *src_ptr,
|
|
uint16_t src_address
|
|
) {
|
|
int8_t disp;
|
|
int16_t target_gb_offset;
|
|
uint16_t target_m68k, target_gb_pc;
|
|
int16_t m68k_disp;
|
|
|
|
disp = (int8_t) READ_BYTE(*src_ptr);
|
|
(*src_ptr)++;
|
|
|
|
// jr displacement is relative to PC after the jr instruction
|
|
// *src_ptr now points to the byte after jr, so target = *src_ptr + disp
|
|
target_gb_offset = (int16_t) *src_ptr + disp;
|
|
|
|
// Check if this is a backward jump to a location we've already compiled
|
|
if (target_gb_offset >= 0 && target_gb_offset < (int16_t) (*src_ptr - 2)) {
|
|
// Backward jump within block
|
|
target_gb_pc = src_address + target_gb_offset;
|
|
target_m68k = m68k_offsets[target_gb_offset];
|
|
m68k_disp = (int16_t) target_m68k - (int16_t) (block->length + 2);
|
|
|
|
// Register mid-block entry point for this branch target
|
|
if (ctx->cache_store) {
|
|
void *code_ptr = (void *) (block->code + target_m68k);
|
|
ctx->cache_store(target_gb_pc, ctx->current_bank, code_ptr);
|
|
}
|
|
|
|
flush_cycles(block);
|
|
|
|
// Check cycle count, exit to dispatcher if >= budget
|
|
// cmp.l JIT_CTX_WAKE_LIMIT(a4), d2
|
|
emit_cmp_l_disp_an_dn(block, JIT_CTX_WAKE_LIMIT, REG_68K_A_CTX, REG_68K_D_CYCLE_COUNT);
|
|
|
|
// branch if cycles < exit budget
|
|
size_t skip = block->length;
|
|
emit_bcs_b(block, 0);
|
|
// Exit to dispatcher with target PC
|
|
emit_moveq_dn(block, REG_68K_D_NEXT_PC, 0);
|
|
emit_move_w_dn(block, REG_68K_D_NEXT_PC, target_gb_pc);
|
|
emit_block_exit(block, target_gb_pc);
|
|
|
|
// Native branch (cycles < scanline boundary)
|
|
// Recompute displacement since block->length changed
|
|
patch_branch_b(block, skip);
|
|
m68k_disp = (int16_t) target_m68k - (int16_t) (block->length + 2);
|
|
emit_bra_w(block, m68k_disp);
|
|
return;
|
|
}
|
|
|
|
// Forward jump or outside block - go through patchable exit
|
|
target_gb_pc = src_address + target_gb_offset;
|
|
flush_cycles(block);
|
|
emit_moveq_dn(block, REG_68K_D_NEXT_PC, 0);
|
|
emit_move_w_dn(block, REG_68K_D_NEXT_PC, target_gb_pc);
|
|
emit_block_exit(block, target_gb_pc);
|
|
}
|
|
|
|
// Compile conditional relative jump (jr nz, jr z, jr nc, jr c)
|
|
// flag_bit: which bit in D7 to test (2=Z, 0=C)
|
|
// branch_if_set: if true, branch when flag is set; if false, branch when clear
|
|
void compile_jr_cond(
|
|
struct code_block *block,
|
|
struct compile_ctx *ctx,
|
|
uint16_t *src_ptr,
|
|
uint16_t src_address,
|
|
uint8_t flag_bit,
|
|
int branch_if_set
|
|
) {
|
|
int8_t disp;
|
|
int16_t target_gb_offset;
|
|
uint16_t target_m68k, target_gb_pc;
|
|
int16_t m68k_disp;
|
|
|
|
disp = (int8_t) READ_BYTE(*src_ptr);
|
|
(*src_ptr)++;
|
|
|
|
target_gb_offset = (int16_t) *src_ptr + disp;
|
|
|
|
// Test the flag bit in D7
|
|
// btst sets 68k Z=1 if tested bit is 0, Z=0 if tested bit is 1
|
|
emit_btst_imm_dn(block, flag_bit, REG_68K_D_FLAGS);
|
|
|
|
// Check if this is a backward jump within block
|
|
if (target_gb_offset >= 0 && target_gb_offset < (int16_t) (*src_ptr - 2)) {
|
|
// Backward jump - check condition, then maybe interrupt flag
|
|
target_gb_pc = src_address + target_gb_offset;
|
|
target_m68k = m68k_offsets[target_gb_offset];
|
|
|
|
// Register mid-block entry point for this branch target
|
|
if (ctx->cache_store) {
|
|
void *code_ptr = (void *) (block->code + target_m68k);
|
|
ctx->cache_store(target_gb_pc, ctx->current_bank, code_ptr);
|
|
}
|
|
|
|
// Check condition, then cycle count
|
|
// Structure:
|
|
// btst #flag_bit, d7 ; already emitted above
|
|
// bne/beq .check_cycles ; if condition met, check cycles
|
|
// bra.b .fall_through ; condition not met, skip all
|
|
// .check_cycles:
|
|
// add pending + 4 to d2
|
|
// cmp.l JIT_CTX_WAKE_LIMIT(a4), d2
|
|
// bcs.w loop_target ; cycles < exit budget, do native branch
|
|
// moveq #0, d0 ; cycles >= exit budget, exit
|
|
// move.w #target, d0
|
|
// patchable_exit
|
|
// .fall_through:
|
|
|
|
size_t cond = block->length;
|
|
if (branch_if_set) {
|
|
// Branch if flag is set: btst gives Z=0 when bit=1, so use bne
|
|
emit_bne_b(block, 0); // skip the bra.b to .check_cycles
|
|
} else {
|
|
// Branch if flag is clear: btst gives Z=1 when bit=0, so use beq
|
|
emit_beq_b(block, 0);
|
|
}
|
|
|
|
// bra.b to .fall_through
|
|
size_t fall = block->length;
|
|
emit_bra_b(block, 0);
|
|
|
|
// .check_cycles:
|
|
patch_branch_b(block, cond);
|
|
emit_add_cycles(block, pending_cycles + 4);
|
|
emit_cmp_l_disp_an_dn(block, JIT_CTX_WAKE_LIMIT, REG_68K_A_CTX, REG_68K_D_CYCLE_COUNT);
|
|
|
|
// bcs.w to native loop target (cycles < exit budget)
|
|
m68k_disp = (int16_t) target_m68k - (int16_t) (block->length + 2);
|
|
emit_bcs_w(block, m68k_disp);
|
|
|
|
// Exit to dispatcher (cycles >= exit budget)
|
|
emit_moveq_dn(block, REG_68K_D_NEXT_PC, 0);
|
|
emit_move_w_dn(block, REG_68K_D_NEXT_PC, target_gb_pc);
|
|
emit_block_exit(block, target_gb_pc);
|
|
|
|
// .fall_through: block continues deferring
|
|
patch_branch_b(block, fall);
|
|
return;
|
|
}
|
|
|
|
// Forward/external jump - conditionally exit via patchable exit
|
|
// If condition NOT met, skip the exit sequence
|
|
target_gb_pc = src_address + target_gb_offset;
|
|
|
|
size_t skip = block->length;
|
|
if (branch_if_set) {
|
|
// Skip exit if flag is clear (btst Z=1 when bit=0)
|
|
emit_beq_b(block, 0);
|
|
} else {
|
|
// Skip exit if flag is set (btst Z=0 when bit=1)
|
|
emit_bne_b(block, 0);
|
|
}
|
|
|
|
emit_add_cycles(block, pending_cycles + 4); // pending + taken extra
|
|
emit_moveq_dn(block, REG_68K_D_NEXT_PC, 0);
|
|
emit_move_w_dn(block, REG_68K_D_NEXT_PC, target_gb_pc);
|
|
emit_block_exit(block, target_gb_pc);
|
|
|
|
patch_branch_b(block, skip);
|
|
}
|
|
|
|
// Compile conditional absolute jump (jp nz, jp z, jp nc, jp c)
|
|
// flag_bit: which bit in D7 to test (2=Z, 0=C)
|
|
// branch_if_set: if true, branch when flag is set; if false, branch when clear
|
|
void compile_jp_cond(
|
|
struct code_block *block,
|
|
struct compile_ctx *ctx,
|
|
uint16_t *src_ptr,
|
|
uint16_t src_address,
|
|
uint8_t flag_bit,
|
|
int branch_if_set
|
|
) {
|
|
uint16_t target = READ_BYTE(*src_ptr) | (READ_BYTE(*src_ptr + 1) << 8);
|
|
size_t skip;
|
|
*src_ptr += 2;
|
|
|
|
// Test the flag bit in D7
|
|
emit_btst_imm_dn(block, flag_bit, REG_68K_D_FLAGS);
|
|
|
|
// If condition NOT met, skip the exit sequence
|
|
skip = block->length;
|
|
if (branch_if_set) {
|
|
// Skip exit if flag is clear (btst Z=1 when bit=0)
|
|
emit_beq_b(block, 0);
|
|
} else {
|
|
// Skip exit if flag is set (btst Z=0 when bit=1)
|
|
emit_bne_b(block, 0);
|
|
}
|
|
|
|
emit_add_cycles(block, pending_cycles + 4); // pending + taken extra
|
|
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);
|
|
|
|
patch_branch_b(block, skip);
|
|
}
|
|
|
|
void compile_call_imm16(
|
|
struct code_block *block,
|
|
struct compile_ctx *ctx,
|
|
uint16_t *src_ptr,
|
|
uint16_t src_address
|
|
) {
|
|
uint16_t target = READ_BYTE(*src_ptr) | (READ_BYTE(*src_ptr + 1) << 8);
|
|
uint16_t ret_addr = src_address + *src_ptr + 2; // address after call
|
|
*src_ptr += 2;
|
|
|
|
flush_cycles(block);
|
|
compile_push_imm16(block, ret_addr);
|
|
|
|
// jump to target
|
|
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);
|
|
}
|
|
|
|
// Compile conditional call (call nz, call z, call nc, call c)
|
|
// flag_bit: which bit in D7 to test (2=Z, 0=C)
|
|
// branch_if_set: if true, call when flag is set; if false, call when clear
|
|
void compile_call_cond(
|
|
struct code_block *block,
|
|
struct compile_ctx *ctx,
|
|
uint16_t *src_ptr,
|
|
uint16_t src_address,
|
|
uint8_t flag_bit,
|
|
int branch_if_set
|
|
) {
|
|
uint16_t target = READ_BYTE(*src_ptr) | (READ_BYTE(*src_ptr + 1) << 8);
|
|
uint16_t ret_addr = src_address + *src_ptr + 2; // address after call
|
|
size_t skip;
|
|
int saved;
|
|
*src_ptr += 2;
|
|
|
|
// Test the flag bit in D7
|
|
emit_btst_imm_dn(block, flag_bit, REG_68K_D_FLAGS);
|
|
|
|
// If condition NOT met, skip the call sequence
|
|
skip = block->length;
|
|
if (branch_if_set) {
|
|
emit_beq_w(block, 0);
|
|
} else {
|
|
emit_bne_w(block, 0);
|
|
}
|
|
|
|
// taken path: materialize pending + extra; restore for the fall-through
|
|
saved = pending_cycles;
|
|
emit_add_cycles(block, pending_cycles + 12);
|
|
pending_cycles = 0;
|
|
compile_push_imm16(block, ret_addr);
|
|
|
|
// Jump to target
|
|
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);
|
|
pending_cycles = saved;
|
|
|
|
patch_branch_w(block, skip);
|
|
}
|
|
|
|
void compile_ret(struct code_block *block)
|
|
{
|
|
compile_pop_pc(block);
|
|
emit_dispatch_jump(block);
|
|
}
|
|
|
|
// Compile conditional return (ret nz, ret z, ret nc, ret c)
|
|
// flag_bit: which bit in D7 to test (2=Z, 0=C)
|
|
// branch_if_set: if true, return when flag is set; if false, return when clear
|
|
void compile_ret_cond(struct code_block *block, uint8_t flag_bit, int branch_if_set)
|
|
{
|
|
size_t skip;
|
|
int saved;
|
|
|
|
// Test the flag bit in D7
|
|
emit_btst_imm_dn(block, flag_bit, REG_68K_D_FLAGS);
|
|
|
|
// If condition NOT met, skip the return sequence
|
|
skip = block->length;
|
|
if (branch_if_set) {
|
|
emit_beq_w(block, 0);
|
|
} else {
|
|
emit_bne_w(block, 0);
|
|
}
|
|
|
|
// taken path: materialize pending + extra; restore for the fall-through
|
|
saved = pending_cycles;
|
|
emit_add_cycles(block, pending_cycles + 12);
|
|
pending_cycles = 0;
|
|
compile_pop_pc(block);
|
|
emit_dispatch_jump(block);
|
|
pending_cycles = saved;
|
|
|
|
patch_branch_w(block, skip);
|
|
}
|
|
|
|
void compile_rst_n(struct code_block *block, uint8_t target, uint16_t ret_addr)
|
|
{
|
|
compile_push_imm16(block, ret_addr);
|
|
|
|
// jump to target (0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38)
|
|
emit_moveq_dn(block, REG_68K_D_NEXT_PC, target);
|
|
emit_block_exit(block, target);
|
|
}
|