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

Когда я пытаюсь скомпилировать этот код:

#include <stdio.h>

main(int argc, char *argv[]) {
   double y = 0;

   __asm__ ("fldl $150;"
            "fsqrt;"
            "fstl %0;" : : "g" (y) );

   printf("%f\n", y);


   return 0;
}

Я получаю эту ошибку:

sqrt.c: Assembler messages:
sqrt.c:6: Error: suffix or operands invalid for `fld'

Почему это не работает? Почему я не могу поместить число "150" в стек для операций с плавающей запятой?


person poundifdef    schedule 29.06.2011    source источник
comment
Попросите компилятор поместить 150.0 в st(0) для вас и скажите, что вы оставите результат в st(0): см. stackoverflow.com/questions/37509596/. Оставьте fld/fst компилятору, поэтому ваш ассемблер включает только "fsqrt" + некоторые ограничения операнда.   -  person Peter Cordes    schedule 26.10.2017
comment
Согласен с Питером. Если вы действительно хотите использовать для этого встроенный ассемблер, можно просто заставить C создать литерал с плавающей запятой и передать его в шаблон. Вы также можете использовать ограничения с плавающей запятой, чтобы помещать объекты на вершину стека FPU и возвращать их туда. Например double y = 150.0; __asm__ ( "fsqrt" : "+t"(y) );   -  person Michael Petch    schedule 26.10.2017


Ответы (5)


Я не знаю языка ассемблера, который поддерживает буквальные константы с плавающей запятой для немедленного использования. Обычный способ — объявить инициализированное хранилище, содержащее константу с плавающей запятой, и ссылаясь на нее:

const1:     dq  1.2345
...
     fldl    const1

В приведенном вами примере это можно сделать более прямо:

printf ("%f\n", sqrt (150));

В противном случае это должен быть искусственно усложненный проект, возможно, домашнее задание.

person wallyk    schedule 29.06.2011
comment
Я думаю, что x87 поддерживает загрузку 1 и 0, если вы считаете их немедленными;) - person tc.; 29.06.2011
comment
@тс. Существуют специальные инструкции для загрузки 1 и 0 на x87; Я бы не назвал их немедленными, точно. @wallyk: ARM на самом деле действительно имеет непосредственные значения с плавающей запятой в VFP ISA (однако не все значения с плавающей запятой могут быть представлены, поскольку непосредственное поле имеет ширину всего 8 бит). - person Stephen Canon; 29.06.2011
comment
Я бы избегал этого метода, потому что он не зависит от позиции. Вместо этого смотрите мой ответ. - person R.. GitHub STOP HELPING ICE; 29.06.2011
comment
Ха-ха-ха, я думаю, под искусственно усложненными вы имеете в виду обучение и любопытство с моей стороны! все хорошо? : P (также, что интересно, вывод gcc -S, когда я использую функцию sqrt() math.h, одновременно вызывает sqrt() и вызывает инструкцию sqrt, окруженную некоторыми условными выражениями и jmps. Но это отдельный вопрос. - person poundifdef; 29.06.2011
comment
Я не знаю языка ассемблера, который поддерживает буквальные константы с плавающей запятой для немедленного использования. GNU AS aarch64 делает это: ="как указать немедленные числа с плавающей запятой с помощью встроенной сборки"> stackoverflow.com/questions/6514537/ и NASM имеет __float32__(1.5) минимальный пример: github.com/cirosantilli/x86-assembly-cheat/blob/ см. также: stackoverflow.com/questions/29925432/ :-) - person Ciro Santilli; 20.10.2018
comment
@CiroSantilli: Кажется, это такая способность. Но он не обрабатывает его как константу с плавающей запятой: что касается инструкции, она перемещает 32-битное целое число. - person wallyk; 20.10.2018

Попробуйте что-нибудь вроде этого

push $0x????????
push $0x????????
fldl (%esp)
addl $8,%esp

Где ???????????? заменены представлением IEEE двойной константы. Преимущество этого метода в том, что он одинаково хорошо работает как в обычном, так и в позиционно-независимом коде (PIC, т.е. разделяемая библиотека).

person R.. GitHub STOP HELPING ICE    schedule 29.06.2011
comment
x86-64 имеет независимый от позиции доступ к статическим данным с помощью fldl myconst(%rip). Независимость от позиции в 32-битном коде стоит дорого; как правило, избегайте этого и позволяйте загрузчику времени выполнения исправлять ошибки во время загрузки программы. (Конечно, с x87, если ваша константа может быть точно представлена ​​как 32-битное float, вы можете сэкономить место только с одним push и загрузкой одинарной точности flds. В любом случае она преобразуется во внутреннее 80-битное представление.) - person Peter Cordes; 22.10.2018

t ограничение

Согласно документам GCC https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html#Machine-Constraints

t

Вершина стека 80387 операций с плавающей запятой (%st(0)).

Итак, мы можем сделать:

#include <assert.h>

int main(void) {
    double io = 4.0;
    __asm__ (
        "fsqrt"
        : "+t" (io)
        :
        :
    );
    assert(io == 2.0);
    return 0;
}

GitHub upstream.

Обновление: + означает, что io будет использоваться как для ввода, так и для вывода.

Протестировано в Ubuntu 19.04.

Сборка GNU GAS ARM поддерживает это

Например. в ARMv8:

main.c

#include <assert.h>

int main(void) {
    float my_float = 1.5;
    __asm__ (
        "fmov s0, 1.0;"
        "fadd %s[my_float], %s[my_float], s0;"
        : [my_float] "+w" (my_float)
        :
        : "s0"
    );
    assert(my_float == 2.5);
}

GitHub upstream.

Скомпилируйте и запустите:

aarch64-linux-gnu-gcc -o main.out -static -std=gnu99 main.c
qemu-aarch64 ./main.out

Модификатор %s упоминается в: встроенная сборка ARMv8 с плавающей запятой

Он также работает на ARMv7.

Однако по какой-то причине он работает только для инструкций с плавающей запятой, таких как fmov, например. следующая попытка сборки ARMv7 не удалась:

mov r0, 1.5

с ошибкой:

Error: garbage following instruction -- `mov r0,1.5'

