Вызов функции с произвольным числом аргументов

Тема в разделе "LANGS.C", создана пользователем Rel, 21 сен 2021.

  1. Rel

    Rel Well-Known Member

    Публикаций:
    2
    Регистрация:
    11 дек 2008
    Сообщения:
    5.323
    Вобщем, есть некая стековая виртуальная машина, написанная на "Це виз классес", которая интерпретирует байткод моего ЯП. Хочу добавить моему ЯП, а следовательно байткоду и ВМ, возможность вызова произвольной функции на Цэ. Хочу добавить опкод для нативного вызова, который будет со стека забирать указанное число параметров вызова и адрес вызова и вызывать функцию. На стеке параметры лежат с информацией о типе, на стеке может лежать int64, int32, int16, int8 и intptr (размер зависим от архитектуры, будет использоваться для передачи указателей). В общем случае хотелось бы как то вызывать нативные Цэшные функции с этими параметрами, при этом уметь это делать для stdcall, cdecl и fastcall. Делать это через указатель на функцию не имеет смысла, так как я столкнусь с "комбинаторным адом", поскольку нативная функция может иметь разное число аргументов, а сами аргументы могут быть разных типов. Поэтому вопрос: можно ли как-то это реализовать средствами языка Цэ, не прибегая к ассемблеру? Может интринсики какие есть? Или может с valist'ами можно что-то замутить? Или только на ассемблере это писать?
     
  2. ormoulu

    ormoulu Well-Known Member

    Публикаций:
    0
    Регистрация:
    24 янв 2011
    Сообщения:
    1.208
    Самое интересное пожалуй с fastcall. Чтобы забрать со стека и положить в регистры, да с переменным числом аргументов, нужно мутить какую-то комбинацию из fastcall, cdecl, naked и подмены собственного адреса возврата.
    Вроде еще новые стандарты сей позволяют создавать на стеке массив переменной длины, но подробностей я сейчас не вспомню.

    Для очистки этой дичи потом, если производительность не критична, можно сделать ход конем и создавать отдельный поток на каждый вызов.
    Пожалуй можно даже создавать поток с заполненным контекстом, тогда решается проблема выставления регистров и стека.
     
  3. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Если под винду то можно заюзать DispCallFunc,правда не знаю дружит ли она с fastcall.
     
  4. Rel

    Rel Well-Known Member

    Публикаций:
    2
    Регистрация:
    11 дек 2008
    Сообщения:
    5.323
    Ну, например, в NewLisp'е есть такой кодец, сначала они все аргументы приводят к UINT (UINT у них на самом деле uintptr_t, а не всегда 32-битное целое, как логично было бы подумать):
    Код (C):
    1.  
    2. // Так у них объявлен CELL
    3. typedef struct
    4. {
    5.   UINT type;
    6.   void * next;
    7.   UINT aux;
    8.   UINT contents;
    9. } CELL;
    10.  
    11. // ...
    12.  
    13. CELL * executeLibfunction(CELL * pCell, CELL * params)
    14. {
    15. CELL * arg;
    16. UINT args[14];
    17. int count;
    18. #ifdef FFI
    19. if(pCell->type == CELL_IMPORT_FFI)
    20.     if(((FFIMPORT *)pCell->aux)->type != 0)
    21.     return executeLibFFI(pCell, params);
    22. #endif
    23. count = 0;
    24. while(params->type != CELL_NIL && count < 14)
    25.     {
    26.     arg = evaluateExpression(params);
    27.     switch(arg->type)
    28.         {
    29.         case CELL_LONG:
    30.         case CELL_STRING:
    31.         case CELL_PRIMITIVE:
    32.             args[count++] = arg->contents;
    33.             break;
    34. #ifndef NEWLISP64
    35.         /* change 64-bit to 32-bit */
    36.         case CELL_INT64:
    37.             args[count++] = *(INT64 *)&arg->aux;
    38.             break;
    39. #endif
    40.         case CELL_FLOAT:
    41. #ifndef NEWLISP64
    42.             args[count++] = arg->aux;
    43. #endif
    44.             args[count++] = arg->contents;
    45.             break;
    46.         default:
    47.             args[count++] = (UINT)arg;
    48.             break;
    49.         }
    50.     params = (CELL *)params->next;
    51.     }
    52. #if defined(WINDOWS) || defined(CYGWIN)
    53. if(pCell->type == CELL_IMPORT_DLL)
    54.     return(stuffInteger(stdcallFunction(pCell->contents, args, count)));
    55. else
    56. #endif
    57. return(stuffInteger(cdeclFunction(pCell->contents, args, count)));
    58. }
    59.  
    А потом вызывают через типизированный указатель (от 0 до 14 UINT аргументов):
    Код (C):
    1.  
    2. UINT cdeclFunction(UINT fAddress, UINT * args, int count)
    3. {
    4. UINT (*function)();
    5. function = (UINT (*)())fAddress;
    6. switch(count)
    7.     {
    8.     case 0:
    9.             return (*function)();
    10.     case 1:
    11.             return  (*function)(args[0]);
    12.     case 2:
    13.             return  (*function)(args[0], args[1]);
    14.     case 3:
    15.             /* printf("args[0] %llx, args[1] %llx, args[2] %llx, args[1]-args[2] %llx\n ",
    16.                     args[0], args[1], args[2], args[1] - args[2]); */
    17.             return  (*function)(args[0], args[1], args[2]);
    18.     case 4:
    19.             return  (*function)(args[0], args[1], args[2], args[3]);
    20.     case 5:
    21.             return  (*function)(args[0], args[1], args[2], args[3],
    22.                      args[4]);
    23.     case 6:
    24.             return  (*function)(args[0], args[1], args[2], args[3],
    25.                      args[4], args[5]);
    26.     case 7:
    27.             return  (*function)(args[0], args[1], args[2], args[3],
    28.                      args[4], args[5], args[6]);
    29.     case 8:
    30.             return  (*function)(args[0], args[1], args[2], args[3],
    31.                      args[4], args[5], args[6], args[7]);
    32.     case 9:
    33.             return  (*function)(args[0], args[1], args[2], args[3],
    34.                      args[4], args[5], args[6], args[7], args[8]);
    35.     case 10:
    36.             return  (*function)(args[0], args[1], args[2], args[3],
    37.                      args[4], args[5], args[6], args[7], args[8], args[9]);
    38.     case 11:
    39.             return  (*function)(args[0], args[1], args[2], args[3],
    40.                 args[4], args[5], args[6], args[7],
    41.                 args[8], args[9], args[10]);
    42.     case 12:
    43.             return  (*function)(args[0], args[1], args[2], args[3],
    44.                 args[4], args[5], args[6], args[7],
    45.                 args[8], args[9], args[10], args[11]);
    46.     case 13:
    47.             return  (*function)(args[0], args[1], args[2], args[3],
    48.                 args[4], args[5], args[6], args[7],
    49.                 args[8], args[9], args[10], args[11],
    50.                 args[12]);
    51.     case 14:
    52.             return  (*function)(args[0], args[1], args[2], args[3],
    53.                 args[4], args[5], args[6], args[7],
    54.                 args[8], args[9], args[10], args[11],
    55.                 args[12], args[13]);
    56.     default:
    57.         break;
    58.     }
    59. return(0);
    60. }
    61. #if defined(WINDOWS) || defined(CYGWIN)
    62. UINT stdcallFunction(UINT fAddress, UINT * args, int count)
    63. {
    64. UINT _stdcall (*function)();
    65. function = (UINT _stdcall (*)())fAddress;
    66. switch(count)
    67.     {
    68.     case 0:
    69.             return (*function)();
    70.     case 1:
    71.             return  (*function)(args[0]);
    72.     case 2:
    73.             return  (*function)(args[0], args[1]);
    74.     case 3:
    75.             return  (*function)(args[0], args[1], args[2]);
    76.     case 4:
    77.             return  (*function)(args[0], args[1], args[2], args[3]);
    78.     case 5:
    79.             return  (*function)(args[0], args[1], args[2], args[3],
    80.                      args[4]);
    81.     case 6:
    82.             return  (*function)(args[0], args[1], args[2], args[3],
    83.                      args[4], args[5]);
    84.     case 7:
    85.             return  (*function)(args[0], args[1], args[2], args[3],
    86.                      args[4], args[5], args[6]);
    87.     case 8:
    88.             return  (*function)(args[0], args[1], args[2], args[3],
    89.                      args[4], args[5], args[6], args[7]);
    90.     case 9:
    91.             return  (*function)(args[0], args[1], args[2], args[3],
    92.                      args[4], args[5], args[6], args[7], args[8]);
    93.     case 10:
    94.             return  (*function)(args[0], args[1], args[2], args[3],
    95.                      args[4], args[5], args[6], args[7], args[8], args[9]);
    96.     case 11:
    97.             return  (*function)(args[0], args[1], args[2], args[3],
    98.                 args[4], args[5], args[6], args[7],
    99.                 args[8], args[9], args[10]);
    100.     case 12:
    101.             return  (*function)(args[0], args[1], args[2], args[3],
    102.                 args[4], args[5], args[6], args[7],
    103.                 args[8], args[9], args[10], args[11]);
    104.     case 13:
    105.             return  (*function)(args[0], args[1], args[2], args[3],
    106.                 args[4], args[5], args[6], args[7],
    107.                 args[8], args[9], args[10], args[11],
    108.                 args[12]);
    109.     case 14:
    110.             return  (*function)(args[0], args[1], args[2], args[3],
    111.                 args[4], args[5], args[6], args[7],
    112.                 args[8], args[9], args[10], args[11],
    113.                 args[12], args[13]);
    114.     default:
    115.         break;
    116.     }
    117. return(0);
    118. }
    119.  
    Нужна пояснительная бригада... Насколько это адекватное решение? Получается 64-битные числа они просто обрезают до 32-битных на X86, а double передают, как два UINT параметра друг за другом. Как на X86 передаются 64-битные числа в cdecl, stdcall и fastcall?
     
  5. ormoulu

    ormoulu Well-Known Member

    Публикаций:
    0
    Регистрация:
    24 янв 2011
    Сообщения:
    1.208
    Ну во-первых это решение с ограничением максимального числа аргументов, оно мне тоже в голову приходило, но задача же стоит сделать без ограничения. Потом, оно не учитывает много вариантов типа того, что в стек затолкают, например, структуру.

    Традиционно и очевидно, как два инта, младший первым если точнее, на стеке он будет первым/верхним, а кладется на стек, соответственно, вторым. Это если на стеке, в фастколе как-то хитрее, надо погуглить.
     
  6. Rel

    Rel Well-Known Member

    Публикаций:
    2
    Регистрация:
    11 дек 2008
    Сообщения:
    5.323
    Ну в идеале да, на практике же вряд ли можно встретить функцию с большим количеством аргументов.

    Ну часто в апи для передачи структуры в функцию будет использован указатель, насколько часто встречаются функции, принимающие структуру не по указателю?

    Про фастколл на мсдн пишут такую вещь:
    Проблемы с фасколлом в этом случае будут, если я хочу поддерживать целые числа и числа с плавающей точкой, тк инты передаются через регистры R*, а флоуты через XMM*. Вряд ли это удастся как-то унифицировать.

    Ну тогда для cdecl и stdcall будет достаточно преобразовать его к двум инт32 параметрам (верхняя и нижняя часть) и вызывать соответветствующий типизированный указатель. Странно, что в NewLisp'е этого не сделали.
     
  7. M0rg0t

    M0rg0t Well-Known Member

    Публикаций:
    0
    Регистрация:
    18 окт 2010
    Сообщения:
    1.576
    так ты напиши, пригодится кому-то еще, всегда интересно узнать что-то новое.
     
  8. M0rg0t

    M0rg0t Well-Known Member

    Публикаций:
    0
    Регистрация:
    18 окт 2010
    Сообщения:
    1.576
    rmn, да в студии есть свои нюансы, скажем для х64 нет почему-то асм вставок.
     
  9. Rel

    Rel Well-Known Member

    Публикаций:
    2
    Регистрация:
    11 дек 2008
    Сообщения:
    5.323
    А в гцц/мингв, кстати, есть. Проблема в том, что решение на чистом Цэ или на некоторых интринсиках будет портабельное, я говорю не только о X86.
     
  10. rmn

    rmn Well-Known Member

    Публикаций:
    0
    Регистрация:
    23 ноя 2004
    Сообщения:
    2.348
    Зачем вставки? asm-файл можно в проект добавить.

    Все равно под каждую платформу свой маршаллер писать.
     
  11. MaKaKa

    MaKaKa Member

    Публикаций:
    0
    Регистрация:
    25 ноя 2020
    Сообщения:
    53
    Тут скорее вопрос в читаемости кода, лично мне код со вставками читать удобнее. Но тут кому как. А так согласен, с добавлением x64 ассемблера в студии проблем явных нет.
     
  12. Microedition

    Microedition Active Member

    Публикаций:
    0
    Регистрация:
    5 июн 2008
    Сообщения:
    814
    Глянь, как это в emacs сделано. Впрочем, там что-то похожее на вариант в NewLisp.
     
  13. TermoSINteZ

    TermoSINteZ Синоби даоса Команда форума

    Публикаций:
    2
    Регистрация:
    11 июн 2004
    Сообщения:
    3.552
    Адрес:
    Russia
    Вам нужен LLVM, и не нужно будет всех этих костылей. Но опять же. Костыли тоже имеют место быть - если принять ограничения.

    как раз на практике встречались идуские коды где было чуть больше 100 аргументов ))
     
  14. Microedition

    Microedition Active Member

    Публикаций:
    0
    Регистрация:
    5 июн 2008
    Сообщения:
    814
    Ага, тащить в проект из пары тысяч строк кода десятки мегабайт приплюснотого говна.
     
    Indy_ нравится это.