# 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<runops_jit> a prederefed copy of the opcode stream is built by
B<init_prederef>. Then B<build_asm> generates the assembler code sequence as
usual. This generated code (shown as B<runops_jit> in B<ddd>) is then executed.
Generate minimal stack frame, save %ebx
0x812c510 <jit_func>: push %ebp
0x812c511 <jit_func+1>: mov %esp,%ebp
0x812c513 <jit_func+3>: push %ebx
Get the program counter to %ebx
0x812c514 <jit_func+4>: mov 0xc(%ebp),%ebx
Push B<interpreter> and B<(opcode_t*) 1> and call B<cgp_core>
0x812c517 <jit_func+7>: push $0x8113db8
0x812c51c <jit_func+12>: push $0x1
0x812c521 <jit_func+17>: mov $0x1,%eax
0x812c526 <jit_func+22>: call 0x80b5830 <cgp_core>
In B<cgp_core> all callee saved registers are saved.
0x80b5830 <cgp_core>: push %ebp
0x80b5831 <cgp_core+1>: mov %esp,%ebp
0x80b5833 <cgp_core+3>: sub $0xdc,%esp
0x80b5839 <cgp_core+9>: lea 0x8(%ebp),%eax
0x80b583c <cgp_core+12>: push %edi
0x80b583d <cgp_core+13>: push %esi
0x80b583e <cgp_core+14>: push %ebx
In B<%eax> the init flag is set to B<-1>
0x80b583f <cgp_core+15>: mov %eax,0xfffffff
The parameter B<*cur_op> (the program counter) is put into B<%esi> and ...
0x80b5842 <cgp_core+18>: mov 0x8(%ebp),%esi
0x80b5845 <cgp_core+21>: test %esi,%esi
0x80b5847 <cgp_core+23>: jne 0x80b5853 <cgp_core+35>
0x80b5849 <cgp_core+25>: mov $0x810ca60,%eax
0x80b584e <cgp_core+30>: jmp 0x80bb470 <cgp_core+23616>
... compared to B<1>
0x80b5853 <cgp_core+35>: cmp $0x1,%esi
0x80b5856 <cgp_core+38>: jne 0x80b5860 <cgp_core+48>
If true, the program jumps to the return address of above function call, i.e.
it jumps back again to JIT code.
0x80b5858 <cgp_core+40>: jmp *0x4(%ebp)
Back again in JIT code, the init flag is checked
0x812c52b <jit_func+27>: test %eax,%eax
0x812c52d <jit_func+29>: jne 0x812c536 <jit_func+38>
... and if zero, the function would be left.
[ 0x812c52f <jit_func+31>: pop %ebx ]
[ 0x812c531 <jit_func+33>: mov %ebp,%esp ]
[ 0x812c533 <jit_func+35>: pop %ebp ]
[ 0x812c535 <jit_func+37>: ret ]
When coming from the init sequence, program flow continues by checking the
B<resume_offset> and jumping to the desired instruction
0x812c536 <jit_func+38>: mov %ebx,%eax
0x812c538 <jit_func+40>: sub $0x400140c0,%eax
0x812c53e <jit_func+46>: mov $0x812c4a8,%edx
0x812c543 <jit_func+51>: jmp *(%edx,%eax,1)
B<set I0, 10> and save_registers
0x812c546 <jit_func+54>: mov $0xa,%ebx
0x812c54b <jit_func+59>: mov %ebx,0x8113db8
Now non-JITed code follows -- get the address from the prederefed op_func_table
and call it:
0x812c551 <jit_func+65>: mov $0x812ac0c,%esi
0x812c556 <jit_func+70>: call *(%esi)
inline op print(in INT) {
printf(INTVAL_FMT, (INTVAL)$1);
goto NEXT();
}
where the B<goto NEXT()> is a simple:
0x80b5b49 <cgp_core+793>: 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 <jit_func+72>: push $0x8113db8
0x812c55d <jit_func+77>: push $0x400140dc
0x812c562 <jit_func+82>: call 0x805be60 <Parrot_bsr_ic>
0x812c567 <jit_func+87>: add $0x8,%esp
... and from the return result in B<%eax>, the new code position in JIT is
calculated and gets jumped to:
0x812c56a <jit_func+90>: sub $0x400140c0,%eax
0x812c570 <jit_func+96>: mov $0x812c4a8,%edx
0x812c575 <jit_func+101>: jmp *(%edx,%eax,1)
Now in the subroutine B<inc>:
0x812c580 <jit_func+112>: mov 0x8113db8,%ebx
0x812c586 <jit_func+118>: inc %ebx
Save register and arguments and call B<pmc_new_noinit>:
0x812c587 <jit_func+119>: push %edx
0x812c588 <jit_func+120>: push $0x11
0x812c58d <jit_func+125>: push $0x8113db8
0x812c592 <jit_func+130>: call 0x806fc60 <pmc_new_noinit>
put the PMC* into Parrot's register:
0x812c597 <jit_func+135>: mov %eax,0x8113fb8
and prepare arguments for a VTABLE call:
0x812c59d <jit_func+141>: push %eax
0x812c59e <jit_func+142>: push $0x8113db8
0x812c5a3 <jit_func+147>: mov 0x10(%eax),%eax
0x812c5a6 <jit_func+150>: call *0x18(%eax)
0x812c5a9 <jit_func+153>: add $0x10,%esp
0x812c5ac <jit_func+156>: pop %edx
and another one:
0x812c5ae <jit_func+158>: push %edx
Here, with the mapped register in B<%ebx>, push B<I0>, the PMC and the
interpreter:
0x812c5af <jit_func+159>: push %ebx
0x812c5b0 <jit_func+160>: mov 0x8113fb8,%eax
0x812c5b6 <jit_func+166>: push %eax
0x812c5b7 <jit_func+167>: push $0x8113db8
and call the vtable:
0x812c5bc <jit_func+172>: mov 0x10(%eax),%eax
0x812c5bf <jit_func+175>: call *0xdc(%eax)
0x812c5c5 <jit_func+181>: add $0xc,%esp
0x812c5c8 <jit_func+184>: pop %edx
As this ends the JITed section, used registers are saved back to Parrot's
register:
0x812c5ca <jit_func+186>: mov %ebx,0x8113db8
and again the code in B<cgp_core> gets called:
0x812c5d0 <jit_func+192>: mov $0x812ac48,%esi
0x812c5d5 <jit_func+197>: call *(%esi)
which after executing the B<print> returns back here in JIT, where the B<ret>
is called:
0x812c5d7 <jit_func+199>: push $0x8113db8
0x812c5dc <jit_func+204>: push $0x40014118
0x812c5e1 <jit_func+209>: call 0x805d5e0 <Parrot_ret>
0x812c5e6 <jit_func+214>: add $0x8,%esp
From the returned PC a JIT address is calculated, which gets executed:
0x812c5e9 <jit_func+217>: sub $0x400140c0,%eax
0x812c5ef <jit_func+223>: mov $0x812c4a8,%edx
0x812c5f4 <jit_func+228>: jmp *(%edx,%eax,1)
Now at the B<end> opcode, the CGP code for HALT() gets jumped to:
0x812c578 <jit_func+104>: mov $0x80b5877,%esi
0x812c57d <jit_func+109>: jmp *%esi
which is:
inline op end() {
HALT();
}
or, set return result:
0x80b8b6f <cgp_core+13119>: xor %eax,%eax
...
and clean up stack frame and ret:
0x80bb470 <cgp_core+23616>: lea 0xffffff18(%ebp),%esp
0x80bb476 <cgp_core+23622>: pop %ebx
0x80bb477 <cgp_core+23623>: pop %esi
0x80bb478 <cgp_core+23624>: pop %edi
0x80bb479 <cgp_core+23625>: mov %ebp,%esp
0x80bb47b <cgp_core+23627>: pop %ebp
0x80bb47c <cgp_core+23628>: ret
This returns after the position where B<cgp_core> was called during the init
sequence, but now the return value B<%eax> is zero and the..
0x812c52b <jit_func+27>: test %eax,%eax
0x812c52d <jit_func+29>: jne 0x812c536 <jit_func+38>
0x812c52f <jit_func+31>: pop %ebx
0x812c531 <jit_func+33>: mov %ebp,%esp
0x812c533 <jit_func+35>: pop %ebp
0x812c535 <jit_func+37>: ret
... whole story ends here, we are back again in B<runops_jit>.
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<lt@toetsch.at>
=head1 VERSION
=head2 CURRENT
14.02.2003 by Leopold Toetsch
syntax highlighted by Code2HTML, v. 0.9.1