I\'d like to write a very small proof-of-concept JIT compiler for a toy language processor I\'ve written (purely academic), but I\'m having some trouble in the middle-altitu
The answer depends on your compiler and where you put the code. See http://encode.ru/threads/1273-Just-In-Time-Compilation-Improvement-For-ZPAQ?p=24902&posted=1#post24902
Testing in 32 bit Vista, Visual C++ gives a DEP (data execution prevention) error whether the code is put on the stack, heap, or static memory. g++, Borland, and Mars can be made to work sometimes. Data accessed by the JIT code needs to be declared volatile.
The Android Dalvik JIT compiler might also be worth looking at. It is supposed to be fairly small and lean (not sure if this helps understanding it or makes things more complicated). It targets Linux as well.
If things are getting more serious, looking at LLVM might be a good choice as well.
The function pointer approach suggested by Jeremiah sounds good. You may want to use the caller's stack anyway and there will probably only be a few registers left (on x86) which you need to preserve or not touch. In this case, it is probably easiest if your compiled code (or the entry stub) saves them on the stack before proceeding. In the end, it all boils down to writing an assembler function and interfacing to it from C.
Youmay want to have a look at libjit which provides exactly the infrastructure you're looking for:
The libjit library implements just-in-time compilation functionality. Unlike other JITs, this one is designed to be independent of any particular virtual machine bytecode format or language.
http://freshmeat.net/projects/libjit
How to JIT - an introduction is a new article (from today!) that addresses some of these issues and describes the bigger picture as well.
In addition to the techniques suggested so far, it might be worthwhile to look into the thread creation functions. If you create a new thread, with the starting address set to your generated code, you know for sure that there are no old registers that need saving or restoring, and the OS handles the setup of the relevant registers for you. I.e you eliminate steps 3, 4 and 6 of your list.
Not sure about linux, but this works on x86/windows.
Update: http://codepad.org/sQoF6kR8
#include <stdio.h>
#include <windows.h>
typedef unsigned char byte;
int arg1;
int arg2;
int res1;
typedef void (*pfunc)(void);
union funcptr {
pfunc x;
byte* y;
};
int main( void ) {
byte* buf = (byte*)VirtualAllocEx( GetCurrentProcess(), 0, 1<<16, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
if( buf==0 ) return 0;
byte* p = buf;
*p++ = 0x50; // push eax
*p++ = 0x52; // push edx
*p++ = 0xA1; // mov eax, [arg2]
(int*&)p[0] = &arg2; p+=sizeof(int*);
*p++ = 0x92; // xchg edx,eax
*p++ = 0xA1; // mov eax, [arg1]
(int*&)p[0] = &arg1; p+=sizeof(int*);
*p++ = 0xF7; *p++ = 0xEA; // imul edx
*p++ = 0xA3; // mov [res1],eax
(int*&)p[0] = &res1; p+=sizeof(int*);
*p++ = 0x5A; // pop edx
*p++ = 0x58; // pop eax
*p++ = 0xC3; // ret
funcptr func;
func.y = buf;
arg1 = 123; arg2 = 321; res1 = 0;
func.x(); // call generated code
printf( "arg1=%i arg2=%i arg1*arg2=%i func(arg1,arg2)=%i\n", arg1,arg2,arg1*arg2,res1 );
}