Подтвердить что ты не робот

Ассемблер x86: сравнение с плавающей запятой

Как часть проекта компилятора я должен написать код ассемблера GNU для x86, чтобы сравнить значения с плавающей запятой. Я попытался найти ресурсы о том, как сделать это онлайн, и насколько я понимаю, это работает так:

Предполагая, что два значения, которые я хочу сравнить, являются единственными значениями в стеке с плавающей запятой, тогда команда fcomi сравнит значения и установит флаги CPU, чтобы можно было использовать инструкции je, jne, jl ,....

Я спрашиваю, потому что это работает только иногда. Например:

.section    .data
msg:    .ascii "Hallo\n\0"
f1:     .float 10.0
f2:     .float 9.0

.globl main
    .type   main, @function
main:
    flds f1
    flds f2
    fcomi
    jg leb
    pushl $msg
    call printf
    addl $4, %esp
leb:
    pushl $0
    call exit

не будет печатать "Привет", хотя я думаю, что это должно, и если вы переключите f1 и f2, он все равно не будет, что является логическим противоречием. je и jne однако, кажется, работает хорошо.

Что я делаю неправильно?

PS: fcomip выдает только одно значение или оба?

4b9b3361

Ответ 1

TL: DR: используйте условия выше/ниже (например, для целого числа без знака), чтобы проверить результат сравнений.

По различным историческим причинам (отображение из слова состояния FP в FLAGS через fstsw/sahf который fcomi fcomi), FP сравнивает набор CF, а не OF или SF. Смотрите также http://www.ray.masmcode.com/tutorial/fpuchap7.htm


Это все из тома 2 Руководств разработчика программного обеспечения для архитектуры Intel 64 и IA-32.

FCOMI устанавливает только некоторые флаги, которые делает CMP. Ваш код имеет %st(0) == 9 и %st(1) == 10. (Так как это стек, в который они загружены), обращаясь к таблице на странице 3-348 в Томе 2А, вы можете видеть, что это случай "ST0 <ST (i)", поэтому он очистит ZF и PF и установит CF. Тем временем на стр. 3-544 том. 2А вы можете прочитать, что JG означает "Прыгай коротко, если больше (ZF = 0 и SF = OF)". Другими словами, он проверяет флаги знака, переполнения и нуля, но FCOMI не устанавливает знак или переполнение!

В зависимости от того, в каких условиях вы хотите прыгнуть, вы должны посмотреть на возможные результаты сравнения и решить, когда вы хотите прыгнуть.

+--------------------+---+---+---+
| Comparison results | Z | P | C |
+--------------------+---+---+---+
| ST0 > ST(i)        | 0 | 0 | 0 |
| ST0 < ST(i)        | 0 | 0 | 1 |
| ST0 = ST(i)        | 1 | 0 | 0 |
| unordered          | 1 | 1 | 1 |  one or both operands were NaN.
+--------------------+---+---+---+

Я сделал эту маленькую таблицу, чтобы было легче разобраться:

+--------------+---+---+-----+------------------------------------+
| Test         | Z | C | Jcc | Notes                              |
+--------------+---+---+-----+------------------------------------+
| ST0 < ST(i)  | X | 1 | JB  | ZF will never be set when CF = 1   |
| ST0 <= ST(i) | 1 | 1 | JBE | Either ZF or CF is ok              |
| ST0 == ST(i) | 1 | X | JE  | CF will never be set in this case  |
| ST0 != ST(i) | 0 | X | JNE |                                    |
| ST0 >= ST(i) | X | 0 | JAE | As long as CF is clear we are good |
| ST0 > ST(i)  | 0 | 0 | JA  | Both CF and ZF must be clear       |
+--------------+---+---+-----+------------------------------------+
Legend: X: don't care, 0: clear, 1: set

Другими словами, коды условий соответствуют кодам для использования беззнаковых сравнений. То же самое происходит, если вы используете FMOVcc.

Если один или оба операнда для fcomi равен NaN, он устанавливает ZF=1 PF=1 CF=1. (Сравнение FP имеет 4 возможных результата: >, <, == или неупорядоченный). Если вас волнует, что ваш код делает с NaN, вам может понадобиться дополнительный jp или jnp. Но не всегда: например, ja имеет значение true, только если CF = 0 и ZF = 0, поэтому он не будет принят в неупорядоченном случае. Если вы хотите, чтобы неупорядоченный случай использовал тот же путь выполнения, что и ниже или равный, тогда ja - это все, что вам нужно.


Здесь вы должны использовать JA если хотите, чтобы он печатал (т.е. if (!(f2 > f1)) { puts("hello"); }) и JBE если вы этого не делаете (соответствует if (!(f2 <= f1)) { puts("hello"); }). (Обратите внимание, что это может немного сбить с толку из-за того, что мы печатаем, только если не прыгаем).


Что касается вашего второго вопроса: по умолчанию fcomi ничего не fcomi. Вы хотите его близкого кузена fcomip который выдает %st0. Вы должны всегда очищать стек регистров fpu после использования, так что вся ваша программа заканчивается так, если вы хотите, чтобы сообщение было напечатано:

.section    .rodata
msg:    .ascii "Hallo\n\0"
f1:     .float 10.0
f2:     .float 9.0 

.globl main
    .type   main, @function
main:
    flds   f1
    flds   f2
    fcomip
    fstp   %st(0) # to clear stack
    ja     leb # won't jump, jbe will
    pushl  $msg
    call   printf
    addl   $4, %esp
leb:
    pushl  $0
    call   exit