जवाबों:
mov
-स्टीमेट कांस्टेंट के लिए महंगा हैयह स्पष्ट हो सकता है, लेकिन मैं अभी भी इसे यहाँ रखूँगा। सामान्य तौर पर यह किसी संख्या के बिट-स्तरीय प्रतिनिधित्व के बारे में सोचने के लिए भुगतान करता है जब आपको किसी मूल्य को शुरू करने की आवश्यकता होती है।
eax
साथ प्रारंभिक 0
:b8 00 00 00 00 mov $0x0,%eax
को छोटा किया जाना चाहिए ( प्रदर्शन के साथ-साथ कोड-आकार के लिए )
31 c0 xor %eax,%eax
eax
साथ प्रारंभिक -1
:b8 ff ff ff ff mov $-1,%eax
को छोटा किया जा सकता है
31 c0 xor %eax,%eax
48 dec %eax
या
83 c8 ff or $-1,%eax
या अधिक आम तौर पर, किसी भी 8-बिट साइन-विस्तारित मान को 3 बाइट्स push -12
(2 बाइट्स) / pop %eax
(1 बाइट) के साथ बनाया जा सकता है । यह भी कोई अतिरिक्त REX उपसर्ग के साथ 64-बिट रजिस्टरों के लिए काम करता है; push
/ pop
डिफ़ॉल्ट ऑपरेंड-आकार = 64।
6a f3 pushq $0xfffffffffffffff3
5d pop %rbp
या एक रजिस्टर में एक ज्ञात स्थिरांक दिया जाता है, आप lea 123(%eax), %ecx
(3 बाइट्स) का उपयोग करके पास में एक और निरंतर बना सकते हैं । यह आसान है यदि आपको एक शून्य रजिस्टर और एक स्थिर की आवश्यकता है; xor-zero (2 बाइट्स) + lea-disp8
(3 बाइट्स)।
31 c0 xor %eax,%eax
8d 48 0c lea 0xc(%eax),%ecx
यह भी देखें कुशलता से 1 करने के लिए सीपीयू रजिस्टर में सभी बिट्स सेट
dec
, उदाहरण के लिए एक रजिस्टर शुरू करने के लिएxor eax, eax; dec eax
push imm8
/ pop reg
3 बाइट्स है, और x86-64 पर 64-बिट स्थिरांक के लिए शानदार है, जहां dec
/ inc
2 बाइट्स है। और push r64
/ pop 64
(2 बाइट्स) एक 3 बाइट mov r64, r64
(REX के साथ 3 बाइट्स ) को भी बदल सकता है । यह भी देखें कि सीपीयू रजिस्टर में सभी बिट्स को 1 के लिए कुशलतापूर्वक सेट करें जैसे lea eax, [rcx-1]
कि किसी ज्ञात मान में दिए गए सामान के लिए eax
(जैसे कि एक शून्य रजिस्टर और दूसरे स्थिरांक की आवश्यकता है, बस पुश / पॉप के बजाय LEA का उपयोग करें
बहुत सारे मामलों में, संचायक-आधारित निर्देश (यानी जो (R|E)AX
गंतव्य संचालक के रूप में लेते हैं) सामान्य मामले के निर्देशों की तुलना में 1 बाइट से छोटे होते हैं; StackOverflow पर यह प्रश्न देखें ।
al, imm8
विशेष मामले होते हैं , जैसे or al, 0x20
/ sub al, 'a'
/ cmp al, 'z'-'a'
/ ja .non_alphabetic
2 2 बाइट्स प्रत्येक के बजाय, 3. के बजाय al
चरित्र डेटा का उपयोग भी अनुमति देता है lodsb
और / या stosb
। या al
EAX के कम बाइट के बारे में कुछ परीक्षण करने के लिए उपयोग करें, जैसे lodsd
/ test al, 1
/ setnz cl
बनाता है cl = 1 या 0 विषम / सम के लिए। लेकिन दुर्लभ मामले में जहां आपको 32-बिट तत्काल की आवश्यकता होती है, फिर सुनिश्चित करें op eax, imm32
, जैसे मेरे क्रोमा-कुंजी उत्तर में
आपके उत्तर की भाषा asm (वास्तव में मशीन कोड) है, इसलिए इसे asm में लिखे गए प्रोग्राम के भाग के रूप में मानिए, C- संकलित-for-x86 के लिए नहीं। आपका फ़ंक्शन किसी भी मानक कॉलिंग कन्वेंशन के साथ C से आसानी से कॉल करने योग्य नहीं है। अगर यह आपको किसी भी अतिरिक्त बाइट खर्च नहीं करता है, तो यह एक अच्छा बोनस है।
एक शुद्ध एएसएम कार्यक्रम में, कुछ सहायक कार्यों के लिए एक कॉलिंग सम्मेलन का उपयोग करना सामान्य है जो उनके लिए और उनके कॉलर के लिए सुविधाजनक है। इस तरह के फ़ंक्शन टिप्पणियों के साथ अपने कॉलिंग कन्वेंशन (इनपुट्स / आउटपुट / क्लोबर्स) को दस्तावेज करते हैं।
वास्तविक जीवन में, यहां तक कि asm प्रोग्राम भी करते हैं (मुझे लगता है) ज्यादातर फ़ंक्शन (विशेषकर विभिन्न स्रोत फ़ाइलों के लिए) के लिए लगातार कॉलिंग कन्वेंशन का उपयोग करते हैं, लेकिन कोई भी महत्वपूर्ण फ़ंक्शन कुछ विशेष कर सकता है। कोड-गोल्फ में, आप एक एकल फ़ंक्शन से बकवास का अनुकूलन कर रहे हैं, इसलिए स्पष्ट रूप से यह महत्वपूर्ण / विशेष है।
C प्रोग्राम से अपने फंक्शन को टेस्ट करने के लिए, एक रैपर लिख सकते हैं, जो सही जगहों पर आर्गन डालता है, आपके द्वारा क्लोब किए गए किसी भी अतिरिक्त रजिस्टर को बचाता / पुनर्स्थापित करता है, और रिटर्न वैल्यू डालता है e/rax
अगर यह पहले से ही नहीं था।
कॉल / रिटेल पर डीएफ ( lods
/ stos
/ आदि के लिए स्ट्रिंग दिशा ध्वज ) स्पष्ट (ऊपर) होना सामान्य है। इसे कॉल / रीफ़ पर अपरिभाषित होने देना ठीक रहेगा। इसे साफ़ करने या प्रवेश पर सेट करने की आवश्यकता होती है, लेकिन जब आप वापस लौटते हैं तो यह संशोधित होता है।
X87 में FP मान लौटाना st0
उचित है, लेकिन st3
अन्य x87 रजिस्टर में कचरा के साथ वापस नहीं आता है। फोन करने वाले को x87 स्टैक को साफ करना होगा। यहां तक कि st0
गैर-खाली उच्च स्टैक रजिस्टरों के साथ वापसी भी संदिग्ध होगी (जब तक कि आप कई मान वापस नहीं कर रहे हैं)।
call
, इसलिए [rsp]
आपका रिटर्न पता है। आप कर सकते हैं से बचने के call
/ ret
की तरह कड़ी रजिस्टर का उपयोग कर 86 पर lea rbx, [ret_addr]
/ jmp function
और साथ वापसी jmp rbx
, लेकिन है कि "उचित" नहीं है। यह कॉल / रिटेल जितना कुशल नहीं है, इसलिए यह ऐसा कुछ नहीं है जिसे आप वास्तविक कोड में खोज लेंगे।बॉर्डरलाइन मामले: एक फ़ंक्शन लिखते हैं जो एक सरणी में एक अनुक्रम पैदा करता है, जिसे फ़ंक्शन आर्ग्स के रूप में पहले 2 तत्व दिए गए हैं । मैंने कॉलर को अनुक्रम में सरणी में स्टोर करना शुरू कर दिया और सरणी के लिए केवल एक पॉइंटर पास किया। यह निश्चित रूप से प्रश्न की आवश्यकताओं को झुका रहा है। मैंने इसके xmm0
लिए पैक किए गए आर्ग को लेने पर विचार किया movlps [rdi], xmm0
, जो एक अजीब कॉलिंग कन्वेंशन भी होगा।
OS X सिस्टम कॉल ऐसा करता है ( CF=0
इसका कोई अर्थ नहीं है): क्या झंडे रजिस्टर को बूलियन रिटर्न मान के रूप में उपयोग करना बुरा माना जाता है? ।
किसी भी शर्त को एक जेसीसी के साथ जांचा जा सकता है, पूरी तरह से उचित है, खासकर यदि आप उस समस्या के लिए किसी भी अर्थ संबंधी प्रासंगिकता को चुन सकते हैं। (उदाहरण के लिए तुलनात्मक फ़ंक्शन झंडे सेट कर सकता है इसलिए jne
यदि वे समान नहीं थे, तो इसे ले लिया जाएगा)।
char
) की आवश्यकता है संकेत या शून्य को 32 या 64 बिट तक विस्तारित किया जाना चाहिए।यह अनुचित नहीं है; आधुनिक x86 asm में आंशिक-पंजीयन मंदी का उपयोग करना movzx
या उससे movsx
बचना सामान्य है। वास्तव में क्लैंग / एलएलवीएम पहले से ही कोड बनाता है जो कि x86-64 सिस्टम वी कॉलिंग कन्वेंशन के लिए एक अनियोजित एक्सटेंशन पर निर्भर करता है: 32 बिट्स की तुलना में संकरा संकरा साइन या शून्य को कॉलर द्वारा 32 बिट्स तक बढ़ाया जाता है ।
यदि आप चाहें तो आप अपने दस्तावेज़ों में uint64_t
या int64_t
अपने प्रोटोटाइप में 64 बिट्स के एक्सटेंशन का वर्णन / वर्णन कर सकते हैं। उदाहरण के लिए, आप एक loop
निर्देश का उपयोग कर सकते हैं , जो RCX के पूरे 64 बिट्स का उपयोग करता है जब तक कि आप 32-बिट ECX (हाँ वास्तव में, पता-आकार नहीं ऑपरेंड-आकार) को ओवरराइड करने के लिए पता-आकार उपसर्ग का उपयोग करते हैं।
ध्यान दें कि long
Windows 64-बिट ABI और Linux x32 ABI में केवल 32-बिट प्रकार है ; uint64_t
प्रकार से अस्पष्ट और छोटा है unsigned long long
।
विंडोज 32-बिट __fastcall
, पहले से ही एक और जवाब द्वारा सुझाया गया : पूर्णांक में आर्गन्स ecx
और edx
।
x86-64 सिस्टम V : रजिस्टरों में बहुत सारे आर्गन पास करता है, और इसमें बहुत सारे कॉल-क्लोबर्ड रजिस्टर्स होते हैं जिन्हें आप REX उपसर्गों के बिना उपयोग कर सकते हैं। इससे भी महत्वपूर्ण बात यह है कि वास्तव memcpy
में rep movsb
आसानी से इनलाइन या मेमसेट को कंपाइल करने की अनुमति देने के लिए चुना गया था : पहले 6 पूर्णांक / सूचक आर्गन RDI, RSI, RDX, RCX, R8, R9 में पारित किए जाते हैं।
यदि आपका फ़ंक्शन लूप के अंदर lodsd
/ stosd
अंदर का उपयोग करता है जो rcx
कई बार ( loop
निर्देश के साथ ) चलता है , तो आप कह सकते हैं कि "C से int foo(int *rdi, const int *rsi, int dummy, uint64_t len)
x86-64 सिस्टम V कॉलिंग कन्वेंशन के साथ कॉल करने योग्य है"। उदाहरण: क्रोमैकी ।
32-बिट GCC regparm
: EAX EAX , ECX, EDX में वापसी करता है, EAX (या EDX: EAX) में वापस आता है। रिटर्न मान के रूप में एक ही रजिस्टर में पहला आर्ग होने के बाद कुछ अनुकूलन की अनुमति देता है, जैसे उदाहरण कॉलर और फ़ंक्शन विशेषता के साथ एक प्रोटोटाइप । और निश्चित रूप से AL / EAX कुछ निर्देशों के लिए विशेष है।
लिनक्स x32 एबीआई 32-बिट पॉइंटर्स को लंबे मोड में उपयोग करता है, इसलिए आप एक पॉइंटर ( उदाहरण उपयोग-केस ) को संशोधित करते समय एक आरईएक्स उपसर्ग को बचा सकते हैं । आप अभी भी 64-बिट पता-आकार का उपयोग कर सकते हैं, जब तक कि आपके पास रजिस्टर में 32-बिट नकारात्मक पूर्णांक शून्य-विस्तारित न हो (इसलिए यदि आपने किया तो यह एक बड़ा अहस्ताक्षरित मान होगा [rdi + rdx]
)।
ध्यान दें कि push rsp
/ pop rax
2 बाइट्स है, और इसके बराबर है mov rax,rsp
, इसलिए आप अभी भी 2 बाइट्स में पूर्ण 64-बिट रजिस्टर कॉपी कर सकते हैं ।
ret 16
; वे वापसी पते को पॉप नहीं करते हैं, फिर एक सरणी धक्का, push rcx
/ ret
। कॉल करने वाले को सरणी का आकार जानना होगा या खुद को खोजने के लिए स्टैक के बाहर कहीं आरएसपी को बचाया होगा।
AL / AX / EAX, और अन्य लघु रूपों और एकल-बाइट निर्देशों के लिए विशेष-केस शॉर्ट-फॉर्म एन्कोडिंग का उपयोग करें
उदाहरण 32/64-बिट मोड को मानते हैं, जहां डिफ़ॉल्ट ऑपरेंड का आकार 32 बिट्स है। एक ऑपरेंड-आकार के उपसर्ग EAX (या 16-बिट मोड में रिवर्स) के बजाय निर्देश को AX में बदल देता है।
inc/dec
एक रजिस्टर (8-बिट के अलावा): inc eax
/ dec ebp
। (नहीं x86-64: 0x4x
ओपकोड बाइट्स को आरईएक्स उपसर्गों के रूप में पुनर्निर्मित किया गया था, इसलिए inc r/m32
यह एकमात्र एन्कोडिंग है।)
8-बिट inc bl
2 बाइट्स है, जिसमें inc r/m8
opcode + ModR / M ऑपरेंड एन्कोडिंग का उपयोग किया गया है । इसलिए वेतन वृद्धि का उपयोग inc ebx
करें bl
, अगर यह सुरक्षित है। (उदाहरण के लिए यदि आपको उन मामलों में जेडएफ परिणाम की आवश्यकता नहीं है जहां ऊपरी बाइट्स गैर-शून्य हो सकते हैं)।
scasd
: e/rdi+=4
, आवश्यकता है कि रजिस्टर पढ़ने योग्य स्मृति को इंगित करता है। कभी-कभी उपयोगी भी अगर आप FLAGS परिणाम (जैसे cmp eax,[rdi]
/ rdi+=4
) के बारे में परवाह नहीं करते हैं । और 64-बिट मोड में, scasb
1-बाइट के रूप में काम कर सकता हैinc rdi
, अगर लॉस्डब या स्टॉस्ब उपयोगी नहीं हैं।
xchg eax, r32
: यह वह जगह है जहां 0x90 एनओपी से आया था: xchg eax,eax
। उदाहरण: दो के साथ 3 रजिस्टरों फिर से व्यवस्था xchg
एक निर्देश cdq
/ idiv
पाश 8 बाइट में GCD के लिए जहां निर्देश के सबसे एकल-बाइट कर रहे हैं, का दुरुपयोग सहित inc ecx
/ loop
के बजाय test ecx,ecx
/jnz
cdq
EDX में साइन-ईएक्स का विस्तार करें: EAX, यानी EDX के सभी बिट्स के लिए EAX के उच्च बिट की प्रतिलिपि बनाना। ज्ञात गैर-नकारात्मक के साथ एक शून्य बनाने के लिए, या जोड़ने के लिए / उप या मुखौटा के साथ 0 / -1 प्राप्त करने के लिए। x86 इतिहास पाठ: cltq
बनामmovslq
, और इसके लिए एटी एंड टी बनाम इंटेल mnemonics भी और संबंधित cdqe
।
लॉस्ब / डी : जैसे mov eax, [rsi]
/ rsi += 4
बिना क्लब्बरिंग के झंडे। (मान लें कि DF स्पष्ट है, फ़ंक्शन प्रविष्टि पर मानक कॉलिंग सम्मेलनों की आवश्यकता है।) इसके अलावा stosb / d, कभी-कभी scas, और अधिक शायद ही कभी movs / cmps।
push
/ pop reg
। जैसे 64-बिट मोड में, push rsp
/ pop rdi
2 बाइट्स है, लेकिन mov rdi, rsp
REX उपसर्ग की जरूरत है और 3 बाइट्स है।
xlatb
मौजूद है, लेकिन शायद ही कभी उपयोगी है। बचने के लिए एक बड़ी देखने की मेज कुछ है। मुझे एएए / डीएए या अन्य पैक-बीसीडी या 2-एएससीआईआई-अंकों के निर्देशों का उपयोग कभी नहीं मिला।
1-बाइट lahf
/ sahf
शायद ही कभी उपयोगी होते हैं। आप एक विकल्प के रूप में / कर सकते हैं , लेकिन यह आमतौर पर उपयोगी नहीं है।lahf
and ah, 1
setc ah
और सीएफ के लिए विशेष रूप से, sbb eax,eax
0 / -1, या यहां तक कि संयुक्त राष्ट्र के दस्तावेज लेकिन सार्वभौमिक रूप से समर्थित 1-बाइट salc
(कैरी से सेट एएल) प्राप्त करना है, जो प्रभावी रूप से sbb al,al
झंडे को प्रभावित किए बिना करता है । (X86-64 में निकाला गया)। मैंने यूजर एप्रिसिएशन चैलेंज # 1: डेनिस I में SALC का उपयोग किया ।
1-बाइट cmc
/ clc
/ stc
(फ्लिप ("सप्लीमेंट"), स्पष्ट, या सेट सीएफ) शायद ही कभी उपयोगी होते हैं, हालांकि मैंने बेस 10 ^ 9 चंक्स के साथ विस्तारित-सटीक जोड़ के लिए उपयोग पायाcmc
। CF को बिना शर्त सेट / क्लियर करने के लिए, आमतौर पर दूसरे इंस्ट्रक्शन के हिस्से के रूप में होने की व्यवस्था करते हैं, जैसे xor eax,eax
कि CF के साथ-साथ EAX को भी क्लियर करते हैं। अन्य हालत झंडे के लिए कोई समान निर्देश नहीं हैं, बस DF (स्ट्रिंग दिशा) और IF (व्यवधान) हैं। कैरी फ्लैग कई निर्देशों के लिए विशेष है; पारियों ने इसे निर्धारित किया, adc al, 0
इसे 2 बाइट में एएल में जोड़ सकते हैं, और मैंने पहले अनिर्दिष्ट सैल्क का उल्लेख किया था।
std
/ cld
शायद ही कभी इसके लायक लगता है । विशेष रूप से 32-बिट कोड में, यह सिर्फ उपयोग करने के लिए बेहतर है dec
एक सूचक है और एक पर mov
या स्मृति स्रोत संकार्य बजाय DF तो स्थापित करने का एक ALU अनुदेश के lodsb
/ stosb
ऊपर के नीचे के बजाय जाओ। आम तौर पर अगर आप सभी पर नीचे की जरूरत है, तो आप अभी भी एक और सूचक ऊपर जा रहा है, तो आप एक से अधिक आवश्यकता होगी है std
और cld
उपयोग करने के लिए पूरे समारोह में lods
/ stos
दोनों के लिए। इसके बजाय, केवल ऊपर की दिशा के लिए स्ट्रिंग निर्देशों का उपयोग करें। (मानक कॉलिंग कन्वेंशन फ़ंक्शन प्रविष्टि पर DF = 0 की गारंटी देते हैं, इसलिए आप यह मान सकते हैं कि उपयोग किए बिना मुफ्त में cld
।)
मूल 8086 में, कुल्हाड़ी बहुत ही खास था: निर्देश की तरह lodsb
/ stosb
, cbw
, mul
/ div
और दूसरों को परोक्ष का उपयोग करें। यह अभी भी पाठ्यक्रम का मामला है; वर्तमान x86 ने 8086 के किसी भी ऑपकोड को नहीं गिराया है (कम से कम आधिकारिक रूप से प्रलेखित किसी को भी नहीं)। लेकिन बाद में सीपीयू ने नए निर्देशों को जोड़ा, जो चीजों को कॉपी करने या स्वैप करने के लिए बेहतर / अधिक कुशल तरीके देता था, उन्हें पहले AX को स्वैप करना। (या 32-बिट मोड में EAX के लिए।)
उदाहरण के लिए, 8086 में बाद के परिवर्धन की कमी थी जैसे movsx
/ movzx
लोड करना या स्थानांतरित करना + साइन-एक्सटेंशन, या 2 और 3-ऑपरैंड imul cx, bx, 1234
जो उच्च-हाफ परिणाम नहीं देते हैं और कोई अंतर्निहित ऑपरेंड नहीं है।
इसके अलावा, 8086 का मुख्य अड़चन निर्देश-भ्रूण था, इसलिए प्रदर्शन के लिए कोड-आकार के लिए अनुकूलन महत्वपूर्ण था । 8086 के ISA डिज़ाइनर (स्टीफन मोर्स) ने सभी मूल तात्कालिक src ALU- निर्देशों के लिए विशेष (E) AX / AL- गंतव्य opcodes सहित AX / AL के लिए विशेष मामलों पर बहुत सारे opcode कोडिंग स्पेस में खर्च किए , बसकोड + तत्काल कोई मॉडआर / एम बाइट के साथ। 2-बाइट add/sub/and/or/xor/cmp/test/... AL,imm8
या AX,imm16
या (32-बिट मोड में) EAX,imm32
।
लेकिन इसके लिए कोई विशेष मामला नहीं है EAX,imm8
, इसलिए नियमित मोडआर / एम एन्कोडिंग add eax,4
कम है।
धारणा यह है कि यदि आप कुछ डेटा पर काम करने जा रहे हैं, तो आप इसे AX / AL में चाहेंगे, इसलिए AX के साथ एक रजिस्टर को स्वैप करना कुछ ऐसा है जो आप करना चाहते हैं, शायद इससे भी अधिक बार एक रजिस्टर को AX से कॉपी करना mov
।
8086 निर्देश एन्कोडिंग के बारे में सब कुछ इस प्रतिमान का समर्थन करता है, निर्देश के लिए जैसे lodsb/w
ईएएक्स के साथ तत्काल के लिए सभी विशेष-केस एन्कोडिंग के लिए इसके निहित उपयोग के लिए भी गुणा / विभाजन के लिए।
दूर मत जाओ; यह EAX के लिए सब कुछ स्वैप करने के लिए स्वचालित रूप से जीत नहीं है, खासकर यदि आपको 8-बिट के बजाय 32-बिट रजिस्टरों के साथ तुरंत उपयोग करने की आवश्यकता है। या यदि आपको एक ही बार में रजिस्टरों में कई वेरिएबल्स पर संचालन को बाधित करने की आवश्यकता है। या यदि आप 2 रजिस्टरों के साथ निर्देशों का उपयोग कर रहे हैं, तो बिल्कुल भी नहीं।
लेकिन हमेशा ध्यान रखें: क्या मैं ऐसा कुछ कर रहा हूं जो EAX / AL में छोटा होगा? क्या मैं इसे पुनर्व्यवस्थित कर सकता हूं इसलिए मेरे पास एएल में यह है, या क्या मैं वर्तमान में एएल का बेहतर लाभ उठा रहा हूं जो मैं पहले से ही इसका उपयोग कर रहा हूं।
जब भी ऐसा करने के लिए सुरक्षित हो तो लाभ लेने के लिए 8-बिट और 32-बिट संचालन को स्वतंत्र रूप से मिलाएं (आपको पूर्ण रजिस्टर या जो भी हो) की आवश्यकता नहीं है।
cdq
उपयोगी है div
जिसके लिए edx
कई मामलों में शून्य की आवश्यकता होती है।
cdq
अहस्ताक्षरित होने से पहले दुरुपयोग कर सकते हैं । आम तौर पर (कोड-गोल्फ के बाहर) आप सेटअप के रूप में और इससे पहलेdiv
eax
cdq
idiv
xor edx,edx
div
fastcall
सम्मेलनों का उपयोग करेंx86 प्लेटफॉर्म में कई कॉलिंग कन्वेंशन हैं । आपको उन लोगों का उपयोग करना चाहिए जो रजिस्टरों में पैरामीटर पास करते हैं। X86_64 पर, पहले कुछ पैरामीटर वैसे भी रजिस्टरों में पारित किए जाते हैं, इसलिए वहां कोई समस्या नहीं है। 32-बिट प्लेटफार्मों पर, डिफ़ॉल्ट कॉलिंग कन्वेंशन ( cdecl
) स्टैक में पैरामीटर पास करता है, जो कि गोल्फ के लिए अच्छा नहीं है - स्टैक पर मापदंडों तक पहुंचने के लिए लंबे निर्देशों की आवश्यकता होती है।
का उपयोग करते समय fastcall
32-बिट प्लेटफार्मों पर, 2 पहले पैरामीटर आमतौर पर में पारित कर रहे हैं ecx
और edx
। यदि आपके फ़ंक्शन में 3 पैरामीटर हैं, तो आप इसे 64-बिट प्लेटफ़ॉर्म पर लागू करने पर विचार कर सकते हैं।
fastcall
कन्वेंशन के लिए सी फ़ंक्शन प्रोटोटाइप ( इस उदाहरण के उत्तर से लिया गया ):
extern int __fastcall SwapParity(int value); // MSVC
extern int __attribute__((fastcall)) SwapParity(int value); // GNU
0100 81C38000 ADD BX,0080
0104 83EB80 SUB BX,-80
समान रूप से, जोड़ -128, घटाव 128 के बजाय
< 128
में <= 127
के लिए एक तत्काल संकार्य की भयावहता को कम करने cmp
, या जीसीसी हमेशा उलटफेर पसंद तुलना को कम करने के लिए तुलना करता है भले ही यह -129 बनाम -128 न हो।
mul
(फिर inc
/ के साथ dec
+1 / -1 प्राप्त करने के लिए शून्य के साथ ) 3 शून्य बनाएंआप तीसरे रजिस्टर में शून्य गुणा और ईएक्सएक्स को शून्य से गुणा कर सकते हैं।
xor ebx, ebx ; 2B ebx = 0
mul ebx ; 2B eax=edx = 0
inc ebx ; 1B ebx=1
केवल चार बाइट्स में EAX, EDX और EBX सभी शून्य हो जाएंगे। आप EAX और EDX को तीन बाइट्स में शून्य कर सकते हैं:
xor eax, eax
cdq
लेकिन उस शुरुआती बिंदु से आप एक और बाइट में तीसरा शून्य रजिस्टर नहीं कर सकते हैं, या दूसरे 2 बाइट्स में +1 या -1 रजिस्टर कर सकते हैं। इसके बजाय, मुल तकनीक का उपयोग करें।
उदाहरण का उपयोग-मामला: द्विआधारी में फाइबोनैचि संख्या को समाप्त करना ।
ध्यान दें कि LOOP
लूप खत्म होने के बाद , ECX शून्य होगा और इसका उपयोग EDX और EAX को शून्य करने के लिए किया जा सकता है; आपको हमेशा पहला शून्य बनाने की आवश्यकता नहीं है xor
।
हम यह मान सकते हैं कि सीपीयू प्लेटफॉर्म और ओएस पर आधारित एक ज्ञात और प्रलेखित डिफ़ॉल्ट स्थिति में है।
उदाहरण के लिए:
डॉस http://www.fysnet.net/yourhelp.htm
लिनक्स x86 ELF http://asm.sourceforge.net/articles/startup.html
_start
। तो हाँ यह उचित खेल है कि अगर आप एक समारोह के बजाय एक कार्यक्रम लिख रहे हैं तो इसका लाभ उठाएं । मैंने एक्सट्रीम फाइबोनैचि में ऐसा किया । (एक गतिशील रूप से जुड़े निष्पादन में, रन ld.so अपने को कूदने से पहले _start
, और करता है रजिस्टरों में छुट्टी कचरा है, लेकिन स्थिर सिर्फ अपने कोड है।)
1 जोड़ने या घटाने के लिए, एक बाइट inc
या dec
निर्देशों का उपयोग करें जो मल्टीबाइट ऐड और उप निर्देशों से छोटे हैं।
inc/dec r32
जिसमें ओपकोड में एनकोडेड रजिस्टर नंबर है। तो inc ebx
1 बाइट है, लेकिन inc bl
है 2. अभी भी add bl, 1
पाठ्यक्रम से छोटा है , के अलावा अन्य रजिस्टरों के लिए al
। यह भी ध्यान दें कि inc
/ dec
CF अनमॉडिफाइड छोड़ दें, लेकिन अन्य झंडे अपडेट करें।
lea
गणित के लिएयह शायद x86 के बारे में जानने वाली पहली चीजों में से एक है, लेकिन मैं इसे एक अनुस्मारक के रूप में यहां छोड़ता हूं। lea
2, 3, 4, 5, 8, या 9 से गुणा करने और ऑफ़सेट जोड़ने के लिए उपयोग किया जा सकता है।
उदाहरण के लिए, ebx = 9*eax + 3
एक निर्देश में गणना करने के लिए (32-बिट मोड में):
8d 5c c0 03 lea 0x3(%eax,%eax,8),%ebx
यहाँ यह एक ऑफसेट के बिना है:
8d 1c c0 lea (%eax,%eax,8),%ebx
वाह! बेशक, सरणी अनुक्रमण की गणना के लिए lea
गणित की तरह भी किया जा सकता है ebx = edx + 8*eax + 3
।
lea eax, [rcx + 13]
64-बिट मोड के लिए कोई अतिरिक्त-अतिरिक्त उपसर्ग संस्करण नहीं है। 32-बिट ऑपरेंड-आकार (परिणाम के लिए) और 64-बिट पता आकार (इनपुट्स के लिए)।
लूप और स्ट्रिंग निर्देश वैकल्पिक निर्देश अनुक्रम से छोटे हैं। सबसे उपयोगी है loop <label>
जो दो अनुदेश अनुक्रम से छोटा होता है dec ECX
और jnz <label>
, और lodsb
से छोटा होता है mov al,[esi]
और inc si
।
mov
जब लागू होता है तो छोटे तुरंत रजिस्टर में आ जाते हैंयदि आप पहले से ही जानते हैं कि एक रजिस्टर के ऊपरी बिट्स 0 हैं, तो आप कम रजिस्टरों में तत्काल स्थानांतरित करने के लिए एक छोटे निर्देश का उपयोग कर सकते हैं।
b8 0a 00 00 00 mov $0xa,%eax
बनाम
b0 0a mov $0xa,%al
push
/ का उपयोग करेंpop
इसका श्रेय पीटर कॉर्ड्स को जाता है। xor
/ mov
4 बाइट्स है, लेकिन push
/ pop
केवल 3 है!
6a 0a push $0xa
58 pop %eax
mov al, 0xa
अच्छा है अगर आपको इसकी आवश्यकता नहीं है तो इसे पूर्ण-शून्य पर बढ़ाया जा सकता है। लेकिन अगर आप करते हैं, तो xor / mov 4 बाइट्स बनाम 3 है पुश imm8 / पॉप के लिए या lea
किसी अन्य ज्ञात स्थिरांक से। यह 4 बाइट्स में शून्य 3 रजिस्टरों के साथmul
संयोजन में उपयोगी हो सकता है , या cdq
, यदि आपको बहुत अधिक स्थिरांक की आवश्यकता होती है, हालांकि।
[0x80..0xFF]
, जो एक संकेत-विस्तारित imm8 के रूप में प्रतिनिधित्व करने योग्य नहीं हैं। या यदि आप पहले से ही ऊपरी बाइट्स को जानते हैं, उदाहरण mov cl, 0x10
के लिए एक loop
निर्देश के बाद , क्योंकि loop
कूदने का एकमात्र तरीका यह नहीं है जब इसे बनाया गया हो rcx=0
। (मुझे लगता है कि आपने यह कहा था, लेकिन आपका उदाहरण उपयोग करता है xor
)। आप किसी अन्य चीज़ के लिए रजिस्टर के निम्न बाइट का भी उपयोग कर सकते हैं, जब तक कि कुछ और इसे शून्य (या जो कुछ भी) के रूप में वापस करता है। उदाहरण के लिए मेरा फाइबोनैचि कार्यक्रम-1024
ईबेक्स में रहता है, और ब्ल का उपयोग करता है।
xchg eax, r32
) जैसे mov bl, 10
/ dec bl
/ jnz
तो अपने कोड Rbx के उच्च बाइट्स के बारे में परवाह नहीं है।
कई अंकगणितीय निर्देशों के बाद, कैरी फ्लैग (अहस्ताक्षरित) और ओवरफ्लो फ्लैग (हस्ताक्षरित) स्वचालित रूप से ( अधिक जानकारी ) सेट किए जाते हैं । साइन फ्लैग और जीरो फ्लैग को कई अंकगणित और तार्किक संचालन के बाद सेट किया गया है। यह सशर्त शाखाओं के लिए इस्तेमाल किया जा सकता है।
उदाहरण:
d1 f8 sar %eax
ZF इस निर्देश द्वारा निर्धारित किया गया है, इसलिए हम इसे कंडेंशियल ब्रांचिंग के लिए उपयोग कर सकते हैं।
test al,1
; आपको आमतौर पर वह मुफ्त में नहीं मिलता है। (या and al,1
विषम / सम के आधार पर पूर्णांक 0/1 बनाने के लिए।)
test
/ cmp
", तो यह बहुत बुनियादी शुरुआती x86 होगा, लेकिन फिर भी एक मूल्य के लायक है।
यह x86 विशिष्ट नहीं है, लेकिन व्यापक रूप से लागू शुरुआती विधानसभा टिप है। यदि आप जानते हैं कि एक लूप कम से कम एक बार चलेगा, तो लूप को डू-टाइम लूप के रूप में फिर से लिखना होगा, अंत में लूप कंडीशन चेक करने के साथ, अक्सर एक 2 बाइट जंप इंस्ट्रक्शन सेव करता है। एक विशेष मामले में आप भी उपयोग करने में सक्षम हो सकते हैं loop
।
do{}while()
विधानसभा में प्राकृतिक लूपिंग मुहावरे (विशेष रूप से दक्षता के लिए) क्यों है। यह भी ध्यान दें कि एक लूप से पहले 2-बाइट्स jecxz
/ "शून्य समय चलने की आवश्यकता" केस को "कुशलता से" संभालने के लिए jrcxz
बहुत अच्छी तरह से काम करता है loop
(दुर्लभ सीपीयू पर जहां loop
धीमा नहीं है)। लूप के अंदरjecxz
भी प्रयोग करने योग्य हैwhile(ecx){}
, एकjmp
तल पर लागू करने के लिए ।
सिस्टम वी 86 ढेर का उपयोग करता है और सिस्टम वी x86-64 का उपयोग करता है rdi
, rsi
, rdx
, rcx
, आदि इनपुट पैरामीटर के लिए, और rax
वापसी मान के रूप में है, लेकिन यह पूरी तरह से अपने खुद के फोन करने के सम्मेलन उपयोग करने के लिए उचित है। __fastcall का उपयोग करता है ecx
और edx
इनपुट पैरामीटर, और जैसा कि अन्य compilers / OSes अपने स्वयं परंपराओं का उपयोग । सुविधाजनक होने पर स्टैक और जो भी रजिस्टर / आउटपुट के रूप में उपयोग करें।
उदाहरण: दोहरावदार बाइट काउंटर , 1 बाइट समाधान के लिए एक चतुर कॉलिंग कन्वेंशन का उपयोग करना।
मेटा: रजिस्टरों को इनपुट लिखना , रजिस्टरों को आउटपुट लिखना
अन्य संसाधन: कन्वेंशन बुलाने पर एग्नर फॉग के नोट्स
int 0x80
कि सेटअप की एक गुच्छा की आवश्यकता है।
int 0x80
32-बिट कोड में, या syscall
64-बिट कोड में, इनवॉइस करने sys_write
का एकमात्र तरीका है। यह वही है जो मैंने चरम फाइबोनैचि के लिए उपयोग किया था । 64-बिट कोड में __NR_write = 1 = STDOUT_FILENO
, ताकि आप कर सकें mov eax, edi
। या यदि EAX की ऊपरी बाइट्स mov al, 4
32-बिट कोड में शून्य हैं । आप भी call printf
कर सकते हैं या puts
, मुझे लगता है, और "x86 asm Linux के लिए glibc" उत्तर लिखें। मुझे लगता है कि पीएलटी या जीओटी प्रविष्टि स्थान, या पुस्तकालय कोड की गणना न करना उचित है।
char*buf
स्ट्रिंग का उत्पादन करूंगा , जिसमें मैन्युअल स्वरूपण होगा। इस तरह से (गति के लिए अजीब तरह से अनुकूलित) asm FizzBuzz , जहां मुझे रजिस्टर में स्ट्रिंग डेटा मिला और फिर इसे संग्रहीत किया गया mov
, क्योंकि तार छोटे और निश्चित लंबाई के थे।
CMOVcc
और सेट का उपयोग करेंSETcc
यह खुद के लिए एक अनुस्मारक है, लेकिन सशर्त सेट निर्देश मौजूद हैं और प्रोसेसर पी 6 (पेंटियम प्रो) या नए पर सशर्त चाल निर्देश मौजूद हैं। ऐसे कई निर्देश हैं जो EFLAGS में स्थापित एक या अधिक झंडे पर आधारित हैं।
cmov
इसमें 2-बाइट ओपकोड ( 0F 4x +ModR/M
) है, इसलिए यह 3 बाइट न्यूनतम है। लेकिन स्रोत r / m32 है, इसलिए आप सशर्त रूप से 3 बाइट्स में लोड कर सकते हैं। ब्रांचिंग के अलावा, setcc
से अधिक मामलों में उपयोगी है cmovcc
। फिर भी, पूरे निर्देश सेट पर विचार करें, न कि केवल आधारभूत 386 निर्देश। (हालांकि SSE2 और BMI / BMI2 निर्देश इतने बड़े हैं कि वे शायद ही कभी उपयोगी होते हैं। rorx eax, ecx, 32
6 बाइट्स, लंबे + रोर की तुलना में अधिक अच्छा है। प्रदर्शन के लिए अच्छा है, जब तक कि POPCNT या PDEP बहुत से आईएस को बचाता नहीं है)
setcc
।
jmp
बाइट्स पर सेव करके अगर / उसके बजाय अगर / फिर / तोयह निश्चित रूप से बहुत बुनियादी है, बस मैंने सोचा था कि जब गोल्फिंग के बारे में सोचने के लिए मैं इसे पोस्ट करूंगा। एक उदाहरण के रूप में, हेक्साडेसिमल अंक वर्ण को डिकोड करने के लिए निम्नलिखित सरल कोड पर विचार करें:
cmp $'A', %al
jae .Lletter
sub $'0', %al
jmp .Lprocess
.Lletter:
sub $('A'-10), %al
.Lprocess:
movzbl %al, %eax
...
यह दो बाइट्स को "तब" केस को "और" मामले में गिरने से छोटा किया जा सकता है:
cmp $'A', %al
jb .digit
sub $('A'-'0'-10), %eax
.digit:
sub $'0', %eax
movzbl %al, %eax
...
sub
एक मामले के लिए महत्वपूर्ण पथ पर अतिरिक्त विलंबता पाश-चालित निर्भरता श्रृंखला का हिस्सा नहीं होता है (जैसे यहां जहां प्रत्येक इनपुट अंक 4-बिट विचलन विलय तक स्वतंत्र है। )। लेकिन मुझे लगता है कि वैसे भी +1। BTW, आपके उदाहरण में एक अलग मिस्ड ऑप्टिमाइज़ेशन है: यदि आपको movzx
वैसे भी अंत में ज़रूरत पड़ने वाली है , तो sub $imm, %al
No-modrm 2-बाइट एन्कोडिंग का लाभ लेने के लिए EAX का उपयोग न करें op $imm, %al
।
cmp
हैं sub $'A'-10, %al
; jae .was_alpha
; add $('A'-10)-'0'
। (मुझे लगता है कि मुझे तर्क सही लगा)। ध्यान दें कि 'A'-10 > '9'
कोई अस्पष्टता नहीं है। एक पत्र के लिए सुधार को घटाना एक दशमलव अंक लपेटेगा। तो यह सुरक्षित है अगर हम मान रहे हैं कि हमारा इनपुट वैसा ही है, जैसा आपका है।
आप एसआईआई से एसआईएफ की स्थापना करके और लॉस्ड / एक्सचग रेज, ईएएनएक्स के अनुक्रम का प्रदर्शन करके स्टैक से अनुक्रमिक ऑब्जेक्ट ला सकते हैं।
pop eax
/ pop edx
/ से बेहतर क्यों है ...? यदि आपको उन्हें स्टैक पर छोड़ने की आवश्यकता है, तो आप push
ईएसपी को पुनर्स्थापित करने के बाद सभी को वापस कर सकते हैं, फिर भी बिना किसी आवश्यकता के साथ प्रति ऑब्जेक्ट 2 बाइट्स mov esi,esp
। या आप 64-बिट कोड में 4-बाइट ऑब्जेक्ट्स के लिए क्या मतलब है जहां pop
8 बाइट्स मिलेगा? BTW, तुम भी pop
बेहतर प्रदर्शन के साथ एक बफर पर पाश का उपयोग कर सकते हैं lodsd
, उदाहरण के लिए एक्सट्रीम फाइबोनैचि में विस्तारित सटीक परिशुद्धता के
64-बिट रजिस्टर की प्रतिलिपि बनाने के लिए, उपयोग करें push rcx
; pop rdx
3-बाइट के बजाय mov
।
REX उपसर्ग की आवश्यकता के बिना पुश / पॉप का डिफ़ॉल्ट ऑपरेंड-आकार 64-बिट है।
51 push rcx
5a pop rdx
vs.
48 89 ca mov rdx,rcx
(एक ऑपरेंड-आकार का उपसर्ग पुश / पॉप आकार को 16-बिट तक ओवरराइड कर सकता है, लेकिन 32-बिट पुश / पॉप ऑपरेंड-आकार 64-बिट मोड में REX.W = 0 के साथ भी एन्कोड करने योग्य नहीं है।)
यदि या तो दोनों रजिस्टर हैं r8
.. r15
, का उपयोग करें mov
क्योंकि पुश और / या पॉप को REX उपसर्ग की आवश्यकता होगी। सबसे बुरी स्थिति यह वास्तव में खो देती है यदि दोनों को आरईएक्स उपसर्गों की आवश्यकता होती है। जाहिर है आप आमतौर पर कोड गोल्फ में r8..r15 से बचना चाहिए।
आप इस NASM मैक्रो के साथ विकसित होते हुए अपने स्रोत को अधिक पठनीय रख सकते हैं । बस याद रखें कि यह आरएसपी से नीचे 8 बाइट्स पर कदम रखता है। (X86-64 सिस्टम V में रेड-ज़ोन में)। लेकिन सामान्य परिस्थितियों में यह 64-बिट mov r64,r64
या के लिए एक ड्रॉप-इन प्रतिस्थापन हैmov r64, -128..127
; mov %1, %2 ; use this macro to copy 64-bit registers in 2 bytes (no REX prefix)
%macro MOVE 2
push %2
pop %1
%endmacro
उदाहरण:
MOVE rax, rsi ; 2 bytes (push + pop)
MOVE rbp, rdx ; 2 bytes (push + pop)
mov ecx, edi ; 2 bytes. 32-bit operand size doesn't need REX prefixes
MOVE r8, r10 ; 4 bytes, don't use
mov r8, r10 ; 3 bytes, REX prefix has W=1 and the bits for reg and r/m being high
xchg eax, edi ; 1 byte (special xchg-with-accumulator opcodes)
xchg rax, rdi ; 2 bytes (REX.W + that)
xchg ecx, edx ; 2 bytes (normal xchg + modrm)
xchg rcx, rdx ; 3 bytes (normal REX + xchg + modrm)
xchg
उदाहरण का हिस्सा यह है क्योंकि कभी-कभी आपको EAX या RAX में मान प्राप्त करने की आवश्यकता होती है और पुरानी प्रति को संरक्षित करने की परवाह नहीं करते हैं। पुश / पॉप वास्तव में आपको विनिमय करने में मदद नहीं करता है, हालांकि।
push 200; pop edx
- इनिशियलाइज़ेशन के लिए 3 बाइट्स का उपयोग करें ।