предположительно потому, что он использует инструкцию mov, которая действует на регистры общего назначения, а не на регистры с плавающей запятой.

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

vmov s0, 1.5
vmov s1, 2.5
fadds s2, s0, s1
vmov s3, 4.0
/* Compare two floating point registers. Stores results in fpscr:
 * (floating point status and control register).
 */
vcmp.f32 s2, s3
/* Move the nzcv bits from fpscr to apsr */
vmrs apsr_nzcv, fpscr
/* This branch uses the Z bit of apsr, which was set accordingly. */
beq theyre_equal

GitHub upstream.

Меня никогда не перестанет забавлять, что GNU GAS имеет слегка отличающийся синтаксис для каждой арки!

Однако я не смог найти шестнадцатеричный синтаксис с плавающей запятой: использовать шестнадцатеричные литералы с плавающей запятой в GNU GAS?

Протестировано на Ubuntu 18.04.

person Ciro Santilli    schedule 20.10.2018

Единственными допустимыми операндами для инструкции fld являются память или регистр стека с плавающей запятой.

(Кроме того, вы указали y в качестве входного операнда для блока asm, в то время как он должен быть выходным. Вероятно, безопаснее также ограничить его памятью ("m", а не "g").)

Если вы действительно хотите сделать это с помощью встроенной сборки:

#include <stdio.h>

int main(void)
{
   double y;
   const double k = 150.0;

   __asm__ ("fldl %1;"
            "fsqrt;"
            "fstl %0;" : "=m" (y) : "m" (k) );

   printf("%f\n", y);

   return 0;
}
person Matthew Slattery    schedule 29.06.2011
comment
Кажется бессмысленным запрашивать константу в памяти вместо того, чтобы запрашивать ее на вершине стека FP и позволять компилятору выдавать fld. Каждый раз, когда первая и/или последняя инструкция в вашем ассемблерном шаблоне представляет собой mov или другую загрузку/сохранение, вы обычно делаете это неправильно. См. stackoverflow.com/questions/39728398/ и особенно это, обертывание fadds выводом регистра x87, вводом из st(0) и памяти. - person Peter Cordes; 26.10.2017

Вы можете обойти многие ассемблеры, отказывающиеся поддерживать литералы с плавающей запятой, если PHP предварительно обработает литерал для вас. (rawSingleHex взят из здесь). В идеальном мире было бы достаточно препроцессора C, но на данный момент это не так.

<?php
function rawSingleHex($num) {
    return '0x' . strrev(unpack('h*', pack('f', $num))[1]);
}
?>

#include <stdio.h>

int main(int argc, char **argv) {
   float y = 0;

   __asm__ ("pushl $<?php echo rawSingleHex(150);?>\n"
            "fsqrt\n"
            "fstl %0\n" : : "g" (y));

   printf("%f\n", y);


   return 0;
}

запустите php, чтобы сгенерировать файл c, и запустите компилятор c, чтобы скомпилировать вашу программу: P

person Dmitry    schedule 26.10.2017
comment
Мои глаза, они болят! :-) - person Ciro Santilli; 20.10.2018
comment
Это даже не работает; pushl помещает в стек вызовов (память, на которую указывает %ESP), а не в стек регистров x87. Кроме того, выходной операнд указан как входной, поэтому при оптимизации 32-битный компилятор будет выполнять распространение констант и использовать целочисленное нажатие, чтобы поместить нулевой аргумент для printf, независимо от того, что оператор asm искажает значение памяти (или регистра). из y. - person Peter Cordes; 22.06.2019