2016-12-06 12 views
4

Как вызвать переданную функцию (* f2) в третьем аргументе функции f1 в сборке? декларация выглядит следующим образом:Как вызвать функцию через указатель функции, переданный как аргумент?

extern float f1(int v1, float v2, float (*f2)(int v3, float v4)); 

Я хочу передать v1 в v3, v2 к v4, вызов функции f2, и возвращаемое значение

f1: 
    push rbp   
    mov rbp, rsp 

    mov rdx, rdi ; v1 to v3 
    mov xmm1, xmm0 ; v2 to v4 
    call ??? 
    mov xmm0, xmm1 

    mov rsp, rbp  
    pop rbp  
ret 

что я поставил вместо знаков вопроса?

+0

Что такое бинарный интерфейс приложения на платформе, на которой вы находитесь? Это определит, где находятся параметры и что такое соглашение о вызове. –

+0

@ DavidHoelzer Abi64 – helloFromTheOtherSide

+0

что это? Нет такого ABI. –

ответ

5

Нет такой вещи, как «Abi64». Поскольку вы отметили вопрос MASM, мы можем догадаться, что вы используете платформу Windows, и, очевидно, «64» означает, что это 64-разрядный код, так что это существенно сужает возможности. Тем не менее, есть еще два общих соглашения о вызовах для 64-битного кода в Windows. Один из них - __vectorcall, а другой - соглашение о назначении Microsoft x64 (тот, который изначально был изобретен, чтобы сделать все другие соглашения об использовании устаревшими, но has hell, а не).

Поскольку соглашение о назначении Microsoft x64 является наиболее распространенным, и в этом конкретном случае использование __vectorcall ничего не изменит, я предполагаю, что это тот, который вы используете. И тогда требуемый код становится абсолютно тривиальным. Все, что вам нужно сделать, это перейти от f1 к f2, так как стек будет настроен одинаково. Первые два параметра f1 - это два параметра, которые должны быть переданы в f2, а возвращаемое значение f2 - это возвращаемое значение f1. Поэтому:

f1: 
    rex_jmp r8 ; third parameter (pointer to f2) is passed in r8 

Это не только тривиальное писать, но она является наиболее оптимальной реализации для обоих размеров и скорости.
Вы даже можете изменить параметры v1 или v2 заранее, если вы хотите, например:

f1: 
    inc  ecx  ; increment v1 (passed in ecx) 

    ; multiply v2 (xmm1) by v1 (ecx) 
    movd  xmm0, ecx 
    cvtdq2ps xmm0, xmm0 
    mulss xmm1, xmm0 

    rex_jmp r8 ; third parameter (pointer to f2) is passed in r8 

В случае, если вы хотите сделать что-то более сложное, вот как это будет работать:

f1: 
    sub rsp, 40  ; allocate the required space on the stack 
    call r8   ; call f2 through the pointer, passed in r8 
    add rsp, 40  ; clean up the stack 
    ret 

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

Однако, перетасовка параметров, которые вы делали в примере кода, показанного в вопросе, это неправильный! В соглашении о вызове Microsoft x64 первые до четырех целых аргументов передаются в регистры слева направо в RCX, RDX, R8 и R9. Все остальные целочисленные аргументы передаются в стек.Первые четыре значения с плавающей запятой также передаются в регистры слева направо в XMM0, XMM1, XMM2 и XMM3. Остальные передаются в стек, а также слишком большие для регистров структуры.

Странно, однако, что слоты «исправлены», поэтому можно использовать только 4 общих регистраных аргумента, даже если у вас есть сочетание целых и аргументов fp. Таким образом:

╔═══════════╦══════════════════════════╗ 
║   ║   TYPE   ║ 
║ PARAMETER ╠═════════╦════════════════╣ 
║   ║ Integer ║ Floating-Point ║ 
╠═══════════╬═════════╬════════════════╣ 
║ First  ║ RCX ║  XMM0  ║ 
╠═══════════╬═════════╬════════════════╣ 
║ Second ║ RDX ║  XMM1  ║ 
╠═══════════╬═════════╬════════════════╣ 
║ Third  ║ R8 ║  XMM2  ║ 
╠═══════════╬═════════╬════════════════╣ 
║ Fourth ║ R9 ║  XMM3  ║ 
╠═══════════╬═════════╩════════════════╣ 
║ (rest) ║   on stack   ║ 
╚═══════════╩══════════════════════════╝ 

