dosbox-wii/src/cpu/core_dynrec.cpp

345 lines
9.2 KiB
C++

/*
* Copyright (C) 2002-2019 The DOSBox Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "dosbox.h"
#if (C_DYNREC)
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#if defined (WIN32)
#include <windows.h>
#include <winbase.h>
#endif
#if (C_HAVE_MPROTECT)
#include <sys/mman.h>
#include <limits.h>
#ifndef PAGESIZE
#define PAGESIZE 4096
#endif
#endif /* C_HAVE_MPROTECT */
#include "callback.h"
#include "regs.h"
#include "mem.h"
#include "cpu.h"
#include "debug.h"
#include "paging.h"
#include "inout.h"
#include "lazyflags.h"
#include "pic.h"
#define CACHE_MAXSIZE (4096*2)
#define CACHE_TOTAL (1024*1024*8)
#define CACHE_PAGES (512)
#define CACHE_BLOCKS (128*1024)
#define CACHE_ALIGN (16)
#define DYN_HASH_SHIFT (4)
#define DYN_PAGE_HASH (4096>>DYN_HASH_SHIFT)
#define DYN_LINKS (16)
//#define DYN_LOG 1 //Turn Logging on.
#if C_FPU
#define CPU_FPU 1 //Enable FPU escape instructions
#endif
// the emulated x86 registers
#define DRC_REG_EAX 0
#define DRC_REG_ECX 1
#define DRC_REG_EDX 2
#define DRC_REG_EBX 3
#define DRC_REG_ESP 4
#define DRC_REG_EBP 5
#define DRC_REG_ESI 6
#define DRC_REG_EDI 7
// the emulated x86 segment registers
#define DRC_SEG_ES 0
#define DRC_SEG_CS 1
#define DRC_SEG_SS 2
#define DRC_SEG_DS 3
#define DRC_SEG_FS 4
#define DRC_SEG_GS 5
// access to a general register
#define DRCD_REG_VAL(reg) (&cpu_regs.regs[reg].dword)
// access to a segment register
#define DRCD_SEG_VAL(seg) (&Segs.val[seg])
// access to the physical value of a segment register/selector
#define DRCD_SEG_PHYS(seg) (&Segs.phys[seg])
// access to an 8bit general register
#define DRCD_REG_BYTE(reg,idx) (&cpu_regs.regs[reg].byte[idx?BH_INDEX:BL_INDEX])
// access to 16/32bit general registers
#define DRCD_REG_WORD(reg,dwrd) ((dwrd)?((void*)(&cpu_regs.regs[reg].dword[DW_INDEX])):((void*)(&cpu_regs.regs[reg].word[W_INDEX])))
enum BlockReturn {
BR_Normal=0,
BR_Cycles,
BR_Link1,BR_Link2,
BR_Opcode,
#if (C_DEBUG)
BR_OpcodeFull,
#endif
BR_Iret,
BR_CallBack,
BR_SMCBlock
};
// identificator to signal self-modification of the currently executed block
#define SMC_CURRENT_BLOCK 0xffff
static void IllegalOptionDynrec(const char* msg) {
E_Exit("DynrecCore: illegal option in %s",msg);
}
static struct {
BlockReturn (*runcode)(Bit8u*); // points to code that can start a block
Bitu callback; // the occurred callback
Bitu readdata; // spare space used when reading from memory
Bit32u protected_regs[8]; // space to save/restore register values
} core_dynrec;
#include "core_dynrec/cache.h"
#define X86 0x01
#define X86_64 0x02
#define MIPSEL 0x03
#define ARMV4LE 0x04
#define ARMV7LE 0x05
#define POWERPC 0x06
#define ARMV8LE 0x07
#if C_TARGETCPU == X86_64
#include "core_dynrec/risc_x64.h"
#elif C_TARGETCPU == X86
#include "core_dynrec/risc_x86.h"
#elif C_TARGETCPU == MIPSEL
#include "core_dynrec/risc_mipsel32.h"
#elif (C_TARGETCPU == ARMV4LE) || (C_TARGETCPU == ARMV7LE)
#include "core_dynrec/risc_armv4le.h"
#elif C_TARGETCPU == POWERPC
#include "core_dynrec/risc_ppc.h"
#elif C_TARGETCPU == ARMV8LE
#include "core_dynrec/risc_armv8le.h"
#endif
#if !defined(WORDS_BIGENDIAN)
#define gen_add_LE gen_add
#define gen_mov_LE_word_to_reg gen_mov_word_to_reg
#endif
#include "core_dynrec/decoder.h"
CacheBlockDynRec * LinkBlocks(BlockReturn ret) {
CacheBlockDynRec * block=NULL;
// the last instruction was a control flow modifying instruction
Bitu temp_ip=SegPhys(cs)+reg_eip;
CodePageHandlerDynRec * temp_handler=(CodePageHandlerDynRec *)get_tlb_readhandler(temp_ip);
if (temp_handler->flags & (cpu.code.big ? PFLAG_HASCODE32:PFLAG_HASCODE16)) {
// see if the target is an already translated block
block=temp_handler->FindCacheBlock(temp_ip & 4095);
if (block) { // found it, link the current block to
cache.block.running->LinkTo(ret==BR_Link2,block);
}
}
return block;
}
/*
The core tries to find the block that should be executed next.
If such a block is found, it is run, otherwise the instruction
stream starting at ip_point is translated (see decoder.h) and
makes up a new code block that will be run.
When control is returned to CPU_Core_Dynrec_Run (which might
be right after the block is run, or somewhen long after that
due to the direct cacheblock linking) the returncode decides
the next action. This might be continuing the translation and
execution process, or returning from the core etc.
*/
Bits CPU_Core_Dynrec_Run(void) {
for (;;) {
// Determine the linear address of CS:EIP
PhysPt ip_point=SegPhys(cs)+reg_eip;
#if C_HEAVY_DEBUG
if (DEBUG_HeavyIsBreakpoint()) return debugCallback;
#endif
CodePageHandlerDynRec * chandler=0;
// see if the current page is present and contains code
if (GCC_UNLIKELY(MakeCodePage(ip_point,chandler))) {
// page not present, throw the exception
CPU_Exception(cpu.exception.which,cpu.exception.error);
continue;
}
// page doesn't contain code or is special
if (GCC_UNLIKELY(!chandler)) return CPU_Core_Normal_Run();
// find correct Dynamic Block to run
CacheBlockDynRec * block=chandler->FindCacheBlock(ip_point&4095);
if (!block) {
// no block found, thus translate the instruction stream
// unless the instruction is known to be modified
if (!chandler->invalidation_map || (chandler->invalidation_map[ip_point&4095]<4)) {
// translate up to 32 instructions
block=CreateCacheBlock(chandler,ip_point,32);
} else {
// let the normal core handle this instruction to avoid zero-sized blocks
Bitu old_cycles=CPU_Cycles;
CPU_Cycles=1;
Bits nc_retcode=CPU_Core_Normal_Run();
if (!nc_retcode) {
CPU_Cycles=old_cycles-1;
continue;
}
CPU_CycleLeft+=old_cycles;
return nc_retcode;
}
}
run_block:
cache.block.running=0;
// now we're ready to run the dynamic code block
// BlockReturn ret=((BlockReturn (*)(void))(block->cache.start))();
BlockReturn ret=core_dynrec.runcode(block->cache.start);
switch (ret) {
case BR_Iret:
#if C_DEBUG
#if C_HEAVY_DEBUG
if (DEBUG_HeavyIsBreakpoint()) return debugCallback;
#endif
#endif
if (!GETFLAG(TF)) {
if (GETFLAG(IF) && PIC_IRQCheck) return CBRET_NONE;
break;
}
// trapflag is set, switch to the trap-aware decoder
cpudecoder=CPU_Core_Dynrec_Trap_Run;
return CBRET_NONE;
case BR_Normal:
// the block was exited due to a non-predictable control flow
// modifying instruction (like ret) or some nontrivial cpu state
// changing instruction (for example switch to/from pmode),
// or the maximum number of instructions to translate was reached
#if C_DEBUG
#if C_HEAVY_DEBUG
if (DEBUG_HeavyIsBreakpoint()) return debugCallback;
#endif
#endif
break;
case BR_Cycles:
// cycles went negative, return from the core to handle
// external events, schedule the pic...
#if C_DEBUG
#if C_HEAVY_DEBUG
if (DEBUG_HeavyIsBreakpoint()) return debugCallback;
#endif
#endif
return CBRET_NONE;
case BR_CallBack:
// the callback code is executed in dosbox.conf, return the callback number
FillFlags();
return core_dynrec.callback;
case BR_SMCBlock:
// LOG_MSG("selfmodification of running block at %x:%x",SegValue(cs),reg_eip);
cpu.exception.which=0;
// fallthrough, let the normal core handle the block-modifying instruction
case BR_Opcode:
// some instruction has been encountered that could not be translated
// (thus it is not part of the code block), the normal core will
// handle this instruction
CPU_CycleLeft+=CPU_Cycles;
CPU_Cycles=1;
return CPU_Core_Normal_Run();
#if (C_DEBUG)
case BR_OpcodeFull:
CPU_CycleLeft+=CPU_Cycles;
CPU_Cycles=1;
return CPU_Core_Full_Run();
#endif
case BR_Link1:
case BR_Link2:
block=LinkBlocks(ret);
if (block) goto run_block;
break;
default:
E_Exit("Invalid return code %d", ret);
}
}
return CBRET_NONE;
}
Bits CPU_Core_Dynrec_Trap_Run(void) {
Bits oldCycles = CPU_Cycles;
CPU_Cycles = 1;
cpu.trap_skip = false;
// let the normal core execute the next (only one!) instruction
Bits ret=CPU_Core_Normal_Run();
// trap to int1 unless the last instruction deferred this
// (allows hardware interrupts to be served without interaction)
if (!cpu.trap_skip) CPU_HW_Interrupt(1);
CPU_Cycles = oldCycles-1;
// continue (either the trapflag was clear anyways, or the int1 cleared it)
cpudecoder = &CPU_Core_Dynrec_Run;
return ret;
}
void CPU_Core_Dynrec_Init(void) {
}
void CPU_Core_Dynrec_Cache_Init(bool enable_cache) {
// Initialize code cache and dynamic blocks
cache_init(enable_cache);
}
void CPU_Core_Dynrec_Cache_Close(void) {
cache_close();
}
#endif