2 * tramp-amd64-gsharedvt.c: libcorkscrew-based native unwinder
5 * Zoltan Varga <vargaz@gmail.com>
6 * Rodrigo Kumpera <kumpera@gmail.com>
7 * Andi McClure <andi.mcclure@xamarin.com>
8 * Johan Lorensson <johan.lorensson@xamarin.com>
10 * Copyright 2015 Xamarin, Inc (http://www.xamarin.com)
11 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
16 #include <mono/metadata/abi-details.h>
17 #include <mono/metadata/appdomain.h>
18 #include <mono/metadata/marshal.h>
19 #include <mono/metadata/tabledefs.h>
20 #include <mono/metadata/profiler-private.h>
21 #include <mono/metadata/gc-internals.h>
22 #include <mono/arch/amd64/amd64-codegen.h>
24 #include <mono/utils/memcheck.h>
27 #include "mini-amd64.h"
28 #include "mini-amd64-gsharedvt.h"
29 #include "debugger-agent.h"
31 #if defined (MONO_ARCH_GSHAREDVT_SUPPORTED)
33 #define ALIGN_TO(val,align) ((((guint64)val) + ((align) - 1)) & ~((align) - 1))
35 #define SRC_REG_SHIFT 0
36 #define SRC_REG_MASK 0xFFFF
38 #define SRC_DESCRIPTOR_MARSHAL_SHIFT 16
39 #define SRC_DESCRIPTOR_MARSHAL_MASK 0x0FF
41 #define SLOT_COUNT_SHIFT 24
42 #define SLOT_COUNT_MASK 0xFF
45 mono_amd64_start_gsharedvt_call (GSharedVtCallInfo *info, gpointer *caller, gpointer *callee, gpointer mrgctx_reg)
49 #ifdef DEBUG_AMD64_GSHAREDVT
50 printf ("mono_amd64_start_gsharedvt_call info %p caller %p callee %p ctx %p\n", info, caller, callee, mrgctx_reg);
52 for (i = 0; i < PARAM_REGS; ++i)
53 printf ("\treg [%d] -> %p\n", i, caller [i]);
56 /* Set vtype ret arg */
57 if (info->vret_slot != -1) {
58 DEBUG_AMD64_GSHAREDVT_PRINT ("vret handling\n[%d] < &%d (%p)\n", info->vret_arg_reg, info->vret_slot, &callee [info->vret_slot]);
59 g_assert (info->vret_slot);
60 callee [info->vret_arg_reg] = &callee [info->vret_slot];
63 for (i = 0; i < info->map_count; ++i) {
64 int src = info->map [i * 2];
65 int dst = info->map [(i * 2) + 1];
66 int arg_marshal = (src >> SRC_DESCRIPTOR_MARSHAL_SHIFT) & SRC_DESCRIPTOR_MARSHAL_MASK;
68 int source_reg = src & SRC_REG_MASK;
69 int dest_reg = dst & SRC_REG_MASK;
71 DEBUG_AMD64_GSHAREDVT_PRINT ("source %x dest %x marshal %d: ", src, dst, arg_marshal);
72 switch (arg_marshal) {
73 case GSHAREDVT_ARG_NONE:
74 callee [dest_reg] = caller [source_reg];
75 DEBUG_AMD64_GSHAREDVT_PRINT ("[%d] <- %d (%p) <- (%p)\n", dest_reg, source_reg, &callee [dest_reg], caller [source_reg]);
77 case GSHAREDVT_ARG_BYVAL_TO_BYREF:
78 /* gsharedvt argument passed by addr in reg/stack slot */
79 callee [dest_reg] = &caller [source_reg];
80 DEBUG_AMD64_GSHAREDVT_PRINT ("[%d] <- &%d (%p) <- (%p)\n", dest_reg, source_reg, &callee [dest_reg], &caller [source_reg]);
82 case GSHAREDVT_ARG_BYREF_TO_BYVAL: {
83 int slot_count = (src >> SLOT_COUNT_SHIFT) & SLOT_COUNT_MASK;
85 gpointer *addr = caller [source_reg];
87 for (j = 0; j < slot_count; ++j)
88 callee [dest_reg + j] = addr [j];
89 DEBUG_AMD64_GSHAREDVT_PRINT ("[%d] <- [%d] (%d words) (%p) <- (%p)\n", dest_reg, source_reg, slot_count, &callee [dest_reg], &caller [source_reg]);
93 g_error ("cant handle arg marshal %d\n", arg_marshal);
97 //Can't handle for now
98 if (info->vcall_offset != -1){
99 MonoObject *this_obj = caller [0];
101 DEBUG_AMD64_GSHAREDVT_PRINT ("target is a vcall at offset %d\n", info->vcall_offset / 8);
102 if (G_UNLIKELY (!this_obj))
104 if (info->vcall_offset == MONO_GSHAREDVT_DEL_INVOKE_VT_OFFSET)
105 /* delegate invoke */
106 return ((MonoDelegate*)this_obj)->invoke_impl;
108 return *(gpointer*)((char*)this_obj->vtable + info->vcall_offset);
109 } else if (info->calli) {
110 /* The address to call is passed in the mrgctx reg */
113 DEBUG_AMD64_GSHAREDVT_PRINT ("target is %p\n", info->addr);
123 * mono_arch_get_gsharedvt_arg_trampoline:
125 * See tramp-x86.c for documentation.
128 mono_arch_get_gsharedvt_arg_trampoline (MonoDomain *domain, gpointer arg, gpointer addr)
130 guint8 *code, *start;
135 start = code = mono_domain_code_reserve (domain, buf_len);
137 amd64_mov_reg_imm (code, AMD64_RAX, arg);
138 amd64_jump_code (code, addr);
139 g_assert ((code - start) < buf_len);
141 mono_arch_flush_icache (start, code - start);
142 mono_profiler_code_buffer_new (start, code - start, MONO_PROFILER_CODE_BUFFER_GENERICS_TRAMPOLINE, NULL);
144 mono_tramp_info_register (mono_tramp_info_create (NULL, start, code - start, NULL, NULL), domain);
150 mono_arch_get_gsharedvt_trampoline (MonoTrampInfo **info, gboolean aot)
153 int buf_len, cfa_offset;
154 GSList *unwind_ops = NULL;
155 MonoJumpInfo *ji = NULL;
156 int n_arg_regs, n_arg_fregs, framesize, i;
157 int info_offset, offset, rgctx_arg_reg_offset;
158 int caller_reg_area_offset, callee_reg_area_offset, callee_stack_area_offset;
159 guint8 *br_out, *br [64], *br_ret [64];
164 buf = code = mono_global_codeman_reserve (buf_len + MONO_MAX_TRAMPOLINE_UNWINDINFO_SIZE);
167 * We are being called by an gsharedvt arg trampoline, the info argument is in AMD64_RAX.
169 n_arg_regs = PARAM_REGS;
170 n_arg_fregs = FLOAT_PARAM_REGS;
172 /* Compute stack frame size and offsets */
175 info_offset = offset;
179 rgctx_arg_reg_offset = offset;
182 /*callconv in regs */
183 caller_reg_area_offset = offset;
184 reg_area_size = ALIGN_TO ((n_arg_regs + n_arg_fregs) * 8, MONO_ARCH_FRAME_ALIGNMENT);
185 offset += reg_area_size;
189 g_assert (framesize % MONO_ARCH_FRAME_ALIGNMENT == 0);
190 g_assert (reg_area_size % MONO_ARCH_FRAME_ALIGNMENT == 0);
192 /* unwind markers 1/3 */
193 cfa_offset = sizeof (gpointer);
194 mono_add_unwind_op_def_cfa (unwind_ops, code, buf, AMD64_RSP, cfa_offset);
195 mono_add_unwind_op_offset (unwind_ops, code, buf, AMD64_RIP, -cfa_offset);
197 /* save the old frame pointer */
198 amd64_push_reg (code, AMD64_RBP);
200 /* unwind markers 2/3 */
201 cfa_offset += sizeof (gpointer);
202 mono_add_unwind_op_def_cfa_offset (unwind_ops, code, buf, cfa_offset);
203 mono_add_unwind_op_offset (unwind_ops, code, buf, AMD64_RBP, - cfa_offset);
205 /* set it as the new frame pointer */
206 amd64_mov_reg_reg (code, AMD64_RBP, AMD64_RSP, sizeof(mgreg_t));
208 /* unwind markers 3/3 */
209 mono_add_unwind_op_def_cfa_reg (unwind_ops, code, buf, AMD64_RBP);
210 mono_add_unwind_op_fp_alloc (unwind_ops, code, buf, AMD64_RBP, 0);
212 /* setup the frame */
213 amd64_alu_reg_imm (code, X86_SUB, AMD64_RSP, framesize);
218 amd64_mov_membase_reg (code, AMD64_RSP, info_offset, AMD64_RAX, sizeof (mgreg_t));
220 amd64_mov_membase_reg (code, AMD64_RSP, rgctx_arg_reg_offset, MONO_ARCH_RGCTX_REG, sizeof (mgreg_t));
222 for (i = 0; i < n_arg_regs; ++i)
223 amd64_mov_membase_reg (code, AMD64_RSP, caller_reg_area_offset + i * 8, param_regs [i], sizeof (mgreg_t));
225 for (i = 0; i < n_arg_fregs; ++i)
226 amd64_sse_movsd_membase_reg (code, AMD64_RSP, caller_reg_area_offset + (i + n_arg_regs) * 8, i);
228 /* TODO Allocate stack area used to pass arguments to the method */
231 /* Allocate callee register area just below the caller area so it can be accessed from start_gsharedvt_call using negative offsets */
232 /* XXX figure out alignment */
233 callee_reg_area_offset = reg_area_size - ((n_arg_regs + n_arg_fregs) * 8); /* Ensure alignment */
234 callee_stack_area_offset = callee_reg_area_offset + reg_area_size;
235 amd64_alu_reg_imm (code, X86_SUB, AMD64_RSP, reg_area_size);
237 /* Allocate stack area used to pass arguments to the method */
238 amd64_mov_reg_membase (code, AMD64_R11, AMD64_RAX, MONO_STRUCT_OFFSET (GSharedVtCallInfo, stack_usage), 4);
239 amd64_alu_reg_reg (code, X86_SUB, AMD64_RSP, AMD64_R11);
241 /* The stack now looks like this:
243 <caller stack params area>
246 <caller registers area>
253 /* Call start_gsharedvt_call () */
255 amd64_mov_reg_reg (code, MONO_AMD64_ARG_REG1, AMD64_RAX, sizeof(mgreg_t));
256 /* arg2 = caller stack area */
257 amd64_lea_membase (code, MONO_AMD64_ARG_REG2, AMD64_RBP, -(framesize - caller_reg_area_offset));
259 /* arg3 == callee stack area */
260 amd64_lea_membase (code, MONO_AMD64_ARG_REG3, AMD64_RSP, callee_reg_area_offset);
262 /* arg4 = mrgctx reg */
263 amd64_mov_reg_reg (code, MONO_AMD64_ARG_REG4, MONO_ARCH_RGCTX_REG, sizeof(mgreg_t));
266 code = mono_arch_emit_load_aotconst (buf, code, &ji, MONO_PATCH_INFO_JIT_ICALL_ADDR, "mono_amd64_start_gsharedvt_call");
268 /* Since we are doing a call as part of setting up stackframe, the reserved shadow stack used by Windows platform is allocated up in
269 the callee stack area but currently the callee reg area is in between. Windows calling convention dictates that room is made on stack where
270 callee can save any parameters passed in registers. Since Windows x64 calling convention
271 uses 4 registers for the first 4 parameters, stack needs to be adjusted before making the call.
272 NOTE, Windows calling convention assumes that space for all registers have been reserved, regardless
273 of the number of function parameters actually used.
275 int shadow_reg_size = 0;
277 shadow_reg_size = ALIGN_TO (PARAM_REGS * sizeof(gpointer), MONO_ARCH_FRAME_ALIGNMENT);
278 amd64_alu_reg_imm (code, X86_SUB, AMD64_RSP, shadow_reg_size);
279 amd64_call_reg (code, AMD64_R11);
280 amd64_alu_reg_imm (code, X86_ADD, AMD64_RSP, shadow_reg_size);
282 amd64_call_reg (code, AMD64_R11);
285 amd64_call_code (code, mono_amd64_start_gsharedvt_call);
288 /* Method to call is now on RAX. Restore regs and jump */
289 amd64_mov_reg_reg (code, AMD64_R11, AMD64_RAX, sizeof(mgreg_t));
291 for (i = 0; i < n_arg_regs; ++i)
292 amd64_mov_reg_membase (code, param_regs [i], AMD64_RSP, callee_reg_area_offset + i * 8, sizeof (mgreg_t));
294 for (i = 0; i < n_arg_fregs; ++i)
295 amd64_sse_movsd_reg_membase (code, i, AMD64_RSP, callee_reg_area_offset + (i + n_arg_regs) * 8);
298 amd64_mov_reg_membase (code, MONO_ARCH_RGCTX_REG, AMD64_RBP, -(framesize - rgctx_arg_reg_offset), sizeof (mgreg_t));
300 /* Clear callee reg area */
301 amd64_alu_reg_imm (code, X86_ADD, AMD64_RSP, reg_area_size);
304 amd64_call_reg (code, AMD64_R11);
306 /* Marshal return value. Available registers: R10 and R11 */
307 /* Load info struct */
308 amd64_mov_reg_membase (code, AMD64_R10, AMD64_RBP, -(framesize - info_offset), sizeof (mgreg_t));
310 /* Branch to the in/out handling code */
311 amd64_alu_membase_imm_size (code, X86_CMP, AMD64_R10, MONO_STRUCT_OFFSET (GSharedVtCallInfo, gsharedvt_in), 1, 4);
315 x86_branch32 (code, X86_CC_NE, 0, TRUE);
322 /* Use first input parameter register as scratch since it is volatile on all platforms */
323 amd64_mov_reg_membase (code, MONO_AMD64_ARG_REG1, AMD64_R10, MONO_STRUCT_OFFSET (GSharedVtCallInfo, vret_slot), 4);
324 amd64_alu_reg_imm (code, X86_SUB, MONO_AMD64_ARG_REG1, n_arg_regs + n_arg_fregs);
325 amd64_shift_reg_imm (code, X86_SHL, MONO_AMD64_ARG_REG1, 3);
327 /* vret address is RBP - (framesize - caller_reg_area_offset) */
328 amd64_mov_reg_reg (code, AMD64_R11, AMD64_RSP, sizeof(mgreg_t));
329 amd64_alu_reg_reg (code, X86_ADD, AMD64_R11, MONO_AMD64_ARG_REG1);
331 /* Load ret marshal type */
332 /* Load vret address in R11 */
333 amd64_mov_reg_membase (code, AMD64_R10, AMD64_R10, MONO_STRUCT_OFFSET (GSharedVtCallInfo, ret_marshal), 4);
335 for (i = GSHAREDVT_RET_NONE; i < GSHAREDVT_RET_NUM; ++i) {
336 amd64_alu_reg_imm (code, X86_CMP, AMD64_R10, i);
338 amd64_branch8 (code, X86_CC_EQ, 0, TRUE);
340 x86_breakpoint (code); /* unhandled case */
342 for (i = GSHAREDVT_RET_NONE; i < GSHAREDVT_RET_NUM; ++i) {
343 mono_amd64_patch (br [i], code);
345 case GSHAREDVT_RET_NONE:
347 case GSHAREDVT_RET_I1:
348 amd64_widen_membase (code, AMD64_RAX, AMD64_R11, 0, TRUE, FALSE);
350 case GSHAREDVT_RET_U1:
351 amd64_widen_membase (code, AMD64_RAX, AMD64_R11, 0, FALSE, FALSE);
353 case GSHAREDVT_RET_I2:
354 amd64_widen_membase (code, AMD64_RAX, AMD64_R11, 0, TRUE, TRUE);
356 case GSHAREDVT_RET_U2:
357 amd64_widen_membase (code, AMD64_RAX, AMD64_R11, 0, FALSE, TRUE);
359 case GSHAREDVT_RET_I4: // CORRECT
360 case GSHAREDVT_RET_U4: // THIS IS INCORRECT. WHY IS IT NOT FAILING?
361 amd64_movsxd_reg_membase (code, AMD64_RAX, AMD64_R11, 0);
363 case GSHAREDVT_RET_I8:
364 amd64_mov_reg_membase (code, AMD64_RAX, AMD64_R11, 0, 8);
366 case GSHAREDVT_RET_IREGS_1:
367 amd64_mov_reg_membase (code, return_regs [i - GSHAREDVT_RET_IREGS_1], AMD64_R11, 0, 8);
369 case GSHAREDVT_RET_R8:
370 amd64_sse_movsd_reg_membase (code, AMD64_XMM0, AMD64_R11, 0);
373 x86_breakpoint (code); /* can't handle specific case */
376 br_ret [b_ret_index ++] = code;
377 x86_jump32 (code, 0);
383 mono_amd64_patch (br_out, code);
386 Address to write return to is in the original value of the register specified by vret_arg_reg.
387 This will be either RSI, RDI (System V) or RCX, RDX (Windows) depending on whether this is a static call.
389 We alloc 'framesize' bytes below RBP to save regs, info and rgctx. RSP = RBP - framesize
390 We store RDI (System V), RCX (Windows) at RSP + caller_reg_area_offset + slot_index_of (register) * 8.
392 address: RBP - framesize + caller_reg_area_offset + 8*slot
395 int caller_vret_offset = caller_reg_area_offset - framesize;
397 /* Load vret address in R11 */
398 /* Position to return to is passed as a hidden argument. Load 'vret_arg_slot' to find it */
399 amd64_movsxd_reg_membase (code, AMD64_R11, AMD64_R10, MONO_STRUCT_OFFSET (GSharedVtCallInfo, vret_arg_reg));
401 // In the GSHAREDVT_RET_NONE case, vret_arg_slot is -1. In this case, skip marshalling.
402 amd64_alu_reg_imm (code, X86_CMP, AMD64_R11, 0);
403 br_ret [b_ret_index ++] = code;
404 amd64_branch32 (code, X86_CC_LT, 0, TRUE);
406 /* Compute ret area address in the caller frame, *( ((gpointer *)RBP) [R11+2] ) */
407 amd64_shift_reg_imm (code, X86_SHL, AMD64_R11, 3);
408 amd64_alu_reg_imm (code, X86_ADD, AMD64_R11, caller_vret_offset);
409 amd64_alu_reg_reg (code, X86_ADD, AMD64_R11, AMD64_RBP);
410 amd64_mov_reg_membase (code, AMD64_R11, AMD64_R11, 0, sizeof (gpointer));
412 /* Load ret marshal type in R10 */
413 amd64_mov_reg_membase (code, AMD64_R10, AMD64_R10, MONO_STRUCT_OFFSET (GSharedVtCallInfo, ret_marshal), 4);
415 // Switch table for ret_marshal value
416 for (i = GSHAREDVT_RET_NONE; i < GSHAREDVT_RET_NUM; ++i) {
417 amd64_alu_reg_imm (code, X86_CMP, AMD64_R10, i);
419 amd64_branch8 (code, X86_CC_EQ, 0, TRUE);
421 x86_breakpoint (code); /* unhandled case */
423 for (i = GSHAREDVT_RET_NONE; i < GSHAREDVT_RET_NUM; ++i) {
424 mono_amd64_patch (br [i], code);
426 case GSHAREDVT_RET_NONE:
428 case GSHAREDVT_RET_IREGS_1:
429 amd64_mov_membase_reg (code, AMD64_R11, 0, return_regs [i - GSHAREDVT_RET_IREGS_1], 8);
431 case GSHAREDVT_RET_R8:
432 amd64_sse_movsd_membase_reg (code, AMD64_R11, 0, AMD64_XMM0);
435 x86_breakpoint (code); /* can't handle specific case */
438 br_ret [b_ret_index ++] = code;
439 x86_jump32 (code, 0);
443 for (i = 0; i < b_ret_index; ++i)
444 mono_amd64_patch (br_ret [i], code);
448 amd64_lea_membase (code, AMD64_RSP, AMD64_RBP, 0);
449 amd64_pop_reg (code, AMD64_RBP);
450 mono_add_unwind_op_same_value (unwind_ops, code, buf, AMD64_RBP);
456 g_assert ((code - buf) < buf_len);
457 g_assert_checked (mono_arch_unwindinfo_validate_size (unwind_ops, MONO_MAX_TRAMPOLINE_UNWINDINFO_SIZE));
460 *info = mono_tramp_info_create ("gsharedvt_trampoline", buf, code - buf, ji, unwind_ops);
462 mono_arch_flush_icache (buf, code - buf);
469 mono_arch_get_gsharedvt_arg_trampoline (MonoDomain *domain, gpointer arg, gpointer addr)
471 g_assert_not_reached ();
476 mono_arch_get_gsharedvt_trampoline (MonoTrampInfo **info, gboolean aot)
478 g_assert_not_reached ();
487 mono_amd64_start_gsharedvt_call (GSharedVtCallInfo *info, gpointer *caller, gpointer *callee, gpointer mrgctx_reg)
489 g_assert_not_reached ();