Как заставить NASM кодировать [1 + rax * 2] как disp32 + index * 2 вместо disp8 + base + index?

Для эффективного выполнения x = x*10 + 1, вероятно, оптимально использовать

lea   eax, [rax + rax*4]   ; x*=5
lea   eax, [1 + rax*2]     ; x = x*2 + 1

3-компонентный LEA имеет более высокую задержку на современных процессорах Intel, например 3 цикла против 1 в семействе Sandybridge, поэтому disp32 + index*2 быстрее, чем disp8 + base + index*1 в семействе SnB, то есть в большинстве основных процессоров x86, для которых мы заботимся об оптимизации. (В основном это относится только к LEA, не загружает / сохраняет, потому что LEA работает на исполнительных модулях ALU, а не на AGU в большинстве современных процессоров x86.) Процессоры AMD имеют более медленный LEA с 3 компонентами или scale > 1 (http://agner.org/optimize/)

Но NASM и YASM оптимизируют размер кода, используя [1 + rax + rax*1] для второго LEA, которому требуется только disp8 вместо disp32. (Режимы адресации всегда имеют базовый регистр или disp32).

т.е. они всегда разбивают reg*2 на base+index, потому что это никогда не хуже для размера кода.

Я могу принудительно использовать disp32 с lea eax, [dword 1 + rax*2], но это не мешает NASM или YASM разделить режим адресации. Руководство NASM, похоже, не описывает способ использования ключевого слова strict От масштабного коэффициента, а [1 + strict rax*2] не собирается. Есть ли способ использовать strict или какой-либо другой синтаксис для принудительного выбора желаемой кодировки режима адресации?


nasm -O0 отключить оптимизацию не получается. Очевидно, это контролирует только многопроходную оптимизацию смещения ветвей, а не все оптимизации, которые делает NASM. Конечно, вы не хотите делать это в первую очередь для всего исходного файла, даже если он действительно работает. Я все еще получаю

8d 84 00 01 00 00 00    lea    eax,[rax+rax*1+0x1]

Единственное решение, которое я могу придумать, - это закодировать его вручную с помощью db. Это довольно неудобно. Для записи, ручное кодирование:

db 0x8d, 0x04, 0x45  ; opcode, modrm, SIB  for lea eax, [disp32 + rax*2]
dd 1                 ; disp32

Масштабный коэффициент кодируется в старших 2 битах байта SIB. Я собрал lea eax, [dword 1 + rax*4], чтобы получить машинный код для правильных регистров, потому что оптимизация NASM работает только для *2. SIB был 0x85, и уменьшение этого 2-битного поля в верхней части байта уменьшило масштабный коэффициент с 4 до 2.


Но возникает вопрос: как записать его в удобочитаемом виде, чтобы упростить изменение регистров и заставить NASM кодировать режим адресации за вас? (Полагаю, гигантский макрос мог бы сделать это с помощью обработка текста и ручное db кодирование, но это не совсем тот ответ, который я ищу. На самом деле мне это ни для чего не нужно прямо сейчас, я в основном хочу знать, есть ли у NASM или YASM синтаксис для принудительного выполнения этого.)

Другие известные мне оптимизации, такие как mov rax, 1 сборка в 5-байтовый mov eax,1, являются чистым выигрышем для всех процессоров, если вам не нужны более длинные инструкции для получения заполнения без NOP, и может быть отключен с помощью mov rax, strict dword 1, чтобы получить 7-байтовую кодировку с расширенным знаком, или strict qword для 10-байтового imm64.


gas не выполняет эту или большинство других оптимизаций (только размеры непосредственных смещений и смещений ветвей): lea 1(,%rax,2), %eax собирается в
8d 04 45 01 00 00 00 lea eax,[rax*2+0x1], и то же самое для версии .intel_syntax noprefix.

Тем не менее, ответы для MASM или других ассемблеров также были бы интересны.


person Peter Cordes    schedule 18.02.2018    source источник


Ответы (1)


NOSPLIT:

Точно так же NASM разделит [eax*2] на [eax+eax], потому что это позволяет отсутствовать полю смещения и сохранять пространство; фактически, он также разделит [eax*2+offset] на [eax+eax+offset].
С таким поведением можно бороться с помощью ключевого слова NOSPLIT: [nosplit eax*2] заставит [eax*2+0] генерироваться буквально.
[nosplit eax*1] также имеет такой же эффект. Также можно использовать разделенную форму советника [0, eax*2]. Однако NOSPLIT в [nosplit eax+eax] будет проигнорировано, потому что намерение пользователя здесь рассматривается как [eax+eax].

lea eax, [NOSPLIT 1+rax*2]
lea eax, [1+rax*2]

00000000  8D044501000000    lea eax,[rax*2+0x1]
00000007  8D440001          lea eax,[rax+rax+0x1]
person Margaret Bloom    schedule 18.02.2018
comment
Спасибо, я подумал, что вспомнил, что где-то видел синтаксис для этого. Я пропустил это при поиске сегодня, потому что я предполагал, что это будет связано с strict. (И я не очень усердно искал, потому что хотел написать часть производительности на SO: P) - person Peter Cordes; 18.02.2018
comment
Добро пожаловать, @Peter. В документе Nasm отсутствует глоссарий ключевых слов IMO, мне пришлось посмотреть исходный код. Я смотрел, что делает другой ассемблер: TASM не оптимизируется, YASM имеет NOSPLIT, MASM 5 или старше не должен оптимизировать, новый MASM, который я не знаю (не уверен, смогу ли я найти его без Visual Studio и заставить его работать в Debian). - person Margaret Bloom; 18.02.2018
comment
Хм, да, глоссарий был бы полезен так же, как существующий index, где вы должны знать, что вы ищете. Оглядываясь назад, было бы разумно взглянуть на раздел руководства, посвященный эффективному адресу. - person Peter Cordes; 18.02.2018