Вобщем, есть некая стековая виртуальная машина, написанная на "Це виз классес", которая интерпретирует байткод моего ЯП. Хочу добавить моему ЯП, а следовательно байткоду и ВМ, возможность вызова произвольной функции на Цэ. Хочу добавить опкод для нативного вызова, который будет со стека забирать указанное число параметров вызова и адрес вызова и вызывать функцию. На стеке параметры лежат с информацией о типе, на стеке может лежать int64, int32, int16, int8 и intptr (размер зависим от архитектуры, будет использоваться для передачи указателей). В общем случае хотелось бы как то вызывать нативные Цэшные функции с этими параметрами, при этом уметь это делать для stdcall, cdecl и fastcall. Делать это через указатель на функцию не имеет смысла, так как я столкнусь с "комбинаторным адом", поскольку нативная функция может иметь разное число аргументов, а сами аргументы могут быть разных типов. Поэтому вопрос: можно ли как-то это реализовать средствами языка Цэ, не прибегая к ассемблеру? Может интринсики какие есть? Или может с valist'ами можно что-то замутить? Или только на ассемблере это писать?
Самое интересное пожалуй с fastcall. Чтобы забрать со стека и положить в регистры, да с переменным числом аргументов, нужно мутить какую-то комбинацию из fastcall, cdecl, naked и подмены собственного адреса возврата. Вроде еще новые стандарты сей позволяют создавать на стеке массив переменной длины, но подробностей я сейчас не вспомню. Для очистки этой дичи потом, если производительность не критична, можно сделать ход конем и создавать отдельный поток на каждый вызов. Пожалуй можно даже создавать поток с заполненным контекстом, тогда решается проблема выставления регистров и стека.
Ну, например, в NewLisp'е есть такой кодец, сначала они все аргументы приводят к UINT (UINT у них на самом деле uintptr_t, а не всегда 32-битное целое, как логично было бы подумать): Код (C): // Так у них объявлен CELL typedef struct { UINT type; void * next; UINT aux; UINT contents; } CELL; // ... CELL * executeLibfunction(CELL * pCell, CELL * params) { CELL * arg; UINT args[14]; int count; #ifdef FFI if(pCell->type == CELL_IMPORT_FFI) if(((FFIMPORT *)pCell->aux)->type != 0) return executeLibFFI(pCell, params); #endif count = 0; while(params->type != CELL_NIL && count < 14) { arg = evaluateExpression(params); switch(arg->type) { case CELL_LONG: case CELL_STRING: case CELL_PRIMITIVE: args[count++] = arg->contents; break; #ifndef NEWLISP64 /* change 64-bit to 32-bit */ case CELL_INT64: args[count++] = *(INT64 *)&arg->aux; break; #endif case CELL_FLOAT: #ifndef NEWLISP64 args[count++] = arg->aux; #endif args[count++] = arg->contents; break; default: args[count++] = (UINT)arg; break; } params = (CELL *)params->next; } #if defined(WINDOWS) || defined(CYGWIN) if(pCell->type == CELL_IMPORT_DLL) return(stuffInteger(stdcallFunction(pCell->contents, args, count))); else #endif return(stuffInteger(cdeclFunction(pCell->contents, args, count))); } А потом вызывают через типизированный указатель (от 0 до 14 UINT аргументов): Код (C): UINT cdeclFunction(UINT fAddress, UINT * args, int count) { UINT (*function)(); function = (UINT (*)())fAddress; switch(count) { case 0: return (*function)(); case 1: return (*function)(args[0]); case 2: return (*function)(args[0], args[1]); case 3: /* printf("args[0] %llx, args[1] %llx, args[2] %llx, args[1]-args[2] %llx\n ", args[0], args[1], args[2], args[1] - args[2]); */ return (*function)(args[0], args[1], args[2]); case 4: return (*function)(args[0], args[1], args[2], args[3]); case 5: return (*function)(args[0], args[1], args[2], args[3], args[4]); case 6: return (*function)(args[0], args[1], args[2], args[3], args[4], args[5]); case 7: return (*function)(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); case 8: return (*function)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); case 9: return (*function)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); case 10: return (*function)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); case 11: return (*function)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]); case 12: return (*function)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11]); case 13: return (*function)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12]); case 14: return (*function)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13]); default: break; } return(0); } #if defined(WINDOWS) || defined(CYGWIN) UINT stdcallFunction(UINT fAddress, UINT * args, int count) { UINT _stdcall (*function)(); function = (UINT _stdcall (*)())fAddress; switch(count) { case 0: return (*function)(); case 1: return (*function)(args[0]); case 2: return (*function)(args[0], args[1]); case 3: return (*function)(args[0], args[1], args[2]); case 4: return (*function)(args[0], args[1], args[2], args[3]); case 5: return (*function)(args[0], args[1], args[2], args[3], args[4]); case 6: return (*function)(args[0], args[1], args[2], args[3], args[4], args[5]); case 7: return (*function)(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); case 8: return (*function)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); case 9: return (*function)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); case 10: return (*function)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); case 11: return (*function)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]); case 12: return (*function)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11]); case 13: return (*function)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12]); case 14: return (*function)(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13]); default: break; } return(0); } Нужна пояснительная бригада... Насколько это адекватное решение? Получается 64-битные числа они просто обрезают до 32-битных на X86, а double передают, как два UINT параметра друг за другом. Как на X86 передаются 64-битные числа в cdecl, stdcall и fastcall?
Ну во-первых это решение с ограничением максимального числа аргументов, оно мне тоже в голову приходило, но задача же стоит сделать без ограничения. Потом, оно не учитывает много вариантов типа того, что в стек затолкают, например, структуру. Традиционно и очевидно, как два инта, младший первым если точнее, на стеке он будет первым/верхним, а кладется на стек, соответственно, вторым. Это если на стеке, в фастколе как-то хитрее, надо погуглить.
Ну в идеале да, на практике же вряд ли можно встретить функцию с большим количеством аргументов. Ну часто в апи для передачи структуры в функцию будет использован указатель, насколько часто встречаются функции, принимающие структуру не по указателю? Про фастколл на мсдн пишут такую вещь: Проблемы с фасколлом в этом случае будут, если я хочу поддерживать целые числа и числа с плавающей точкой, тк инты передаются через регистры R*, а флоуты через XMM*. Вряд ли это удастся как-то унифицировать. Ну тогда для cdecl и stdcall будет достаточно преобразовать его к двум инт32 параметрам (верхняя и нижняя часть) и вызывать соответветствующий типизированный указатель. Странно, что в NewLisp'е этого не сделали.
А в гцц/мингв, кстати, есть. Проблема в том, что решение на чистом Цэ или на некоторых интринсиках будет портабельное, я говорю не только о X86.
Зачем вставки? asm-файл можно в проект добавить. Все равно под каждую платформу свой маршаллер писать.
Тут скорее вопрос в читаемости кода, лично мне код со вставками читать удобнее. Но тут кому как. А так согласен, с добавлением x64 ассемблера в студии проблем явных нет.
Вам нужен LLVM, и не нужно будет всех этих костылей. Но опять же. Костыли тоже имеют место быть - если принять ограничения. как раз на практике встречались идуские коды где было чуть больше 100 аргументов ))