542 строки
16 KiB
Objective-C
542 строки
16 KiB
Objective-C
|
|
#if defined(__x86_64__)
|
|
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <objc/objc.h>
|
|
#include <objc/runtime.h>
|
|
#include <objc/message.h>
|
|
|
|
#include "trampolines-internal.h"
|
|
#include "xamarin/runtime.h"
|
|
#include "runtime-internal.h"
|
|
#include "trampolines-x86_64.h"
|
|
|
|
//#define TRACE
|
|
#ifdef TRACE
|
|
#define LOGZ(...) fprintf (stderr, __VA_ARGS__);
|
|
#else
|
|
#define LOGZ(...) ;
|
|
#endif
|
|
|
|
/*
|
|
* Standard x86_64 calling convention:
|
|
* Input:
|
|
* %rdi, %rsi, %rdx, %rcx, %r8, %r9
|
|
* %xmm0 - %xmm7
|
|
* Output:
|
|
* %rax
|
|
* %xmm0 - %xmm1
|
|
*
|
|
* Preserved registers: %rbp, %rbx, %r12-%r15
|
|
*
|
|
* Stack:
|
|
* 16(%rbp): stack-allocated parameters
|
|
* 8(%rbp): return address
|
|
* 0(%rbp): previous %rbp value
|
|
*/
|
|
|
|
static guint32
|
|
create_mt_exception (char *msg)
|
|
{
|
|
MonoException *ex = xamarin_create_exception (msg);
|
|
xamarin_free (msg);
|
|
return mono_gchandle_new ((MonoObject *) ex, FALSE);
|
|
}
|
|
|
|
static size_t
|
|
get_primitive_size (char type)
|
|
{
|
|
switch (type) {
|
|
case _C_ID: return sizeof (id);
|
|
case _C_CLASS: return sizeof (Class);
|
|
case _C_SEL: return sizeof (SEL);
|
|
case _C_CHR: return sizeof (char);
|
|
case _C_UCHR: return sizeof (unsigned char);
|
|
case _C_SHT: return sizeof (short);
|
|
case _C_USHT: return sizeof (unsigned short);
|
|
case _C_INT: return sizeof (int);
|
|
case _C_UINT: return sizeof (unsigned int);
|
|
case _C_LNG: return sizeof (long);
|
|
case _C_ULNG: return sizeof (unsigned long);
|
|
case _C_LNG_LNG: return sizeof (long long);
|
|
case _C_ULNG_LNG: return sizeof (unsigned long long);
|
|
case _C_FLT: return sizeof (float);
|
|
case _C_DBL: return sizeof (double);
|
|
case _C_BOOL: return sizeof (BOOL);
|
|
case _C_VOID: return 0;
|
|
case _C_PTR: return sizeof (void *);
|
|
case _C_CHARPTR: return sizeof (char *);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
#ifdef TRACE
|
|
static void
|
|
dump_state (struct CallState *state, id self, SEL sel)
|
|
{
|
|
fprintf (stderr, "type: %llu is_stret: %i self: %p SEL: %s rdi: 0x%llx rsi: 0x%llx rdx: 0x%llx rcx: 0x%llx r8: 0x%llx r9: 0x%llx rbp: 0x%llx -- xmm0: %Lf xmm1: %Lf xmm2: %Lf xmm3: %Lf xmm4: %Lf xmm5: %Lf xmm6: %Lf xmm7: %Lf\n",
|
|
state->type, (state->type & Tramp_Stret) == Tramp_Stret, self, sel_getName (sel), state->rdi, state->rsi, state->rdx, state->rcx, state->r8, state->r9, state->rbp,
|
|
state->xmm0, state->xmm1, state->xmm2, state->xmm3, state->xmm4, state->xmm5, state->xmm6, state->xmm7);
|
|
}
|
|
#else
|
|
#define dump_state(...)
|
|
#endif
|
|
|
|
const char* registers[] = { "rdi", "rsi", "rdx", "rcx", "r8", "r9", "err" };
|
|
|
|
static const char *
|
|
skip_type_name (const char *ptr)
|
|
{
|
|
const char *t = ptr;
|
|
do {
|
|
if (*t == '=') {
|
|
t++;
|
|
return t;
|
|
}
|
|
t++;
|
|
} while (*t != 0);
|
|
|
|
return ptr;
|
|
}
|
|
|
|
static int
|
|
param_read_primitive (struct ParamIterator *it, const char **type_ptr, void *target, size_t total_size, guint32 *exception_gchandle)
|
|
{
|
|
// COOP: does not access managed memory: any mode.
|
|
char type = **type_ptr;
|
|
|
|
switch (type) {
|
|
case _C_FLT: {
|
|
// there are 8 xmm registers, each 4 floats big.
|
|
// only 2 floats are ever put in each xmm register.
|
|
// this means the last location is 7 * 4 + 1 = 29
|
|
if (it->float_count <= 29) {
|
|
if (target != NULL) {
|
|
*(float*) target = *(it->float_count + (float*) &it->state->xmm0);
|
|
LOGZ (" reading float at xmm%i (%s half) into %p: %f\n", it->float_count / 4, it->float_count % 2 ? "second" : "first", target, *(float *) target);
|
|
}
|
|
it->float_count++;
|
|
if (it->float_count % 2 == 0)
|
|
it->float_count += 2; // only the first 64 bits of the 128-bit long xmm registers are used, so skip the last 64 bits.
|
|
} else {
|
|
if (target != NULL) {
|
|
*(float*) target = *(float*) it->stack_next;
|
|
LOGZ (" reading float at stack %p into %p: %f\n", it->stack_next, target, *(float*)target);
|
|
}
|
|
it->stack_next += 4;
|
|
}
|
|
return 4;
|
|
}
|
|
case _C_DBL: {
|
|
while (it->float_count % 4 != 0) {
|
|
it->float_count++;
|
|
LOGZ (" advancing float pointer to read double value from full register\n");
|
|
}
|
|
// there are 8 xmm registers, each 4 floats big.
|
|
// only 1 double is ever put in each xmm register.
|
|
// this means the last location is 7 * 4 = 28
|
|
if (it->float_count <= 28) {
|
|
if (target != NULL) {
|
|
*(double*) target = *(double*) (it->float_count + (float*) &it->state->xmm0);
|
|
LOGZ (" reading double at xmm%i = %p into %p: %f\n", it->float_count / 4, (double *) target, target, *(double *) target);
|
|
}
|
|
it->float_count += 4; // each xmm register is 128-bit = 4 floats
|
|
} else {
|
|
if (target != NULL) {
|
|
*(double*) target = *(double*) it->stack_next;
|
|
LOGZ (" reading double at stack %p into %p: %f\n", it->stack_next, target, *(double *) target);
|
|
}
|
|
it->stack_next += 8;
|
|
}
|
|
return 8;
|
|
}
|
|
case _C_PTR: { // ^
|
|
// Need to skip what's pointed to
|
|
int nesting = 0;
|
|
do {
|
|
(*type_ptr)++;
|
|
if (**type_ptr == '{')
|
|
nesting++;
|
|
else if (**type_ptr == '}')
|
|
nesting--;
|
|
} while (**type_ptr != 0 && nesting > 0);
|
|
// fallthrough
|
|
}
|
|
default: {
|
|
size_t size = get_primitive_size (type);
|
|
|
|
if (size == 0)
|
|
return 0;
|
|
|
|
uint8_t *ptr;
|
|
bool read_register = true;
|
|
if (it->byte_count >= 48) { // 48 == 6 registers * 8 bytes
|
|
read_register = false;
|
|
} else if (48 - it->byte_count < total_size) {
|
|
read_register = false;
|
|
LOGZ (" total size (%i) is less that available register size (%i)", (int) total_size, 48 - it->byte_count);
|
|
}
|
|
|
|
if (read_register) {
|
|
if (it->byte_count / 8 != (it->byte_count + size - 1) / 8) {
|
|
// align to next register if the one we're currently reading
|
|
// doesn't contain the entire value we need.
|
|
it->byte_count += 8 - it->byte_count % 8;
|
|
LOGZ (" skipping until next register #%i = %s, can't read primitive of size %i from current register\n", (it->byte_count / 8) + 1, registers [it->byte_count / 8], (int) size);
|
|
}
|
|
|
|
ptr = it->byte_count + (uint8_t *) &it->state->rdi;
|
|
if (target != NULL)
|
|
LOGZ (" reading primitive of size %i from register #%i = %s (byte count %i offset %i) at %p into %p; ", (int) size, (it->byte_count / 8) + 1, registers [it->byte_count / 8], it->byte_count, it->byte_count % 8, ptr, target);
|
|
it->byte_count += size;
|
|
} else {
|
|
ptr = (uint8_t *) it->stack_next;
|
|
if (target != NULL)
|
|
LOGZ (" reading primitive of size %i from stack (byte count %i offset %i) at %p into %p: ", (int) size, it->byte_count, it->byte_count % 8, ptr, target);
|
|
it->stack_next += size;
|
|
}
|
|
|
|
if (target == NULL)
|
|
return size;
|
|
|
|
switch (size) {
|
|
case 8:
|
|
*(uint64_t *) target = *(uint64_t *) ptr;
|
|
LOGZ ("0x%llx = %llu\n", * (uint64_t *) target, * (uint64_t *) target);
|
|
break;
|
|
case 4:
|
|
*(uint32_t *) target = *(uint32_t *) ptr;
|
|
LOGZ ("0x%x = %u\n", * (uint32_t *) target, * (uint32_t *) target);
|
|
break;
|
|
case 2:
|
|
*(uint16_t *) target = *(uint16_t *) ptr;
|
|
LOGZ ("0x%x = %u\n", (int) * (uint32_t *) target, (int) * (uint32_t *) target);
|
|
break;
|
|
case 1:
|
|
*(uint8_t *) target = *(uint8_t *) ptr;
|
|
LOGZ ("0x%x = %u = '%c'\n", (int) * (uint8_t *) target, (int) * (uint8_t *) target, (char) * (uint8_t *) target);
|
|
break;
|
|
default:
|
|
*exception_gchandle = create_mt_exception (xamarin_strdup_printf ("Xamarin.iOS: Cannot marshal parameter type %c (size: %i): invalid size.\n", type, (int) size));
|
|
return 0;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
param_iter_next (enum IteratorAction action, void *context, const char *type, size_t size, void *target, guint32 *exception_gchandle)
|
|
{
|
|
// COOP: does not access managed memory: any mode.
|
|
struct ParamIterator *it = (struct ParamIterator *) context;
|
|
|
|
if (action == IteratorStart) {
|
|
it->byte_count = (it->state->type & Tramp_Stret) == Tramp_Stret ? 24 : 16;
|
|
it->float_count = 0;
|
|
it->stack_next = (uint8_t *) (2 + (uint64_t *) it->state->rbp);
|
|
LOGZ("initialized parameter iterator to %p stret to %p\n", it->stack_next, it->stret);
|
|
return;
|
|
} else if (action == IteratorEnd) {
|
|
return;
|
|
}
|
|
|
|
const char *t = type;
|
|
uint8_t *targ = (uint8_t *) target;
|
|
|
|
// target must be at least pointer sized, and we need to zero it out first.
|
|
if (target != NULL)
|
|
*(uint64_t *) target = 0;
|
|
|
|
if (size > 16) {
|
|
// passed on the stack
|
|
if (target != NULL)
|
|
memcpy (target, it->stack_next, size);
|
|
// increment stack pointer
|
|
it->stack_next += size;
|
|
// and round up to 8 bytes.
|
|
if (size % 8 != 0)
|
|
it->stack_next += 8 - size % 8;
|
|
return;
|
|
}
|
|
|
|
// passed in registers (and on the stack if not enough registers)
|
|
do {
|
|
// skip over any struct names
|
|
if (*t == '{') {
|
|
do {
|
|
t++;
|
|
if (*t == '=') {
|
|
t++;
|
|
break;
|
|
}
|
|
} while (*t != 0);
|
|
}
|
|
|
|
if (*t == 0)
|
|
break;
|
|
|
|
int c = param_read_primitive (it, &t, targ, size, exception_gchandle);
|
|
if (*exception_gchandle != 0)
|
|
return;
|
|
if (targ != NULL)
|
|
targ += c;
|
|
} while (*++t);
|
|
|
|
// Each argument uses at least one full register, so round up.
|
|
while (it->float_count % 4 != 0)
|
|
it->float_count++;
|
|
if (it->byte_count % 8 != 0)
|
|
it->byte_count += 8 - it->byte_count % 8;
|
|
// CHECK: do we need to round the stack pointer to?
|
|
}
|
|
|
|
static void
|
|
marshal_return_value (void *context, const char *type, size_t size, void *vvalue, MonoType *mtype, bool retain, MonoMethod *method, guint32 *exception_gchandle)
|
|
{
|
|
// COOP: accessing managed memory (as input), so must be in unsafe mode.
|
|
MONO_ASSERT_GC_UNSAFE;
|
|
|
|
MonoObject *value = (MonoObject *) vvalue;
|
|
struct ParamIterator *it = (struct ParamIterator *) context;
|
|
|
|
LOGZ (" marshalling return value %p as %s\n", value, type);
|
|
|
|
it->state->xmm0 = 0;
|
|
it->state->xmm1 = 0;
|
|
|
|
switch (type [0]) {
|
|
case _C_FLT:
|
|
// single floating point return value
|
|
*(float*)&it->state->xmm0 = *(float *) mono_object_unbox (value);
|
|
break;
|
|
case _C_DBL:
|
|
// double floating point return value
|
|
*(double*)&it->state->xmm0 = *(double *) mono_object_unbox (value);
|
|
break;
|
|
case _C_STRUCT_B:
|
|
/*
|
|
* Structures, this is ugly :|
|
|
*
|
|
* Fortunately we don't have to implement support for the full x86_64 ABI, since we don't need
|
|
* to support all the types. We only have to implement support for two classes of types:
|
|
*
|
|
* INTEGER: all variants of ints/uints/pointers. IOW anything that fits into a pointer-sized variable and isn't a floating point value.
|
|
* FLOAT: float, double.
|
|
*
|
|
* To make things more interesting, struct fields are joined together until the reach 64-bit size,
|
|
* so for instance two int fields will be stuffed into one 64-bit INTEGER register. Same for floats,
|
|
* two floats will be put into one 64-bit FLOAT register. If there's a mix of floats
|
|
* and ints the ints win, so a float+int will be put into a 64-bit INTEGER register.
|
|
* There are also two registers available for each class:
|
|
*
|
|
* INTEGER: %rax and %rdi
|
|
* FLOAT: %xmm0 and %xmm1
|
|
*
|
|
* Up to 2 registers (either both INTEGER, both FLOAT or a mix) can be used. This means that
|
|
* structs up to 16 bytes can be (and are) passed in registers.
|
|
*
|
|
* A few examples (d=double f=float i=int c=char):
|
|
*
|
|
* M(d); // xmm0
|
|
* M(dd); // xmm0 + xmm1
|
|
* M(ddd); // stack
|
|
* M(dddd); // stack
|
|
* M(i); // eax
|
|
* M(id); // eax + xmm0
|
|
* M(di); // xmm0 + eax
|
|
* M(ddi); // stack
|
|
* M(ii); // rax
|
|
* M(iii); // rax + edx
|
|
* M(iiii); // rax + rdx
|
|
* M(iiiii); // stack
|
|
* M(idi); // stack
|
|
* M(iid); // rax + xmm0
|
|
* M(ll); // rax + rdx
|
|
* M(lll); // stack
|
|
* M(cccc); // eax
|
|
* M(ffff); // xmm0 + xmm1
|
|
* M(if_); // rax
|
|
* M(f); // xmm0
|
|
* M(iff); // rax + xmm0 (if: rax, f: xmm0)
|
|
* M(iiff); // rax + xmm0
|
|
* M(fi); // rax
|
|
*
|
|
*/
|
|
|
|
if ((it->state->type & Tramp_Stret) == Tramp_Stret) {
|
|
memcpy ((void *) it->state->rdi, mono_object_unbox (value), size);
|
|
break;
|
|
}
|
|
|
|
if (size > 8 && size <= 16) {
|
|
uint64_t *i_ptr = &it->state->rax;
|
|
uint64_t *f_ptr = (uint64_t *) &it->state->xmm0;
|
|
uint64_t *reg_ptr = f_ptr;
|
|
|
|
void *unboxed = mono_object_unbox (value);
|
|
|
|
// read the struct into 2 64bit values.
|
|
uint64_t v[2];
|
|
v[0] = *(uint64_t *) unboxed;
|
|
// read as much as we can of the second value
|
|
unboxed = 1 + (uint64_t *) unboxed;
|
|
if (size == 16) {
|
|
v[1] = *(uint64_t *) unboxed;
|
|
} else if (size == 12) {
|
|
v[1] = *(uint32_t *) unboxed;
|
|
} else if (size == 10) {
|
|
v[1] = *(uint16_t *) unboxed;
|
|
} else if (size == 9) {
|
|
v[1] = *(uint8_t *) unboxed;
|
|
}
|
|
// figure out where to put the values.
|
|
const char *t = skip_type_name (type);
|
|
int acc = 0;
|
|
int stores = 0;
|
|
|
|
while (true) {
|
|
if (*t == 0) {
|
|
if (stores >= 2 && acc > 0) {
|
|
*exception_gchandle = create_mt_exception (xamarin_strdup_printf ("Xamarin.iOS: Cannot marshal return type %s (size: %i): more than 2 64-bit values found.\n", type, (int) size));
|
|
return;
|
|
} else if (stores < 2) {
|
|
*reg_ptr = v [stores];
|
|
}
|
|
break;
|
|
}
|
|
|
|
bool is_float = *t == _C_FLT || *t == _C_DBL;
|
|
int s = get_primitive_size (*t);
|
|
|
|
t++;
|
|
|
|
if (s == 0)
|
|
continue;
|
|
|
|
if (acc + s == 8) {
|
|
// We have exactly the amount of data we need for one register.
|
|
// Store the value and start over again.
|
|
reg_ptr = is_float ? reg_ptr : i_ptr;
|
|
acc = 0;
|
|
} else if (acc + s < 8) {
|
|
// We haven't filled up a register yet.
|
|
// Continue iterating.
|
|
reg_ptr = is_float ? reg_ptr : i_ptr;
|
|
acc += s;
|
|
// find next.
|
|
continue;
|
|
} else {
|
|
// We've overflown. Store the value and start over again,
|
|
// setting the current total to the size of the current type.
|
|
acc = s;
|
|
}
|
|
|
|
if (stores >= 2) {
|
|
*exception_gchandle = create_mt_exception (xamarin_strdup_printf ("Xamarin.iOS: Cannot marshal return type %s (size: %i): more than 2 64-bit values found.\n", type, (int) size));
|
|
return;
|
|
}
|
|
|
|
// Write the current value to the correct register.
|
|
*reg_ptr = v [stores++];
|
|
if (reg_ptr == f_ptr) {
|
|
f_ptr += 2; // xmm0/xmm1 are 128-bit wide (long double).
|
|
} else {
|
|
i_ptr++;
|
|
}
|
|
|
|
if (acc == s) {
|
|
// Overflown codepath from above.
|
|
reg_ptr = is_float ? f_ptr : i_ptr;
|
|
} else {
|
|
reg_ptr = f_ptr;
|
|
}
|
|
|
|
};
|
|
} else if (size == 8) {
|
|
type = skip_type_name (type);
|
|
if (!strncmp (type, "ff}", 3) || !strncmp (type, "d}", 2)) {
|
|
// the only two fully fp combinations are: ff and d
|
|
memcpy (&it->state->xmm0, mono_object_unbox (value), 8);
|
|
} else {
|
|
// all other combinations would contain at least one INTEGER-class type.
|
|
it->state->rax = *(uint64_t *) mono_object_unbox (value);
|
|
}
|
|
} else if (size < 8) {
|
|
memcpy (&it->state->rax, mono_object_unbox (value), size);
|
|
} else {
|
|
*exception_gchandle = create_mt_exception (xamarin_strdup_printf ("Xamarin.iOS: Cannot marshal struct return type %s (size: %i)\n", type, (int) size));
|
|
return;
|
|
}
|
|
break;
|
|
// For primitive types we get a pointer to the actual value
|
|
case _C_BOOL: // signed char
|
|
case _C_CHR:
|
|
case _C_UCHR:
|
|
it->state->rax = *(uint8_t *) mono_object_unbox (value);
|
|
break;
|
|
case _C_SHT:
|
|
case _C_USHT:
|
|
it->state->rax = *(uint16_t *) mono_object_unbox (value);
|
|
break;
|
|
case _C_INT:
|
|
case _C_UINT:
|
|
it->state->rax = *(uint32_t *) mono_object_unbox (value);
|
|
break;
|
|
case _C_LNG:
|
|
case _C_ULNG:
|
|
case _C_LNG_LNG:
|
|
case _C_ULNG_LNG:
|
|
it->state->rax = *(uint64_t *) mono_object_unbox (value);
|
|
break;
|
|
|
|
// For pointer types we get the value itself.
|
|
case _C_CLASS:
|
|
case _C_SEL:
|
|
case _C_ID:
|
|
case _C_CHARPTR:
|
|
case _C_PTR:
|
|
if (value == NULL) {
|
|
it->state->rax = 0;
|
|
break;
|
|
}
|
|
|
|
it->state->rax = (uint64_t) xamarin_marshal_return_value (mtype, type, value, retain, method, exception_gchandle);
|
|
break;
|
|
case _C_VOID:
|
|
break;
|
|
case '|': // direct pointer value
|
|
default:
|
|
if (size == 8) {
|
|
it->state->rax = (uint64_t) value;
|
|
} else {
|
|
*exception_gchandle = create_mt_exception (xamarin_strdup_printf ("Xamarin.iOS: Cannot marshal return type %s (size: %i)\n", type, (int) size));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
xamarin_arch_trampoline (struct CallState *state)
|
|
{
|
|
// COOP: called from ObjC, and does not access managed memory.
|
|
MONO_ASSERT_GC_SAFE;
|
|
|
|
enum TrampolineType type = (enum TrampolineType) state->type;
|
|
bool is_stret = (type & Tramp_Stret) == Tramp_Stret;
|
|
id self = is_stret ? (id) state->rsi : (id) state->rdi;
|
|
SEL sel = is_stret ? (SEL) state->rdx : (SEL) state->rsi;
|
|
dump_state (state, self, sel);
|
|
struct ParamIterator iter;
|
|
iter.state = state;
|
|
xamarin_invoke_trampoline (type, self, sel, param_iter_next, marshal_return_value, &iter);
|
|
dump_state (state, self, sel);
|
|
}
|
|
|
|
#endif /* __x86_64__ */ |