Bagaimana cara memaksa NASM untuk menyandikan [1 + rax*2] sebagai disp32 + indeks*2 alih-alih disp8 + base + indeks?

Untuk melakukan x = x*10 + 1 secara efisien, mungkin optimal untuk digunakan

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

LEA 3 komponen memiliki latensi lebih tinggi pada CPU Intel modern, misalnya. 3 siklus vs. 1 pada keluarga Sandybridge, jadi disp32 + index*2 lebih cepat daripada disp8 + base + index*1 pada keluarga SnB, yaitu sebagian besar CPU x86 mainstream yang ingin kami optimalkan. (Ini sebagian besar hanya berlaku untuk LEA, bukan memuat/menyimpan, karena LEA berjalan pada unit eksekusi ALU, bukan AGU di sebagian besar CPU x86 modern.) CPU AMD memiliki LEA yang lebih lambat dengan 3 komponen atau scale > 1 (http://agner.org/optimize/)

Namun NASM dan YASM akan mengoptimalkan ukuran kode dengan menggunakan [1 + rax + rax*1] untuk LEA ke-2, yang hanya memerlukan disp8 dan bukan disp32. (Mode pengalamatan selalu memiliki register dasar atau disp32).

yaitu mereka selalu membagi reg*2 menjadi base+index, karena itu tidak lebih buruk untuk ukuran kode.

Saya dapat memaksa menggunakan disp32 dengan lea eax, [dword 1 + rax*2], tetapi itu tidak menghentikan NASM atau YASM untuk memisahkan mode pengalamatan. Manual NASM sepertinya tidak mendokumentasikan cara menggunakan kata kunci strict pada faktor skala, dan [1 + strict rax*2] tidak berkumpul. Apakah ada cara untuk menggunakan strict atau sintaksis lain untuk memaksakan pengkodean mode pengalamatan yang diinginkan?


nasm -O0 untuk menonaktifkan optimasi tidak berfungsi. Rupanya itu hanya mengontrol optimasi perpindahan cabang multi-pass, bukan semua optimasi yang dilakukan NASM. Tentu saja Anda tidak ingin melakukan hal itu untuk seluruh file sumber, meskipun itu berhasil. saya masih mengerti

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

Satu-satunya solusi yang dapat saya pikirkan adalah menyandikannya secara manual dengan db. Hal ini cukup merepotkan. Sebagai catatan, pengkodean manualnya adalah:

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

Faktor skala dikodekan dalam 2 bit tinggi byte SIB. Saya merakit lea eax, [dword 1 + rax*4] untuk mendapatkan kode mesin untuk register yang benar, karena optimasi NASM hanya berfungsi untuk *2. SIB-nya adalah 0x85, dan pengurangan bidang 2-bit di bagian atas byte akan mengurangi faktor skala dari 4 menjadi 2.


Namun pertanyaannya adalah: bagaimana menulisnya dengan cara yang mudah dibaca sehingga memudahkan untuk mengubah register, dan membuat NASM menyandikan mode pengalamatan untuk Anda? (Saya kira makro raksasa dapat melakukan ini dengan pemrosesan teks dan pengkodean db manual, tapi itu bukan jawaban yang saya cari. Saya sebenarnya tidak memerlukan ini untuk apa pun saat ini, saya paling ingin tahu apakah NASM atau YASM memiliki sintaks untuk memaksakan ini.)

Pengoptimalan lain yang saya ketahui, seperti mov rax, 1 merakit menjadi 5-byte mov eax,1 adalah kemenangan murni pada semua CPU kecuali Anda menginginkan instruksi yang lebih panjang untuk mendapatkan padding tanpa NOP, dan dapat dinonaktifkan dengan mov rax, strict dword 1 untuk mendapatkan pengkodean perluasan tanda 7-byte, atau strict qword untuk imm64 10-byte.


gas tidak melakukan ini atau sebagian besar pengoptimalan lainnya (hanya ukuran perpindahan langsung dan cabang): lea 1(,%rax,2), %eax dirakit menjadi
8d 04 45 01 00 00 00 lea eax,[rax*2+0x1], dan sama untuk versi .intel_syntax noprefix.

Jawaban untuk MASM atau assembler lain juga menarik.


person Peter Cordes    schedule 18.02.2018    source sumber


Jawaban (1)


NOSPLIT:

Demikian pula, NASM akan membagi [eax*2] menjadi [eax+eax] karena hal ini memungkinkan bidang offset tidak ada dan ruang dapat dihemat; bahkan, ini juga akan membagi [eax*2+offset] menjadi [eax+eax+offset].
Anda dapat mengatasi perilaku ini dengan menggunakan kata kunci NOSPLIT: [nosplit eax*2] akan memaksa [eax*2+0] dihasilkan secara harfiah.
[nosplit eax*1] juga memiliki efek yang sama. Dengan cara lain, formulir EA terpisah [0, eax*2] juga dapat digunakan. Namun, NOSPLIT di [nosplit eax+eax] akan diabaikan karena niat pengguna di sini dianggap sebagai [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
Terima kasih, saya pikir saya ingat melihat sintaks untuk ini disebutkan di suatu tempat. Saya melewatkannya saat menelusuri hari ini karena saya berasumsi itu akan melibatkan strict. (Dan saya tidak mencari terlalu keras karena saya ingin menulis bagian pertunjukan di SO :P) - person Peter Cordes; 18.02.2018
comment
Sama-sama @Peter. Dokumen Nasm tidak memiliki glosarium kata kunci IMO, saya harus melihat kode sumbernya. Saya melihat apa yang dilakukan assembler lain: TASM tidak mengoptimalkan, YASM memiliki NOSPLIT, MASM 5 atau lebih lama tidak boleh dioptimalkan, MASM baru Saya tidak tahu (Tidak yakin apakah saya dapat menemukannya tanpa Visual Studio dan membuatnya berfungsi di Debian). - person Margaret Bloom; 18.02.2018
comment
Hmm, ya, glosarium akan berguna serta indeks yang ada, di mana Anda harus mengetahui apa yang Anda cari. Kalau dipikir-pikir, masuk akal untuk melihat bagian alamat efektif dari manual ini. - person Peter Cordes; 18.02.2018