Есть ли лучший способ сохранить DF в функции или подпрограмме

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

OJBECTIVE: вернуть DF вызывающему абоненту без изменений.

00  55       push   bp
01  89E5     mov    bp, sp
03  9C       pushfw

04  FD       std

    ... Function/Subroutine body

05  58       pop    ax          ; Retrieve original flags
06  F6C404   test   ah, DF      ; Was DF already set
09  7506     jnz    Exit        ; If so, nothing to do

; Reset DF without altering state of any other flags

0B  9C       pushfw
0C  8066FFFB and byte [bp-1], 0FBH  ; Strip DF from MSB of EFLAGS
10  9D       popfw

       Exit:
11  C9       leave
12  C3       ret

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


Кто-нибудь реализовал метод, который приходит жестче, чем этот?

1 ответ

Решение

У вас есть один хороший вариант:

  • требуется очистка DF при входе / выходе из функции (за исключением частных вспомогательных функций, которые могут использовать пользовательские соглашения о вызовах). Функции, которые хотят использовать std может тогда просто использовать cld перед любым call или же ret Они делают.

    Использование других флагов как части возвращаемого значения тривиально: cld а также std не влияйте на них.

    В редком случае, когда вам нужно сделать вызов функции внутри цикла, который проходит в порядке убывания, возможно, не используйте lods или другие строковые инструкции вообще. Они не волшебные, а случайные dec si / mov al, [si] или что-то еще не является катастрофой для размера кода. Или это означает cld а также std внутри цикла, только 1 байт каждый.

    Большую часть времени вы хотите, чтобы DF сбрасывался, поэтому вы зацикливаетесь вверх, и в этом случае вы можете вызывать функции внутри циклов без каких-либо проблем. (Не всегда, но этот дизайн является лучшим для общего случая, и необычные случаи могут быть обработаны без особых проблем).

Один довольно хороший вариант:

  • Все флаги (включая DF) должны быть закрыты (и, если хотите, использованы как часть возвращаемого значения). Каждая строка операции нуждается в cld или же std внутри той же функции. Так что это не очень хороший вариант.

И один посредственный вариант:

  • Ваше текущее соглашение о вызовах, где DF сохраняется при вызове и имеет неизвестное значение при входе в функцию. Каждая функция, которая использует строковую инструкцию, нуждается в cld или же std потому что он ничего не может предположить, и сохранить / восстановить флаги. (Если вы не оптимизируете на основе известных абонентов и состояния, в которое они помещают DF).

Единственный раз, когда это имеет преимущество, - это цикл, содержащий вызов функции, который также хочет установить DF.

Если вы хотите вернуть статус в кодах условий FLAGS, а также сохранить / восстановить DF, просто поместите инструкцию, которая устанавливает коды условий в FLAGS после popf это восстанавливает DF звонящего.

В функциях, которые не имеют интересного статуса в кодах условий, просто используйте popf восстановить все вызывающие флаги. Вам не нужно объединять DF вызывающего с FLAGS текущей функции в функциях, которые не возвращают ничего интересного во FLAGS.

В редком случае, когда вы не можете легко переместить последнюю строковую инструкцию перед последней инструкцией установки флага, может быть меньше, чтобы эмулировать строковую инструкцию. Вместо inc или dec оставьте флаги без изменений lea si, [si +- 1], (И SI, и DI действительны в 16-битных режимах адресации, поэтому lea можно использовать на них.)

Ни один из lods / stos / movs волшебны, даже не их rep версии. Если вам не важна производительность (только размер кода), вы можете эмулировать даже rep movs не касаясь флагов, используя медленный loop инструкция и запасной регистр (сохранить / восстановить один с push / pop если нужно)


 0B  9C       pushfw
 0C  8066FFFB and byte [bp-1], 0FBH  ; Strip DF from MSB of EFLAGS
 10  9D       popfw

Ваш пример кода не восстанавливает DF вызывающего, он безоговорочно очищает его. Это эквивалентно cld,

Чтобы восстановить DF вызывающего абонента, вам нужно извлечь бит DF из первого pushf, объедините его в значение FLAGS из pushf в конце функции, а затем popf, Это очевидно возможно, но значительно более неэффективно (и больше по размеру кода), чем то, что вы показываете.


Также обратите внимание, что popf медленный: на Haswell это 9 моп, и имеет пропускную способность по одному на 18 циклов. Если вы заботитесь только о размере кода, а не о производительности, о проектах, которые требуют pushf / popf не обязательно плохие, но мне кажется, что требование DF clear при входе / выходе в большинстве случаев выигрывает как по размеру кода, так и по производительности.

Это то, что 32 и 64-битные соглашения о вызовах выбирают для обработки DF, и я не понимаю, почему это не сработает и в 16-битном коде.

Другие вопросы по тегам