DOSBox-X
|
00001 /* 00002 * Copyright (C) 2002-2020 The DOSBox Team 00003 * 00004 * This program is free software; you can redistribute it and/or modify 00005 * it under the terms of the GNU General Public License as published by 00006 * the Free Software Foundation; either version 2 of the License, or 00007 * (at your option) any later version. 00008 * 00009 * This program is distributed in the hope that it will be useful, 00010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 * GNU General Public License for more details. 00013 * 00014 * You should have received a copy of the GNU General Public License along 00015 * with this program; if not, write to the Free Software Foundation, Inc., 00016 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00017 */ 00018 00019 00020 #include "dosbox.h" 00021 00022 #if (C_DYNREC) 00023 00024 #include <assert.h> 00025 #include <stdarg.h> 00026 #include <stdio.h> 00027 #include <string.h> 00028 #include <stddef.h> 00029 #include <stdlib.h> 00030 00031 #if defined (WIN32) 00032 #include <windows.h> 00033 #include <winbase.h> 00034 #endif 00035 00036 #if (C_HAVE_MPROTECT) 00037 #include <sys/mman.h> 00038 00039 #include <limits.h> 00040 #ifndef PAGESIZE 00041 #define PAGESIZE 4096 00042 #endif 00043 #endif /* C_HAVE_MPROTECT */ 00044 00045 #include "callback.h" 00046 #include "regs.h" 00047 #include "mem.h" 00048 #include "cpu.h" 00049 #include "debug.h" 00050 #include "paging.h" 00051 #include "inout.h" 00052 #include "lazyflags.h" 00053 #include "pic.h" 00054 00055 #define CACHE_MAXSIZE (4096*2) 00056 #define CACHE_TOTAL (1024*1024*8) 00057 #define CACHE_PAGES (512) 00058 #define CACHE_BLOCKS (128*1024) 00059 #define CACHE_ALIGN (16) 00060 #define DYN_HASH_SHIFT (4) 00061 #define DYN_PAGE_HASH (4096>>DYN_HASH_SHIFT) 00062 #define DYN_LINKS (16) 00063 00064 00065 //#define DYN_LOG 1 //Turn Logging on. 00066 00067 00068 #if C_FPU 00069 #define CPU_FPU 1 //Enable FPU escape instructions 00070 #endif 00071 00072 00073 // the emulated x86 registers 00074 #define DRC_REG_EAX 0 00075 #define DRC_REG_ECX 1 00076 #define DRC_REG_EDX 2 00077 #define DRC_REG_EBX 3 00078 #define DRC_REG_ESP 4 00079 #define DRC_REG_EBP 5 00080 #define DRC_REG_ESI 6 00081 #define DRC_REG_EDI 7 00082 00083 // the emulated x86 segment registers 00084 #define DRC_SEG_ES 0 00085 #define DRC_SEG_CS 1 00086 #define DRC_SEG_SS 2 00087 #define DRC_SEG_DS 3 00088 #define DRC_SEG_FS 4 00089 #define DRC_SEG_GS 5 00090 00091 00092 // access to a general register 00093 #define DRCD_REG_VAL(reg) (&cpu_regs.regs[reg].dword) 00094 // access to a segment register 00095 #define DRCD_SEG_VAL(seg) (&Segs.val[seg]) 00096 // access to the physical value of a segment register/selector 00097 #define DRCD_SEG_PHYS(seg) (&Segs.phys[seg]) 00098 00099 // access to an 8bit general register 00100 #define DRCD_REG_BYTE(reg,idx) (&cpu_regs.regs[reg].byte[idx?BH_INDEX:BL_INDEX]) 00101 // access to 16/32bit general registers 00102 #define DRCD_REG_WORD(reg,dwrd) ((dwrd)?((void*)(&cpu_regs.regs[reg].dword[DW_INDEX])):((void*)(&cpu_regs.regs[reg].word[W_INDEX]))) 00103 00104 00105 enum BlockReturn { 00106 BR_Normal=0, 00107 BR_Cycles, 00108 BR_Link1,BR_Link2, 00109 BR_Opcode, 00110 #if (C_DEBUG) 00111 BR_OpcodeFull, 00112 #endif 00113 BR_Iret, 00114 BR_CallBack, 00115 BR_SMCBlock 00116 }; 00117 00118 // identificator to signal self-modification of the currently executed block 00119 #define SMC_CURRENT_BLOCK 0xffff 00120 00121 00122 static void IllegalOptionDynrec(const char* msg) { 00123 E_Exit("DynrecCore: illegal option in %s",msg); 00124 } 00125 00126 static struct { 00127 BlockReturn (*runcode)(Bit8u*); // points to code that can start a block 00128 Bitu callback; // the occurred callback 00129 Bitu readdata; // spare space used when reading from memory 00130 Bit32u protected_regs[8]; // space to save/restore register values 00131 } core_dynrec; 00132 00133 00134 #include "core_dynrec/cache.h" 00135 00136 #define X86 0x01 00137 #define X86_64 0x02 00138 #define MIPSEL 0x03 00139 #define ARMV4LE 0x04 00140 #define ARMV7LE 0x05 00141 #define ARMV8LE 0x07 00142 00143 #if !defined(C_TARGETCPU) 00144 # if defined(_MSC_VER) && defined(_M_AMD64) 00145 # define C_TARGETCPU X86_64 00146 # elif defined(_MSC_VER) && defined(_M_ARM) 00147 # define C_TARGETCPU ARMV7LE 00148 # elif defined(_MSC_VER) && defined(_M_ARM64) 00149 # define C_TARGETCPU ARMV8LE 00150 # endif 00151 #endif 00152 00153 #if C_TARGETCPU == X86_64 00154 #include "core_dynrec/risc_x64.h" 00155 #elif C_TARGETCPU == X86 00156 #include "core_dynrec/risc_x86.h" 00157 #elif C_TARGETCPU == MIPSEL 00158 #include "core_dynrec/risc_mipsel32.h" 00159 #elif (C_TARGETCPU == ARMV4LE) || (C_TARGETCPU == ARMV7LE) 00160 #ifdef _MSC_VER 00161 #pragma message("warning: Using ARMV4 or ARMV7") 00162 #else 00163 #warning Using ARMV4 or ARMV7 00164 #endif 00165 #include "core_dynrec/risc_armv4le.h" 00166 #elif C_TARGETCPU == ARMV8LE 00167 #include "core_dynrec/risc_armv8le.h" 00168 #endif 00169 00170 #include "core_dynrec/decoder.h" 00171 00172 /* Dynarec on Windows ARM32 works, but only on Windows 10. 00173 * Windows RT 8.x only runs ARMv7 Thumb-2 code, which we don't have a backend for 00174 * Attempting to use dynarec on RT will result in an instant crash. 00175 * So we need to detect the Windows version before enabling dynarec. */ 00176 #if defined(WIN32) && defined(_M_ARM) 00177 bool IsWin10OrGreater() { 00178 HMODULE handle = GetModuleHandleW(L"ntdll.dll"); 00179 if (handle) { 00180 typedef LONG(WINAPI* RtlGetVersionPtr)(RTL_OSVERSIONINFOW*); 00181 RtlGetVersionPtr getVersionPtr = (RtlGetVersionPtr)GetProcAddress(handle, "RtlGetVersion"); 00182 if (getVersionPtr != NULL) { 00183 RTL_OSVERSIONINFOW info = { 0 }; 00184 info.dwOSVersionInfoSize = sizeof(info); 00185 if (getVersionPtr(&info) == 0) { /* STATUS_SUCCESS == 0 */ 00186 if (info.dwMajorVersion >= 10) 00187 return true; 00188 } 00189 } 00190 } 00191 return false; 00192 } 00193 00194 static bool is_win10 = false; 00195 static bool winrt_warning = true; 00196 #endif 00197 00198 00199 CacheBlockDynRec * LinkBlocks(BlockReturn ret) { 00200 CacheBlockDynRec * block=NULL; 00201 // the last instruction was a control flow modifying instruction 00202 Bit32u temp_ip=SegPhys(cs)+reg_eip; 00203 CodePageHandlerDynRec * temp_handler=(CodePageHandlerDynRec *)get_tlb_readhandler(temp_ip); 00204 if (temp_handler->flags & PFLAG_HASCODE) { 00205 // see if the target is an already translated block 00206 block=temp_handler->FindCacheBlock(temp_ip & 4095); 00207 if (!block) return NULL; 00208 00209 // found it, link the current block to 00210 cache.block.running->LinkTo(ret==BR_Link2,block); 00211 return block; 00212 } 00213 return NULL; 00214 } 00215 00216 /* 00217 The core tries to find the block that should be executed next. 00218 If such a block is found, it is run, otherwise the instruction 00219 stream starting at ip_point is translated (see decoder.h) and 00220 makes up a new code block that will be run. 00221 When control is returned to CPU_Core_Dynrec_Run (which might 00222 be right after the block is run, or somewhen long after that 00223 due to the direct cacheblock linking) the returncode decides 00224 the next action. This might be continuing the translation and 00225 execution process, or returning from the core etc. 00226 */ 00227 00228 extern bool use_dynamic_core_with_paging; 00229 extern int dynamic_core_cache_block_size; 00230 00231 static bool paging_warning = true; 00232 00233 Bits CPU_Core_Dynrec_Run(void) { 00234 if (CPU_Cycles <= 0) 00235 return CBRET_NONE; 00236 00237 #if defined(WIN32) && defined(_M_ARM) 00238 if (!is_win10) { 00239 if (winrt_warning) { 00240 LOG_MSG("Dynamic core warning: Windows RT 8.x requires ARMv7 Thumb-2 dynarec core, which is not supported yet."); 00241 winrt_warning = false; 00242 } 00243 return CPU_Core_Normal_Run(); 00244 } 00245 #endif 00246 00247 /* Dynamic core is NOT compatible with the way page faults 00248 * in the guest are handled in this emulator. Do not use 00249 * dynamic core if paging is enabled. Do not comment this 00250 * out, even if it happens to work for a minute, a half 00251 * hour, a day, because it will turn around and cause 00252 * Windows 95 to crash when you've become most comfortable 00253 * with the idea that it works. This code cannot handle 00254 * the sudden context switch of a page fault and it never 00255 * will. Don't do it. You have been warned. */ 00256 if (paging.enabled && !use_dynamic_core_with_paging) { 00257 if (paging_warning) { 00258 LOG_MSG("Dynamic core warning: The guest OS/Application has just switched on 80386 paging, which is not supported by the dynamic core. The normal core will be used until paging is switched off again."); 00259 paging_warning = false; 00260 } 00261 00262 return CPU_Core_Normal_Run(); 00263 } 00264 00265 for (;;) { 00266 // Determine the linear address of CS:EIP 00267 PhysPt ip_point=SegPhys(cs)+reg_eip; 00268 #if C_HEAVY_DEBUG 00269 if (DEBUG_HeavyIsBreakpoint()) return (Bits)debugCallback; 00270 #endif 00271 00272 CodePageHandlerDynRec * chandler=0; 00273 // see if the current page is present and contains code 00274 if (GCC_UNLIKELY(MakeCodePage(ip_point,chandler))) { 00275 // page not present, throw the exception 00276 CPU_Exception(cpu.exception.which,cpu.exception.error); 00277 continue; 00278 } 00279 00280 // page doesn't contain code or is special 00281 if (GCC_UNLIKELY(!chandler)) return CPU_Core_Normal_Run(); 00282 00283 // find correct Dynamic Block to run 00284 CacheBlockDynRec * block=chandler->FindCacheBlock(ip_point&4095); 00285 if (!block) { 00286 // no block found, thus translate the instruction stream 00287 // unless the instruction is known to be modified 00288 if (!chandler->invalidation_map || (chandler->invalidation_map[ip_point&4095]<4)) { 00289 // translate up to 32 instructions 00290 block=CreateCacheBlock(chandler,ip_point,32); 00291 } else { 00292 // let the normal core handle this instruction to avoid zero-sized blocks 00293 cpu_cycles_count_t old_cycles=CPU_Cycles; 00294 CPU_Cycles=1; 00295 Bits nc_retcode=CPU_Core_Normal_Run(); 00296 if (!nc_retcode) { 00297 CPU_Cycles=old_cycles-1; 00298 continue; 00299 } 00300 CPU_CycleLeft+=old_cycles; 00301 return nc_retcode; 00302 } 00303 } 00304 00305 run_block: 00306 cache.block.running=0; 00307 // now we're ready to run the dynamic code block 00308 // BlockReturn ret=((BlockReturn (*)(void))(block->cache.start))(); 00309 BlockReturn ret=core_dynrec.runcode(block->cache.start); 00310 00311 if (sizeof(CPU_Cycles) > 4) { 00312 // HACK: All dynrec cores for each processor assume CPU_Cycles is 32-bit wide. 00313 // The purpose of this hack is to sign-extend the lower 32 bits so that 00314 // when CPU_Cycles goes negative it doesn't suddenly appear as a very 00315 // large integer value. 00316 // 00317 // This hack is needed for dynrec to work on x86_64 targets. 00318 CPU_Cycles = (Bits)((int32_t)CPU_Cycles); 00319 } 00320 00321 switch (ret) { 00322 case BR_Iret: 00323 #if C_DEBUG 00324 #if C_HEAVY_DEBUG 00325 if (DEBUG_HeavyIsBreakpoint()) return (Bits)debugCallback; 00326 #endif 00327 #endif 00328 if (!GETFLAG(TF)) { 00329 if (GETFLAG(IF) && PIC_IRQCheck) return CBRET_NONE; 00330 break; 00331 } 00332 // trapflag is set, switch to the trap-aware decoder 00333 cpudecoder=CPU_Core_Dynrec_Trap_Run; 00334 return CBRET_NONE; 00335 00336 case BR_Normal: 00337 // the block was exited due to a non-predictable control flow 00338 // modifying instruction (like ret) or some nontrivial cpu state 00339 // changing instruction (for example switch to/from pmode), 00340 // or the maximum number of instructions to translate was reached 00341 #if C_DEBUG 00342 #if C_HEAVY_DEBUG 00343 if (DEBUG_HeavyIsBreakpoint()) return (Bits)debugCallback; 00344 #endif 00345 #endif 00346 break; 00347 00348 case BR_Cycles: 00349 // cycles went negative, return from the core to handle 00350 // external events, schedule the pic... 00351 #if C_DEBUG 00352 #if C_HEAVY_DEBUG 00353 if (DEBUG_HeavyIsBreakpoint()) return (Bits)debugCallback; 00354 #endif 00355 #endif 00356 return CBRET_NONE; 00357 00358 case BR_CallBack: 00359 // the callback code is executed in dosbox.conf, return the callback number 00360 FillFlags(); 00361 return (Bits)core_dynrec.callback; 00362 00363 case BR_SMCBlock: 00364 // LOG_MSG("selfmodification of running block at %x:%x",SegValue(cs),reg_eip); 00365 cpu.exception.which=0; 00366 // fallthrough, let the normal core handle the block-modifying instruction 00367 case BR_Opcode: 00368 #if (C_DEBUG) 00369 case BR_OpcodeFull: 00370 #endif 00371 // some instruction has been encountered that could not be translated 00372 // (thus it is not part of the code block), the normal core will 00373 // handle this instruction 00374 CPU_CycleLeft+=CPU_Cycles; 00375 CPU_Cycles=1; 00376 return CPU_Core_Normal_Run(); 00377 00378 case BR_Link1: 00379 case BR_Link2: 00380 block=LinkBlocks(ret); 00381 if (block) goto run_block; 00382 break; 00383 00384 default: 00385 E_Exit("Invalid return code %d", ret); 00386 } 00387 } 00388 return CBRET_NONE; 00389 } 00390 00391 Bits CPU_Core_Dynrec_Trap_Run(void) { 00392 Bits oldCycles = CPU_Cycles; 00393 CPU_Cycles = 1; 00394 cpu.trap_skip = false; 00395 00396 // let the normal core execute the next (only one!) instruction 00397 Bits ret=CPU_Core_Normal_Run(); 00398 00399 // trap to int1 unless the last instruction deferred this 00400 // (allows hardware interrupts to be served without interaction) 00401 if (!cpu.trap_skip) CPU_HW_Interrupt(1); 00402 00403 CPU_Cycles = oldCycles-1; 00404 // continue (either the trapflag was clear anyways, or the int1 cleared it) 00405 cpudecoder = &CPU_Core_Dynrec_Run; 00406 00407 return ret; 00408 } 00409 00410 void CPU_Core_Dynrec_Init(void) { 00411 #if defined(WIN32) && defined(_M_ARM) 00412 is_win10 = IsWin10OrGreater(); 00413 #endif 00414 } 00415 00416 void CPU_Core_Dynrec_Cache_Init(bool enable_cache) { 00417 // Initialize code cache and dynamic blocks 00418 cache_init(enable_cache); 00419 } 00420 00421 void CPU_Core_Dynrec_Cache_Close(void) { 00422 cache_close(); 00423 } 00424 00425 void CPU_Core_Dynrec_Cache_Reset(void) { 00426 cache_reset(); 00427 } 00428 #endif