2 * tramp-x86-gsharedvt.c: gsharedvt support code for x86
5 * Zoltan Varga <vargaz@gmail.com>
7 * Copyright 2013 Xamarin, Inc (http://www.xamarin.com)
8 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
11 #include <mono/metadata/abi-details.h>
13 #ifdef MONO_ARCH_GSHAREDVT_SUPPORTED
16 mono_x86_start_gsharedvt_call (GSharedVtCallInfo *info, gpointer *caller, gpointer *callee, gpointer mrgctx_reg)
21 /* Set vtype ret arg */
22 if (info->vret_arg_slot != -1) {
23 callee [info->vret_arg_slot] = &callee [info->vret_slot];
25 /* Copy data from the caller argument area to the callee */
26 for (i = 0; i < info->map_count; ++i) {
27 int src = map [i * 2];
28 int dst = map [i * 2 + 1];
30 switch ((src >> 16) & 0x3) {
32 callee [dst] = caller [src];
38 /* gsharedvt->normal */
40 arg = (gpointer*)caller [src & 0xffff];
41 for (j = 0; j < nslots; ++j)
42 callee [dst + j] = arg [j];
46 /* gsharedvt arg, have to take its address */
47 callee [dst] = caller + (src & 0xffff);
50 int dst = map [i * 2 + 1];
52 /* gsharedvt arg, have to take its address */
53 callee [dst - 0xffff] = caller + map [i * 2];
55 callee [dst] = caller [map [i * 2]];
61 if (info->vcall_offset != -1) {
62 MonoObject *this_obj = caller [0];
64 if (G_UNLIKELY (!this_obj))
66 if (info->vcall_offset == MONO_GSHAREDVT_DEL_INVOKE_VT_OFFSET)
68 return ((MonoDelegate*)this_obj)->invoke_impl;
70 return *(gpointer*)((char*)this_obj->vtable + info->vcall_offset);
71 } else if (info->calli) {
72 /* The address to call is passed in the mrgctx reg */
81 mono_arch_get_gsharedvt_trampoline (MonoTrampInfo **info, gboolean aot)
84 int buf_len, cfa_offset;
85 GSList *unwind_ops = NULL;
86 MonoJumpInfo *ji = NULL;
87 guint8 *br_out, *br [16];
88 int info_offset, mrgctx_offset;
91 buf = code = mono_global_codeman_reserve (buf_len);
94 * This trampoline is responsible for marshalling calls between normal code and gsharedvt code. The
95 * caller is a normal or gshared method which uses the signature of the inflated method to make the call, while
96 * the callee is a gsharedvt method which has a signature which uses valuetypes in place of type parameters, i.e.
100 * T=<type used to represent vtype type arguments, currently TypedByRef>
102 * The trampoline is responsible for marshalling the arguments and marshalling the result back. To simplify
103 * things, we create our own stack frame, and do most of the work in a C function, which receives a
104 * GSharedVtCallInfo structure as an argument. The structure should contain information to execute the C function to
105 * be as fast as possible. The argument is received in EAX from a gsharedvt trampoline. So the real
106 * call sequence looks like this:
107 * caller -> gsharedvt trampoline -> gsharevt in trampoline -> start_gsharedvt_call
108 * FIXME: Optimize this.
111 cfa_offset = sizeof (gpointer);
112 mono_add_unwind_op_def_cfa (unwind_ops, code, buf, X86_ESP, cfa_offset);
113 mono_add_unwind_op_offset (unwind_ops, code, buf, X86_NREG, -cfa_offset);
114 x86_push_reg (code, X86_EBP);
115 cfa_offset += sizeof (gpointer);
116 mono_add_unwind_op_def_cfa_offset (unwind_ops, code, buf, cfa_offset);
117 mono_add_unwind_op_offset (unwind_ops, code, buf, X86_EBP, - cfa_offset);
118 x86_mov_reg_reg (code, X86_EBP, X86_ESP, sizeof (gpointer));
119 mono_add_unwind_op_def_cfa_reg (unwind_ops, code, buf, X86_EBP);
120 /* Alloc stack frame/align stack */
121 x86_alu_reg_imm (code, X86_SUB, X86_ESP, 8);
124 /* The info struct is put into EAX by the gsharedvt trampoline */
125 /* Save info struct addr */
126 x86_mov_membase_reg (code, X86_EBP, info_offset, X86_EAX, 4);
128 x86_mov_membase_reg (code, X86_EBP, mrgctx_offset, MONO_ARCH_RGCTX_REG, 4);
130 /* Allocate stack area used to pass arguments to the method */
131 x86_mov_reg_membase (code, X86_EAX, X86_EAX, MONO_STRUCT_OFFSET (GSharedVtCallInfo, stack_usage), sizeof (gpointer));
132 x86_alu_reg_reg (code, X86_SUB, X86_ESP, X86_EAX);
135 /* Stack alignment check */
136 x86_mov_reg_reg (code, X86_ECX, X86_ESP, 4);
137 x86_alu_reg_imm (code, X86_AND, X86_ECX, MONO_ARCH_FRAME_ALIGNMENT - 1);
138 x86_alu_reg_imm (code, X86_CMP, X86_ECX, 0);
139 x86_branch_disp (code, X86_CC_EQ, 3, FALSE);
140 x86_breakpoint (code);
143 /* ecx = caller argument area */
144 x86_mov_reg_reg (code, X86_ECX, X86_EBP, 4);
145 x86_alu_reg_imm (code, X86_ADD, X86_ECX, 8);
146 /* eax = callee argument area */
147 x86_mov_reg_reg (code, X86_EAX, X86_ESP, 4);
149 /* Call start_gsharedvt_call */
151 x86_push_membase (code, X86_EBP, mrgctx_offset);
153 x86_push_reg (code, X86_EAX);
155 x86_push_reg (code, X86_ECX);
157 x86_push_membase (code, X86_EBP, info_offset);
159 code = mono_arch_emit_load_aotconst (buf, code, &ji, MONO_PATCH_INFO_JIT_ICALL_ADDR, "mono_x86_start_gsharedvt_call");
160 x86_call_reg (code, X86_EAX);
162 x86_call_code (code, mono_x86_start_gsharedvt_call);
164 x86_alu_reg_imm (code, X86_ADD, X86_ESP, 4 * 4);
165 /* The address to call is in eax */
166 /* The stack is now setup for the real call */
167 /* Load info struct */
168 x86_mov_reg_membase (code, X86_ECX, X86_EBP, info_offset, 4);
170 x86_mov_reg_membase (code, MONO_ARCH_RGCTX_REG, X86_EBP, mrgctx_offset, sizeof (gpointer));
172 x86_call_reg (code, X86_EAX);
173 /* The return value is either in registers, or stored to an area beginning at sp [info->vret_slot] */
174 /* EAX/EDX might contain the return value, only ECX is free */
175 /* Load info struct */
176 x86_mov_reg_membase (code, X86_ECX, X86_EBP, info_offset, 4);
178 /* Branch to the in/out handling code */
179 x86_alu_membase_imm (code, X86_CMP, X86_ECX, MONO_STRUCT_OFFSET (GSharedVtCallInfo, gsharedvt_in), 1);
181 x86_branch32 (code, X86_CC_NE, 0, TRUE);
187 /* Load ret marshal type */
188 x86_mov_reg_membase (code, X86_ECX, X86_ECX, MONO_STRUCT_OFFSET (GSharedVtCallInfo, ret_marshal), 4);
189 x86_alu_reg_imm (code, X86_CMP, X86_ECX, GSHAREDVT_RET_NONE);
191 x86_branch8 (code, X86_CC_NE, 0, TRUE);
193 /* Normal return, no marshalling required */
197 /* Return value marshalling */
198 x86_patch (br [0], code);
199 /* Load info struct */
200 x86_mov_reg_membase (code, X86_EAX, X86_EBP, info_offset, 4);
201 /* Load 'vret_slot' */
202 x86_mov_reg_membase (code, X86_EAX, X86_EAX, MONO_STRUCT_OFFSET (GSharedVtCallInfo, vret_slot), 4);
203 /* Compute ret area address */
204 x86_shift_reg_imm (code, X86_SHL, X86_EAX, 2);
205 x86_alu_reg_reg (code, X86_ADD, X86_EAX, X86_ESP);
206 /* The callee does a ret $4, so sp is off by 4 */
207 x86_alu_reg_imm (code, X86_SUB, X86_EAX, sizeof (gpointer));
209 /* Branch to specific marshalling code */
210 // FIXME: Move the I4 case to the top */
211 x86_alu_reg_imm (code, X86_CMP, X86_ECX, GSHAREDVT_RET_DOUBLE_FPSTACK);
213 x86_branch8 (code, X86_CC_E, 0, TRUE);
214 x86_alu_reg_imm (code, X86_CMP, X86_ECX, GSHAREDVT_RET_FLOAT_FPSTACK);
216 x86_branch8 (code, X86_CC_E, 0, TRUE);
217 x86_alu_reg_imm (code, X86_CMP, X86_ECX, GSHAREDVT_RET_STACK_POP);
219 x86_branch8 (code, X86_CC_E, 0, TRUE);
220 x86_alu_reg_imm (code, X86_CMP, X86_ECX, GSHAREDVT_RET_I1);
222 x86_branch8 (code, X86_CC_E, 0, TRUE);
223 x86_alu_reg_imm (code, X86_CMP, X86_ECX, GSHAREDVT_RET_U1);
225 x86_branch8 (code, X86_CC_E, 0, TRUE);
226 x86_alu_reg_imm (code, X86_CMP, X86_ECX, GSHAREDVT_RET_I2);
228 x86_branch8 (code, X86_CC_E, 0, TRUE);
229 x86_alu_reg_imm (code, X86_CMP, X86_ECX, GSHAREDVT_RET_U2);
231 x86_branch8 (code, X86_CC_E, 0, TRUE);
233 /* Load both eax and edx for simplicity */
234 x86_mov_reg_membase (code, X86_EDX, X86_EAX, sizeof (gpointer), sizeof (gpointer));
235 x86_mov_reg_membase (code, X86_EAX, X86_EAX, 0, sizeof (gpointer));
238 /* DOUBLE_FPSTACK case */
239 x86_patch (br [1], code);
240 x86_fld_membase (code, X86_EAX, 0, TRUE);
244 /* FLOAT_FPSTACK case */
245 x86_patch (br [2], code);
246 x86_fld_membase (code, X86_EAX, 0, FALSE);
250 x86_patch (br [3], code);
252 x86_ret_imm (code, 4);
254 x86_patch (br [4], code);
255 x86_widen_membase (code, X86_EAX, X86_EAX, 0, TRUE, FALSE);
259 x86_patch (br [5], code);
260 x86_widen_membase (code, X86_EAX, X86_EAX, 0, FALSE, FALSE);
264 x86_patch (br [6], code);
265 x86_widen_membase (code, X86_EAX, X86_EAX, 0, TRUE, TRUE);
269 x86_patch (br [7], code);
270 x86_widen_membase (code, X86_EAX, X86_EAX, 0, FALSE, TRUE);
278 x86_patch (br_out, code);
279 /* Load ret marshal type into ECX */
280 x86_mov_reg_membase (code, X86_ECX, X86_ECX, MONO_STRUCT_OFFSET (GSharedVtCallInfo, ret_marshal), 4);
281 x86_alu_reg_imm (code, X86_CMP, X86_ECX, GSHAREDVT_RET_NONE);
283 x86_branch8 (code, X86_CC_NE, 0, TRUE);
285 /* Normal return, no marshalling required */
289 /* Return value marshalling */
290 x86_patch (br [0], code);
292 /* EAX might contain the return value */
294 x86_push_reg (code, X86_EAX);
296 /* Load info struct */
297 x86_mov_reg_membase (code, X86_EAX, X86_EBP, info_offset, 4);
298 /* Load 'vret_arg_slot' */
299 x86_mov_reg_membase (code, X86_EAX, X86_EAX, MONO_STRUCT_OFFSET (GSharedVtCallInfo, vret_arg_slot), 4);
300 /* Compute ret area address in the caller frame in EAX */
301 x86_shift_reg_imm (code, X86_SHL, X86_EAX, 2);
302 x86_alu_reg_reg (code, X86_ADD, X86_EAX, X86_EBP);
303 x86_alu_reg_imm (code, X86_ADD, X86_EAX, 8);
304 x86_mov_reg_membase (code, X86_EAX, X86_EAX, 0, sizeof (gpointer));
306 /* Branch to specific marshalling code */
307 x86_alu_reg_imm (code, X86_CMP, X86_ECX, GSHAREDVT_RET_DOUBLE_FPSTACK);
309 x86_branch8 (code, X86_CC_E, 0, TRUE);
310 x86_alu_reg_imm (code, X86_CMP, X86_ECX, GSHAREDVT_RET_FLOAT_FPSTACK);
312 x86_branch8 (code, X86_CC_E, 0, TRUE);
313 x86_alu_reg_imm (code, X86_CMP, X86_ECX, GSHAREDVT_RET_STACK_POP);
315 x86_branch8 (code, X86_CC_E, 0, TRUE);
316 x86_alu_reg_imm (code, X86_CMP, X86_ECX, GSHAREDVT_RET_IREGS);
318 x86_branch8 (code, X86_CC_E, 0, TRUE);
320 x86_mov_reg_reg (code, X86_ECX, X86_EAX, sizeof (gpointer));
321 x86_pop_reg (code, X86_EAX);
322 x86_mov_membase_reg (code, X86_ECX, 0, X86_EAX, sizeof (gpointer));
324 x86_ret_imm (code, 4);
326 x86_patch (br [4], code);
327 x86_mov_reg_reg (code, X86_ECX, X86_EAX, sizeof (gpointer));
328 x86_pop_reg (code, X86_EAX);
329 x86_mov_membase_reg (code, X86_ECX, sizeof (gpointer), X86_EDX, sizeof (gpointer));
330 x86_mov_membase_reg (code, X86_ECX, 0, X86_EAX, sizeof (gpointer));
332 x86_ret_imm (code, 4);
333 /* DOUBLE_FPSTACK case */
334 x86_alu_reg_imm (code, X86_ADD, X86_ESP, 4);
335 x86_patch (br [1], code);
336 x86_fst_membase (code, X86_EAX, 0, TRUE, TRUE);
339 x86_ret_imm (code, 4);
340 /* FLOAT_FPSTACK case */
341 x86_alu_reg_imm (code, X86_ADD, X86_ESP, 4);
342 x86_patch (br [2], code);
343 x86_fst_membase (code, X86_EAX, 0, FALSE, TRUE);
345 x86_ret_imm (code, 4);
347 x86_patch (br [3], code);
349 x86_ret_imm (code, 4);
351 g_assert ((code - buf) < buf_len);
354 *info = mono_tramp_info_create ("gsharedvt_trampoline", buf, code - buf, ji, unwind_ops);
356 mono_arch_flush_icache (buf, code - buf);
360 #endif /* MONO_ARCH_GSHAREDVT_SUPPORTED */