Не имеет значения, что второй параметр является первым передаваемым значением с плавающей запятой. Он не входит в XMM0, потому что это первое значение с плавающей запятой, оно идет в XMM1, потому что это второй параметр и, следовательно, во втором «слоте». (Это отличается от the x86-64 System V ABI, где первые 6 целых args идут в регистры, есть или нет аргументы FP).

Дополнительная документация по передаче параметров Windows доступна here, включая примеры.

+1

* самая оптимальная реализация для размера и скорости *: для размера вы должны использовать [CVTSI2SS] (http://www.felixcloutier.com/x86/CVTSI2SS.html) и игнорировать ложную зависимость от старого значения XMM0 (спасибо за близорукий дизайн для PIII, Intel). Я ожидал, что gcc делает ('pxor xmm0, xmm0' /' cvtsi2ss xmm0, ecx') оптимальным для скорости, но на самом деле он выглядит как MOVD, а затем упакованное преобразование может быть тем же самым меньшим латентным временем и меньшим количеством всех uops. Может быть задержка байпаса 1 с от MOVD до CVTDQ2PS. (Агнер перечисляет его как 3 + 2c на Nehalem, поэтому CVTDQ2PS хочет туда поплавок.) –

+0

К сожалению. Я имел в виду этот комментарий, чтобы применить к нему код, не обязательно мое преобразование значения с плавающей запятой. Я не писал это для скорости! Спасибо за информацию и исследования, хотя! Код, который я показал, - это то, что MSVC будет генерировать. Agner не показывает более поздние поколения процессоров как имеющие такое же наказание, поэтому он, вероятно, предпочитает оптимизировать для них как наиболее вероятные цели 64-битной сборки. Таков был бы мой выбор, по крайней мере. Хотя, возможно, код GCC более читабельен. –

+0

Почему вы говорите '__fastcall' против« соглашения о вызовах Microsoft x64 »? Вы, кажется, говорите, что vectorcall и fastcall (почти?) Одно и тоже, и отличаются от некоторых других конвенций. Но я думал, что x64 '__fastcall' был редко используемым термином для исходного соглашения Microsoft x64, который не передает векторы в правилах XMM/YMM. (BTW, это [руководство по Intel для asm для Windows] (https://software.intel.com/en-us/articles/introduction-to-x64-assembly) содержит подробное описание соглашения о вызове и все -хорошо.) –

-2

Код сборки отличается от используемого микроконтроллера.

Не совсем то, что вы ищете, но следующий код сборки, генерируемый на платформе Windows, имеющий ядро ​​Intel I7: -

C код: -

extern float f1(int v1, float v2, float (*f2)(int v3, float v4)) 
{ 
    float a = 10.0; 
    int b = 12; 

    f2(b, a); 
    return a+ 12.5; 
} 

код сборки: -

_f1: 
pushl %ebp 
movl %esp, %ebp 
subl $40, %esp 
movl LC0, %eax 

movl %eax, -12(%ebp) 
movl $12, -16(%ebp) 
movl -12(%ebp), %eax 
movl %eax, 4(%esp) 
movl -16(%ebp), %eax 
movl %eax, (%esp) 
movl 16(%ebp), %eax 
call *%eax 
fstp %st(0) 
flds -12(%ebp) 
flds LC1 
faddp %st, %st(1) 
leave 
ret 

Надеюсь, это поможет.

+1

Очевидно, вы скомпилировали этот код без включенной оптимизации, что является плохой идеей, потому что это дает вам много неуместного шума. Вы также, по-видимому, скомпилировали его на компиляторе, который * не * использует соглашение об использовании Microsoft x64, что сделало бы его запутанным и бесполезным для человека, который задал вопрос. Конечно, вопрос был уточнен, но, безусловно, ясно, что он использует MASM и нацеливается на x64. Это не синтаксис MASM, и никакое соглашение о вызове x64 в Windows никогда не пропускает первые четыре целочисленных или с плавающей запятой в стеке. –

 Смежные вопросы

  • Нет связанных вопросов^_^