/* * Copyright (c) 2008-2021 Jonathan Schleifer <js@nil.im> * * 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" #import "invoke-x86_64.h" #import "macros.h" struct call_context { uint64_t GPR[NUM_GPR_IN + NUM_GPR_OUT]; __m128 SSE[NUM_SSE_INOUT]; long double X87[NUM_X87_OUT]; uint8_t numSSEUsed; uint8_t returnType; uint64_t stackSize; uint64_t stack[]; }; extern void of_invocation_call(struct call_context *); static void pushGPR(struct call_context **context, uint_fast8_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)->stackSize + 1) * 8)) == NULL) { free(*context); @throw [OFOutOfMemoryException exceptionWithRequestedSize: sizeof(**context) + ((*context)->stackSize + 1) * 8]; } newContext->stack[newContext->stackSize] = value; newContext->stackSize++; *context = newContext; } static void pushDouble(struct call_context **context, uint_fast8_t *currentSSE, double value) { struct call_context *newContext; if (*currentSSE < NUM_SSE_INOUT) { (*context)->SSE[(*currentSSE)++] = (__m128)_mm_set_sd(value); (*context)->numSSEUsed++; return; } if ((newContext = realloc(*context, sizeof(**context) + ((*context)->stackSize + 1) * 8)) == NULL) { free(*context); @throw [OFOutOfMemoryException exceptionWithRequestedSize: sizeof(**context) + ((*context)->stackSize + 1) * 8]; } memcpy(&newContext->stack[newContext->stackSize], &value, 8); newContext->stackSize++; *context = newContext; } static void pushQuad(struct call_context **context, uint_fast8_t *currentSSE, double low, double high) { size_t stackSize; struct call_context *newContext; if (*currentSSE + 1 < NUM_SSE_INOUT) { (*context)->SSE[(*currentSSE)++] = (__m128)_mm_set_sd(low); (*context)->SSE[(*currentSSE)++] = (__m128)_mm_set_sd(high); (*context)->numSSEUsed += 2; return; } stackSize = (*context)->stackSize + 2; if ((newContext = realloc(*context, sizeof(**context) + stackSize * 8)) == NULL) { free(*context); @throw [OFOutOfMemoryException exceptionWithRequestedSize: sizeof(**context) + stackSize * 8]; } memset(&newContext->stack[newContext->stackSize], '\0', (stackSize - newContext->stackSize) * 8); memcpy(&newContext->stack[stackSize - 2], &low, 8); memcpy(&newContext->stack[stackSize - 1], &high, 8); newContext->stackSize = stackSize; *context = newContext; } static void pushLongDouble(struct call_context **context, long double value) { struct call_context *newContext; if ((newContext = realloc(*context, sizeof(**context) + ((*context)->stackSize + 2) * 8)) == NULL) { free(*context); @throw [OFOutOfMemoryException exceptionWithRequestedSize: sizeof(**context) + ((*context)->stackSize + 2) * 8]; } memcpy(&newContext->stack[newContext->stackSize], &value, 16); newContext->stackSize += 2; *context = newContext; } static void pushLongDoublePair(struct call_context **context, long double value[2]) { size_t stackSize; struct call_context *newContext; stackSize = OF_ROUND_UP_POW2(2UL, (*context)->stackSize) + 4; if ((newContext = realloc(*context, sizeof(**context) + stackSize * 8)) == NULL) { free(*context); @throw [OFOutOfMemoryException exceptionWithRequestedSize: sizeof(**context) + stackSize * 8]; } memset(&newContext->stack[newContext->stackSize], '\0', (stackSize - newContext->stackSize) * 8); memcpy(&newContext->stack[stackSize - 4], value, 32); newContext->stackSize = stackSize; *context = newContext; } #if defined(__SIZEOF_INT128__) && !defined(__clang__) static void pushInt128(struct call_context **context, uint_fast8_t *currentGPR, uint64_t value[2]) { size_t stackSize; struct call_context *newContext; if (*currentGPR + 1 < NUM_GPR_IN) { (*context)->GPR[(*currentGPR)++] = value[0]; (*context)->GPR[(*currentGPR)++] = value[1]; return; } stackSize = OF_ROUND_UP_POW2(2, (*context)->stackSize) + 2; if ((newContext = realloc(*context, sizeof(**context) + stackSize * 8)) == NULL) { free(*context); @throw [OFOutOfMemoryException exceptionWithRequestedSize: sizeof(**context) + stackSize * 8]; } memset(&newContext->stack[newContext->stackSize], '\0', (stackSize - newContext->stackSize) * 8); memcpy(&newContext->stack[stackSize - 2], value, 16); newContext->stackSize = 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; uint_fast8_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, (uint64_t)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) CASE_GPR('B', _Bool) CASE_GPR('*', char *) CASE_GPR('@', id) CASE_GPR('#', Class) /* * Using SEL triggers a warning that casting a SEL to an * integer is deprecated. */ CASE_GPR(':', void *) CASE_GPR('^', void *) #undef CASE_GPR #ifdef __SIZEOF_INT128__ case 't': case 'T':; uint64_t int128Tmp[2]; [invocation getArgument: &int128Tmp atIndex: i]; # ifndef __clang__ pushInt128(&context, ¤tGPR, int128Tmp); # else /* See https://bugs.llvm.org/show_bug.cgi?id=34646 */ pushGPR(&context, ¤tGPR, int128Tmp[0]); pushGPR(&context, ¤tGPR, int128Tmp[1]); # endif break; #endif case 'f':; double floatTmp = 0; [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 'j': switch (typeEncoding[1]) { case 'f':; double complexFloatTmp; [invocation getArgument: &complexFloatTmp atIndex: i]; pushDouble(&context, ¤tSSE, complexFloatTmp); break; case 'd':; double complexDoubleTmp[2]; [invocation getArgument: &complexDoubleTmp atIndex: i]; pushQuad(&context, ¤tSSE, complexDoubleTmp[0], complexDoubleTmp[1]); break; case 'D':; long double complexLongDoubleTmp[2]; [invocation getArgument: &complexLongDoubleTmp atIndex: i]; pushLongDoublePair(&context, complexLongDoubleTmp); break; default: free(context); @throw [OFInvalidFormatException exception]; } break; /* TODO: '[' */ /* TODO: '{' */ /* TODO: '(' */ default: free(context); @throw [OFInvalidFormatException exception]; } } typeEncoding = methodSignature.methodReturnType; if (*typeEncoding == 'r') typeEncoding++; switch (*typeEncoding) { case 'v': case 'c': case 'C': case 'i': case 'I': case 's': case 'S': case 'l': case 'L': case 'q': case 'Q': case 'B': case '*': case '@': case '#': case ':': case '^': #ifdef __SIZEOF_INT128__ case 't': case 'T': #endif case 'f': case 'd': context->returnType = RETURN_TYPE_NORMAL; break; case 'D': context->returnType = RETURN_TYPE_X87; break; case 'j': switch (typeEncoding[1]) { case 'f': case 'd': context->returnType = RETURN_TYPE_NORMAL; break; case 'D': context->returnType = RETURN_TYPE_COMPLEX_X87; break; default: free(context); @throw [OFInvalidFormatException exception]; } break; /* TODO: '[' */ /* TODO: '{' */ /* TODO: '(' */ default: free(context); @throw [OFInvalidFormatException exception]; } of_invocation_call(context); switch (*typeEncoding) { case 'v': break; #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) CASE_GPR('B', _Bool) CASE_GPR('*', char *) CASE_GPR('@', id) CASE_GPR('#', Class) CASE_GPR(':', SEL) CASE_GPR('^', void *) #undef CASE_GPR #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 'j': switch (typeEncoding[1]) { case 'f':; double complexFloatTmp; _mm_store_sd(&complexFloatTmp, (__m128d)context->SSE[0]); [invocation setReturnValue: &complexFloatTmp]; break; case 'd':; double complexDoubleTmp[2]; _mm_store_sd(&complexDoubleTmp[0], (__m128d)context->SSE[0]); _mm_store_sd(&complexDoubleTmp[1], (__m128d)context->SSE[1]); [invocation setReturnValue: &complexDoubleTmp]; break; case 'D': [invocation setReturnValue: context->X87]; break; default: free(context); @throw [OFInvalidFormatException exception]; } break; /* TODO: '[' */ /* TODO: '{' */ /* TODO: '(' */ default: free(context); @throw [OFInvalidFormatException exception]; } free(context); }