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>
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/mono-debug-debugger.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))
37 mono_arch_gsharedvt_sig_supported (MonoMethodSignature
*sig
)
43 storage_name (ArgStorage st
)
46 case ArgInIReg
: return "ArgInIReg";
47 case ArgInFloatSSEReg
: return "ArgInFloatSSEReg";
48 case ArgInDoubleSSEReg
: return "ArgInDoubleSSEReg";
49 case ArgOnStack
: return "ArgOnStack";
50 case ArgValuetypeInReg
: return "ArgValuetypeInReg";
51 case ArgValuetypeAddrInIReg
: return "ArgValuetypeAddrInIReg";
52 case ArgValuetypeAddrOnStack
: return "ArgValuetypeAddrOnStack";
53 case ArgGSharedVtInReg
: return "ArgGSharedVtInReg";
54 case ArgGSharedVtOnStack
: return "ArgGSharedVtOnStack";
55 case ArgNone
: return "ArgNone";
56 default: return "unknown";
61 arg_info_desc (ArgInfo
*info
)
63 GString
*str
= g_string_new ("");
65 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
);
66 if (info
->storage
== ArgValuetypeInReg
)
67 g_string_append_printf (str
, " {(%s %s), (%s %s)",
68 storage_name (info
->pair_storage
[0]),
69 mono_arch_regname (info
->pair_regs
[0]),
70 storage_name (info
->pair_storage
[1]),
71 mono_arch_regname (info
->pair_regs
[1]));
73 return g_string_free (str
, FALSE
);
77 add_to_map (GPtrArray
*map
, int src
, int dst
)
79 g_ptr_array_add (map
, GUINT_TO_POINTER (src
));
80 g_ptr_array_add (map
, GUINT_TO_POINTER (dst
));
87 * 0..5 - rdi, rsi, rdx, rcx, r8, r9
92 * 0..3 - rcx, rdx, r8, r9
101 for (i
= 0; i
< PARAM_REGS
; ++i
) {
102 if (param_regs
[i
] == reg
)
105 g_error ("Invalid argument register number %d", reg
);
112 return reg
+ PARAM_REGS
;
116 map_stack_slot (int slot
)
118 return slot
+ PARAM_REGS
+ FLOAT_PARAM_REGS
;
122 Format for the source descriptor:
125 Format for the destination descriptor:
126 bits 0:15 - source register
127 bits 16:23 - return marshal
128 bits 24:32 - slot count
130 #define SRC_DESCRIPTOR_MARSHAL_SHIFT 16
131 #define SRC_DESCRIPTOR_MARSHAL_MASK 0x0Ff
133 #define SLOT_COUNT_SHIFT 24
134 #define SLOT_COUNT_MASK 0xff
135 #define SLOT_BYTE_SIZE 8
138 get_arg_slots (ArgInfo
*ainfo
, int **out_slots
, gboolean is_source_argument
)
140 int sreg
= ainfo
->reg
;
141 int sslot
= ainfo
->offset
/ 8;
145 switch (ainfo
->storage
) {
148 src
= g_malloc (nsrc
* sizeof (int));
149 src
[0] = map_reg (sreg
);
151 case ArgValuetypeInReg
:
153 src
= g_malloc (nsrc
* sizeof (int));
154 for (i
= 0; i
< ainfo
->nregs
; ++i
)
155 src
[i
] = map_reg (ainfo
->pair_regs
[i
]);
158 nsrc
= ainfo
->arg_size
/ SLOT_BYTE_SIZE
;
159 src
= g_malloc (nsrc
* sizeof (int));
160 // is_source_argument adds 2 because we're skipping over the old BBP and the return address
161 // XXX this is a very fragile setup as changes in alignment for the caller reg array can cause the magic number be 3
162 for (i
= 0; i
< nsrc
; ++i
)
163 src
[i
] = map_stack_slot (sslot
+ i
+ (is_source_argument
? 2 : 0));
165 case ArgInDoubleSSEReg
:
166 case ArgInFloatSSEReg
:
168 src
= g_malloc (nsrc
* sizeof (int));
169 src
[0] = map_freg (sreg
);
171 case ArgValuetypeAddrInIReg
:
173 src
= g_malloc (nsrc
* sizeof (int));
174 src
[0] = map_reg (ainfo
->pair_regs
[0]);
176 case ArgValuetypeAddrOnStack
:
178 src
= g_malloc (nsrc
* sizeof (int));
179 // is_source_argument adds 2 because we're skipping over the old BBP and the return address
180 // XXX this is a very fragile setup as changes in alignment for the caller reg array can cause the magic number be 3
181 src
[0] = map_stack_slot (sslot
+ (is_source_argument
? 2 : 0));
192 // Once src is known, operate on the dst
194 handle_marshal_when_src_gsharedvt (ArgInfo
*dst_info
, int *arg_marshal
, int *arg_slots
)
196 switch (dst_info
->storage
) {
198 case ArgInDoubleSSEReg
:
199 case ArgInFloatSSEReg
:
200 *arg_marshal
= GSHAREDVT_ARG_BYREF_TO_BYVAL
;
204 *arg_marshal
= GSHAREDVT_ARG_BYREF_TO_BYVAL
;
205 g_assert (dst_info
->arg_size
% SLOT_BYTE_SIZE
== 0); // Assert quadword aligned
206 *arg_slots
= dst_info
->arg_size
/ SLOT_BYTE_SIZE
;
208 case ArgValuetypeInReg
:
209 *arg_marshal
= GSHAREDVT_ARG_BYREF_TO_BYVAL
;
210 *arg_slots
= dst_info
->nregs
;
212 case ArgValuetypeAddrInIReg
:
213 case ArgValuetypeAddrOnStack
:
214 *arg_marshal
= GSHAREDVT_ARG_NONE
;
215 *arg_slots
= dst_info
->nregs
;
218 NOT_IMPLEMENTED
; // Inappropriate value: if dst and src are gsharedvt at once, we shouldn't be here
223 // Once dst is known, operate on the src
225 handle_marshal_when_dst_gsharedvt (ArgInfo
*src_info
, int *arg_marshal
)
227 switch (src_info
->storage
) {
229 case ArgInDoubleSSEReg
:
230 case ArgInFloatSSEReg
:
231 case ArgValuetypeInReg
:
233 *arg_marshal
= GSHAREDVT_ARG_BYVAL_TO_BYREF
;
235 case ArgValuetypeAddrInIReg
:
236 case ArgValuetypeAddrOnStack
:
237 *arg_marshal
= GSHAREDVT_ARG_NONE
;
240 NOT_IMPLEMENTED
; // See above
246 handle_map_when_gsharedvt_in_reg (ArgInfo
*reg_info
, int *n
, int **map
)
249 *map
= g_new0 (int, 1);
250 (*map
) [0] = map_reg (reg_info
->reg
);
254 handle_map_when_gsharedvt_on_stack (ArgInfo
*reg_info
, int *n
, int **map
, gboolean is_source_argument
)
257 *map
= g_new0 (int, 1);
258 int sslot
= reg_info
->offset
/ SLOT_BYTE_SIZE
;
259 (*map
) [0] = map_stack_slot (sslot
+ (is_source_argument
? 2 : 0)); // see get_arg_slots
263 mono_arch_get_gsharedvt_call_info (gpointer addr
, MonoMethodSignature
*normal_sig
, MonoMethodSignature
*gsharedvt_sig
, gboolean gsharedvt_in
, gint32 vcall_offset
, gboolean calli
)
265 GSharedVtCallInfo
*info
;
266 CallInfo
*caller_cinfo
, *callee_cinfo
;
267 MonoMethodSignature
*caller_sig
, *callee_sig
;
269 gboolean var_ret
= FALSE
;
270 CallInfo
*cinfo
, *gcinfo
;
271 MonoMethodSignature
*sig
, *gsig
;
275 caller_sig
= normal_sig
;
276 callee_sig
= gsharedvt_sig
;
277 caller_cinfo
= mono_arch_get_call_info (NULL
, caller_sig
);
278 callee_cinfo
= mono_arch_get_call_info (NULL
, callee_sig
);
280 callee_sig
= normal_sig
;
281 caller_sig
= gsharedvt_sig
;
282 callee_cinfo
= mono_arch_get_call_info (NULL
, callee_sig
);
283 caller_cinfo
= mono_arch_get_call_info (NULL
, caller_sig
);
287 * If GSHAREDVT_IN is true, this means we are transitioning from normal to gsharedvt code. The caller uses the
288 * normal call signature, while the callee uses the gsharedvt signature.
289 * If GSHAREDVT_IN is false, its the other way around.
292 /* sig/cinfo describes the normal call, while gsig/gcinfo describes the gsharedvt call */
296 cinfo
= caller_cinfo
;
297 gcinfo
= callee_cinfo
;
301 cinfo
= callee_cinfo
;
302 gcinfo
= caller_cinfo
;
305 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
306 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
)));
308 if (gcinfo
->ret
.storage
== ArgGsharedvtVariableInReg
) {
310 * The return type is gsharedvt
316 * The stack looks like this:
320 * We have to map the stack slots in <arguments> to the stack slots in <call area>.
322 map
= g_ptr_array_new ();
324 for (aindex
= 0; aindex
< cinfo
->nargs
; ++aindex
) {
325 ArgInfo
*src_info
= &caller_cinfo
->args
[aindex
];
326 ArgInfo
*dst_info
= &callee_cinfo
->args
[aindex
];
327 int *src
= NULL
, *dst
= NULL
;
328 int nsrc
= -1, ndst
= -1, nslots
= 0;
330 int arg_marshal
= GSHAREDVT_ARG_NONE
;
331 int arg_slots
= 0; // Size in quadwords
332 DEBUG_AMD64_GSHAREDVT_PRINT ("-- arg %d in (%s) out (%s)\n", aindex
, arg_info_desc (src_info
), arg_info_desc (dst_info
));
334 switch (src_info
->storage
) {
336 case ArgInDoubleSSEReg
:
337 case ArgInFloatSSEReg
:
338 case ArgValuetypeInReg
:
340 nsrc
= get_arg_slots (src_info
, &src
, TRUE
);
342 case ArgGSharedVtInReg
:
343 handle_marshal_when_src_gsharedvt (dst_info
, &arg_marshal
, &arg_slots
);
344 handle_map_when_gsharedvt_in_reg (src_info
, &nsrc
, &src
);
346 case ArgGSharedVtOnStack
:
347 handle_marshal_when_src_gsharedvt (dst_info
, &arg_marshal
, &arg_slots
);
348 handle_map_when_gsharedvt_on_stack (src_info
, &nsrc
, &src
, TRUE
);
350 case ArgValuetypeAddrInIReg
:
351 case ArgValuetypeAddrOnStack
:
352 nsrc
= get_arg_slots (src_info
, &src
, TRUE
);
355 g_error ("Gsharedvt can't handle source arg type %d", (int)src_info
->storage
); // Inappropriate value: ArgValuetypeAddrInIReg is for returns only
358 switch (dst_info
->storage
) {
360 case ArgInDoubleSSEReg
:
361 case ArgInFloatSSEReg
:
363 case ArgValuetypeInReg
:
364 ndst
= get_arg_slots (dst_info
, &dst
, FALSE
);
366 case ArgGSharedVtInReg
:
367 handle_marshal_when_dst_gsharedvt (src_info
, &arg_marshal
);
368 handle_map_when_gsharedvt_in_reg (dst_info
, &ndst
, &dst
);
370 case ArgGSharedVtOnStack
:
371 handle_marshal_when_dst_gsharedvt (src_info
, &arg_marshal
);
372 handle_map_when_gsharedvt_on_stack (dst_info
, &ndst
, &dst
, FALSE
);
374 case ArgValuetypeAddrInIReg
:
375 case ArgValuetypeAddrOnStack
:
376 ndst
= get_arg_slots (dst_info
, &dst
, FALSE
);
379 g_error ("Gsharedvt can't handle dest arg type %d", (int)dst_info
->storage
); // See above
382 src
[0] |= (arg_marshal
<< SRC_DESCRIPTOR_MARSHAL_SHIFT
) | (arg_slots
<< SLOT_COUNT_SHIFT
);
384 /* Merge and add to the global list*/
385 nslots
= MIN (nsrc
, ndst
);
386 DEBUG_AMD64_GSHAREDVT_PRINT ("nsrc %d ndst %d\n", nsrc
, ndst
);
388 for (i
= 0; i
< nslots
; ++i
)
389 add_to_map (map
, src
[i
], dst
[i
]);
395 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
);
397 if (cinfo
->ret
.storage
== ArgValuetypeAddrInIReg
) {
398 /* Both the caller and the callee pass the vtype ret address in r8 (System V) and RCX or RDX (Windows) */
399 g_assert (gcinfo
->ret
.storage
== ArgValuetypeAddrInIReg
|| gcinfo
->ret
.storage
== ArgGsharedvtVariableInReg
);
400 add_to_map (map
, map_reg (cinfo
->ret
.reg
), map_reg (cinfo
->ret
.reg
));
403 info
= mono_domain_alloc0 (mono_domain_get (), sizeof (GSharedVtCallInfo
) + (map
->len
* sizeof (int)));
405 info
->stack_usage
= callee_cinfo
->stack_usage
;
406 info
->ret_marshal
= GSHAREDVT_RET_NONE
;
407 info
->gsharedvt_in
= gsharedvt_in
? 1 : 0;
408 info
->vret_slot
= -1;
412 g_assert (gcinfo
->ret
.storage
== ArgGsharedvtVariableInReg
);
413 info
->vret_arg_reg
= map_reg (gcinfo
->ret
.reg
);
414 DEBUG_AMD64_GSHAREDVT_PRINT ("mapping vreg_arg_reg to %d in reg %s\n", info
->vret_arg_reg
, mono_arch_regname (gcinfo
->ret
.reg
));
416 info
->vret_arg_reg
= -1;
419 #ifdef DEBUG_AMD64_GSHAREDVT
420 printf ("final map:\n");
421 for (i
= 0; i
< map
->len
; i
+= 2) {
422 printf ("\t[%d] src %x dst %x\n ",
424 GPOINTER_TO_UINT (g_ptr_array_index (map
, i
)),
425 GPOINTER_TO_UINT (g_ptr_array_index (map
, i
+ 1)));
429 info
->vcall_offset
= vcall_offset
;
430 info
->map_count
= map
->len
/ 2;
431 for (i
= 0; i
< map
->len
; ++i
)
432 info
->map
[i
] = GPOINTER_TO_UINT (g_ptr_array_index (map
, i
));
433 g_ptr_array_free (map
, TRUE
);
435 /* Compute return value marshalling */
437 /* Compute return value marshalling */
438 switch (cinfo
->ret
.storage
) {
440 if (!gsharedvt_in
|| sig
->ret
->byref
) {
441 info
->ret_marshal
= GSHAREDVT_RET_IREGS_1
;
443 MonoType
*ret
= sig
->ret
;
446 if (ret
->type
== MONO_TYPE_VALUETYPE
)
447 ret
= mini_type_get_underlying_type (ret
);
451 info
->ret_marshal
= GSHAREDVT_RET_I1
;
453 case MONO_TYPE_BOOLEAN
:
455 info
->ret_marshal
= GSHAREDVT_RET_U1
;
458 info
->ret_marshal
= GSHAREDVT_RET_I2
;
462 info
->ret_marshal
= GSHAREDVT_RET_U2
;
465 info
->ret_marshal
= GSHAREDVT_RET_I4
;
468 info
->ret_marshal
= GSHAREDVT_RET_U4
;
473 case MONO_TYPE_FNPTR
:
474 case MONO_TYPE_CLASS
:
475 case MONO_TYPE_OBJECT
:
476 case MONO_TYPE_SZARRAY
:
477 case MONO_TYPE_ARRAY
:
478 case MONO_TYPE_STRING
:
481 info
->ret_marshal
= GSHAREDVT_RET_I8
;
483 case MONO_TYPE_GENERICINST
:
484 g_assert (!mono_type_generic_inst_is_valuetype (ret
));
485 info
->ret_marshal
= GSHAREDVT_RET_I8
;
488 g_error ("Gsharedvt can't handle dst type [%d]", (int)sig
->ret
->type
);
492 case ArgValuetypeInReg
:
493 info
->ret_marshal
= GSHAREDVT_RET_IREGS_1
- 1 + cinfo
->ret
.nregs
;
494 g_assert (cinfo
->ret
.nregs
== 1); // ABI supports 2-register return but we do not implement this.
496 case ArgInDoubleSSEReg
:
497 case ArgInFloatSSEReg
:
498 info
->ret_marshal
= GSHAREDVT_RET_R8
;
500 case ArgValuetypeAddrInIReg
:
503 g_error ("Can't marshal return of storage [%d] %s", (int)cinfo
->ret
.storage
, storage_name (cinfo
->ret
.storage
));
506 if (gsharedvt_in
&& cinfo
->ret
.storage
!= ArgValuetypeAddrInIReg
) {
507 /* Allocate stack space for the return value */
508 info
->vret_slot
= map_stack_slot (info
->stack_usage
/ sizeof (gpointer
));
509 info
->stack_usage
+= mono_type_stack_size_internal (normal_sig
->ret
, NULL
, FALSE
) + sizeof (gpointer
);
511 DEBUG_AMD64_GSHAREDVT_PRINT ("RET marshal is %s\n", ret_marshal_name
[info
->ret_marshal
]);
514 info
->stack_usage
= ALIGN_TO (info
->stack_usage
, MONO_ARCH_FRAME_ALIGNMENT
);
516 g_free (callee_cinfo
);
517 g_free (caller_cinfo
);
519 DEBUG_AMD64_GSHAREDVT_PRINT ("allocated an info at %p stack usage %d\n", info
, info
->stack_usage
);