/*
* Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017
* Jonathan Schleifer <js@heap.zone>
*
* All rights reserved.
*
* This file is part of ObjFW. It may be distributed under the terms of the
* Q Public License 1.0, which can be found in the file LICENSE.QPL included in
* the packaging of this file.
*
* Alternatively, it may be distributed under the terms of the GNU General
* Public License, either version 2 or 3, which can be found in the file
* LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
* file.
*/
#include "config.h"
#include <stdint.h>
#include <stdlib.h>
#include <xmmintrin.h>
#import "OFInvocation.h"
#import "OFMethodSignature.h"
#import "OFInvalidFormatException.h"
#import "OFOutOfMemoryException.h"
#define NUM_GPR_IN 6
#define NUM_GPR_OUT 2
#define NUM_SSE_IN 8
#define NUM_X87_OUT 2
enum {
RETURN_TYPE_NORMAL,
RETURN_TYPE_STRUCT,
RETURN_TYPE_X87,
RETURN_TYPE_JMP,
RETURN_TYPE_JMP_STRET
};
struct call_context {
uint64_t gpr[NUM_GPR_IN + NUM_GPR_OUT];
__m128 sse[NUM_SSE_IN];
long double x87[NUM_X87_OUT];
uint8_t num_sse_used;
uint8_t return_type;
uint64_t stack_size;
uint64_t stack[];
};
extern void of_invocation_call(struct call_context *);
static void
pushGPR(struct call_context **context, size_t *currentGPR, uint64_t value)
{
struct call_context *newContext;
if (*currentGPR < NUM_GPR_IN) {
(*context)->gpr[(*currentGPR)++] = value;
return;
}
if ((newContext = realloc(*context,
sizeof(**context) + ((*context)->stack_size + 1) * 8)) == NULL) {
free(*context);
@throw [OFOutOfMemoryException exceptionWithRequestedSize:
sizeof(**context) + ((*context)->stack_size + 1) * 8];
}
newContext->stack[newContext->stack_size] = value;
newContext->stack_size++;
*context = newContext;
}
static void
pushDouble(struct call_context **context, size_t *currentSSE, double value)
{
struct call_context *newContext;
if (*currentSSE < NUM_SSE_IN) {
(*context)->sse[(*currentSSE)++] = (__m128)_mm_set_sd(value);
(*context)->num_sse_used++;
return;
}
if ((newContext = realloc(*context,
sizeof(**context) + ((*context)->stack_size + 1) * 8)) == NULL) {
free(*context);
@throw [OFOutOfMemoryException exceptionWithRequestedSize:
sizeof(**context) + ((*context)->stack_size + 1) * 8];
}
memcpy(&newContext->stack[newContext->stack_size], &value, 8);
newContext->stack_size++;
*context = newContext;
}
static void
pushLongDouble(struct call_context **context, long double value)
{
struct call_context *newContext;
if ((newContext = realloc(*context,
sizeof(**context) + ((*context)->stack_size + 2) * 8)) == NULL) {
free(*context);
@throw [OFOutOfMemoryException exceptionWithRequestedSize:
sizeof(**context) + ((*context)->stack_size + 2) * 8];
}
memcpy(&newContext->stack[newContext->stack_size], &value, 16);
newContext->stack_size += 2;
*context = newContext;
}
#ifndef __clang__
static void
alignStack(struct call_context **context, size_t alignment)
{
size_t stackSize = (*context)->stack_size;
struct call_context *newContext;
if (stackSize % alignment == 0)
return;
stackSize += alignment - stackSize % alignment;
if ((newContext = realloc(*context,
sizeof(**context) + stackSize * 8)) == NULL) {
free(*context);
@throw [OFOutOfMemoryException exceptionWithRequestedSize:
sizeof(**context) + stackSize * 8];
}
memset(&newContext->stack[newContext->stack_size], '\0',
(stackSize - newContext->stack_size) * 8);
newContext->stack_size = stackSize;
*context = newContext;
}
#endif
void
of_invocation_invoke(OFInvocation *invocation)
{
OFMethodSignature *methodSignature = [invocation methodSignature];
size_t numberOfArguments = [methodSignature numberOfArguments];
struct call_context *context;
const char *typeEncoding;
size_t currentGPR = 0, currentSSE = 0;
if ((context = calloc(sizeof(*context), 1)) == NULL)
@throw [OFOutOfMemoryException exception];
for (size_t i = 0; i < numberOfArguments; i++) {
typeEncoding = [methodSignature argumentTypeAtIndex: i];
if (*typeEncoding == 'r')
typeEncoding++;
switch (*typeEncoding) {
#define CASE_GPR(encoding, type) \
case encoding: \
{ \
type tmp; \
[invocation getArgument: &tmp \
atIndex: i]; \
pushGPR(&context, ¤tGPR, tmp); \
} \
break;
CASE_GPR('c', char)
CASE_GPR('C', unsigned char)
CASE_GPR('i', int)
CASE_GPR('I', unsigned int)
CASE_GPR('s', short)
CASE_GPR('S', unsigned short)
CASE_GPR('l', long)
CASE_GPR('L', unsigned long)
CASE_GPR('q', long long)
CASE_GPR('Q', unsigned long long)
#ifdef __SIZEOF_INT128__
case 't':
case 'T':;
uint64_t int128Tmp[2];
[invocation getArgument: int128Tmp
atIndex: i];
# ifndef __clang__
/*
* Clang violates the x86_64 ABI and does not properly
* align __int128 on the stack.
*/
alignStack(&context, 2);
# endif
pushGPR(&context, ¤tGPR, int128Tmp[0]);
pushGPR(&context, ¤tGPR, int128Tmp[1]);
break;
#endif
case 'f':;
float floatTmp;
[invocation getArgument: &floatTmp
atIndex: i];
pushDouble(&context, ¤tSSE, floatTmp);
break;
case 'd':;
double doubleTmp;
[invocation getArgument: &doubleTmp
atIndex: i];
pushDouble(&context, ¤tSSE, doubleTmp);
break;
case 'D':;
long double longDoubleTmp;
[invocation getArgument: &longDoubleTmp
atIndex: i];
pushLongDouble(&context, longDoubleTmp);
break;
CASE_GPR('B', _Bool)
CASE_GPR('*', uintptr_t)
CASE_GPR('@', uintptr_t)
CASE_GPR('#', uintptr_t)
CASE_GPR(':', uintptr_t)
/* TODO: '[' */
/* TODO: '{' */
/* TODO: '(' */
CASE_GPR('^', uintptr_t)
#ifndef __STDC_NO_COMPLEX__
/* TODO: 'j' */
#endif
default:
free(context);
@throw [OFInvalidFormatException exception];
#undef CASE_GPR
}
}
typeEncoding = [methodSignature methodReturnType];
if (*typeEncoding == 'r')
typeEncoding++;
switch (*typeEncoding) {
case 'c':
case 'C':
case 'i':
case 'I':
case 's':
case 'S':
case 'l':
case 'L':
case 'q':
case 'Q':
#ifdef __SIZEOF_INT128__
case 't':
case 'T':
#endif
case 'f':
case 'd':
case 'B':
case '*':
case '@':
case '#':
case ':':
case '^':
context->return_type = RETURN_TYPE_NORMAL;
break;
case 'D':
context->return_type = RETURN_TYPE_X87;
break;
/* TODO: '[' */
/* TODO: '{' */
/* TODO: '(' */
#ifndef __STDC_NO_COMPLEX__
/* TODO: 'j' */
#endif
default:
free(context);
@throw [OFInvalidFormatException exception];
}
of_invocation_call(context);
switch (*typeEncoding) {
#define CASE_GPR(encoding, type) \
case encoding: \
{ \
type tmp = (type)context->gpr[NUM_GPR_IN]; \
[invocation setReturnValue: &tmp]; \
} \
break;
CASE_GPR('c', char)
CASE_GPR('C', unsigned char)
CASE_GPR('i', int)
CASE_GPR('I', unsigned int)
CASE_GPR('s', short)
CASE_GPR('S', unsigned short)
CASE_GPR('l', long)
CASE_GPR('L', unsigned long)
CASE_GPR('q', long long)
CASE_GPR('Q', unsigned long long)
#ifdef __SIZEOF_INT128__
case 't':
case 'T':;
[invocation setReturnValue: &context->gpr[NUM_GPR_IN]];
break;
#endif
case 'f':;
float floatTmp;
_mm_store_ss(&floatTmp, context->sse[0]);
[invocation setReturnValue: &floatTmp];
break;
case 'd':;
double doubleTmp;
_mm_store_sd(&doubleTmp, (__m128d)context->sse[0]);
[invocation setReturnValue: &doubleTmp];
break;
case 'D':
[invocation setReturnValue: &context->x87[0]];
break;
CASE_GPR('B', _Bool)
CASE_GPR('*', uintptr_t)
CASE_GPR('@', uintptr_t)
CASE_GPR('#', uintptr_t)
CASE_GPR(':', uintptr_t)
/* TODO: '[' */
/* TODO: '{' */
/* TODO: '(' */
CASE_GPR('^', uintptr_t)
#ifndef __STDC_NO_COMPLEX__
/* TODO: 'j' */
#endif
default:
free(context);
@throw [OFInvalidFormatException exception];
#undef CASE_GPR
}
free(context);
}