[hybrid] Enable gsharedvt trampoline on amd64
[mono.git] / mono / mini / tramp-amd64-gsharedvt.c
1 /*
2  * tramp-amd64-gsharedvt.c: libcorkscrew-based native unwinder
3  *
4  * Authors:
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>
9  *
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.
12  */
13 #include <config.h>
14 #include <glib.h>
15
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>
24
25 #include <mono/utils/memcheck.h>
26
27 #include "mini.h"
28 #include "mini-amd64.h"
29 #include "mini-amd64-gsharedvt.h"
30 #include "debugger-agent.h"
31
32 #if defined (MONO_ARCH_GSHAREDVT_SUPPORTED)
33
34 #define ALIGN_TO(val,align) ((((guint64)val) + ((align) - 1)) & ~((align) - 1))
35
36 #define SRC_REG_SHIFT 0
37 #define SRC_REG_MASK 0xFFFF
38
39 #define SRC_DESCRIPTOR_MARSHAL_SHIFT 16
40 #define SRC_DESCRIPTOR_MARSHAL_MASK 0x0FF
41
42 #define SLOT_COUNT_SHIFT 24
43 #define SLOT_COUNT_MASK 0xFF
44
45 gpointer
46 mono_amd64_start_gsharedvt_call (GSharedVtCallInfo *info, gpointer *caller, gpointer *callee, gpointer mrgctx_reg)
47 {
48         int i;
49
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);
52
53         for (i = 0; i < PARAM_REGS; ++i)
54                 printf ("\treg [%d] -> %p\n", i, caller [i]);
55 #endif
56
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];
62         }
63
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;
68
69                 int source_reg = src & SRC_REG_MASK;
70                 int dest_reg = dst & SRC_REG_MASK;
71
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]);
77                         break;
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]);
82                         break;
83                 case GSHAREDVT_ARG_BYREF_TO_BYVAL: {
84                         int slot_count = (src >> SLOT_COUNT_SHIFT) & SLOT_COUNT_MASK;
85                         int j;
86                         gpointer *addr = caller [source_reg];
87
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]);
91                         break;
92                 }
93                 default:
94                         g_error ("cant handle arg marshal %d\n", arg_marshal);
95                 }
96         }
97
98         //Can't handle for now
99         if (info->vcall_offset != -1){
100                 MonoObject *this_obj = caller [0];
101
102                 DEBUG_AMD64_GSHAREDVT_PRINT ("target is a vcall at offset %d\n", info->vcall_offset / 8);
103                 if (G_UNLIKELY (!this_obj))
104                         return NULL;
105                 if (info->vcall_offset == MONO_GSHAREDVT_DEL_INVOKE_VT_OFFSET)
106                         /* delegate invoke */
107                         return ((MonoDelegate*)this_obj)->invoke_impl;
108                 else
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 */
112                 return mrgctx_reg;
113         } else {
114                 DEBUG_AMD64_GSHAREDVT_PRINT ("target is %p\n", info->addr);
115                 return info->addr;
116         }
117 }
118
119 #ifndef DISABLE_JIT
120
121 // Compiler support
122
123 /*
124  * mono_arch_get_gsharedvt_arg_trampoline:
125  *
126  *   See tramp-x86.c for documentation.
127  */
128 gpointer
129 mono_arch_get_gsharedvt_arg_trampoline (MonoDomain *domain, gpointer arg, gpointer addr)
130 {
131         guint8 *code, *start;
132         int buf_len;
133
134         buf_len = 32;
135
136         start = code = mono_domain_code_reserve (domain, buf_len);
137
138         amd64_mov_reg_imm (code, AMD64_RAX, arg);
139         amd64_jump_code (code, addr);
140         g_assert ((code - start) < buf_len);
141
142         mono_arch_flush_icache (start, code - start);
143         mono_profiler_code_buffer_new (start, code - start, MONO_PROFILER_CODE_BUFFER_GENERICS_TRAMPOLINE, NULL);
144
145         mono_tramp_info_register (mono_tramp_info_create (NULL, start, code - start, NULL, NULL), domain);
146
147         return start;
148 }
149
150 gpointer
151 mono_arch_get_gsharedvt_trampoline (MonoTrampInfo **info, gboolean aot)
152 {
153         guint8 *code, *buf;
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];
161         int b_ret_index;
162         int reg_area_size;
163
164         buf_len = 2048;
165         buf = code = mono_global_codeman_reserve (buf_len);
166
167         /*
168          * We are being called by an gsharedvt arg trampoline, the info argument is in AMD64_RAX.
169          */
170         n_arg_regs = PARAM_REGS;
171         n_arg_fregs = FLOAT_PARAM_REGS;
172
173         /* Compute stack frame size and offsets */
174         offset = 0;
175         /* info reg */
176         info_offset = offset;
177         offset += 8;
178
179         /* rgctx reg */
180         rgctx_arg_reg_offset = offset;
181         offset += 8;
182
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;
187
188         framesize = offset;
189
190         g_assert (framesize % MONO_ARCH_FRAME_ALIGNMENT == 0);
191         g_assert (reg_area_size % MONO_ARCH_FRAME_ALIGNMENT == 0);
192
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);
197
198         /* save the old frame pointer */
199         amd64_push_reg (code, AMD64_RBP);
200
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);
205
206         /* set it as the new frame pointer */
207         amd64_mov_reg_reg (code, AMD64_RBP, AMD64_RSP, sizeof(mgreg_t));
208
209         /* unwind markers 3/3 */
210         mono_add_unwind_op_def_cfa_reg (unwind_ops, code, buf, AMD64_RBP);
211
212         /* setup the frame */
213         amd64_alu_reg_imm (code, X86_SUB, AMD64_RSP, framesize);
214         
215         /* save stuff */
216
217         /* save info */
218         amd64_mov_membase_reg (code, AMD64_RSP, info_offset, AMD64_RAX, sizeof (mgreg_t));
219         /* save rgctx */
220         amd64_mov_membase_reg (code, AMD64_RSP, rgctx_arg_reg_offset, MONO_ARCH_RGCTX_REG, sizeof (mgreg_t));
221
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));
224
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);
227
228         /* TODO Allocate stack area used to pass arguments to the method */
229
230
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);
236
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);
240
241         /* The stack now looks like this:
242
243         <caller stack params area>
244         <return address>
245         <old frame pointer>
246         <caller registers area>
247         <rgctx>
248         <gsharedvt info>
249         <callee stack area>
250         <callee reg area>
251          */
252
253         /* Call start_gsharedvt_call () */
254         /* arg1 == info */
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)); 
258
259         /* arg3 == callee stack area */
260         amd64_lea_membase (code, MONO_AMD64_ARG_REG3, AMD64_RSP, callee_reg_area_offset);
261
262         /* arg4 = mrgctx reg */
263         amd64_mov_reg_reg (code, MONO_AMD64_ARG_REG4, MONO_ARCH_RGCTX_REG, sizeof(mgreg_t));
264
265         if (aot) {
266                 code = mono_arch_emit_load_aotconst (buf, code, &ji, MONO_PATCH_INFO_JIT_ICALL_ADDR, "mono_amd64_start_gsharedvt_call");
267                 #ifdef TARGET_WIN32
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.
274                         */
275                         int shadow_reg_size = 0;
276
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);
281                 #else
282                         amd64_call_reg (code, AMD64_R11);
283                 #endif
284         } else {
285                 amd64_call_code (code, mono_amd64_start_gsharedvt_call);
286         }
287
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));
290
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));
293
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);
296
297         //load rgctx
298         amd64_mov_reg_membase (code, MONO_ARCH_RGCTX_REG, AMD64_RBP, -(framesize - rgctx_arg_reg_offset), sizeof (mgreg_t));
299
300         /* Clear callee reg area */
301         amd64_alu_reg_imm (code, X86_ADD, AMD64_RSP, reg_area_size);
302
303         /* Call the thing */
304         amd64_call_reg (code, AMD64_R11);
305
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));
309
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);
312
313         b_ret_index = 0;
314         br_out = code;
315         x86_branch32 (code, X86_CC_NE, 0, TRUE);
316
317         /*
318          * IN CASE
319          */
320
321         /* Load vret_slot */
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);
326
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);
330
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);
334
335         for (i = GSHAREDVT_RET_NONE; i < GSHAREDVT_RET_NUM; ++i) {
336                 amd64_alu_reg_imm (code, X86_CMP, AMD64_R10, i);
337                 br [i] = code;
338                 amd64_branch8 (code, X86_CC_EQ, 0, TRUE);
339         }
340         x86_breakpoint (code); /* unhandled case */
341
342         for (i = GSHAREDVT_RET_NONE; i < GSHAREDVT_RET_NUM; ++i) {
343                 mono_amd64_patch (br [i], code);
344                 switch (i) {
345                 case GSHAREDVT_RET_NONE:
346                         break;
347                 case GSHAREDVT_RET_I1:
348                         amd64_widen_membase (code, AMD64_RAX, AMD64_R11, 0, TRUE, FALSE);
349                         break;
350                 case GSHAREDVT_RET_U1:
351                         amd64_widen_membase (code, AMD64_RAX, AMD64_R11, 0, FALSE, FALSE);
352                         break;
353                 case GSHAREDVT_RET_I2:
354                         amd64_widen_membase (code, AMD64_RAX, AMD64_R11, 0, TRUE, TRUE);
355                         break;
356                 case GSHAREDVT_RET_U2:
357                         amd64_widen_membase (code, AMD64_RAX, AMD64_R11, 0, FALSE, TRUE);
358                         break;
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);
362                         break;
363                 case GSHAREDVT_RET_I8:
364                         amd64_mov_reg_membase (code, AMD64_RAX, AMD64_R11, 0, 8);
365                         break;
366                 case GSHAREDVT_RET_IREGS_1:
367                         amd64_mov_reg_membase (code, return_regs [i - GSHAREDVT_RET_IREGS_1], AMD64_R11, 0, 8);
368                         break;
369                 case GSHAREDVT_RET_R8:
370                         amd64_sse_movsd_reg_membase (code, AMD64_XMM0, AMD64_R11, 0);
371                         break;
372                 default:
373                         x86_breakpoint (code); /* can't handle specific case */
374                 }
375
376                 br_ret [b_ret_index ++] = code;
377                 x86_jump32 (code, 0);
378         }
379
380         /*
381          * OUT CASE
382          */
383         mono_amd64_patch (br_out, code);
384
385         /*
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.
388                 Its location:
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.
391
392                 address: RBP - framesize + caller_reg_area_offset + 8*slot
393         */
394
395         int caller_vret_offset = caller_reg_area_offset - framesize;
396
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));
400
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);
405
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));
411
412         /* Load ret marshal type in R10 */
413         amd64_mov_reg_membase (code, AMD64_R10, AMD64_R10, MONO_STRUCT_OFFSET (GSharedVtCallInfo, ret_marshal), 4);
414
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);
418                 br [i] = code;
419                 amd64_branch8 (code, X86_CC_EQ, 0, TRUE);
420         }
421         x86_breakpoint (code); /* unhandled case */
422
423         for (i = GSHAREDVT_RET_NONE; i < GSHAREDVT_RET_NUM; ++i) {
424                 mono_amd64_patch (br [i], code);
425                 switch (i) {
426                 case GSHAREDVT_RET_NONE:
427                         break;
428                 case GSHAREDVT_RET_IREGS_1:
429                         amd64_mov_membase_reg (code, AMD64_R11, 0, return_regs [i - GSHAREDVT_RET_IREGS_1], 8);
430                         break;
431                 case GSHAREDVT_RET_R8:
432                         amd64_sse_movsd_membase_reg (code, AMD64_R11, 0, AMD64_XMM0);
433                         break;
434                 default:
435                         x86_breakpoint (code); /* can't handle specific case */
436                 }
437
438                 br_ret [b_ret_index ++] = code;
439                 x86_jump32 (code, 0);
440         }
441
442         /* exit path */
443         for (i = 0; i < b_ret_index; ++i)
444                 mono_amd64_patch (br_ret [i], code);
445
446         /* Exit code path */
447         amd64_leave (code);
448         amd64_ret (code);
449
450         g_assert ((code - buf) < buf_len);
451
452         if (info)
453                 *info = mono_tramp_info_create ("gsharedvt_trampoline", buf, code - buf, ji, unwind_ops);
454
455         mono_arch_flush_icache (buf, code - buf);
456         return buf;
457 }
458
459 #else
460
461 gpointer
462 mono_arch_get_gsharedvt_arg_trampoline (MonoDomain *domain, gpointer arg, gpointer addr)
463 {
464         g_assert_not_reached ();
465         return NULL;
466 }
467
468 gpointer
469 mono_arch_get_gsharedvt_trampoline (MonoTrampInfo **info, gboolean aot)
470 {
471         g_assert_not_reached ();
472         return NULL;
473 }
474
475 #endif
476
477 #else
478
479 gpointer
480 mono_amd64_start_gsharedvt_call (GSharedVtCallInfo *info, gpointer *caller, gpointer *callee, gpointer mrgctx_reg)
481 {
482         g_assert_not_reached ();
483         return NULL;
484 }
485
486 #endif