У меня есть процедура сборки, которая в общем случае вызывает функцию, которая использует stdcall
конвенция и вернуть float
, Эта функция используется фреймворком для выставления stdcall
функции для скриптового языка.
Вот функция, использующая встроенную сборку GNU, которая компилируется в MinGW 4.3, Win32:
inline uint64_t stdcall_invoke_return_float(int args_size_bytes,
const char * args_ptr,
void * func_ptr)
{
uint64_t result;
assert(
0 == args_size_bytes % 4
|| !"argument size must be a multiple of 4 bytes");
#if defined(__GNUC__)
asm
(
/* INPUT PARAMS: %0 is the address where top of FP stack to be stored
* %1 is the number of BYTES to push onto the stack, */
/* and during the copy loop it is the address of */
/* the next word to push */
/* %2 is the base address of the array */
/* %3 is the address of the function to call */
"testl %1, %1 # If zero argument bytes given, skip \n\t""je 2f # right to the function call. \n\t""addl %2, %1\n""1:\n\t""subl $4, %1 # Push arguments onto the stack in \n\t""pushl (%1) # reverse order. Keep looping while \n\t""cmp %2, %1 # addr to push (%1) > base addr (%2) \n\t""jg 1b # Callee cleans up b/c __stdcall. \n""2:\n\t""call * %3 # Callee will leave result in ST0 \n\t""fsts %0 # Copy 32-bit float from ST0->result": "=m" (result)
: "r" (args_size_bytes), "r" (args_ptr), "mr" (func_ptr)
: "%eax", "%edx", "%ecx" /* eax, ecx, edx are caller-save */, "cc");
#else
#pragma error "Replacement for inline assembler required"#endif
return result;
}
Это всего лишь небольшой клей, чтобы упростить написание тестовых случаев:
template<typename FuncPtr, typename ArgType>
float float_invoke(FuncPtr f, int nargs, ArgType * args)
{
uint64_t result = stdcall_invoke_return_float(
nargs * sizeof(ArgType),
reinterpret_cast<const char *>(args),
reinterpret_cast<void *>(f)
);
return *reinterpret_cast<float *>(&result);
}
Теперь у меня есть несколько тестов, которые вызывают эту функцию:
__stdcall float TestReturn1_0Float()
{ return 1.0f; }
__stdcall float TestFloat(float a)
{ return a; }
__stdcall float TestSum2Floats(float a, float b)
{ return a + b; }
static const float args[2] = { 10.0f, -1.0f };
assert_equals(1.0f, float_invoke(TestReturn1_0Float, 0, args)); // test 1
assert_equals(10.0f, float_invoke(TestFloat, 1, args)); // test 2
assert_equals(-1.0f, float_invoke(TestFloat, 1, args + 1)); // test 3
assert_equals(9.0f, float_invoke(TestSumTwoFloats, 2, args)); // test 4
Случайно, тест 3 дает мне вывод мусора вместо возврата -1.0.
Мне интересно, если я
call
инструкция? fsts
инструкция?float
значение из stdcall
функция, которая возвращает float
????Вся помощь высоко ценится.
Не имея машины с Windows, я не могу полностью проверить это; в Linux следующее возвращает код возврата функции с плавающей точкой:
extern float something(int);
#include
#include
int main(int argc, char **argv)
{
int val = atoi(argv[1]);
float ret;
asm("pushl %1\n\t""call * %2\n\t""addl $4, %%esp": "=t"(ret)
: "r"(val), "r"(something)
: "%eax", "%ecx", "%edx", "memory", "cc");
printf("something(%d) == %f\n", val, ret);
return 0;
}
Ключ является использование "=t"(ret)
ограничение — это получает вершина стека с плавающей запятой, увидеть Машинные ограничения (из руководства gcc). Если Windows stdcall
возвращается float
результаты в ST(0)
а также, это должно работать, не нужно fld
/fst
как компилятор может сделать это для вас, если это необходимо.
Вам также необходимо указать memory
а также cc
Clobbers при вызове функций из встроенной сборки.
Вы разрешаете ссылку на память для указателя функции, GCC может построить ссылку относительно указателя стека при неверном предположении, что встроенная сборка не изменит его.