What is trampoline code in Kaffe?

Introduction

From version 1.0 of Kaffe, a free implementation of Java Virtual Machine, with the Just-In-Time (JIT) compiling capability, we have to provide the trampoline function. This memo describes what is trampoline code in Kaffe, and how it works.

The purpose of trampoline code.

In JIT mode, Kaffe have to 'compile' loaded classes on the fly. If once, a class, or actually a method, is compiled the invocation to the method is similarly done as native method. More precisely, a call to the method is handled by sysdepCallMethod in callMethodA (in kaffe/kaffevm/support.c) or callMethodV (in kaffe/kaffevm/support.c) functions. The sysdepCallMethod is usually a C language macro and it calls a function specified by 'function' field in the argument data.

Since JIT assumes each methods invoked are native, we have to compile the methods prior to execution. There are several possibility when the compilation is done. The easiest, and simplest timing should be when the method is loaded. But Kaffe uses different way to compile methods. When you load a method, you can not forecast whether it is later executed or not, it is better to postpone actual compilation when the method is really needed. But then, you have to check everytime whether a method has already been compiled or not. The trampoline function is used to fill this needs.

Implementation detail

We now go into the detail of the trampoline function. When a class is loaded, the 'buildDispathTable' (in kaffe/kaffevm/classMethod.c) function is called. This function prepares data for each meathods in the class including the starting addresses of the native methods (machine code). Of course when the class is just loaded, there should not be any machine code exists, and actually an address of a small code is put. This small code is called 'trampoline'.

Since the trampoline code is put into so many locations, you have to write a macro to put this code segment into a specified location. I use sparc's code as an example to describe the contents.

typedef struct _methodTrampoline {
        unsigned code[3];
        struct _methods *meth;
} methodTrampoline;

extern void sparc_do_fixup_trampoline(void);

#define FILL_IN_TRAMPOLINE(t,m)                                         \
        do {                                                            \
                (t)->code[0] = 0x8210000f;      /* mov %o7, %g1 */      \
                /* call sparc_do_fixup_trampoline */                    \
                (t)->code[1] = ((size_t)sparc_do_fixup_trampoline       \
                                - ((size_t)(t) + 4)) / 4 | 0x40000000;  \
                (t)->code[2] = 0x01000000;      /* nop */               \
                (t)->meth = (m);                                        \
        } while (0)

This code fragment is extracted from config/sparc/jit.h, and the FILL_IN_TRAMPOLINE macro puts trampoline code into the data area in dispatch tables. In this case trampoline code and some other data are packed into 4 integer size data (totally 16 bytes), and they shall be conjunctive. The first 3 field is used to put three sparc machine code, and the most important one is the second. This instruction is a subroutine call to 'sparc_do_fixup_trampoline' function (described later) but the actual execution shall not be returned.

Just after the third instruction (which fills slot for jmp instruction), we have the address of method associated with this trampoline code.

The sparc_do_fixup_trampoline function is defined in config/sparc/trampolines.c.

asm(
        ".text							\n
	.align 4						\n
	.global _sparc_do_fixup_trampoline			\n
_sparc_do_fixup_trampoline:			                \n
        save    %sp,-64,%sp                                     \n
        ld      [%i7+8],%o0                                     \n
        call    _soft_fixup_trampoline				\n
        mov     %g1,%i7                                         \n
        jmp     %o0                                             \n
        restore"
);

This function calls '_soft_fixup_trampoline' function after setting up required parameter. The '_soft_fixup_trampoline' (in kaffe/kaffevm/soft.c) takes one argument defined as FIXUP_TRAMPOLINE_DECL. The actual definition of this argument is machine dependent. But anyway, it is used to get the method associated with the machine code generated.

In the case of sparc, it is defined as the pointer to data type 'Method'. And where does this data comes from?

This is the reason why we use subroutine call rather than simple jump instruction in FILL_IN_TRAMPOLINE macro. The method address is stored in just after the trampoline code, and we can get the address of this as the saved programming counter for subroutine call. This is moved to the register where next function can treat it as input parameter.

I am not so familiar with sparc assembler, and some description in this section may be wrong.

Arguments passed by stack

In most CISC machine, function arguments are all passed by using stack. This makes writing trampoline code much easier.

Following example is extracted from m68k case (config/m68k/trampolines.c) and similar technique is used in i386.

typedef struct _methodTrampoline {
	unsigned short call;
	int fixup;
	struct _methods* meth;
} methodTrampoline;

extern void m68k_do_fixup_trampoline(void);

#define FILL_IN_TRAMPOLINE(t,m)					\
	do {							\
		(t)->call = 0x4eb9;   /* jsr abs.l */		\
		(t)->fixup = (int)m68k_do_fixup_trampoline;	\
		(t)->meth = (m);				\
	} while (0)

#define FIXUP_TRAMPOLINE_DECL   Method** _pmeth
#define FIXUP_TRAMPOLINE_INIT   (meth = *_pmeth)

The FILL_IN_TRAMPLINE (in config/m68k/netbsd1/jit.h) is similar to sparc's case. But the major difference is the definition of FIXUP_TRAMPOLINE_DECL and FIXUP_TRAMPOLINE_INIT. In sparc's case it is the pointer to data type 'Method' but in this case it is the pointer to the pointer of 'Method'.

asm(
	".text							\n
	.even							\n
	.globl	_m68k_do_fixup_trampoline			\n
_m68k_do_fixup_trampoline:					\n
	jbsr	_soft_fixup_trampoline				\n
	addqw	#4,sp						\n
	movel	d0,a0						\n
	jmp	a0@"
);

The _m68k_do_fixup_trampoline (in config/m68k/trampolines.c) does the same thing as _sparc_do_fixup_trampoline. But you can not find any instruction which actually copy the address of the method to the argument.

Since you come here by the subroutine jmp, and the return address are at the top of stack. The next from the top of stack is used for the argument, if you just make subroutine call to 'soft_fixup_trampoline' (which pushes the return address in m68k_do_fixup_trampoline, and previous return address becomes second from the top of stack), everything works fine.


Last modified 1998/7/27 ??:?? (JST).