2 * mini-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>
9 * Copyright 2015 Xamarin, Inc (http://www.xamarin.com)
10 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
15 #include <mono/metadata/abi-details.h>
16 #include <mono/metadata/appdomain.h>
17 #include <mono/metadata/marshal.h>
18 #include <mono/metadata/tabledefs.h>
19 #include <mono/metadata/mono-debug-debugger.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))
36 mono_arch_gsharedvt_sig_supported (MonoMethodSignature *sig)
42 storage_name (ArgStorage st)
45 case ArgInIReg: return "ArgInIReg";
46 case ArgInFloatSSEReg: return "ArgInFloatSSEReg";
47 case ArgInDoubleSSEReg: return "ArgInDoubleSSEReg";
48 case ArgOnStack: return "ArgOnStack";
49 case ArgValuetypeInReg: return "ArgValuetypeInReg";
50 case ArgValuetypeAddrInIReg: return "ArgValuetypeAddrInIReg";
51 case ArgGSharedVtInReg: return "ArgGSharedVtInReg";
52 case ArgGSharedVtOnStack: return "ArgGSharedVtOnStack";
53 case ArgNone: return "ArgNone";
54 default: return "unknown";
59 arg_info_desc (ArgInfo *info)
61 GString *str = g_string_new ("");
63 g_string_append_printf (str, "offset %d reg %s storage %s nregs %d", info->offset, mono_arch_regname (info->reg), storage_name (info->storage), info->nregs);
64 if (info->storage == ArgValuetypeInReg)
65 g_string_append_printf (str, " {(%s %s), (%s %s)",
66 storage_name (info->pair_storage [0]),
67 mono_arch_regname (info->pair_regs [0]),
68 storage_name (info->pair_storage [1]),
69 mono_arch_regname (info->pair_regs [1]));
71 return g_string_free (str, FALSE);
75 add_to_map (GPtrArray *map, int src, int dst)
77 g_ptr_array_add (map, GUINT_TO_POINTER (src));
78 g_ptr_array_add (map, GUINT_TO_POINTER (dst));
83 * 0..5 - rdi, rsi, rdx, rcx, r8, r9
91 for (i = 0; i < PARAM_REGS; ++i) {
92 if (param_regs [i] == reg)
95 g_error ("Invalid argument register number %d", reg);
102 return reg + PARAM_REGS;
106 map_stack_slot (int slot)
108 return slot + PARAM_REGS + FLOAT_PARAM_REGS;
112 Format for the source descriptor:
115 Format for the destination descriptor:
116 bits 0:15 - source register
117 bits 16:23 - return marshal
118 bits 24:32 - slot count
120 #define SRC_DESCRIPTOR_MARSHAL_SHIFT 16
121 #define SRC_DESCRIPTOR_MARSHAL_MASK 0x0Ff
123 #define SLOT_COUNT_SHIFT 24
124 #define SLOT_COUNT_MASK 0xff
125 #define SLOT_BYTE_SIZE 8
128 get_arg_slots (ArgInfo *ainfo, int **out_slots, gboolean is_source_argument)
130 int sreg = ainfo->reg;
131 int sslot = ainfo->offset / 8;
135 switch (ainfo->storage) {
138 src = g_malloc (nsrc * sizeof (int));
139 src [0] = map_reg (sreg);
141 case ArgValuetypeInReg:
143 src = g_malloc (nsrc * sizeof (int));
144 for (i = 0; i < ainfo->nregs; ++i)
145 src [i] = map_reg (ainfo->pair_regs [i]);
148 nsrc = ainfo->arg_size / SLOT_BYTE_SIZE;
149 src = g_malloc (nsrc * sizeof (int));
150 // is_source_argument adds 2 because we're skipping over the old BBP and the return address
151 // XXX this is a very fragile setup as changes in alignment for the caller reg array can cause the magic number be 3
152 for (i = 0; i < nsrc; ++i)
153 src [i] = map_stack_slot (sslot + i + (is_source_argument ? 2 : 0));
155 case ArgInDoubleSSEReg:
156 case ArgInFloatSSEReg:
158 src = g_malloc (nsrc * sizeof (int));
159 src [0] = map_freg (sreg);
170 // Once src is known, operate on the dst
172 handle_marshal_when_src_gsharedvt (ArgInfo *dst_info, int *arg_marshal, int *arg_slots)
174 switch (dst_info->storage) {
176 case ArgInDoubleSSEReg:
177 case ArgInFloatSSEReg:
178 *arg_marshal = GSHAREDVT_ARG_BYREF_TO_BYVAL;
182 *arg_marshal = GSHAREDVT_ARG_BYREF_TO_BYVAL;
183 g_assert (dst_info->arg_size % SLOT_BYTE_SIZE == 0); // Assert quadword aligned
184 *arg_slots = dst_info->arg_size / SLOT_BYTE_SIZE;
186 case ArgValuetypeInReg:
187 *arg_marshal = GSHAREDVT_ARG_BYREF_TO_BYVAL;
188 *arg_slots = dst_info->nregs;
191 NOT_IMPLEMENTED; // Inappropriate value: if dst and src are gsharedvt at once, we shouldn't be here
196 // Once dst is known, operate on the src
198 handle_marshal_when_dst_gsharedvt (ArgInfo *src_info, int *arg_marshal)
200 switch (src_info->storage) {
202 case ArgInDoubleSSEReg:
203 case ArgInFloatSSEReg:
204 case ArgValuetypeInReg:
206 *arg_marshal = GSHAREDVT_ARG_BYVAL_TO_BYREF;
209 NOT_IMPLEMENTED; // See above
215 handle_map_when_gsharedvt_in_reg (ArgInfo *reg_info, int *n, int **map)
218 *map = g_new0 (int, 1);
219 (*map) [0] = map_reg (reg_info->reg);
223 handle_map_when_gsharedvt_on_stack (ArgInfo *reg_info, int *n, int **map, gboolean is_source_argument)
226 *map = g_new0 (int, 1);
227 int sslot = reg_info->offset / SLOT_BYTE_SIZE;
228 (*map) [0] = map_stack_slot (sslot + (is_source_argument ? 2 : 0)); // see get_arg_slots
232 mono_arch_get_gsharedvt_call_info (gpointer addr, MonoMethodSignature *normal_sig, MonoMethodSignature *gsharedvt_sig, gboolean gsharedvt_in, gint32 vcall_offset, gboolean calli)
234 GSharedVtCallInfo *info;
235 CallInfo *caller_cinfo, *callee_cinfo;
236 MonoMethodSignature *caller_sig, *callee_sig;
238 gboolean var_ret = FALSE;
239 CallInfo *cinfo, *gcinfo;
240 MonoMethodSignature *sig, *gsig;
244 caller_sig = normal_sig;
245 callee_sig = gsharedvt_sig;
246 caller_cinfo = mono_arch_get_call_info (NULL, caller_sig);
247 callee_cinfo = mono_arch_get_call_info (NULL, callee_sig);
249 callee_sig = normal_sig;
250 caller_sig = gsharedvt_sig;
251 callee_cinfo = mono_arch_get_call_info (NULL, callee_sig);
252 caller_cinfo = mono_arch_get_call_info (NULL, caller_sig);
256 * If GSHAREDVT_IN is true, this means we are transitioning from normal to gsharedvt code. The caller uses the
257 * normal call signature, while the callee uses the gsharedvt signature.
258 * If GSHAREDVT_IN is false, its the other way around.
261 /* sig/cinfo describes the normal call, while gsig/gcinfo describes the gsharedvt call */
265 cinfo = caller_cinfo;
266 gcinfo = callee_cinfo;
270 cinfo = callee_cinfo;
271 gcinfo = caller_cinfo;
274 DEBUG_AMD64_GSHAREDVT_PRINT ("source sig: (%s) return (%s)\n", mono_signature_get_desc (caller_sig, FALSE), mono_type_full_name (mono_signature_get_return_type (caller_sig))); // Leak
275 DEBUG_AMD64_GSHAREDVT_PRINT ("dest sig: (%s) return (%s)\n", mono_signature_get_desc (callee_sig, FALSE), mono_type_full_name (mono_signature_get_return_type (callee_sig)));
277 if (gcinfo->ret.storage == ArgGsharedvtVariableInReg) {
279 * The return type is gsharedvt
285 * The stack looks like this:
289 * We have to map the stack slots in <arguments> to the stack slots in <call area>.
291 map = g_ptr_array_new ();
293 for (aindex = 0; aindex < cinfo->nargs; ++aindex) {
294 ArgInfo *src_info = &caller_cinfo->args [aindex];
295 ArgInfo *dst_info = &callee_cinfo->args [aindex];
296 int *src = NULL, *dst = NULL;
297 int nsrc = -1, ndst = -1, nslots = 0;
299 int arg_marshal = GSHAREDVT_ARG_NONE;
300 int arg_slots = 0; // Size in quadwords
301 DEBUG_AMD64_GSHAREDVT_PRINT ("-- arg %d in (%s) out (%s)\n", aindex, arg_info_desc (src_info), arg_info_desc (dst_info));
303 switch (src_info->storage) {
305 case ArgInDoubleSSEReg:
306 case ArgInFloatSSEReg:
307 case ArgValuetypeInReg:
309 nsrc = get_arg_slots (src_info, &src, TRUE);
311 case ArgGSharedVtInReg:
312 handle_marshal_when_src_gsharedvt (dst_info, &arg_marshal, &arg_slots);
313 handle_map_when_gsharedvt_in_reg (src_info, &nsrc, &src);
315 case ArgGSharedVtOnStack:
316 handle_marshal_when_src_gsharedvt (dst_info, &arg_marshal, &arg_slots);
317 handle_map_when_gsharedvt_on_stack (src_info, &nsrc, &src, TRUE);
320 g_error ("Gsharedvt can't handle source arg type %d", (int)src_info->storage); // Inappropriate value: ArgValuetypeAddrInIReg is for returns only
323 switch (dst_info->storage) {
325 case ArgInDoubleSSEReg:
326 case ArgInFloatSSEReg:
328 case ArgValuetypeInReg:
329 ndst = get_arg_slots (dst_info, &dst, FALSE);
331 case ArgGSharedVtInReg:
332 handle_marshal_when_dst_gsharedvt (src_info, &arg_marshal);
333 handle_map_when_gsharedvt_in_reg (dst_info, &ndst, &dst);
335 case ArgGSharedVtOnStack:
336 handle_marshal_when_dst_gsharedvt (src_info, &arg_marshal);
337 handle_map_when_gsharedvt_on_stack (dst_info, &ndst, &dst, FALSE);
340 g_error ("Gsharedvt can't handle dest arg type %d", (int)dst_info->storage); // See above
343 src [0] |= (arg_marshal << SRC_DESCRIPTOR_MARSHAL_SHIFT) | (arg_slots << SLOT_COUNT_SHIFT);
345 /* Merge and add to the global list*/
346 nslots = MIN (nsrc, ndst);
347 DEBUG_AMD64_GSHAREDVT_PRINT ("nsrc %d ndst %d\n", nsrc, ndst);
349 for (i = 0; i < nslots; ++i)
350 add_to_map (map, src [i], dst [i]);
356 DEBUG_AMD64_GSHAREDVT_PRINT ("-- return in (%s) out (%s) var_ret %d\n", arg_info_desc (&caller_cinfo->ret), arg_info_desc (&callee_cinfo->ret), var_ret);
358 if (cinfo->ret.storage == ArgValuetypeAddrInIReg) {
359 /* Both the caller and the callee pass the vtype ret address in r8 */
360 g_assert (gcinfo->ret.storage == ArgValuetypeAddrInIReg || gcinfo->ret.storage == ArgGsharedvtVariableInReg);
361 add_to_map (map, map_reg (cinfo->ret.reg), map_reg (cinfo->ret.reg));
364 info = mono_domain_alloc0 (mono_domain_get (), sizeof (GSharedVtCallInfo) + (map->len * sizeof (int)));
366 info->stack_usage = callee_cinfo->stack_usage;
367 info->ret_marshal = GSHAREDVT_RET_NONE;
368 info->gsharedvt_in = gsharedvt_in ? 1 : 0;
369 info->vret_slot = -1;
373 g_assert (gcinfo->ret.storage == ArgGsharedvtVariableInReg);
374 info->vret_arg_reg = map_reg (gcinfo->ret.reg);
375 DEBUG_AMD64_GSHAREDVT_PRINT ("mapping vreg_arg_reg to %d in reg %s\n", info->vret_arg_reg, mono_arch_regname (gcinfo->ret.reg));
377 info->vret_arg_reg = -1;
380 #ifdef DEBUG_AMD64_GSHAREDVT
381 printf ("final map:\n");
382 for (i = 0; i < map->len; i += 2) {
383 printf ("\t[%d] src %x dst %x\n ",
385 GPOINTER_TO_UINT (g_ptr_array_index (map, i)),
386 GPOINTER_TO_UINT (g_ptr_array_index (map, i + 1)));
390 info->vcall_offset = vcall_offset;
391 info->map_count = map->len / 2;
392 for (i = 0; i < map->len; ++i)
393 info->map [i] = GPOINTER_TO_UINT (g_ptr_array_index (map, i));
394 g_ptr_array_free (map, TRUE);
396 /* Compute return value marshalling */
398 /* Compute return value marshalling */
399 switch (cinfo->ret.storage) {
401 if (!gsharedvt_in || sig->ret->byref) {
402 info->ret_marshal = GSHAREDVT_RET_IREGS_1;
404 MonoType *ret = sig->ret;
407 if (ret->type == MONO_TYPE_VALUETYPE)
408 ret = mini_type_get_underlying_type (ret);
412 info->ret_marshal = GSHAREDVT_RET_I1;
414 case MONO_TYPE_BOOLEAN:
416 info->ret_marshal = GSHAREDVT_RET_U1;
419 info->ret_marshal = GSHAREDVT_RET_I2;
423 info->ret_marshal = GSHAREDVT_RET_U2;
426 info->ret_marshal = GSHAREDVT_RET_I4;
429 info->ret_marshal = GSHAREDVT_RET_U4;
434 case MONO_TYPE_FNPTR:
435 case MONO_TYPE_CLASS:
436 case MONO_TYPE_OBJECT:
437 case MONO_TYPE_SZARRAY:
438 case MONO_TYPE_ARRAY:
439 case MONO_TYPE_STRING:
442 info->ret_marshal = GSHAREDVT_RET_I8;
446 g_error ("Gsharedvt can't handle dst type [%d]", (int)sig->ret->type);
450 case ArgValuetypeInReg:
451 info->ret_marshal = GSHAREDVT_RET_IREGS_1 - 1 + cinfo->ret.nregs;
452 g_assert (cinfo->ret.nregs == 1); // ABI supports 2-register return but we do not implement this.
454 case ArgInDoubleSSEReg:
455 case ArgInFloatSSEReg:
456 info->ret_marshal = GSHAREDVT_RET_R8;
458 case ArgValuetypeAddrInIReg:
461 g_error ("Can't marshal return of storage [%d] %s", (int)cinfo->ret.storage, storage_name (cinfo->ret.storage));
464 if (gsharedvt_in && cinfo->ret.storage != ArgValuetypeAddrInIReg) {
465 /* Allocate stack space for the return value */
466 info->vret_slot = map_stack_slot (info->stack_usage / sizeof (gpointer));
467 info->stack_usage += mono_type_stack_size_internal (normal_sig->ret, NULL, FALSE) + sizeof (gpointer);
469 DEBUG_AMD64_GSHAREDVT_PRINT ("RET marshal is %s\n", ret_marshal_name [info->ret_marshal]);
472 info->stack_usage = ALIGN_TO (info->stack_usage, MONO_ARCH_FRAME_ALIGNMENT);
474 DEBUG_AMD64_GSHAREDVT_PRINT ("allocated an info at %p stack usage %d\n", info, info->stack_usage);