Welcome to mirror list, hosted at ThFree Co, Russian Federation.

core_dynrec.cpp « cpu « src - github.com/dosbox-staging/dosbox-staging.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 619a4a972f56907b9763de0c761525d631f6744c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
/*
 *  Copyright (C) 2002-2021  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 <cassert>
#include <cstdarg>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <type_traits>

#if defined (WIN32)
#include <windows.h>
#include <winbase.h>
#endif

#if defined(HAVE_MPROTECT) || defined(HAVE_MMAP)
#include <sys/mman.h>

#include <limits.h>

#endif // HAVE_MPROTECT

#include "callback.h"
#include "cpu.h"
#include "debug.h"
#include "inout.h"
#include "lazyflags.h"
#include "mem.h"
#include "paging.h"
#include "pic.h"
#include "regs.h"
#include "tracy.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);
}

struct core_dynrec_t {
	BlockReturn (*runcode)(const uint8_t*);		// points to code that can start a block
	Bitu callback;				// the occurred callback
	Bitu readdata;				// spare space used when reading from memory
	uint32_t protected_regs[8];	// space to save/restore register values
};

static core_dynrec_t core_dynrec;

// core_dynrec is often being used this way:
//
//   function_expecting_int16_ptr((uint16_t*)(&core_dynrec.readdata));
//
// These uses are ok and safe as long as core_dynrec.readdata is correctly
// aligned.
//
static_assert(std::is_standard_layout<core_dynrec_t>::value,
              "core_dynrec_t must be a standard layout type, otherwise "
              "offsetof calculation is undefined behaviour.");
static_assert(offsetof(core_dynrec_t, readdata) % sizeof(uint16_t) == 0,
              "core_dynrec.readdata must be word aligned");
static_assert(offsetof(core_dynrec_t, readdata) % sizeof(uint32_t) == 0,
              "core_dynrec.readdata must be double-word aligned");

#include "dyn_cache.h"

#define X86			0x01
#define X86_64		0x02
#define MIPSEL		0x03
#define ARMV4LE		0x04
#define ARMV7LE		0x05
#define POWERPC		0x06
#define ARMV8LE		0x07
#define PPC64LE		0x08

#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"
#elif C_TARGETCPU == PPC64LE
#include "core_dynrec/risc_ppc64le.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"

CacheBlock *LinkBlocks(BlockReturn ret)
{
	CacheBlock *block = NULL;
	// the last instruction was a control flow modifying instruction
	Bitu temp_ip=SegPhys(cs)+reg_eip;
	CodePageHandler *temp_handler = (CodePageHandler *)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) {
	ZoneScoped
	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

		CodePageHandler *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
		CacheBlock *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;
			// let the normal core handle the block-modifying
			// instruction
			[[fallthrough]];
		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_DebugException(DBINT_STEP,reg_eip);

	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