# Copyright (C) 2001-2006, The Perl Foundation. # $Id: jit_i386.pod 20926 2007-08-30 21:25:32Z chromatic $ =head1 NAME docs/dev/jit_i386.pod - Parrot JIT (i386/gcc) =head1 ABSTRACT This PDD describes the i386 gcc JIT implementation. =head1 DESCRIPTION JIT i386/gcc is a combination of unrolled assembly instructions and the Computed Goto Predereferenced (CGP) run loop. For branch instructions the function implementation in the standard core is called. Another difference of JIT/i386 is that most vtable functions are JITed instructions which use register mappings. For a better understanding of the control flow between these basically 3 run loop cores, an example shows the gory details. =head1 EXAMPLE Given the following PASM program, the righthand three columns show where each opcode gets executed: PASM JIT ops Normal CGP ops (call cgp_core) (jmp back) set I0, 10 set_i_ic print I0 (call) print_i print "\n" print_sc bsr inc (call) bsr_ic cpu_ret end (jmp) HALT end (ret) end (ret) inc: inc I0 inc_i new P0, 'String' new_p_sc set P0, I0 set_p_i print P0 (call) print_p print "\n" print_sc ret (call) ret cpu_ret =head2 Startup sequence In B a prederefed copy of the opcode stream is built by B. Then B generates the assembler code sequence as usual. This generated code (shown as B in B) is then executed. Generate minimal stack frame, save %ebx 0x812c510 : push %ebp 0x812c511 : mov %esp,%ebp 0x812c513 : push %ebx Get the program counter to %ebx 0x812c514 : mov 0xc(%ebp),%ebx Push B and B<(opcode_t*) 1> and call B 0x812c517 : push $0x8113db8 0x812c51c : push $0x1 0x812c521 : mov $0x1,%eax 0x812c526 : call 0x80b5830 In B all callee saved registers are saved. 0x80b5830 : push %ebp 0x80b5831 : mov %esp,%ebp 0x80b5833 : sub $0xdc,%esp 0x80b5839 : lea 0x8(%ebp),%eax 0x80b583c : push %edi 0x80b583d : push %esi 0x80b583e : push %ebx In B<%eax> the init flag is set to B<-1> 0x80b583f : mov %eax,0xfffffff The parameter B<*cur_op> (the program counter) is put into B<%esi> and ... 0x80b5842 : mov 0x8(%ebp),%esi 0x80b5845 : test %esi,%esi 0x80b5847 : jne 0x80b5853 0x80b5849 : mov $0x810ca60,%eax 0x80b584e : jmp 0x80bb470 ... compared to B<1> 0x80b5853 : cmp $0x1,%esi 0x80b5856 : jne 0x80b5860 If true, the program jumps to the return address of above function call, i.e. it jumps back again to JIT code. 0x80b5858 : jmp *0x4(%ebp) Back again in JIT code, the init flag is checked 0x812c52b : test %eax,%eax 0x812c52d : jne 0x812c536 ... and if zero, the function would be left. [ 0x812c52f : pop %ebx ] [ 0x812c531 : mov %ebp,%esp ] [ 0x812c533 : pop %ebp ] [ 0x812c535 : ret ] When coming from the init sequence, program flow continues by checking the B and jumping to the desired instruction 0x812c536 : mov %ebx,%eax 0x812c538 : sub $0x400140c0,%eax 0x812c53e : mov $0x812c4a8,%edx 0x812c543 : jmp *(%edx,%eax,1) B and save_registers 0x812c546 : mov $0xa,%ebx 0x812c54b : mov %ebx,0x8113db8 Now non-JITed code follows -- get the address from the prederefed op_func_table and call it: 0x812c551 : mov $0x812ac0c,%esi 0x812c556 : call *(%esi) inline op print(in INT) { printf(INTVAL_FMT, (INTVAL)$1); goto NEXT(); } where the B is a simple: 0x80b5b49 : jmp *(%esi) op print(in STR) { ... goto NEXT(); } As the last instruction of the non-JITed code sequence is a branch, this is not executed in CGP, but the opcode: inline op cpu_ret() { #ifdef __GNUC__ # ifdef I386 asm("ret") is executed. This opcode is patched into the prederefed code stream by Parrot_jit_normal_op at the end of a non-JITed code sequence. This returns to JIT code again, where the next instruction gets called as a function in the standard core ... 0x812c558 : push $0x8113db8 0x812c55d : push $0x400140dc 0x812c562 : call 0x805be60 0x812c567 : add $0x8,%esp ... and from the return result in B<%eax>, the new code position in JIT is calculated and gets jumped to: 0x812c56a : sub $0x400140c0,%eax 0x812c570 : mov $0x812c4a8,%edx 0x812c575 : jmp *(%edx,%eax,1) Now in the subroutine B: 0x812c580 : mov 0x8113db8,%ebx 0x812c586 : inc %ebx Save register and arguments and call B: 0x812c587 : push %edx 0x812c588 : push $0x11 0x812c58d : push $0x8113db8 0x812c592 : call 0x806fc60 put the PMC* into Parrot's register: 0x812c597 : mov %eax,0x8113fb8 and prepare arguments for a VTABLE call: 0x812c59d : push %eax 0x812c59e : push $0x8113db8 0x812c5a3 : mov 0x10(%eax),%eax 0x812c5a6 : call *0x18(%eax) 0x812c5a9 : add $0x10,%esp 0x812c5ac : pop %edx and another one: 0x812c5ae : push %edx Here, with the mapped register in B<%ebx>, push B, the PMC and the interpreter: 0x812c5af : push %ebx 0x812c5b0 : mov 0x8113fb8,%eax 0x812c5b6 : push %eax 0x812c5b7 : push $0x8113db8 and call the vtable: 0x812c5bc : mov 0x10(%eax),%eax 0x812c5bf : call *0xdc(%eax) 0x812c5c5 : add $0xc,%esp 0x812c5c8 : pop %edx As this ends the JITed section, used registers are saved back to Parrot's register: 0x812c5ca : mov %ebx,0x8113db8 and again the code in B gets called: 0x812c5d0 : mov $0x812ac48,%esi 0x812c5d5 : call *(%esi) which after executing the B returns back here in JIT, where the B is called: 0x812c5d7 : push $0x8113db8 0x812c5dc : push $0x40014118 0x812c5e1 : call 0x805d5e0 0x812c5e6 : add $0x8,%esp From the returned PC a JIT address is calculated, which gets executed: 0x812c5e9 : sub $0x400140c0,%eax 0x812c5ef : mov $0x812c4a8,%edx 0x812c5f4 : jmp *(%edx,%eax,1) Now at the B opcode, the CGP code for HALT() gets jumped to: 0x812c578 : mov $0x80b5877,%esi 0x812c57d : jmp *%esi which is: inline op end() { HALT(); } or, set return result: 0x80b8b6f : xor %eax,%eax ... and clean up stack frame and ret: 0x80bb470 : lea 0xffffff18(%ebp),%esp 0x80bb476 : pop %ebx 0x80bb477 : pop %esi 0x80bb478 : pop %edi 0x80bb479 : mov %ebp,%esp 0x80bb47b : pop %ebp 0x80bb47c : ret This returns after the position where B was called during the init sequence, but now the return value B<%eax> is zero and the.. 0x812c52b : test %eax,%eax 0x812c52d : jne 0x812c536 0x812c52f : pop %ebx 0x812c531 : mov %ebp,%esp 0x812c533 : pop %ebp 0x812c535 : ret ... whole story ends here, we are back again in B. So this is rather simple once it gets going. =head1 BUGS The floating point registers do not get saved to Parrot before vtable calls. This assumes that external routines preserve the FP stack pointer and don't use more the 4 floating point registers at once. =head1 AUTHOR Leopold Toetsch C =head1 VERSION =head2 CURRENT 14.02.2003 by Leopold Toetsch