x86 32 bit makine kodu (32 bit tam sayılar): 17 bayt.
(ayrıca, 32 bit veya 64 bit için 16 bayt, DF = 1 arama kuralı dahil olmak üzere, aşağıdaki diğer sürümlere bakın.)
Arayan bir işareti içeren kayıtları args, geçer sonuna bir çıkış tamponunun (gibi benim C cevap ; gerekçe ve algoritmanın açıklaması için görüyorum.) Glibc iç _itoa
yapar sadece kod golf için yapmacık değil yani. Argüman geçiren kayıtlar, EDX yerine EAX'te bir argümanımız olması dışında, x86-64 System V'ye yakındır.
Dönüşte, EDI çıkış arabelleğindeki 0-sonlandırılmış bir C dizesinin ilk baytına işaret eder. Her zamanki getiri-değer yazıcısı EAX / RAX'dır, fakat assembly dilinde bir işlev için uygun olan arama kuralını kullanabilirsiniz. ( xchg eax,edi
sonunda 1 bayt ekler).
Arayan, isterse açık bir uzunluk hesaplayabilir buffer_end - edi
. Ancak, işlev aslında hem başlangıç + bitiş işaretçilerini hem de işaretçi + uzunluğu döndürmedikçe sonlandırıcıyı atlamayı haklı çıkartabileceğimizi sanmıyorum. Bu sürümde 3 byte tasarruf sağlar, ancak haklı olduğunu sanmıyorum.
- EAX = n = kod çözülecek sayı. (Çünkü
idiv
. Diğer argümanlar gizli operandlar değildir.)
- EDI = çıkış arabelleğinin sonu (64 bit sürüm hala kullanılıyor
dec edi
, bu nedenle düşük 4GiB'de olmalı)
- ESI / RSI = arama tablosu, aka LUT. gizlenmemiş.
- ECX = tablonun uzunluğu = taban. gizlenmemiş.
nasm -felf32 ascii-compress-base.asm -l /dev/stdout | cut -b -30,$((30+10))-
(Elleri yorumlamak için düzenlendi, satır numaralandırma tuhaf.)
32-bit: 17 bytes ; 64-bit: 18 bytes
; same source assembles as 32 or 64-bit
3 %ifidn __OUTPUT_FORMAT__, elf32
5 %define rdi edi
6 address %define rsi esi
11 machine %endif
14 code %define DEF(funcname) funcname: global funcname
16 bytes
22 ;;; returns: pointer in RDI to the start of a 0-terminated string
24 ;;; clobbers:; EDX (tmp remainder)
25 DEF(ascii_compress_nostring)
27 00000000 C60700 mov BYTE [rdi], 0
28 .loop: ; do{
29 00000003 99 cdq ; 1 byte shorter than xor edx,edx / div
30 00000004 F7F9 idiv ecx ; edx=n%B eax=n/B
31
32 00000006 8A1416 mov dl, [rsi + rdx] ; dl = LUT[n%B]
33 00000009 4F dec edi ; --output ; 2B in x86-64
34 0000000A 8817 mov [rdi], dl ; *output = dl
35
36 0000000C 85C0 test eax,eax ; div/idiv don't write flags in practice, and the manual says they're undefined.
37 0000000E 75F3 jnz .loop ; }while(n);
38
39 00000010 C3 ret
0x11 bytes = 17
40 00000011 11 .size: db $ - .start
Temelde hız / boyut geçişi olmayan en basit sürümün, en aza inen , ancak azalan düzende kullanmak ve hala ortak DF = 0 arama kuralını takip etmek için kullanılan std
/ cld
maliyetin 2 bayt olması şaşırtıcıdır stosb
. (STOS sakladıktan sonra azalır , işaretçiyi loop çıkışında bir bayt işaretini çok düşük bırakır ve etrafta çalışmamız için ekstra bayt tutar.)
sürümleri:
4 farklı uygulama hilesi kullandım (basit mov
yük / depo kullanarak (yukarıda), lea
/ kullanarak movsb
(düzgün ama optimal değil), xchg
/ xlatb
/ stosb
/ kullanarak xchg
ve üst üste binen bir talimat kesmesiyle döngüye giren bir tane kullandım). . Sonuncusu 0
, çıkış dizgisi sonlandırıcısı olarak kopyalamak için arama tablosundaki bir sonuca ihtiyaç duyuyor , bu yüzden bunu +1 bayt olarak sayıyorum. 32/64-bit (1 bayt inc
veya değil) ve arayanın DF = 1 ( stosb
azalan) veya her neyse, farklı versiyonların (bağlı olduğu) en kısa sürdüğünü varsayamayacağımıza bağlı olarak.
Azalan düzende saklamak için DF = 1, onu xchg / stosb / xchg'ye kazanır, ancak arayan çoğu zaman bunu istemez; Arayana gerekçelendirilmesi zor bir şekilde boşaltma çalışması gibi geliyor. Ama 64 bit kodunda, (tipik fazladan bir çaba. Arayan bir asm maliyeti olmayan özel arg-geçiyordu ve dönüş-değeri kayıtlarını aksine) cld
/ scasb
olarak çalışır inc rdi
bazen bu yüzden, 32-bit çıkış işaretçisi kesiliyor kaçınarak, 64 bit temizleme işlevlerinde DF = 1 değerini korumak sakıncalıdır. . (Statik kod / verilere yönelik işaretçiler, Linux'ta x86-64'te PIE olmayan çalıştırılabilir dosyalarda 32 bit, ve her zaman Linux x32 ABI'de, bu nedenle 32 bit işaretleyicileri kullanan bir x86-64 sürümü bazı durumlarda kullanılabilir.) Bu etkileşim farklı gereksinimlerin kombinasyonlarına bakmayı ilginçleştirir.
- Giriş / çıkış arama kuralında DF = 0 olan IA32: 17B (
nostring
) .
- IA32: 16B (bir DF = 1 kuralına göre:
stosb_edx_arg
veya skew
) ; veya gelen DF = umurumda değil, ayarlanmış halde bırakarak: 16 + 1Bstosb_decode_overlap
veya 17Bstosb_edx_arg
- 64 bit işaretçilerle x86-64 ve giriş / çıkış arama konvansiyonunda bir DF = 0: 17 + 1 bayt (
stosb_decode_overlap
) , 18B ( stosb_edx_arg
veya skew
)
64 bit işaretçilerle x86-64, diğer DF işlemleriyle: 16B (DF = 1 skew
) , 17B ( nostring
yerine DF = 1 scasb
ile dec
). 18B ( stosb_edx_arg
DF = 1'i 3 bayt ile koruyarak inc rdi
).
Veya dizeden önce göstergenin 1 bayta dönmesine izin verirsek, 15B ( stosb_edx_arg
olmadan inc
sonunda). Hepsi tekrar arayacak ve başka bir dizgeyi farklı bir taban / tabloya sahip arabellek içine genişletecek şekilde ayarlanmış ... Ama eğer bir sonlandırıcı depolamazsak 0
, işlev gövdesini bir döngünün içine koyabilirsin, bu gerçekten bir Ayrı sorun.
32 bit çıkış işaretçisiyle x86-64, DF = 0 arama kuralı: 64 bit çıkış işaretçisinde daha fazla gelişme olmaz, ancak 18B ( nostring
) şimdi bağlanır.
- 32 bit çıkış işaretçisiyle x86-64: en iyi 64 bit işaretçi sürümlerinde hiçbir iyileştirme yok, bu nedenle 16B (DF = 1
skew
). Veya DF = 1 set ve için, 17B bırakmak skew
ile std
değil cld
. Veya 17 + 1B içinstosb_decode_overlap
birlikte inc edi
yerine sonundaki cld
/ ' scasb
.
DF = 1 arama kuralı ile: 16 bayt (IA32 veya x86-64)
Girişte DF = 1 gerekir, ayarlanmış halde bırakılır. En azından işlevsellik temelinde zar zor mantıklı . Yukarıdaki sürümle aynı şeyi yapar, ancak XLATB'den önce / sonra geri kalanı almak / almak için xchg ile xchg (temel olarak R / EBX ile tablo araması) ve STOSB ( *output-- = al
).
Giriş / çıkış kurallarında normal bir DF = 0 olduğunda , std
/ cld
/ scasb
sürümü 32 ve 64 bit kod için 18 bayttır ve 64 bit temizdir (64 bit çıkış işaretçisiyle çalışır).
Giriş argümanlarının tablo için RBX de dahil olmak üzere farklı kayıtlarda olduğunu unutmayın (for xlatb
). Ayrıca, bu döngünün AL'yi depolayarak başladığını ve henüz kaydedilmemiş son karakterle sona erdiğini unutmayın (bu nedenle mov
sonunda). Bu yüzden döngü diğerlerine göre "çarpıktır", dolayısıyla adı.
;DF=1 version. Uncomment std/cld for DF=0
;32-bit and 64-bit: 16B
157 DEF(ascii_compress_skew)
158 ;;; inputs
159 ;; O in RDI = end of output buffer
160 ;; I in RBX = lookup table for xlatb
161 ;; n in EDX = number to decode
162 ;; B in ECX = length of table = modulus
163 ;;; returns: pointer in RDI to the start of a 0-terminated string
164 ;;; clobbers:; EDX=0, EAX=last char
165 .start:
166 ; std
167 00000060 31C0 xor eax,eax
168 .loop: ; do{
169 00000062 AA stosb
170 00000063 92 xchg eax, edx
171
172 00000064 99 cdq ; 1 byte shorter than xor edx,edx / div
173 00000065 F7F9 idiv ecx ; edx=n%B eax=n/B
174
175 00000067 92 xchg eax, edx ; eax=n%B edx=n/B
176 00000068 D7 xlatb ; al = byte [rbx + al]
177
178 00000069 85D2 test edx,edx
179 0000006B 75F5 jnz .loop ; }while(n = n/B);
180
181 0000006D 8807 mov [rdi], al ; stosb would move RDI away
182 ; cld
183 0000006F C3 ret
184 00000070 10 .size: db $ - .start
Benzer olmayan bir eğri olmayan sürüm EDI / RDI'yı aşıyor ve daha sonra düzeltiyor.
; 32-bit DF=1: 16B 64-bit: 17B (or 18B for DF=0)
70 DEF(ascii_compress_stosb_edx_arg) ; x86-64 SysV arg passing, but returns in RDI
71 ;; O in RDI = end of output buffer
72 ;; I in RBX = lookup table for xlatb
73 ;; n in EDX = number to decode
74 ;; B in ECX = length of table
75 ;;; clobbers EAX,EDX, preserves DF
76 ; 32-bit mode: a DF=1 convention would save 2B (use inc edi instead of cld/scasb)
77 ; 32-bit mode: call-clobbered DF would save 1B (still need STD, but INC EDI saves 1)
79 .start:
80 00000040 31C0 xor eax,eax
81 ; std
82 00000042 AA stosb
83 .loop:
84 00000043 92 xchg eax, edx
85 00000044 99 cdq
86 00000045 F7F9 idiv ecx ; edx=n%B eax=n/B
87
88 00000047 92 xchg eax, edx ; eax=n%B edx=n/B
89 00000048 D7 xlatb ; al = byte [rbx + al]
90 00000049 AA stosb ; *output-- = al
91
92 0000004A 85D2 test edx,edx
93 0000004C 75F5 jnz .loop
94
95 0000004E 47 inc edi
96 ;; cld
97 ;; scasb ; rdi++
98 0000004F C3 ret
99 00000050 10 .size: db $ - .start
16 bytes for the 32-bit DF=1 version
Bunun alternatif bir versiyonunu denedim lea esi, [rbx+rdx]
movsb
iç döngü gövdesi olarak / . (RSI her yinelemeyi sıfırlar, ancak RDI azalır). Ancak sonlandırıcı için xor-zero / stos kullanamaz, bu yüzden 1 bayt daha büyüktür. (Ve LEA'da bir REX öneki olmadan arama tablosu için 64-bit temiz değil.)
Açık uzunlukta LUT ve 0 terminatör : 16 + 1 bayt (32-bit)
Bu sürüm DF = 1 ayarını yapar ve bu şekilde bırakır. Toplam bayt sayımının bir parçası olarak gereken ekstra LUT baytını sayıyorum.
Buradaki harika numara , aynı baytların iki farklı yolla kodunu çözmesidir. . Kalan dönemin ortasına remainder = base ve quotient = input numarası ile düşersek 0 terminatörünü yerine kopyalarız.
İşlev boyunca ilk defa, döngünün ilk 3 baytı bir LEA için bir disp32'nin yüksek baytı olarak tüketilir. LEA, tabanı (modülü) EDX'e kopyalar, idiv
kalanları daha sonraki yinelemeler için üretir.
2. bayt idiv ebp
DİR FD
için işlem kodu, std
işe bu fonksiyon ihtiyaçları olduğunu talimat. (Bu şanslı bir keşif ben bu bakarak edilmişti. İdi div
ile ayrılıyor, hangi önceki idiv
kullanarak /r
. ModRM içinde bitleri 2. bayt div epb
olarak deşifre cmc
yararlı zararsız ama değil. Ama birlikte idiv ebp
biz aslında kaldırabilirsiniz std
üstten Fonksiyonun
Giriş kayıtlarının bir kez daha farklılık gösterdiğini unutmayın: Temel için EBP.
103 DEF(ascii_compress_stosb_decode_overlap)
104 ;;; inputs
105 ;; n in EAX = number to decode
106 ;; O in RDI = end of output buffer
107 ;; I in RBX = lookup table, 0-terminated. (first iter copies LUT[base] as output terminator)
108 ;; B in EBP = base = length of table
109 ;;; returns: pointer in RDI to the start of a 0-terminated string
110 ;;; clobbers: EDX (=0), EAX, DF
111 ;; Or a DF=1 convention allows idiv ecx (STC). Or we could put xchg after stos and not run IDIV's modRM
112 .start:
117 ;2nd byte of div ebx = repz. edx=repnz.
118 ; div ebp = cmc. ecx=int1 = icebp (hardware-debug trap)
119 ;2nd byte of idiv ebp = std = 0xfd. ecx=stc
125
126 ;lea edx, [dword 0 + ebp]
127 00000040 8D9500 db 0x8d, 0x95, 0 ; opcode, modrm, 0 for lea edx, [rbp+disp32]. low byte = 0 so DL = BPL+0 = base
128 ; skips xchg, cdq, and idiv.
129 ; decode starts with the 2nd byte of idiv ebp, which decodes as the STD we need
130 .loop:
131 00000043 92 xchg eax, edx
132 00000044 99 cdq
133 00000045 F7FD idiv ebp ; edx=n%B eax=n/B;
134 ;; on loop entry, 2nd byte of idiv ebp runs as STD. n in EAX, like after idiv. base in edx (fake remainder)
135
136 00000047 92 xchg eax, edx ; eax=n%B edx=n/B
137 00000048 D7 xlatb ; al = byte [rbx + al]
138 .do_stos:
139 00000049 AA stosb ; *output-- = al
140
141 0000004A 85D2 test edx,edx
142 0000004C 75F5 jnz .loop
143
144 %ifidn __OUTPUT_FORMAT__, elf32
145 0000004E 47 inc edi ; saves a byte in 32-bit. Makes DF call-clobbered instead of normal DF=0
146 %else
147 cld
148 scasb ; rdi++
149 %endif
150
151 0000004F C3 ret
152 00000050 10 .size: db $ - .start
153 00000051 01 db 1 ; +1 because we require an extra LUT byte
# 16+1 bytes for a 32-bit version.
# 17+1 bytes for a 64-bit version that ends with DF=0
Bu çakışan kod çözme numarası ayrıca ... cmp eax, imm32
: 4 bayt, yalnızca gizleme bayraklarını etkin şekilde atlamak yalnızca 1 bayt alır. (Bu, BT1'deki L1i önbelleğindeki komut sınırlarını işaretleyen CPU'lardaki performans için korkunçtur.)
Ancak burada, bir kayıt defterini kopyalamak ve döngüye atlamak için 3 bayt kullanıyoruz. Bu normalde 2 + 2 (mov + jmp) alacaktır ve XLATB'den önce STOS'tan hemen önce döngüye atlamamıza izin verecektir. Ama sonra ayrı bir STD'ye ihtiyacımız olacaktı ve çok ilginç olmazdı.
Çevrimiçi deneyin! ( _start
sonuçta kullanan bir arayan ile sys_write
)
Bu hata ayıklama için altında çalıştırmak strace
veya çıktının hexdump yapmak için en iyisidir , böylece \0
doğru yerde bir terminatör olduğunu doğrulayabilirsiniz . Fakat bunun gerçekten işe yaradığını ve bunun AAAAAACHOO
için bir girdi ürettiğini görebilirsiniz .
num equ 698911
table: db "CHAO"
%endif
tablen equ $ - table
db 0 ; "terminator" needed by ascii_compress_stosb_decode_overlap
(Aslında xxAAAAAACHOO\0x\0\0...
biz daha önce de 2 byte dan damping çünkü sabit bir uzunluğa dışarı tampon. Biz işlevi gerekiyordu ve oldu bayt yazdığını görebilirsiniz Yani vermedi herhangi bunu olmamalıdır bayt Gazla. işleve geçen start-pointer x
, onu takip eden sıfır olan 2. son karakterdi.