mov
-menengah adalah mahal untuk konstanta
Ini mungkin jelas, tetapi saya masih akan menaruhnya di sini. Secara umum, terbayar untuk memikirkan representasi bit-level dari angka ketika Anda perlu menginisialisasi nilai.
Inisialisasi eax
dengan 0
:
b8 00 00 00 00 mov $0x0,%eax
harus dipersingkat ( untuk kinerja serta ukuran kode ) menjadi
31 c0 xor %eax,%eax
Inisialisasi eax
dengan -1
:
b8 ff ff ff ff mov $-1,%eax
dapat disingkat menjadi
31 c0 xor %eax,%eax
48 dec %eax
atau
83 c8 ff or $-1,%eax
Atau lebih umum, setiap nilai sign-extended 8-bit dapat dibuat dalam 3 byte dengan push -12
(2 byte) / pop %eax
(1 byte). Ini bahkan berfungsi untuk register 64-bit tanpa awalan REX ekstra; push
/ pop
ukuran operan standar = 64.
6a f3 pushq $0xfffffffffffffff3
5d pop %rbp
Atau diberikan konstanta yang diketahui dalam register, Anda dapat membuat konstanta terdekat lainnya menggunakan lea 123(%eax), %ecx
(3 byte) Ini berguna jika Anda membutuhkan register yang memusatkan perhatian dan konstanta; xor-nol (2 byte) + lea-disp8
(3 byte).
31 c0 xor %eax,%eax
8d 48 0c lea 0xc(%eax),%ecx
Lihat juga Atur semua bit dalam register CPU ke 1 secara efisien
push 200; pop edx
- 3 byte untuk inisialisasi.dec
, misalnyaxor eax, eax; dec eax
push imm8
/pop reg
adalah 3 byte, dan fantastis untuk konstanta 64-bit pada x86-64, di manadec
/inc
adalah 2 byte. Danpush r64
/pop 64
(2 byte) bahkan dapat menggantikan 3 bytemov r64, r64
(3 byte dengan REX). Lihat juga Setel semua bit dalam register CPU ke 1 secara efisien untuk hal-hal sepertilea eax, [rcx-1]
diberi nilai yang diketahuieax
(misalnya jika perlu register nol dan konstanta lain, gunakan saja LEA alih-alih push / popDalam banyak kasus, instruksi berbasis akumulator (yaitu yang mengambil
(R|E)AX
sebagai operan tujuan) adalah 1 byte lebih pendek dari instruksi kasus umum; lihat pertanyaan ini di StackOverflow.sumber
al, imm8
kasus khusus, sepertior al, 0x20
/sub al, 'a'
/cmp al, 'z'-'a'
/ja .non_alphabetic
masing-masing 2 byte, bukan 3. Menggunakanal
untuk data karakter juga memungkinkanlodsb
dan / ataustosb
. Atau gunakanal
untuk menguji sesuatu tentang byte rendah EAX, sepertilodsd
/test al, 1
/setnz cl
membuat cl = 1 atau 0 untuk odd / even. Tetapi dalam kasus yang jarang di mana Anda membutuhkan 32-bit segera, maka pastiop eax, imm32
, seperti dalam jawaban kunci kroma sayaPilih konvensi pemanggilan Anda untuk menempatkan args di tempat yang Anda inginkan.
Bahasa jawaban Anda adalah asm (sebenarnya kode mesin), jadi perlakukan itu sebagai bagian dari program yang ditulis dalam asm, bukan C-compile-for-x86. Fungsi Anda tidak harus mudah dipanggil dari C dengan konvensi panggilan standar apa pun. Itu bonus yang bagus jika tidak dikenakan biaya byte tambahan.
Dalam program asm murni, adalah normal bagi beberapa fungsi pembantu untuk menggunakan konvensi panggilan yang nyaman bagi mereka dan bagi pemanggil mereka. Fungsi-fungsi tersebut mendokumentasikan konvensi panggilan mereka (input / output / clobbers) dengan komentar.
Dalam kehidupan nyata, bahkan program AS pun cenderung (saya pikir) cenderung menggunakan konvensi pemanggilan yang konsisten untuk sebagian besar fungsi (terutama di seluruh file sumber yang berbeda), tetapi fungsi penting yang diberikan dapat melakukan sesuatu yang istimewa. Dalam kode-golf, Anda mengoptimalkan omong kosong dari satu fungsi tunggal, jadi jelas ini penting / spesial.
Untuk menguji fungsi Anda dari program C, dapat menulis pembungkus yang menempatkan args di tempat yang tepat, menyimpan / mengembalikan register tambahan yang Anda clobber, dan memasukkan nilai
e/rax
baliknya jika belum ada di sana.Batas-batas apa yang masuk akal: apa pun yang tidak membebani penelepon:
Memerlukan DF (tanda arah string untuk
lods
/stos
/ dll.) Agar jelas (ke atas) saat panggilan / ret adalah normal. Membiarkannya tidak ditentukan pada panggilan / ret akan baik-baik saja. Membutuhkannya untuk dihapus atau ditetapkan pada entri tetapi kemudian membiarkannya dimodifikasi ketika Anda kembali akan aneh.Mengembalikan nilai FP di x87
st0
masuk akal, tetapi kembalist3
dengan sampah di register x87 lain tidak. Penelepon harus membersihkan tumpukan x87. Bahkan kembalist0
dengan register tumpukan tinggi yang tidak kosong juga akan dipertanyakan (kecuali jika Anda mengembalikan beberapa nilai).call
, demikian[rsp]
juga alamat pengirim Anda. Anda dapat menghindaricall
/ret
pada x86 menggunakan register tautan sepertilea rbx, [ret_addr]
/jmp function
dan kembali denganjmp rbx
, tetapi itu tidak "masuk akal". Itu tidak seefisien panggilan / ret, jadi itu bukan sesuatu yang masuk akal Anda akan menemukan dalam kode nyata.Borderline cases: menulis fungsi yang menghasilkan urutan dalam array, mengingat 2 elemen pertama sebagai argumen fungsi . Saya memilih agar penelepon menyimpan awal urutan ke dalam array dan hanya meneruskan pointer ke array. Ini jelas menekuk persyaratan pertanyaan itu. Aku dianggap mengambil args dikemas ke dalam
xmm0
untukmovlps [rdi], xmm0
, yang juga akan menjadi konvensi pemanggilan aneh.Kembalikan boolean dalam FLAGS (kode kondisi)
Panggilan sistem OS X melakukan ini (
CF=0
berarti tidak ada kesalahan): Apakah dianggap praktik yang buruk untuk menggunakan register bendera sebagai nilai pengembalian boolean? .Segala kondisi yang dapat diperiksa dengan satu JCC sangat masuk akal, terutama jika Anda dapat memilih satu yang memiliki relevansi semantik dengan masalah tersebut. (misalnya fungsi membandingkan mungkin mengatur bendera sehingga
jne
akan diambil jika mereka tidak sama).Membutuhkan arg yang sempit (seperti a
char
) untuk bertanda atau nol diperpanjang menjadi 32 atau 64 bit.Ini tidak masuk akal; menggunakan
movzx
ataumovsx
untuk menghindari penurunan parsial register adalah normal dalam asm x86 modern. Bahkan dentang / LLVM sudah membuat kode yang tergantung pada ekstensi tidak berdokumen pada konvensi pemanggilan Sistem x86-64: args yang lebih sempit dari 32 bit bertanda atau nol diperluas menjadi 32 bit oleh pemanggil .Anda dapat mendokumentasikan / mendeskripsikan ekstensi ke 64 bit dengan menulis
uint64_t
atauint64_t
dalam prototipe Anda jika Anda mau. mis. sehingga Anda dapat menggunakanloop
instruksi, yang menggunakan seluruh 64 bit RCX kecuali Anda menggunakan awalan ukuran-alamat untuk menimpa ukuran ke ECX 32 bit (ya, ukuran alamat bukan ukuran operan).Perhatikan bahwa
long
hanya tipe 32-bit pada Windows 64-bit ABI, dan Linux x32 ABI ;uint64_t
tidak ambigu dan lebih pendek untuk diketik daripadaunsigned long long
.Konvensi panggilan yang ada:
Windows 32-bit
__fastcall
, sudah disarankan oleh jawaban lain : integer args diecx
danedx
.x86-64 Sistem V : melewatkan banyak argumen dalam register, dan memiliki banyak register yang berantakan yang dapat Anda gunakan tanpa awalan REX. Lebih penting lagi, itu sebenarnya dipilih untuk memungkinkan kompiler untuk inline
memcpy
atau memset denganrep movsb
mudah: 6 integer / pointer arg pertama dilewatkan dalam RDI, RSI, RDX, RCX, R8, R9.Jika fungsi Anda menggunakan
lodsd
/stosd
di dalam loop yang berjalanrcx
kali (denganloop
instruksi), Anda dapat mengatakan "callable from C sepertiint foo(int *rdi, const int *rsi, int dummy, uint64_t len)
dengan konvensi pemanggilan System V x86-64". contoh: chromakey .GCC 32-bit
regparm
: Integer args dalam EAX , ECX, EDX, return dalam EAX (atau EDX: EAX). Memiliki arg pertama dalam register yang sama dengan nilai pengembalian memungkinkan beberapa optimasi, seperti kasus ini dengan contoh pemanggil dan prototipe dengan atribut fungsi . Dan tentu saja AL / EAX khusus untuk beberapa instruksi.Linux x32 ABI menggunakan pointer 32-bit dalam mode panjang, sehingga Anda dapat menyimpan awalan REX saat memodifikasi pointer ( contoh case-use ). Anda masih dapat menggunakan ukuran alamat 64-bit, kecuali jika Anda memiliki bilangan bulat negatif 32-bit yang diperpanjang di register (jadi itu akan menjadi nilai besar yang tidak ditandatangani jika Anda melakukannya
[rdi + rdx]
).Perhatikan bahwa
push rsp
/pop rax
adalah 2 byte, dan setara denganmov rax,rsp
, sehingga Anda masih dapat menyalin register 64-bit penuh dalam 2 byte.sumber
ret 16
; mereka tidak memunculkan alamat pengirim, mendorong array, lalupush rcx
/ret
. Penelepon harus mengetahui ukuran array atau telah menyimpan RSP di suatu tempat di luar tumpukan untuk menemukan sendiri.Gunakan penyandian bentuk pendek kasus khusus untuk AL / AX / EAX, dan formulir pendek lainnya dan instruksi byte tunggal
Contoh mengasumsikan mode 32/64-bit, di mana ukuran operan default adalah 32 bit. Awalan ukuran operan mengubah instruksi ke AX alih-alih EAX (atau terbalik dalam mode 16-bit).
inc/dec
register (selain 8-bit):inc eax
/dec ebp
. (Bukan x86-64:0x4x
byte opcode digunakan kembali sebagai awalan REX, jadiinc r/m32
adalah satu-satunya penyandian.)8-bit
inc bl
adalah 2 byte, menggunakaninc r/m8
opcode + ModR / M operand encoding . Jadi gunakaninc ebx
untuk menambahbl
, jika aman. (mis. jika Anda tidak memerlukan hasil ZF dalam kasus di mana byte atas mungkin tidak nol).scasd
:e/rdi+=4
, mensyaratkan bahwa register menunjuk ke memori yang dapat dibaca. Terkadang bermanfaat bahkan jika Anda tidak peduli dengan hasil FLAGS (seperticmp eax,[rdi]
/rdi+=4
). Dan dalam mode 64-bit,scasb
dapat berfungsi sebagai 1-byteinc rdi
, jika lodsb atau stosb tidak berguna.xchg eax, r32
: Ini adalah di mana 0x90 NOP berasal dari:xchg eax,eax
. Contoh: mengatur ulang 3 register dengan duaxchg
instruksi dalamcdq
/idiv
loop untuk GCD dalam 8 byte di mana sebagian besar instruksi adalah single-byte, termasuk penyalahgunaaninc ecx
/loop
bukannyatest ecx,ecx
/jnz
cdq
: tandatangani-rentangkan EAX ke dalam EDX: EAX, yaitu menyalin bit EAX yang tinggi ke semua bit EDX. Untuk membuat nol dengan diketahui non-negatif, atau untuk mendapatkan 0 / -1 untuk ditambahkan / sub atau topeng. pelajaran sejarah x86:cltq
vs.movslq
, dan juga AT&T vs Intel mnemonics untuk ini dan yang terkaitcdqe
.lodsb / d : suka
mov eax, [rsi]
/rsi += 4
tanpa bendera yang rusak. (Dengan anggapan DF jelas, konvensi panggilan standar mana yang diperlukan pada entri fungsi.) Juga stosb / d, terkadang scas, dan lebih jarang bergerak / cmps.push
/pop reg
. misalnya dalam mode 64-bit,push rsp
/pop rdi
adalah 2 byte, tetapimov rdi, rsp
membutuhkan awalan REX dan 3 byte.xlatb
ada, tetapi jarang bermanfaat. Tabel pencarian besar adalah sesuatu yang harus dihindari. Saya juga tidak pernah menemukan penggunaan untuk AAA / DAA atau instruksi paket-BCD atau 2-ASCII lainnya.1-byte
lahf
/sahf
jarang bermanfaat. Anda bisalahf
/and ah, 1
sebagai alternatifsetc ah
, tetapi biasanya tidak berguna.Dan untuk CF secara khusus, ada
sbb eax,eax
untuk mendapatkan 0 / -1, atau bahkan 1-byte yang tidak didokumentasikan tetapi didukung secara universalsalc
(atur AL dari Carry) yang secara efektif tidaksbb al,al
mempengaruhi flag. (Dihapus di x86-64). Saya menggunakan SALC dalam Tantangan Penghargaan Pengguna # 1: Dennis ♦ .1-byte
cmc
/clc
/stc
(flip ("pelengkap"), jelas, atau set CF) jarang berguna, meskipun saya menemukan penggunaan untukcmc
penambahan presisi-tinggi dengan basis 10 ^ 9 potongan. Untuk mengatur / menghapus CF tanpa syarat, biasanya mengatur agar itu terjadi sebagai bagian dari instruksi lain, misalnyaxor eax,eax
membersihkan CF dan juga EAX. Tidak ada instruksi yang setara untuk flag kondisi lain, hanya DF (arah string) dan IF (interupsi). Bendera carry khusus untuk banyak instruksi; shift mengaturnya,adc al, 0
dapat menambahkannya ke AL dalam 2 byte, dan saya sebutkan sebelumnya SALC tidak berdokumen.std
Sayacld
jarang terlihat sepadan . Khususnya dalam kode 32-bit, lebih baik gunakan sajadec
pada pointer danmov
atau sumber memori operan ke instruksi ALU daripada mengatur DF begitulodsb
/stosb
turun ke bawah bukan ke atas. Biasanya jika Anda perlu ke bawah sama sekali, Anda masih memiliki pointer lain naik, jadi Anda akan membutuhkan lebih dari satustd
dancld
seluruh fungsi untuk menggunakanlods
/stos
untuk keduanya. Sebagai gantinya, cukup gunakan instruksi string untuk arah ke atas. (Konvensi panggilan standar menjamin DF = 0 pada entri fungsi, sehingga Anda dapat menganggap itu gratis tanpa menggunakancld
.)8086 sejarah: mengapa pengkodean ini ada
Di asli 8086, AX sangat istimewa: petunjuk suka
lodsb
/stosb
,cbw
,mul
/div
dan lain-lain menggunakannya secara implisit. Tentu saja masih demikian; x86 saat ini belum menjatuhkan opcodes 8086 (setidaknya tidak ada yang secara resmi didokumentasikan). Tetapi kemudian CPU menambahkan instruksi baru yang memberikan cara yang lebih baik / lebih efisien untuk melakukan sesuatu tanpa menyalin atau menukar mereka ke AX terlebih dahulu. (Atau ke EAX dalam mode 32-bit.)misal 8086 tidak memiliki tambahan tambahan seperti
movsx
/movzx
untuk memuat atau memindahkan + sign-extended, atau 2 dan 3 operanimul cx, bx, 1234
yang tidak menghasilkan hasil setengah tinggi dan tidak memiliki operan implisit.Juga, hambatan utama 8086 adalah instruksi-ambil, jadi mengoptimalkan ukuran kode penting untuk kinerja saat itu . Perancang ISA 8086 (Stephen Morse) menghabiskan banyak ruang pengkodean opcode pada case khusus untuk AX / AL, termasuk opcode khusus (E) AX / AL-tujuan untuk semua instruksi ALU- direct-src dasar , hanya opcode + direct tanpa byte ModR / M. 2-byte
add/sub/and/or/xor/cmp/test/... AL,imm8
atauAX,imm16
atau (dalam mode 32-bit)EAX,imm32
.Tetapi tidak ada kasus khusus untuk
EAX,imm8
, sehingga pengkodean ModR / M reguleradd eax,4
lebih pendek.Asumsinya adalah jika Anda akan mengerjakan beberapa data, Anda akan menginginkannya di AX / AL, jadi bertukar register dengan AX adalah sesuatu yang mungkin ingin Anda lakukan, mungkin bahkan lebih sering daripada menyalin register ke AX dengan
mov
.Segala sesuatu tentang 8086 instruksi pengkodean mendukung paradigma ini, dari instruksi seperti
lodsb/w
untuk semua pengkodean kasus khusus untuk segera dengan EAX hingga penggunaan implisitnya bahkan untuk penggandaan / pembagian.Jangan terbawa; itu tidak otomatis menang untuk menukar semuanya ke EAX, terutama jika Anda perlu menggunakan segera dengan register 32-bit, bukan 8-bit. Atau jika Anda perlu interleave operasi pada beberapa variabel dalam register sekaligus. Atau jika Anda menggunakan instruksi dengan 2 register, tidak segera sama sekali.
Tetapi selalu ingat: apakah saya melakukan sesuatu yang lebih pendek di EAX / AL? Dapatkah saya mengatur ulang sehingga saya memiliki ini dalam AL, atau apakah saya saat ini mengambil keuntungan lebih baik dari AL dengan apa yang sudah saya gunakan untuk itu.
Campurkan operasi 8-bit dan 32-bit secara bebas untuk mengambil keuntungan kapan pun aman untuk melakukannya (Anda tidak perlu melakukan daftar lengkap atau apa pun).
sumber
cdq
berguna untukdiv
kebutuhan yang memusatkan perhatianedx
dalam banyak kasus.cdq
sebelum tidak ditandatanganidiv
jika Anda tahu dividen Anda di bawah 2 ^ 31 (yaitu non-negatif ketika diperlakukan sebagai ditandatangani), atau jika Anda menggunakannya sebelum menetapkaneax
ke nilai berpotensi-besar. Biasanya (di luar kode-golf) Anda akan menggunakancdq
sebagai pengaturan untukidiv
, danxor edx,edx
sebelumdiv
Gunakan
fastcall
konvensiPlatform x86 memiliki banyak konvensi panggilan . Anda harus menggunakan yang lulus parameter dalam register. Pada x86_64, beberapa parameter pertama dilewatkan dalam register, jadi tidak ada masalah di sana. Pada platform 32-bit, konvensi pemanggilan standar (
cdecl
) melewati parameter dalam stack, yang tidak baik untuk bermain golf - mengakses parameter pada stack membutuhkan instruksi panjang.Saat menggunakan
fastcall
platform 32-bit, 2 parameter pertama biasanya diteruskanecx
danedx
. Jika fungsi Anda memiliki 3 parameter, Anda dapat mempertimbangkan untuk mengimplementasikannya pada platform 64-bit.Prototipe fungsi C untuk
fastcall
konvensi (diambil dari contoh jawaban ini ):sumber
Kurangi -128 alih-alih tambahkan 128
Samely, tambahkan -128 bukannya kurangi 128
sumber
< 128
ke dalam<= 127
untuk mengurangi besarnya operan langsung untukcmp
, atau gcc selalu lebih suka menata ulang membandingkan untuk mengurangi besarnya bahkan jika itu bukan -129 vs -128.Buat 3 nol dengan
mul
(laluinc
/dec
untuk mendapatkan +1 / -1 dan juga nol)Anda dapat nol eax dan edx dengan mengalikan dengan nol di register ketiga.
akan menghasilkan EAX, EDX, dan EBX semuanya menjadi nol hanya dalam empat byte. Anda dapat mem-nolkan EAX dan EDX dalam tiga byte:
Tetapi dari titik awal itu Anda tidak bisa mendapatkan register zeroed 3 dalam satu byte lagi, atau register +1 atau -1 dalam 2 byte lainnya. Sebaliknya, gunakan teknik mul.
Contoh penggunaan-huruf: menggabungkan angka-angka Fibonacci dalam biner .
Perhatikan bahwa setelah
LOOP
loop selesai, ECX akan menjadi nol dan dapat digunakan untuk nol EDX dan EAX; Anda tidak selalu harus membuat nol pertama denganxor
.sumber
Register dan flag CPU berada dalam status startup yang dikenal
Kita dapat mengasumsikan bahwa CPU dalam keadaan default yang dikenal dan didokumentasikan berdasarkan pada platform dan OS.
Sebagai contoh:
DOS http://www.fysnet.net/yhelhel.htm
Linux x86 ELF http://asm.sourceforge.net/articles/startup.html
sumber
_start
. Jadi ya itu permainan yang adil untuk mengambil keuntungan dari itu jika Anda sedang menulis sebuah program alih-alih fungsi. Saya melakukannya di Extreme Fibonacci . (Dalam executable yang terhubung secara dinamis, ld.so berjalan sebelum melompat ke Anda_start
, dan tidak meninggalkan sampah di register, tetapi statis hanyalah kode Anda.)Untuk menambah atau mengurangi 1, gunakan satu byte
inc
ataudec
instruksi yang lebih kecil dari multibyte tambah dan sub instruksi.sumber
inc/dec r32
dengan nomor register yang dikodekan dalam opcode. Jadiinc ebx
1 byte, tetapiinc bl
2. Masih lebih keciladd bl, 1
dari tentu saja, untuk register selainal
. Perhatikan juga bahwainc
/dec
biarkan CF tidak dimodifikasi, tetapi perbarui flag lainnya.lea
untuk matematikaIni mungkin salah satu hal pertama yang dipelajari tentang x86, tapi saya tinggalkan di sini sebagai pengingat.
lea
dapat digunakan untuk melakukan perkalian dengan 2, 3, 4, 5, 8, atau 9, dan menambahkan offset.Misalnya, untuk menghitung
ebx = 9*eax + 3
dalam satu instruksi (dalam mode 32-bit):Ini dia tanpa offset:
Wow! Tentu saja,
lea
dapat digunakan juga untuk melakukan matematika sepertiebx = edx + 8*eax + 3
untuk menghitung pengindeksan array.sumber
lea eax, [rcx + 13]
adalah versi awalan tanpa tambahan untuk mode 64-bit. Ukuran operan 32-bit (untuk hasilnya) dan ukuran alamat 64-bit (untuk input).Instruksi loop dan string lebih kecil dari urutan instruksi alternatif. Paling berguna adalah
loop <label>
yang lebih kecil dari dua urutan instruksidec ECX
danjnz <label>
, danlodsb
lebih kecil darimov al,[esi]
daninc si
.sumber
mov
kecil segera masuk ke register yang lebih rendah bila berlakuJika Anda sudah tahu bit atas dari register adalah 0, Anda dapat menggunakan instruksi yang lebih pendek untuk memindahkan langsung ke register yang lebih rendah.
melawan
Gunakan
push
/pop
untuk imm8 ke nol bit atasPenghargaan untuk Peter Cordes.
xor
/mov
adalah 4 byte, tetapipush
/pop
hanya 3!sumber
mov al, 0xa
bagus jika Anda tidak perlu diperpanjang nol ke reg penuh. Tetapi jika Anda melakukannya, xor / mov adalah 4 byte vs 3 untuk push imm8 / pop ataulea
dari konstanta lain yang diketahui. Ini bisa berguna dalam kombinasi denganmul
nol register 3 dalam 4 byte , ataucdq
, jika Anda membutuhkan banyak konstanta.[0x80..0xFF]
, yang tidak dapat direpresentasikan sebagai imm8 yang diperpanjang tanda. Atau jika Anda sudah tahu byte atas, misalnyamov cl, 0x10
setelahloop
instruksi, karena satu-satunya cara untukloop
tidak melompat adalah ketika dibuatrcx=0
. (Saya kira Anda mengatakan ini, tetapi contoh Anda menggunakan axor
). Anda bahkan dapat menggunakan byte rendah dari register untuk sesuatu yang lain, selama sesuatu yang lain mengembalikannya ke nol (atau apa pun) ketika Anda selesai. mis. Program Fibonacci saya terus-1024
naik, dan menggunakan bl.xchg eax, r32
) misalnyamov bl, 10
/dec bl
/jnz
jadi kode Anda tidak peduli dengan byte tinggi RBX.The FLAGS ditetapkan setelah banyak instruksi
Setelah banyak instruksi aritmatika, Bendera Carry (tidak bertanda) dan Bendera Overflow (ditandatangani) diatur secara otomatis ( info lebih lanjut ). Tanda Bendera dan Nol Bendera ditetapkan setelah banyak operasi aritmatika dan logis. Ini dapat digunakan untuk percabangan bersyarat.
Contoh:
ZF diatur oleh instruksi ini, sehingga kami dapat menggunakannya untuk percabangan opsional.
sumber
test al,1
; Anda biasanya tidak mendapatkannya secara gratis. (Atauand al,1
untuk membuat bilangan bulat 0/1 tergantung ganjil / genap.)test
/cmp
", maka itu akan menjadi x86 pemula yang cukup mendasar, tetapi masih layak mendapat upvote.Gunakan do-while loop sebagai ganti while loop
Ini bukan spesifik x86 tetapi tip perakitan pemula yang berlaku luas. Jika Anda tahu loop sementara akan berjalan setidaknya sekali, menulis ulang loop sebagai loop do-while, dengan memeriksa kondisi loop di akhir, sering menyimpan instruksi lompat 2 byte. Dalam kasus khusus Anda bahkan mungkin dapat menggunakan
loop
.sumber
do{}while()
idiom pengulangan alami dalam perakitan (terutama untuk efisiensi). Perhatikan juga bahwa 2-bytejecxz
/jrcxz
sebelum loop bekerja dengan sangat baikloop
untuk menangani "kebutuhan untuk menjalankan nol kali" case "secara efisien" (pada CPU langka di manaloop
tidak lambat).jecxz
juga dapat digunakan di dalam loop untuk mengimplementasikanwhile(ecx){}
, denganjmp
di bagian bawah.Gunakan konvensi panggilan apa pun yang nyaman
Sistem V x86 menggunakan stack dan sistem V x86-64 kegunaan
rdi
,rsi
,rdx
,rcx
, dll untuk parameter input, danrax
sebagai nilai kembali, tetapi masuk akal untuk menggunakan konvensi menelepon Anda sendiri. __Panggilan cepat digunakanecx
danedx
sebagai parameter input, dan kompiler / OS lain menggunakan konvensi mereka sendiri . Gunakan tumpukan dan register apa pun sebagai input / output saat nyaman.Contoh: Penghitung byte berulang , menggunakan konvensi panggilan pintar untuk solusi 1 byte.
Meta: Menulis input ke register , Menulis output ke register
Sumber lain: Catatan Agner Fog tentang konvensi pemanggilan
sumber
int 0x80
yang membutuhkan banyak pengaturan.int 0x80
dalam kode 32-bit, atausyscall
dalam kode 64-bit, untuk memohonsys_write
, adalah satu-satunya cara yang baik. Itu yang saya gunakan untuk Extreme Fibonacci . Dalam kode 64-bit__NR_write = 1 = STDOUT_FILENO
,, jadi Anda bisamov eax, edi
. Atau jika byte atas EAX adalah nol,mov al, 4
dalam kode 32-bit. Anda juga bisacall printf
atauputs
, saya kira, dan menulis jawaban "x86 asm for Linux + glibc". Saya pikir masuk akal untuk tidak menghitung ruang entri PLT atau GOT, atau kode perpustakaan itu sendiri.char*buf
dan menghasilkan string di dalamnya, dengan pemformatan manual. misal seperti ini (canggung dioptimalkan untuk kecepatan) asm FizzBuzz , di mana saya memasukkan data string ke register dan kemudian menyimpannyamov
, karena string pendek dan panjang tetap.Gunakan gerakan
CMOVcc
dan set kondisionalSETcc
Ini lebih merupakan pengingat bagi saya, tetapi instruksi set bersyarat ada dan instruksi pemindahan bersyarat ada pada prosesor P6 (Pentium Pro) atau yang lebih baru. Ada banyak instruksi yang didasarkan pada satu atau lebih dari flag yang diatur dalam EFLAGS.
sumber
cmov
memiliki 2-byte opcode (0F 4x +ModR/M
) sehingga minimal 3 byte. Tetapi sumbernya adalah r / m32, sehingga Anda dapat memuat secara kondisional dalam 3 byte. Selain percabangan,setcc
berguna dalam lebih banyak kasus daripadacmovcc
. Namun, pertimbangkan seluruh rangkaian instruksi, bukan hanya instruksi dasar 386. (Meskipun SSE2 dan BMI / BMI2 instruksi sangat besar sehingga mereka jarang berguna.rorx eax, ecx, 32
Adalah 6 byte, lebih lama dari mov + ror. Bagus untuk kinerja, bukan golf kecuali POPCNT atau PDEP menyimpan banyak isn)setcc
.Menghemat
jmp
byte dengan mengatur if / then daripada if / then / elseIni tentu sangat mendasar, hanya berpikir saya akan memposting ini sebagai sesuatu untuk dipikirkan ketika bermain golf. Sebagai contoh, pertimbangkan kode sederhana berikut untuk mendekode karakter digit heksadesimal:
Ini dapat dipersingkat dua byte dengan membiarkan case "then" jatuh ke case "else":
sumber
sub
latensi ekstra pada jalur kritis untuk satu case bukan bagian dari rantai ketergantungan loop-carry (seperti di sini di mana setiap digit input independen hingga penggabungan potongan 4-bit ). Tapi saya rasa +1 juga. BTW, contoh Anda memiliki pengoptimalan terlewatkan yang terpisah: jika Anda tetap akan membutuhkannyamovzx
di akhir, maka gunakansub $imm, %al
bukan EAX untuk mengambil keuntungan dari pengkodean 2-byte no-modrmop $imm, %al
.cmp
dengan melakukansub $'A'-10, %al
;jae .was_alpha
;add $('A'-10)-'0'
. (Saya pikir saya punya logika yang benar). Perhatikan bahwa'A'-10 > '9'
jadi tidak ada ambiguitas. Mengurangi koreksi untuk huruf akan membungkus angka desimal. Jadi ini aman jika kita mengasumsikan input kita adalah hex yang valid, sama seperti input Anda.Anda dapat mengambil objek berurutan dari tumpukan dengan mengatur esi ke esp, dan melakukan urutan lodsd / xchg reg, eax.
sumber
pop eax
/pop edx
/ ...? Jika Anda harus meninggalkannya di stack, Anda dapatpush
mengembalikan semuanya setelah memulihkan ESP, masih 2 byte per objek tanpa perlumov esi,esp
. Atau maksud Anda untuk objek 4-byte dalam kode 64-bit di manapop
akan mendapatkan 8 byte? BTW, Anda bahkan dapat menggunakanpop
untuk mengulang buffer dengan kinerja yang lebih baik daripadalodsd
, misalnya untuk penambahan presisi yang tinggi dalam Extreme FibonacciUntuk codegolf dan ASM: Gunakan instruksi hanya menggunakan register, tekan pop, meminimalkan memori register atau memori segera
sumber
Untuk menyalin register 64-bit, gunakan
push rcx
;pop rdx
bukannya 3 bytemov
.Ukuran operan standar untuk push / pop adalah 64-bit tanpa memerlukan awalan REX.
(Awalan ukuran operan dapat mengesampingkan ukuran push / pop menjadi 16-bit, tetapi ukuran operan / push 32-bit tidak dapat dikodekan dalam mode 64-bit bahkan dengan REX.W = 0.)
Jika salah satu atau kedua register adalah
r8
..r15
, gunakanmov
karena push dan / atau pop akan memerlukan awalan REX. Kasus terburuk ini sebenarnya hilang jika keduanya membutuhkan awalan REX. Tentunya Anda biasanya harus menghindari r8..r15 pula dalam kode golf.Anda dapat membuat sumber Anda lebih mudah dibaca saat berkembang dengan makro NASM ini . Ingatlah bahwa ia menginjak 8 byte di bawah RSP. (Di zona merah di Sistem x86-64 V). Tetapi dalam kondisi normal itu adalah pengganti drop-in untuk 64-bit
mov r64,r64
ataumov r64, -128..127
Contoh:
Bagian
xchg
dari contoh ini adalah karena kadang-kadang Anda perlu mendapatkan nilai ke EAX atau RAX dan tidak peduli tentang mempertahankan salinan lama. push / pop tidak membantu Anda bertukar sebenarnya.sumber