JIT Emulation?

  • Thread starter Thread starter sfx1999
  • Start date Start date
Status
Not open for further replies.
S

sfx1999

Guest
What exactly is the difference between JIT emulation as apposed to a regular interpreter? I plan on writing an emulator some day, so it would help me a lot if I knew this.
 
Shortly a "Just in Time"-Emulator translates the opcodes in real time whereas other approaches does this work before the actual execution.
 
More precisely, an interpreter simply chews, processes and executes opcode by opcode. It does not produce executable code as output (hence there is no caching), it simply does the appropriate action for each opcode by itself. A compiler (more precisely dynamic recompiler), on the other hand, translates the entire source program into host executable code and then runs this as a normal program on the host. Just-in-time compilation is sort of an optimized recompiler, in that it only recompiles code as needed. When the program jumps into a block of unevaluated code/data, the JIT compiler kicks in and translates the block/function/line/whatever-unit-size into host code, which is then executed. Thus, for large programs only the code that's actually used is translated/recompiled. So in closing, JIT is somewhere between recompiling and interpreting.
 
Well, I already found that information. The only problem I can't find a way past is getting C to execute code it just generated. The only thing I can think of is an array of function pointers that have assembly stored in them. I don't even know if that would work.

Then there is self modifying code. That has to be difficult.
 
Just a thought, but you might not need to use function pointers to build your emulator. Since you'll probably be dealing with the interpretation of opcodes, you can have a setup similar to the following:

Code: [Select]
Code:
char* memory = malloc( SOME_LARGE_AMOUNT );int mem_pointer = 0;while(true){   switch(memory[mem_pointer++]){ // Pick a function based upon the opcode in this part of memory      case 0: // NOP         break;      case 1: // Immediate Mode ADD A to B and store into address C         char a = memory[mem_pointer++];         char b = memory[mem_pointer++];         memory[mem_pointer] = a + b;         break;       // et cetra       default: printf("Unknown Opcode encountered!"); exit(-1);   }}

As you can see, this allows for C to execute particular code based upon the contents of something loaded into memory. In this manner, it's also possible to write self-modifying code, by selectively altering the contents of the memory array.

If you're in a situation that requires you to create new functions on the fly, I suggest looking into Lisp. It's got great functionality for creating and passing around functions at runtime.
 
It is a lot faster to make a bunch of pointers to functions and use the opcodes as a look up table then to use switch/case. That's not what I was talking about, though.
 
It is a lot faster to make a bunch of pointers to functions and use the opcodes as a look up table then to use switch/case.
Are you sure? At one point I looked at the assembly generated by GCC for a 6502 emulator and it actually created a jump table for a big switch statement.
If you want to compile stuff, for example for a JIT compiler, you'll need to implement the instructions in assembler as position independant code.  Then you can copy them into a new block of memory and patch immediate values and jump instructions. Of course, you can make that as complicated as you want, for example with an immediate code that you can optimise.
For old 8 and 16 bit cpus I wouldn't bother with a compiler, though.
 
There is an emulator called Generator that uses a JIT. Anyway, it would be better if I started with something simpler as opposed to trying to write a PS2 emulator first.

I would like to start with NES, though.

Micky, when you did that, did you use any optimazation switches?
 
Micky, when you did that, did you use any optimazation switches?
That was about 7 years ago, when the first emulators came up. Don't ask me which version of gcc, or which switches exactly. I'd guess it was simply -O2.
 
sfx1999:
suggestion for you and making an NES emulator :D
Emutalk/Programing/NES Thread
Emutalk is a decent place, but it has some different rules than here.  However that thread is relatively active.  I would say about 30-40 emulation authors frequent the place.

As for JIT .. there is no point for JIT with a 6502. A modern processor.. can interpret the instructions significantly faster than the original processor could ever run them and have plenty of time left over for graphical and audio update.

Cyb
 
It's a learning experience. Anyway, it could help on PDAs and stuff.
 
It's a learning experience. Anyway, it could help on PDAs and stuff.
That's a possibility
interesting note PalmSource is moving too Linux.  :o
The reason why is many Cell systems in China and India etc are going that route. Since they are 2 billion plus consumers guess what?

However on that sort of platform you'll need to do some mindful work.
So JIT might be helpful most importantly is optimization for emulating the graphics system.

Cyb
 
If you are working with such a simple instruction set as is in the Nintendo Entertainment System, it’s probably best to aggregate the list of instructions.
Nintendo Entertainment System instruction set has 151 opcodes, which, although tediously, can be fit into an aggregate array where each index into the array represents the relative instruction.
Each entry into the array should contain the size of the code, a bitmask for decoding, possibly text in case you want to print the code (extra feature?  Learning experience?  Help with debugging?), and a pointer to a function that interprets the code.

Using a switch case is not necessarily as slow as you would think because the compiler will generate a jump table where each four bytes is a DWORD indicating the location where to go to get the appropriate case.
Then the ASM to actually handle the case would be similar to “jmp DWORD PTR: [ESI+4*EAX]” where ESI already contains the location of the jump table and EAX contains the case number.
It will go to the jump table, go to the correct index in the jump table, and that index will contain a DWORD pointer to the code that has the case where it is jumping.  All done in one instruction.

But it is not recommended; it is even more tedious to construct and you get less out of it.  Every action you want to perform based on the instruction will have to be manually coded into each switch case.
If you use an aggregate list where the opcode is the actual index into the list, when you read the opcode you instantly know where to go to get all the information regarding the opcode.


L. Spiro
 
Now if someone could tell me how to dynamically create code into memory and call it using C, I would really appreciate it.

L. Spiro, I might just use that switch/case if that is how it is done. I can use inline functions to make it a little more readable. Anyway, does that only happen when you turn on optimization?
 
Now if someone could tell me how to dynamically create code into memory and call it using C, I would really appreciate it.
You'll need to know assembler. You can copy around functions written in C in memory under certain conditions (position-independent  code), but I wouldn't depend on it...
1) allocate memory
2) write your machine instructions into memory. Don't forget a return
3) cast a pointer to your memory into a function pointer
4) call the function pointer

For a function without parameters this should work out of the box, if you need parameters you'll have to look up the ABI of the CPU and operating system you're using. Some parameters may be in registers, others are passed on the stack. And you may have to clean up the stack before leaving your generated code.
I don't have a windows box around, so this is from memory:
- don't put code into memory allocated form the stack, some operating systems allocate no-execute pages for that
- memory returned by malloc should be OK. If it isn't you'll have to use VirtualAlloc yourself and make sure you request executable pages
 
I am 92% sure that the switch cases will be optimized that way regardless of compiler optimizations.
Once you have 3 or more cases, they get this optimization.


Be sure to look into the __fastcall keyword, and also you can find the source code for Project64 via Google.


L. Spiro
 
Actually, you don't need to pass parameters at all. I found a tutorial. When you generate the code, you pass pointers to the generated code. It's somewhat simple.
 
That link seems to have been short-lived; it is already down.
Either that or it likes to periodically go down and up.

I will keep checking on it periodically, but you may want to upload to mirex’s bin of trash if you like, and if it is fairly small.


L. Spiro
 
Status
Not open for further replies.
Back
Top