Есть ли лучший способ сохранить 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-битном коде.