3 * libcorkscrew-based native unwinder
6 * Zoltan Varga <vargaz@gmail.com>
7 * Rodrigo Kumpera <kumpera@gmail.com>
8 * Andi McClure <andi.mcclure@xamarin.com>
9 * Johan Lorensson <johan.lorensson@xamarin.com>
11 * Copyright 2015 Xamarin, Inc (http://www.xamarin.com)
12 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
17 #include <mono/metadata/abi-details.h>
18 #include <mono/metadata/appdomain.h>
19 #include <mono/metadata/marshal.h>
20 #include <mono/metadata/tabledefs.h>
21 #include <mono/metadata/profiler-private.h>
22 #include <mono/metadata/gc-internals.h>
23 #include <mono/arch/amd64/amd64-codegen.h>
25 #include <mono/utils/memcheck.h>
28 #include "mini-amd64.h"
29 #include "mini-amd64-gsharedvt.h"
30 #include "debugger-agent.h"
32 #if defined (MONO_ARCH_GSHAREDVT_SUPPORTED)
34 #define ALIGN_TO(val,align) ((((guint64)val) + ((align) - 1)) & ~((align) - 1))
36 #define SRC_REG_SHIFT 0
37 #define SRC_REG_MASK 0xFFFF
39 #define SRC_DESCRIPTOR_MARSHAL_SHIFT 16
40 #define SRC_DESCRIPTOR_MARSHAL_MASK 0x0FF
42 #define SLOT_COUNT_SHIFT 24
43 #define SLOT_COUNT_MASK 0xFF
46 mono_amd64_start_gsharedvt_call (GSharedVtCallInfo *info, gpointer *caller, gpointer *callee, gpointer mrgctx_reg)
50 #ifdef DEBUG_AMD64_GSHAREDVT
51 printf ("mono_amd64_start_gsharedvt_call info %p caller %p callee %p ctx %p\n", info, caller, callee, mrgctx_reg);
53 for (i = 0; i < PARAM_REGS; ++i)
54 printf ("\treg [%d] -> %p\n", i, caller [i]);
57 /* Set vtype ret arg */
58 if (info->vret_slot != -1) {
59 DEBUG_AMD64_GSHAREDVT_PRINT ("vret handling\n[%d] < &%d (%p)\n", info->vret_arg_reg, info->vret_slot, &callee [info->vret_slot]);
60 g_assert (info->vret_slot);
61 callee [info->vret_arg_reg] = &callee [info->vret_slot];
64 for (i = 0; i < info->map_count; ++i) {
65 int src = info->map [i * 2];
66 int dst = info->map [(i * 2) + 1];
67 int arg_marshal = (src >> SRC_DESCRIPTOR_MARSHAL_SHIFT) & SRC_DESCRIPTOR_MARSHAL_MASK;
69 int source_reg = src & SRC_REG_MASK;
70 int dest_reg = dst & SRC_REG_MASK;
72 DEBUG_AMD64_GSHAREDVT_PRINT ("source %x dest %x marshal %d: ", src, dst, arg_marshal);
73 switch (arg_marshal) {
74 case GSHAREDVT_ARG_NONE:
75 callee [dest_reg] = caller [source_reg];
76 DEBUG_AMD64_GSHAREDVT_PRINT ("[%d] <- %d (%p) <- (%p)\n", dest_reg, source_reg, &callee [dest_reg], caller [source_reg]);
78 case GSHAREDVT_ARG_BYVAL_TO_BYREF:
79 /* gsharedvt argument passed by addr in reg/stack slot */
80 callee [dest_reg] = &caller [source_reg];
81 DEBUG_AMD64_GSHAREDVT_PRINT ("[%d] <- &%d (%p) <- (%p)\n", dest_reg, source_reg, &callee [dest_reg], &caller [source_reg]);
83 case GSHAREDVT_ARG_BYREF_TO_BYVAL: {
84 int slot_count = (src >> SLOT_COUNT_SHIFT) & SLOT_COUNT_MASK;
86 gpointer *addr = caller [source_reg];
88 for (j = 0; j < slot_count; ++j)
89 callee [dest_reg + j] = addr [j];
90 DEBUG_AMD64_GSHAREDVT_PRINT ("[%d] <- [%d] (%d words) (%p) <- (%p)\n", dest_reg, source_reg, slot_count, &callee [dest_reg], &caller [source_reg]);
94 g_error ("cant handle arg marshal %d\n", arg_marshal);
98 //Can't handle for now
99 if (info->vcall_offset != -1){
100 MonoObject *this_obj = caller [0];
102 DEBUG_AMD64_GSHAREDVT_PRINT ("target is a vcall at offset %d\n", info->vcall_offset / 8);
103 if (G_UNLIKELY (!this_obj))
105 if (info->vcall_offset == MONO_GSHAREDVT_DEL_INVOKE_VT_OFFSET)
106 /* delegate invoke */
107 return ((MonoDelegate*)this_obj)->invoke_impl;
109 return *(gpointer*)((char*)this_obj->vtable + info->vcall_offset);
110 } else if (info->calli) {
111 /* The address to call is passed in the mrgctx reg */
114 DEBUG_AMD64_GSHAREDVT_PRINT ("target is %p\n", info->addr);
124 * mono_arch_get_gsharedvt_arg_trampoline:
126 * See tramp-x86.c for documentation.
129 mono_arch_get_gsharedvt_arg_trampoline (MonoDomain *domain, gpointer arg, gpointer addr)
131 guint8 *code, *start;
136 start = code = mono_domain_code_reserve (domain, buf_len);
138 amd64_mov_reg_imm (code, AMD64_RAX, arg);
139 amd64_jump_code (code, addr);
140 g_assert ((code - start) < buf_len);
142 mono_arch_flush_icache (start, code - start);
143 mono_profiler_code_buffer_new (start, code - start, MONO_PROFILER_CODE_BUFFER_GENERICS_TRAMPOLINE, NULL);
145 mono_tramp_info_register (mono_tramp_info_create (NULL, start, code - start, NULL, NULL), domain);
151 mono_arch_get_gsharedvt_trampoline (MonoTrampInfo **info, gboolean aot)
154 int buf_len, cfa_offset;
155 GSList *unwind_ops = NULL;
156 MonoJumpInfo *ji = NULL;
157 int n_arg_regs, n_arg_fregs, framesize, i;
158 int info_offset, offset, rgctx_arg_reg_offset;
159 int caller_reg_area_offset, callee_reg_area_offset, callee_stack_area_offset;
160 guint8 *br_out, *br [64], *br_ret [64];
165 buf = code = mono_global_codeman_reserve (buf_len + MONO_MAX_TRAMPOLINE_UNWINDINFO_SIZE);
168 * We are being called by an gsharedvt arg trampoline, the info argument is in AMD64_RAX.
170 n_arg_regs = PARAM_REGS;
171 n_arg_fregs = FLOAT_PARAM_REGS;
173 /* Compute stack frame size and offsets */
176 info_offset = offset;
180 rgctx_arg_reg_offset = offset;
183 /*callconv in regs */
184 caller_reg_area_offset = offset;
185 reg_area_size = ALIGN_TO ((n_arg_regs + n_arg_fregs) * 8, MONO_ARCH_FRAME_ALIGNMENT);
186 offset += reg_area_size;
190 g_assert (framesize % MONO_ARCH_FRAME_ALIGNMENT == 0);
191 g_assert (reg_area_size % MONO_ARCH_FRAME_ALIGNMENT == 0);
193 /* unwind markers 1/3 */
194 cfa_offset = sizeof (gpointer);
195 mono_add_unwind_op_def_cfa (unwind_ops, code, buf, AMD64_RSP, cfa_offset);
196 mono_add_unwind_op_offset (unwind_ops, code, buf, AMD64_RIP, -cfa_offset);
198 /* save the old frame pointer */
199 amd64_push_reg (code, AMD64_RBP);
201 /* unwind markers 2/3 */
202 cfa_offset += sizeof (gpointer);
203 mono_add_unwind_op_def_cfa_offset (unwind_ops, code, buf, cfa_offset);
204 mono_add_unwind_op_offset (unwind_ops, code, buf, AMD64_RBP, - cfa_offset);
206 /* set it as the new frame pointer */
207 amd64_mov_reg_reg (code, AMD64_RBP, AMD64_RSP, sizeof(mgreg_t));
209 /* unwind markers 3/3 */
210 mono_add_unwind_op_def_cfa_reg (unwind_ops, code, buf, AMD64_RBP);
211 mono_add_unwind_op_fp_alloc (unwind_ops, code, buf, AMD64_RBP, 0);
213 /* setup the frame */
214 amd64_alu_reg_imm (code, X86_SUB, AMD64_RSP, framesize);
219 amd64_mov_membase_reg (code, AMD64_RSP, info_offset, AMD64_RAX, sizeof (mgreg_t));
221 amd64_mov_membase_reg (code, AMD64_RSP, rgctx_arg_reg_offset, MONO_ARCH_RGCTX_REG, sizeof (mgreg_t));
223 for (i = 0; i < n_arg_regs; ++i)
224 amd64_mov_membase_reg (code, AMD64_RSP, caller_reg_area_offset + i * 8, param_regs [i], sizeof (mgreg_t));
226 for (i = 0; i < n_arg_fregs; ++i)
227 amd64_sse_movsd_membase_reg (code, AMD64_RSP, caller_reg_area_offset + (i + n_arg_regs) * 8, i);
229 /* TODO Allocate stack area used to pass arguments to the method */
232 /* Allocate callee register area just below the caller area so it can be accessed from start_gsharedvt_call using negative offsets */
233 /* XXX figure out alignment */
234 callee_reg_area_offset = reg_area_size - ((n_arg_regs + n_arg_fregs) * 8); /* Ensure alignment */
235 callee_stack_area_offset = callee_reg_area_offset + reg_area_size;
236 amd64_alu_reg_imm (code, X86_SUB, AMD64_RSP, reg_area_size);
238 /* Allocate stack area used to pass arguments to the method */
239 amd64_mov_reg_membase (code, AMD64_R11, AMD64_RAX, MONO_STRUCT_OFFSET (GSharedVtCallInfo, stack_usage), 4);
240 amd64_alu_reg_reg (code, X86_SUB, AMD64_RSP, AMD64_R11);
242 /* The stack now looks like this:
244 <caller stack params area>
247 <caller registers area>
254 /* Call start_gsharedvt_call () */
256 amd64_mov_reg_reg (code, MONO_AMD64_ARG_REG1, AMD64_RAX, sizeof(mgreg_t));
257 /* arg2 = caller stack area */
258 amd64_lea_membase (code, MONO_AMD64_ARG_REG2, AMD64_RBP, -(framesize - caller_reg_area_offset));
260 /* arg3 == callee stack area */
261 amd64_lea_membase (code, MONO_AMD64_ARG_REG3, AMD64_RSP, callee_reg_area_offset);
263 /* arg4 = mrgctx reg */
264 amd64_mov_reg_reg (code, MONO_AMD64_ARG_REG4, MONO_ARCH_RGCTX_REG, sizeof(mgreg_t));
267 code = mono_arch_emit_load_aotconst (buf, code, &ji, MONO_PATCH_INFO_JIT_ICALL_ADDR, "mono_amd64_start_gsharedvt_call");
269 /* Since we are doing a call as part of setting up stackframe, the reserved shadow stack used by Windows platform is allocated up in
270 the callee stack area but currently the callee reg area is in between. Windows calling convention dictates that room is made on stack where
271 callee can save any parameters passed in registers. Since Windows x64 calling convention
272 uses 4 registers for the first 4 parameters, stack needs to be adjusted before making the call.
273 NOTE, Windows calling convention assumes that space for all registers have been reserved, regardless
274 of the number of function parameters actually used.
276 int shadow_reg_size = 0;
278 shadow_reg_size = ALIGN_TO (PARAM_REGS * sizeof(gpointer), MONO_ARCH_FRAME_ALIGNMENT);
279 amd64_alu_reg_imm (code, X86_SUB, AMD64_RSP, shadow_reg_size);
280 amd64_call_reg (code, AMD64_R11);
281 amd64_alu_reg_imm (code, X86_ADD, AMD64_RSP, shadow_reg_size);
283 amd64_call_reg (code, AMD64_R11);
286 amd64_call_code (code, mono_amd64_start_gsharedvt_call);
289 /* Method to call is now on RAX. Restore regs and jump */
290 amd64_mov_reg_reg (code, AMD64_R11, AMD64_RAX, sizeof(mgreg_t));
292 for (i = 0; i < n_arg_regs; ++i)
293 amd64_mov_reg_membase (code, param_regs [i], AMD64_RSP, callee_reg_area_offset + i * 8, sizeof (mgreg_t));
295 for (i = 0; i < n_arg_fregs; ++i)
296 amd64_sse_movsd_reg_membase (code, i, AMD64_RSP, callee_reg_area_offset + (i + n_arg_regs) * 8);
299 amd64_mov_reg_membase (code, MONO_ARCH_RGCTX_REG, AMD64_RBP, -(framesize - rgctx_arg_reg_offset), sizeof (mgreg_t));
301 /* Clear callee reg area */
302 amd64_alu_reg_imm (code, X86_ADD, AMD64_RSP, reg_area_size);
305 amd64_call_reg (code, AMD64_R11);
307 /* Marshal return value. Available registers: R10 and R11 */
308 /* Load info struct */
309 amd64_mov_reg_membase (code, AMD64_R10, AMD64_RBP, -(framesize - info_offset), sizeof (mgreg_t));
311 /* Branch to the in/out handling code */
312 amd64_alu_membase_imm_size (code, X86_CMP, AMD64_R10, MONO_STRUCT_OFFSET (GSharedVtCallInfo, gsharedvt_in), 1, 4);
316 x86_branch32 (code, X86_CC_NE, 0, TRUE);
323 /* Use first input parameter register as scratch since it is volatile on all platforms */
324 amd64_mov_reg_membase (code, MONO_AMD64_ARG_REG1, AMD64_R10, MONO_STRUCT_OFFSET (GSharedVtCallInfo, vret_slot), 4);
325 amd64_alu_reg_imm (code, X86_SUB, MONO_AMD64_ARG_REG1, n_arg_regs + n_arg_fregs);
326 amd64_shift_reg_imm (code, X86_SHL, MONO_AMD64_ARG_REG1, 3);
328 /* vret address is RBP - (framesize - caller_reg_area_offset) */
329 amd64_mov_reg_reg (code, AMD64_R11, AMD64_RSP, sizeof(mgreg_t));
330 amd64_alu_reg_reg (code, X86_ADD, AMD64_R11, MONO_AMD64_ARG_REG1);
332 /* Load ret marshal type */
333 /* Load vret address in R11 */
334 amd64_mov_reg_membase (code, AMD64_R10, AMD64_R10, MONO_STRUCT_OFFSET (GSharedVtCallInfo, ret_marshal), 4);
336 for (i = GSHAREDVT_RET_NONE; i < GSHAREDVT_RET_NUM; ++i) {
337 amd64_alu_reg_imm (code, X86_CMP, AMD64_R10, i);
339 amd64_branch8 (code, X86_CC_EQ, 0, TRUE);
341 x86_breakpoint (code); /* unhandled case */
343 for (i = GSHAREDVT_RET_NONE; i < GSHAREDVT_RET_NUM; ++i) {
344 mono_amd64_patch (br [i], code);
346 case GSHAREDVT_RET_NONE:
348 case GSHAREDVT_RET_I1:
349 amd64_widen_membase (code, AMD64_RAX, AMD64_R11, 0, TRUE, FALSE);
351 case GSHAREDVT_RET_U1:
352 amd64_widen_membase (code, AMD64_RAX, AMD64_R11, 0, FALSE, FALSE);
354 case GSHAREDVT_RET_I2:
355 amd64_widen_membase (code, AMD64_RAX, AMD64_R11, 0, TRUE, TRUE);
357 case GSHAREDVT_RET_U2:
358 amd64_widen_membase (code, AMD64_RAX, AMD64_R11, 0, FALSE, TRUE);
360 case GSHAREDVT_RET_I4: // CORRECT
361 case GSHAREDVT_RET_U4: // THIS IS INCORRECT. WHY IS IT NOT FAILING?
362 amd64_movsxd_reg_membase (code, AMD64_RAX, AMD64_R11, 0);
364 case GSHAREDVT_RET_I8:
365 amd64_mov_reg_membase (code, AMD64_RAX, AMD64_R11, 0, 8);
367 case GSHAREDVT_RET_IREGS_1:
368 amd64_mov_reg_membase (code, return_regs [i - GSHAREDVT_RET_IREGS_1], AMD64_R11, 0, 8);
370 case GSHAREDVT_RET_R8:
371 amd64_sse_movsd_reg_membase (code, AMD64_XMM0, AMD64_R11, 0);
374 x86_breakpoint (code); /* can't handle specific case */
377 br_ret [b_ret_index ++] = code;
378 x86_jump32 (code, 0);
384 mono_amd64_patch (br_out, code);
387 Address to write return to is in the original value of the register specified by vret_arg_reg.
388 This will be either RSI, RDI (System V) or RCX, RDX (Windows) depending on whether this is a static call.
390 We alloc 'framesize' bytes below RBP to save regs, info and rgctx. RSP = RBP - framesize
391 We store RDI (System V), RCX (Windows) at RSP + caller_reg_area_offset + slot_index_of (register) * 8.
393 address: RBP - framesize + caller_reg_area_offset + 8*slot
396 int caller_vret_offset = caller_reg_area_offset - framesize;
398 /* Load vret address in R11 */
399 /* Position to return to is passed as a hidden argument. Load 'vret_arg_slot' to find it */
400 amd64_movsxd_reg_membase (code, AMD64_R11, AMD64_R10, MONO_STRUCT_OFFSET (GSharedVtCallInfo, vret_arg_reg));
402 // In the GSHAREDVT_RET_NONE case, vret_arg_slot is -1. In this case, skip marshalling.
403 amd64_alu_reg_imm (code, X86_CMP, AMD64_R11, 0);
404 br_ret [b_ret_index ++] = code;
405 amd64_branch32 (code, X86_CC_LT, 0, TRUE);
407 /* Compute ret area address in the caller frame, *( ((gpointer *)RBP) [R11+2] ) */
408 amd64_shift_reg_imm (code, X86_SHL, AMD64_R11, 3);
409 amd64_alu_reg_imm (code, X86_ADD, AMD64_R11, caller_vret_offset);
410 amd64_alu_reg_reg (code, X86_ADD, AMD64_R11, AMD64_RBP);
411 amd64_mov_reg_membase (code, AMD64_R11, AMD64_R11, 0, sizeof (gpointer));
413 /* Load ret marshal type in R10 */
414 amd64_mov_reg_membase (code, AMD64_R10, AMD64_R10, MONO_STRUCT_OFFSET (GSharedVtCallInfo, ret_marshal), 4);
416 // Switch table for ret_marshal value
417 for (i = GSHAREDVT_RET_NONE; i < GSHAREDVT_RET_NUM; ++i) {
418 amd64_alu_reg_imm (code, X86_CMP, AMD64_R10, i);
420 amd64_branch8 (code, X86_CC_EQ, 0, TRUE);
422 x86_breakpoint (code); /* unhandled case */
424 for (i = GSHAREDVT_RET_NONE; i < GSHAREDVT_RET_NUM; ++i) {
425 mono_amd64_patch (br [i], code);
427 case GSHAREDVT_RET_NONE:
429 case GSHAREDVT_RET_IREGS_1:
430 amd64_mov_membase_reg (code, AMD64_R11, 0, return_regs [i - GSHAREDVT_RET_IREGS_1], 8);
432 case GSHAREDVT_RET_R8:
433 amd64_sse_movsd_membase_reg (code, AMD64_R11, 0, AMD64_XMM0);
436 x86_breakpoint (code); /* can't handle specific case */
439 br_ret [b_ret_index ++] = code;
440 x86_jump32 (code, 0);
444 for (i = 0; i < b_ret_index; ++i)
445 mono_amd64_patch (br_ret [i], code);
449 amd64_lea_membase (code, AMD64_RSP, AMD64_RBP, 0);
450 amd64_pop_reg (code, AMD64_RBP);
451 mono_add_unwind_op_same_value (unwind_ops, code, buf, AMD64_RBP);
457 g_assert ((code - buf) < buf_len);
458 g_assert_checked (mono_arch_unwindinfo_validate_size (unwind_ops, MONO_MAX_TRAMPOLINE_UNWINDINFO_SIZE));
461 *info = mono_tramp_info_create ("gsharedvt_trampoline", buf, code - buf, ji, unwind_ops);
463 mono_arch_flush_icache (buf, code - buf);
470 mono_arch_get_gsharedvt_arg_trampoline (MonoDomain *domain, gpointer arg, gpointer addr)
472 g_assert_not_reached ();
477 mono_arch_get_gsharedvt_trampoline (MonoTrampInfo **info, gboolean aot)
479 g_assert_not_reached ();
488 mono_amd64_start_gsharedvt_call (GSharedVtCallInfo *info, gpointer *caller, gpointer *callee, gpointer mrgctx_reg)
490 g_assert_not_reached ();