kode mesin i386 (x86-32), 8 byte (9B untuk unsigned)
+1 1 jika kita perlu menangani b = 0
input.
kode mesin amd64 (x86-64), 9 byte (10B untuk unsigned, atau 14B 13B untuk 64b bilangan bulat ditandatangani atau tidak ditandatangani)
10 9B untuk unsigned pada amd64 yang rusak dengan input = 0
Input adalah bilangan bulat bertanda 32-bit bukan-nol di eax
dan ecx
. Output dalam eax
.
## 32bit code, signed integers: eax, ecx
08048420 <gcd0>:
8048420: 99 cdq ; shorter than xor edx,edx
8048421: f7 f9 idiv ecx
8048423: 92 xchg edx,eax ; there's a one-byte encoding for xchg eax,r32. So this is shorter but slower than a mov
8048424: 91 xchg ecx,eax ; eax = divisor(from ecx), ecx = remainder(from edx), edx = quotient(from eax) which we discard
; loop entry point if we need to handle ecx = 0
8048425: 41 inc ecx ; saves 1B vs. test/jnz in 32bit mode
8048426: e2 f8 loop 8048420 <gcd0>
08048428 <gcd0_end>:
; 8B total
; result in eax: gcd(a,0) = a
Struktur loop ini gagal dalam test case ecx = 0
. ( div
menyebabkan #DE
eksekusi perangkat keras divide by zero. (Di Linux, kernel memberikan a SIGFPE
(floating point exception)). Jika titik entri loop tepat sebelum inc
, kita akan menghindari masalah. Versi x86-64 dapat menanganinya gratis, lihat di bawah.
Jawaban Mike Shlanta adalah titik awal untuk ini . Loop saya melakukan hal yang sama seperti miliknya, tetapi untuk bilangan bulat yang ditandatangani karena cdq
satu byter lebih pendek dari xor edx,edx
. Dan ya, itu berfungsi dengan benar dengan satu atau kedua input negatif. Versi Mike akan berjalan lebih cepat dan mengambil lebih sedikit ruang di cache uop ( xchg
adalah 3 uops pada CPU Intel, dan loop
sangat lambat pada kebanyakan CPU ), tetapi versi ini menang pada ukuran kode mesin.
Saya tidak memperhatikan pada awalnya bahwa pertanyaan itu membutuhkan 32bit tanpa tanda tangan . Kembali ke xor edx,edx
bukan cdq
akan biaya satu byte. div
adalah ukuran yang sama dengan idiv
, dan yang lainnya bisa tetap sama ( xchg
untuk pergerakan data dan inc/loop
masih berfungsi.)
Menariknya, untuk ukuran operan 64bit ( rax
dan rcx
), versi yang ditandatangani dan tidak ditandatangani memiliki ukuran yang sama. Versi yang ditandatangani membutuhkan awalan REX untuk cqo
(2B), tetapi versi yang tidak ditandatangani masih dapat menggunakan 2B xor edx,edx
.
Dalam kode 64bit, inc ecx
adalah 2B: byte-tunggal inc r32
dan dec r32
opcodes repurposed sebagai awalan REX. inc/loop
tidak menyimpan ukuran kode dalam mode 64bit, jadi Anda mungkin juga test/jnz
. Beroperasi pada bilangan bulat 64bit menambahkan satu byte per instruksi dalam awalan REX, kecuali untuk loop
atau jnz
. Mungkin untuk sisanya memiliki semua nol di 32b rendah (misalnya gcd((2^32), (2^32 + 1))
), jadi kita perlu menguji seluruh rcx dan tidak dapat menyimpan byte dengan test ecx,ecx
. Namun, jrcxz
insn yang lebih lambat hanya 2B, dan kita bisa meletakkannya di atas loop untuk menangani ecx=0
entri :
## 64bit code, unsigned 64 integers: rax, rcx
0000000000400630 <gcd_u64>:
400630: e3 0b jrcxz 40063d <gcd_u64_end> ; handles rcx=0 on input, and smaller than test rcx,rcx/jnz
400632: 31 d2 xor edx,edx ; same length as cqo
400634: 48 f7 f1 div rcx ; REX prefixes needed on three insns
400637: 48 92 xchg rdx,rax
400639: 48 91 xchg rcx,rax
40063b: eb f3 jmp 400630 <gcd_u64>
000000000040063d <gcd_u64_end>:
## 0xD = 13 bytes of code
## result in rax: gcd(a,0) = a
Program uji runnable penuh termasuk main
yang menjalankan printf("...", gcd(atoi(argv[1]), atoi(argv[2])) );
sumber dan asm output pada Godbolt Compiler Explorer , untuk versi 32 dan 64b. Diuji dan berfungsi untuk 32bit ( -m32
), 64bit ( -m64
), dan ABI x32 ( -mx32
) .
Juga termasuk: versi yang menggunakan pengurangan yang diulang saja , yaitu 9B untuk yang tidak ditandatangani, bahkan untuk mode x86-64, dan dapat mengambil salah satu inputnya dalam register sewenang-wenang. Namun, ia tidak dapat menangani input menjadi 0 pada entri (ia mendeteksi kapan sub
menghasilkan nol, yang x - 0 tidak pernah lakukan).
GNU C inline asm source untuk versi 32bit (kompilasi dengan gcc -m32 -masm=intel
)
int gcd(int a, int b) {
asm (// ".intel_syntax noprefix\n"
// "jmp .Lentry%=\n" // Uncomment to handle div-by-zero, by entering the loop in the middle. Better: `jecxz / jmp` loop structure like the 64b version
".p2align 4\n" // align to make size-counting easier
"gcd0: cdq\n\t" // sign extend eax into edx:eax. One byte shorter than xor edx,edx
" idiv ecx\n"
" xchg eax, edx\n" // there's a one-byte encoding for xchg eax,r32. So this is shorter but slower than a mov
" xchg eax, ecx\n" // eax = divisor(ecx), ecx = remainder(edx), edx = garbage that we will clear later
".Lentry%=:\n"
" inc ecx\n" // saves 1B vs. test/jnz in 32bit mode, none in 64b mode
" loop gcd0\n"
"gcd0_end:\n"
: /* outputs */ "+a" (a), "+c"(b)
: /* inputs */ // given as read-write outputs
: /* clobbers */ "edx"
);
return a;
}
Biasanya saya akan menulis seluruh fungsi dalam asm, tetapi GNU C inline asm tampaknya menjadi cara terbaik untuk memasukkan potongan yang dapat memiliki / output dalam regs apa pun yang kita pilih. Seperti yang Anda lihat, GNU C inline asm syntax membuat asm jelek dan berisik. Ini juga merupakan cara yang sangat sulit untuk belajar asm .
Ini sebenarnya akan mengkompilasi dan bekerja dalam .att_syntax noprefix
mode, karena semua insns yang digunakan adalah operan tunggal / tidak ada atau xchg
. Bukan observasi yang bermanfaat.