2016-04-21 15:19:32 +03:00
# 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
* /
2016-06-02 13:18:45 +03:00
static guint32
create_mt _exception ( char * msg )
2016-04-21 15:19:32 +03:00
{
MonoException * ex = xamarin_create _exception ( msg ) ;
xamarin_free ( msg ) ;
2016-06-02 13:18:45 +03:00
return mono_gchandle _new ( ( MonoObject * ) ex , FALSE ) ;
2016-04-21 15:19:32 +03:00
}
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
2016-06-02 13:18:45 +03:00
param_read _primitive ( struct ParamIterator * it , const char * * type_ptr , void * target , size_t total_size , guint32 * exception_gchandle )
2016-04-21 15:19:32 +03:00
{
2016-02-15 21:02:14 +03:00
// COOP : does not access managed memory : any mode .
2016-04-21 15:19:32 +03:00
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 :
2016-06-02 13:18:45 +03:00
* exception_gchandle = create_mt _exception ( xamarin_strdup _printf ( "Xamarin.iOS: Cannot marshal parameter type %c (size: %i): invalid size.\n" , type , ( int ) size ) ) ;
2016-04-21 15:19:32 +03:00
return 0 ;
}
return size ;
}
}
}
static void
2016-06-02 13:18:45 +03:00
param_iter _next ( enum IteratorAction action , void * context , const char * type , size_t size , void * target , guint32 * exception_gchandle )
2016-04-21 15:19:32 +03:00
{
2016-02-15 21:02:14 +03:00
// COOP : does not access managed memory : any mode .
2016-04-21 15:19:32 +03:00
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 ;
2016-06-02 13:18:45 +03:00
int c = param_read _primitive ( it , & t , targ , size , exception_gchandle ) ;
if ( * exception_gchandle ! = 0 )
return ;
2016-04-21 15:19:32 +03:00
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
2016-06-02 13:18:45 +03:00
marshal_return _value ( void * context , const char * type , size_t size , void * vvalue , MonoType * mtype , bool retain , MonoMethod * method , guint32 * exception_gchandle )
2016-04-21 15:19:32 +03:00
{
2016-02-15 21:02:14 +03:00
// COOP : accessing managed memory ( as input ) , so must be in unsafe mode .
MONO_ASSERT _GC _UNSAFE ;
2016-04-21 15:19:32 +03:00
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
*
* /
2016-10-11 20:14:00 +03:00
if ( ( it -> state -> type & Tramp_Stret ) = = Tramp_Stret ) {
memcpy ( ( void * ) it -> state -> rdi , mono_object _unbox ( value ) , size ) ;
break ;
}
2016-04-21 15:19:32 +03:00
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 64 bit 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 ) {
2016-06-02 13:18:45 +03:00
* 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 ;
2016-04-21 15:19:32 +03:00
} 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 ;
}
2016-06-02 13:18:45 +03:00
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 ;
}
2016-04-21 15:19:32 +03:00
// 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 ) {
2016-10-11 20:15:01 +03:00
type = skip_type _name ( type ) ;
if ( ! strncmp ( type , "ff}" , 3 ) || ! strncmp ( type , "d}" , 2 ) ) {
2016-04-21 15:19:32 +03:00
// the only two fully fp combinations are : ff and d
2016-10-11 20:15:01 +03:00
memcpy ( & it -> state -> xmm0 , mono_object _unbox ( value ) , 8 ) ;
2016-04-21 15:19:32 +03:00
} else {
// all other combinations would contain at least one INTEGER - class type .
it -> state -> rax = * ( uint64_t * ) mono_object _unbox ( value ) ;
}
2016-10-11 20:15:18 +03:00
} else if ( size < 8 ) {
memcpy ( & it -> state -> rax , mono_object _unbox ( value ) , size ) ;
2016-04-21 15:19:32 +03:00
} else {
2016-06-02 13:18:45 +03:00
* exception_gchandle = create_mt _exception ( xamarin_strdup _printf ( "Xamarin.iOS: Cannot marshal struct return type %s (size: %i)\n" , type , ( int ) size ) ) ;
return ;
2016-04-21 15:19:32 +03:00
}
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 ;
}
2016-06-02 13:18:45 +03:00
it -> state -> rax = ( uint64_t ) xamarin_marshal _return _value ( mtype , type , value , retain , method , exception_gchandle ) ;
2016-04-21 15:19:32 +03:00
break ;
case _C _VOID :
break ;
case ' | ' : // direct pointer value
default :
if ( size = = 8 ) {
it -> state -> rax = ( uint64_t ) value ;
} else {
2016-06-02 13:18:45 +03:00
* exception_gchandle = create_mt _exception ( xamarin_strdup _printf ( "Xamarin.iOS: Cannot marshal return type %s (size: %i)\n" , type , ( int ) size ) ) ;
2016-04-21 15:19:32 +03:00
}
break ;
}
}
void
xamarin_arch _trampoline ( struct CallState * state )
{
2016-02-15 21:02:14 +03:00
// COOP : called from ObjC , and does not access managed memory .
MONO_ASSERT _GC _SAFE ;
2016-04-21 15:19:32 +03:00
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 __ * /