X86 / x64 मशीन कोड में गोल्फिंग के लिए टिप्स


27

मैंने देखा कि ऐसा कोई सवाल नहीं है, इसलिए यहाँ यह है:

क्या आपके पास मशीन कोड में गोल्फिंग के लिए सामान्य सुझाव हैं? यदि टिप केवल एक निश्चित वातावरण या कॉलिंग कन्वेंशन पर लागू होती है, तो कृपया अपने उत्तर में निर्दिष्ट करें।

कृपया प्रति उत्तर केवल एक टिप ( यहां देखें )।

जवाबों:


11

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 करने के लिए सीपीयू रजिस्टर में सभी बिट्स सेट


इसके अलावा, 0 के अलावा एक छोटे (8-बिट) मान के साथ एक रजिस्टर को इनिशियलाइज़ करने के लिए push 200; pop edx- इनिशियलाइज़ेशन के लिए 3 बाइट्स का उपयोग करें ।
एनाटॉलीग

2
BTW को -1, उपयोग dec, उदाहरण के लिए एक रजिस्टर शुरू करने के लिएxor eax, eax; dec eax
anatolyg

@anatolyg: 200 एक खराब उदाहरण है, यह एक हस्ताक्षर-विस्तारित -8 में फिट नहीं है। लेकिन हां, push imm8/ pop reg3 बाइट्स है, और x86-64 पर 64-बिट स्थिरांक के लिए शानदार है, जहां dec/ inc2 बाइट्स है। और push r64/ pop 64(2 बाइट्स) एक 3 बाइट mov r64, r64(REX के साथ 3 बाइट्स ) को भी बदल सकता है । यह भी देखें कि सीपीयू रजिस्टर में सभी बिट्स को 1 के लिए कुशलतापूर्वक सेट करें जैसे lea eax, [rcx-1]कि किसी ज्ञात मान में दिए गए सामान के लिए eax(जैसे कि एक शून्य रजिस्टर और दूसरे स्थिरांक की आवश्यकता है, बस पुश / पॉप के बजाय LEA का उपयोग करें
पीटर कॉर्डेस

10

बहुत सारे मामलों में, संचायक-आधारित निर्देश (यानी जो (R|E)AXगंतव्य संचालक के रूप में लेते हैं) सामान्य मामले के निर्देशों की तुलना में 1 बाइट से छोटे होते हैं; StackOverflow पर यह प्रश्न देखें ।


आम तौर पर सबसे उपयोगी वाले al, imm8विशेष मामले होते हैं , जैसे or al, 0x20/ sub al, 'a'/ cmp al, 'z'-'a'/ ja .non_alphabetic2 2 बाइट्स प्रत्येक के बजाय, 3. के बजाय alचरित्र डेटा का उपयोग भी अनुमति देता है lodsbऔर / या stosb। या alEAX के कम बाइट के बारे में कुछ परीक्षण करने के लिए उपयोग करें, जैसे lodsd/ test al, 1/ setnz clबनाता है cl = 1 या 0 विषम / सम के लिए। लेकिन दुर्लभ मामले में जहां आपको 32-बिट तत्काल की आवश्यकता होती है, फिर सुनिश्चित करें op eax, imm32, जैसे मेरे क्रोमा-कुंजी उत्तर में
पीटर कॉर्ड्स

8

अपने कॉलिंग कन्वेंशन को चुनें जहाँ आप उन्हें चाहते हैं।

आपके उत्तर की भाषा asm (वास्तव में मशीन कोड) है, इसलिए इसे asm में लिखे गए प्रोग्राम के भाग के रूप में मानिए, C- संकलित-for-x86 के लिए नहीं। आपका फ़ंक्शन किसी भी मानक कॉलिंग कन्वेंशन के साथ C से आसानी से कॉल करने योग्य नहीं है। अगर यह आपको किसी भी अतिरिक्त बाइट खर्च नहीं करता है, तो यह एक अच्छा बोनस है।

एक शुद्ध एएसएम कार्यक्रम में, कुछ सहायक कार्यों के लिए एक कॉलिंग सम्मेलन का उपयोग करना सामान्य है जो उनके लिए और उनके कॉलर के लिए सुविधाजनक है। इस तरह के फ़ंक्शन टिप्पणियों के साथ अपने कॉलिंग कन्वेंशन (इनपुट्स / आउटपुट / क्लोबर्स) को दस्तावेज करते हैं।

वास्तविक जीवन में, यहां तक ​​कि asm प्रोग्राम भी करते हैं (मुझे लगता है) ज्यादातर फ़ंक्शन (विशेषकर विभिन्न स्रोत फ़ाइलों के लिए) के लिए लगातार कॉलिंग कन्वेंशन का उपयोग करते हैं, लेकिन कोई भी महत्वपूर्ण फ़ंक्शन कुछ विशेष कर सकता है। कोड-गोल्फ में, आप एक एकल फ़ंक्शन से बकवास का अनुकूलन कर रहे हैं, इसलिए स्पष्ट रूप से यह महत्वपूर्ण / विशेष है।


C प्रोग्राम से अपने फंक्शन को टेस्ट करने के लिए, एक रैपर लिख सकते हैं, जो सही जगहों पर आर्गन डालता है, आपके द्वारा क्लोब किए गए किसी भी अतिरिक्त रजिस्टर को बचाता / पुनर्स्थापित करता है, और रिटर्न वैल्यू डालता है e/raxअगर यह पहले से ही नहीं था।


क्या उचित है: कुछ भी जो कॉलर पर अनुचित बोझ नहीं डालता है:

  • ईएसपी / आरएसपी कॉल-संरक्षित होना चाहिए; अन्य पूर्णांक regs निष्पक्ष खेल हैं। (आरबीपी और आरबीएक्स आमतौर पर सामान्य सम्मेलनों में कॉल-संरक्षित होते हैं, लेकिन आप दोनों को समझ सकते हैं ।)
  • किसी भी रजिस्टर (RSP को छोड़कर) में कोई भी तर्क उचित है, लेकिन कॉल करने वाले को एक ही arg को कई रजिस्टरों में कॉपी करने के लिए नहीं कहा जाता है।
  • कॉल / रिटेल पर डीएफ ( lods/ stos/ आदि के लिए स्ट्रिंग दिशा ध्वज ) स्पष्ट (ऊपर) होना सामान्य है। इसे कॉल / रीफ़ पर अपरिभाषित होने देना ठीक रहेगा। इसे साफ़ करने या प्रवेश पर सेट करने की आवश्यकता होती है, लेकिन जब आप वापस लौटते हैं तो यह संशोधित होता है।

  • X87 में FP मान लौटाना st0उचित है, लेकिन st3अन्य x87 रजिस्टर में कचरा के साथ वापस नहीं आता है। फोन करने वाले को x87 स्टैक को साफ करना होगा। यहां तक ​​कि st0गैर-खाली उच्च स्टैक रजिस्टरों के साथ वापसी भी संदिग्ध होगी (जब तक कि आप कई मान वापस नहीं कर रहे हैं)।

  • आपके फ़ंक्शन के साथ कॉल किया जाएगा call, इसलिए [rsp]आपका रिटर्न पता है। आप कर सकते हैं से बचने के call/ retकी तरह कड़ी रजिस्टर का उपयोग कर 86 पर lea rbx, [ret_addr]/ jmp functionऔर साथ वापसी jmp rbx, लेकिन है कि "उचित" नहीं है। यह कॉल / रिटेल जितना कुशल नहीं है, इसलिए यह ऐसा कुछ नहीं है जिसे आप वास्तविक कोड में खोज लेंगे।
  • RSP के ऊपर असीमित मेमोरी क्लोब करना वाजिब नहीं है, लेकिन स्टैक पर आपके फंक्शन के क्लॉबिंग को सामान्य कॉलिंग कन्वेंशन में अनुमति दी जाती है। x64 विंडोज को रिटर्न एड्रेस के ऊपर 32 बाइट्स छाया स्थान की आवश्यकता होती है, जबकि x86-64 सिस्टम V आपको RSP के नीचे 128 बाइट रेड-ज़ोन देता है, इसलिए दोनों में से कोई भी उचित है। (या बहुत बड़ा रेड-ज़ोन, विशेष रूप से फ़ंक्शन के बजाय एक स्टैंड-अलोन प्रोग्राम में।)

बॉर्डरलाइन मामले: एक फ़ंक्शन लिखते हैं जो एक सरणी में एक अनुक्रम पैदा करता है, जिसे फ़ंक्शन आर्ग्स के रूप में पहले 2 तत्व दिए गए हैं । मैंने कॉलर को अनुक्रम में सरणी में स्टोर करना शुरू कर दिया और सरणी के लिए केवल एक पॉइंटर पास किया। यह निश्चित रूप से प्रश्न की आवश्यकताओं को झुका रहा है। मैंने इसके xmm0लिए पैक किए गए आर्ग को लेने पर विचार किया movlps [rdi], xmm0, जो एक अजीब कॉलिंग कन्वेंशन भी होगा।


FLAGS (शर्त कोड) में एक बूलियन वापस करें

OS X सिस्टम कॉल ऐसा करता है ( CF=0इसका कोई अर्थ नहीं है): क्या झंडे रजिस्टर को बूलियन रिटर्न मान के रूप में उपयोग करना बुरा माना जाता है?

किसी भी शर्त को एक जेसीसी के साथ जांचा जा सकता है, पूरी तरह से उचित है, खासकर यदि आप उस समस्या के लिए किसी भी अर्थ संबंधी प्रासंगिकता को चुन सकते हैं। (उदाहरण के लिए तुलनात्मक फ़ंक्शन झंडे सेट कर सकता है इसलिए jneयदि वे समान नहीं थे, तो इसे ले लिया जाएगा)।


संकीर्ण args (जैसे char) की आवश्यकता है संकेत या शून्य को 32 या 64 बिट तक विस्तारित किया जाना चाहिए।

यह अनुचित नहीं है; आधुनिक x86 asm में आंशिक-पंजीयन मंदी का उपयोग करना movzxया उससे movsx बचना सामान्य है। वास्तव में क्लैंग / एलएलवीएम पहले से ही कोड बनाता है जो कि x86-64 सिस्टम वी कॉलिंग कन्वेंशन के लिए एक अनियोजित एक्सटेंशन पर निर्भर करता है: 32 बिट्स की तुलना में संकरा संकरा साइन या शून्य को कॉलर द्वारा 32 बिट्स तक बढ़ाया जाता है

यदि आप चाहें तो आप अपने दस्तावेज़ों में uint64_tया int64_tअपने प्रोटोटाइप में 64 बिट्स के एक्सटेंशन का वर्णन / वर्णन कर सकते हैं। उदाहरण के लिए, आप एक loopनिर्देश का उपयोग कर सकते हैं , जो RCX के पूरे 64 बिट्स का उपयोग करता है जब तक कि आप 32-बिट ECX (हाँ वास्तव में, पता-आकार नहीं ऑपरेंड-आकार) को ओवरराइड करने के लिए पता-आकार उपसर्ग का उपयोग करते हैं।

ध्यान दें कि longWindows 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 rax2 बाइट्स है, और इसके बराबर है mov rax,rsp, इसलिए आप अभी भी 2 बाइट्स में पूर्ण 64-बिट रजिस्टर कॉपी कर सकते हैं ।


जब चुनौतियां किसी सरणी को वापस करने के लिए कहती हैं, तो क्या आपको लगता है कि स्टैक पर वापस आना उचित है? मुझे लगता है कि एक कंपाइलर क्या करेगा जब मूल्य द्वारा एक संरचना लौटाएगा।
qwr

@qwr: नहीं, मुख्यधारा के कॉलिंग कन्वेंशन रिटर्न मान के लिए एक छिपे हुए सूचक को पास करते हैं। (कुछ परंपराएं रजिस्टरों में छोटी संरचनाओं को पारित / वापस करती हैं)। सी / सी ++ हुड के तहत मूल्य से वापसी संरचना , और देखें कि विधानसभा स्तर पर x86 में ऑब्जेक्ट कैसे काम करते हैं? । ध्यान दें कि पासिंग एरे (स्ट्रक्चर के अंदर) उन्हें x86-64 SysV के लिए स्टैक पर कॉपी करता है: AMD 11 ABI के अनुसार किस प्रकार का C11 डेटा प्रकार एक सरणी है , लेकिन Windows x64 एक नॉन-कास्ट पॉइंटर पास करता है।
पीटर कॉर्डेस

तो आप उचित या नहीं के बारे में क्या सोचते हैं? क्या आप x86 को इस नियम के तहत गिनते हैं codegolf.meta.stackexchange.com/a/8507/17360
qwr

1
@qwr: x86 एक "स्टैक आधारित भाषा" नहीं है। x86 रैम के साथ रजिस्टर मशीन है , स्टैक मशीन नहीं । एक स्टैक मशीन रिवर्स-पॉलिश संकेतन की तरह है, x87 रजिस्टरों की तरह। fld / fld / faddp x86 का कॉल-स्टैक उस मॉडल के अनुरूप नहीं है: सभी सामान्य कॉलिंग कन्वेंशन RSP को अनमोडिफाइड छोड़ देते हैं, या आर्ग्स को पॉप करते हैं ret 16; वे वापसी पते को पॉप नहीं करते हैं, फिर एक सरणी धक्का, push rcx/ ret। कॉल करने वाले को सरणी का आकार जानना होगा या खुद को खोजने के लिए स्टैक के बाहर कहीं आरएसपी को बचाया होगा।
पीटर कॉर्ड्स

कॉल फंक्शन में कॉल के बाद अनुदेश के पते को पुश जेएमपी में कहा जाता है; स्टैक और जेएमपी से पते को उस पते पर
पुन: भेजें

7

AL / AX / EAX, और अन्य लघु रूपों और एकल-बाइट निर्देशों के लिए विशेष-केस शॉर्ट-फॉर्म एन्कोडिंग का उपयोग करें

उदाहरण 32/64-बिट मोड को मानते हैं, जहां डिफ़ॉल्ट ऑपरेंड का आकार 32 बिट्स है। एक ऑपरेंड-आकार के उपसर्ग EAX (या 16-बिट मोड में रिवर्स) के बजाय निर्देश को AX में बदल देता है।

  • inc/decएक रजिस्टर (8-बिट के अलावा): inc eax/ dec ebp। (नहीं x86-64: 0x4xओपकोड बाइट्स को आरईएक्स उपसर्गों के रूप में पुनर्निर्मित किया गया था, इसलिए inc r/m32यह एकमात्र एन्कोडिंग है।)

    8-बिट inc bl2 बाइट्स है, जिसमें inc r/m8opcode + ModR / M ऑपरेंड एन्कोडिंग का उपयोग किया गया है । इसलिए वेतन वृद्धि का उपयोग inc ebxकरें bl, अगर यह सुरक्षित है। (उदाहरण के लिए यदि आपको उन मामलों में जेडएफ परिणाम की आवश्यकता नहीं है जहां ऊपरी बाइट्स गैर-शून्य हो सकते हैं)।

  • scasd: e/rdi+=4, आवश्यकता है कि रजिस्टर पढ़ने योग्य स्मृति को इंगित करता है। कभी-कभी उपयोगी भी अगर आप FLAGS परिणाम (जैसे cmp eax,[rdi]/ rdi+=4) के बारे में परवाह नहीं करते हैं । और 64-बिट मोड में, scasb1-बाइट के रूप में काम कर सकता हैinc rdi , अगर लॉस्डब या स्टॉस्ब उपयोगी नहीं हैं।

  • xchg eax, r32: यह वह जगह है जहां 0x90 एनओपी से आया था: xchg eax,eax। उदाहरण: दो के साथ 3 रजिस्टरों फिर से व्यवस्था xchgएक निर्देश cdq/ idivपाश 8 बाइट में GCD के लिए जहां निर्देश के सबसे एकल-बाइट कर रहे हैं, का दुरुपयोग सहित inc ecx/ loopके बजाय test ecx,ecx/jnz

  • cdqEDX में साइन-ईएक्स का विस्तार करें: 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 rdi2 बाइट्स है, लेकिन mov rdi, rspREX उपसर्ग की जरूरत है और 3 बाइट्स है।

xlatbमौजूद है, लेकिन शायद ही कभी उपयोगी है। बचने के लिए एक बड़ी देखने की मेज कुछ है। मुझे एएए / डीएए या अन्य पैक-बीसीडी या 2-एएससीआईआई-अंकों के निर्देशों का उपयोग कभी नहीं मिला।

1-बाइट lahf/ sahfशायद ही कभी उपयोगी होते हैं। आप एक विकल्प के रूप में / कर सकते हैं , लेकिन यह आमतौर पर उपयोगी नहीं है।lahfand ah, 1setc ah

और सीएफ के लिए विशेष रूप से, sbb eax,eax0 / -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 का इतिहास: ये एनकोड क्यों मौजूद हैं

मूल 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कई मामलों में शून्य की आवश्यकता होती है।
qwr

1
@qwr: ठीक है, यदि आप जानते हैं कि आप अपने लाभांश को 2 ^ 31 (यानी हस्ताक्षर किए जाने पर गैर-नकारात्मक) से नीचे है, या यदि आप संभावित-बड़े मूल्य पर सेट करने से पहले इसका उपयोग करते हैं, तो आप cdqअहस्ताक्षरित होने से पहले दुरुपयोग कर सकते हैं । आम तौर पर (कोड-गोल्फ के बाहर) आप सेटअप के रूप में और इससे पहलेdiveaxcdqidivxor edx,edxdiv
पीटर कॉर्ड्स

5

fastcallसम्मेलनों का उपयोग करें

x86 प्लेटफॉर्म में कई कॉलिंग कन्वेंशन हैं । आपको उन लोगों का उपयोग करना चाहिए जो रजिस्टरों में पैरामीटर पास करते हैं। X86_64 पर, पहले कुछ पैरामीटर वैसे भी रजिस्टरों में पारित किए जाते हैं, इसलिए वहां कोई समस्या नहीं है। 32-बिट प्लेटफार्मों पर, डिफ़ॉल्ट कॉलिंग कन्वेंशन ( cdecl) स्टैक में पैरामीटर पास करता है, जो कि गोल्फ के लिए अच्छा नहीं है - स्टैक पर मापदंडों तक पहुंचने के लिए लंबे निर्देशों की आवश्यकता होती है।

का उपयोग करते समय fastcall32-बिट प्लेटफार्मों पर, 2 पहले पैरामीटर आमतौर पर में पारित कर रहे हैं ecxऔर edx। यदि आपके फ़ंक्शन में 3 पैरामीटर हैं, तो आप इसे 64-बिट प्लेटफ़ॉर्म पर लागू करने पर विचार कर सकते हैं।

fastcallकन्वेंशन के लिए सी फ़ंक्शन प्रोटोटाइप ( इस उदाहरण के उत्तर से लिया गया ):

extern int __fastcall SwapParity(int value);                 // MSVC
extern int __attribute__((fastcall)) SwapParity(int value);  // GNU   

या पूरी तरह से कस्टम कॉलिंग कन्वेंशन का उपयोग करें , क्योंकि आप शुद्ध एएसएम में लिख रहे हैं, जरूरी नहीं कि कोड को सी से बुलाया जाए। FLAGS में रिटर्निंग बूलियंस अक्सर सुविधाजनक होता है।
पीटर कॉर्ड्स

5

128 जोड़ने के बजाय घटाव -128

0100 81C38000      ADD     BX,0080
0104 83EB80        SUB     BX,-80

समान रूप से, जोड़ -128, घटाव 128 के बजाय


1
यह भी दूसरी दिशा में काम करता है निश्चित रूप से: बजाय उप 128. मज़ेदार तथ्य की -128 जोड़ें: compilers इस अनुकूलन पता है, और यह भी मोड़ के संबंधित अनुकूलन कर < 128में <= 127के लिए एक तत्काल संकार्य की भयावहता को कम करने cmp, या जीसीसी हमेशा उलटफेर पसंद तुलना को कम करने के लिए तुलना करता है भले ही यह -129 बनाम -128 न हो।
पीटर कॉर्डेस

4

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


1
यह थोड़ा भ्रमित करने वाला है। क्या आप विस्तार कर सकते हैं?
NoOneIsHere

@NoOneIsHere मेरा मानना ​​है कि वह EAX और EDX सहित 0 के लिए तीन रजिस्टर सेट करना चाहता है।
NieDzejkob

4

सीपीयू रजिस्टर और ध्वज ज्ञात स्टार्टअप राज्यों में हैं

हम यह मान सकते हैं कि सीपीयू प्लेटफॉर्म और ओएस पर आधारित एक ज्ञात और प्रलेखित डिफ़ॉल्ट स्थिति में है।

उदाहरण के लिए:

डॉस http://www.fysnet.net/yourhelp.htm

लिनक्स x86 ELF http://asm.sourceforge.net/articles/startup.html


1
कोड गोल्फ नियम कहता है कि आपके कोड को कम से कम एक कार्यान्वयन पर काम करना है। लिनक्स एक नए उपयोगकर्ता-अंतरिक्ष प्रक्रिया में प्रवेश करने से पहले सभी रेज (RSP को छोड़कर) और स्टैक को शून्य करने का विकल्प चुनता है, हालांकि i386 और x86-64 सिस्टम V ABI डॉक्स कहते हैं कि वे प्रवेश के लिए "अपरिभाषित" हैं _start। तो हाँ यह उचित खेल है कि अगर आप एक समारोह के बजाय एक कार्यक्रम लिख रहे हैं तो इसका लाभ उठाएं । मैंने एक्सट्रीम फाइबोनैचि में ऐसा किया । (एक गतिशील रूप से जुड़े निष्पादन में, रन ld.so अपने को कूदने से पहले _start, और करता है रजिस्टरों में छुट्टी कचरा है, लेकिन स्थिर सिर्फ अपने कोड है।)
पीटर Cordes

3

1 जोड़ने या घटाने के लिए, एक बाइट incया decनिर्देशों का उपयोग करें जो मल्टीबाइट ऐड और उप निर्देशों से छोटे हैं।


ध्यान दें कि 32-बिट मोड में 1-बाइट है inc/dec r32जिसमें ओपकोड में एनकोडेड रजिस्टर नंबर है। तो inc ebx1 बाइट है, लेकिन inc blहै 2. अभी भी add bl, 1पाठ्यक्रम से छोटा है , के अलावा अन्य रजिस्टरों के लिए al। यह भी ध्यान दें कि inc/ decCF अनमॉडिफाइड छोड़ दें, लेकिन अन्य झंडे अपडेट करें।
पीटर कॉर्डेस

1
X86 में 2 +2 और -2 के लिए
l4m2

3

lea गणित के लिए

यह शायद x86 के बारे में जानने वाली पहली चीजों में से एक है, लेकिन मैं इसे एक अनुस्मारक के रूप में यहां छोड़ता हूं। lea2, 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


1
शायद यह ध्यान देने योग्य है कि lea eax, [rcx + 13]64-बिट मोड के लिए कोई अतिरिक्त-अतिरिक्त उपसर्ग संस्करण नहीं है। 32-बिट ऑपरेंड-आकार (परिणाम के लिए) और 64-बिट पता आकार (इनपुट्स के लिए)।
पीटर कॉर्डेस

3

लूप और स्ट्रिंग निर्देश वैकल्पिक निर्देश अनुक्रम से छोटे हैं। सबसे उपयोगी है loop <label>जो दो अनुदेश अनुक्रम से छोटा होता है dec ECXऔर jnz <label>, और lodsbसे छोटा होता है mov al,[esi]और inc si


2

mov जब लागू होता है तो छोटे तुरंत रजिस्टर में आ जाते हैं

यदि आप पहले से ही जानते हैं कि एक रजिस्टर के ऊपरी बिट्स 0 हैं, तो आप कम रजिस्टरों में तत्काल स्थानांतरित करने के लिए एक छोटे निर्देश का उपयोग कर सकते हैं।

b8 0a 00 00 00          mov    $0xa,%eax

बनाम

b0 0a                   mov    $0xa,%al

शून्य ऊपरी बिट्स के लिए imm8 के लिए push/ का उपयोग करेंpop

इसका श्रेय पीटर कॉर्ड्स को जाता है। xor/ mov4 बाइट्स है, लेकिन 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 ईबेक्स में रहता है, और ब्ल का उपयोग करता है।
पीटर कॉर्डेस

@PeterCordes मैंने
qwr

शायद स्थिरांक के बारे में मौजूदा जवाब में जाना चाहिए, जहां अनातोलीग ने पहले ही एक टिप्पणी में इसका सुझाव दिया था । मैं उस उत्तर को संपादित करूँगा। IMO आप (सिवाय अधिक सामान के लिए 8 बिट संकार्य आकार का उपयोग करते हुए सुझाव देने के लिए यह एक rework चाहिए xchg eax, r32) जैसे mov bl, 10/ dec bl/ jnzतो अपने कोड Rbx के उच्च बाइट्स के बारे में परवाह नहीं है।
पीटर कॉर्डेस

@PeterCordes हम्म। मुझे अभी भी यकीन नहीं है कि 8-बिट ऑपरेंड्स का उपयोग कब करना है, इसलिए मुझे यकीन नहीं है कि उस उत्तर में क्या डाला जाए।
qwr

2

FLAGS कई निर्देश के बाद सेट कर रहे हैं

कई अंकगणितीय निर्देशों के बाद, कैरी फ्लैग (अहस्ताक्षरित) और ओवरफ्लो फ्लैग (हस्ताक्षरित) स्वचालित रूप से ( अधिक जानकारी ) सेट किए जाते हैं । साइन फ्लैग और जीरो फ्लैग को कई अंकगणित और तार्किक संचालन के बाद सेट किया गया है। यह सशर्त शाखाओं के लिए इस्तेमाल किया जा सकता है।

उदाहरण:

d1 f8                   sar    %eax

ZF इस निर्देश द्वारा निर्धारित किया गया है, इसलिए हम इसे कंडेंशियल ब्रांचिंग के लिए उपयोग कर सकते हैं।


आपने कभी समता ध्वज का उपयोग कब किया है? आप जानते हैं कि यह परिणाम के निम्न 8 बिट्स का क्षैतिज xor है, है ना? (भले ही ऑपरेंड-साइज़, पीएफ केवल 8 बिट्स से सेट किया गया हो ; यह भी देखें )। सम-संख्या / विषम-संख्या नहीं; उसके बाद ZF की जाँच करें test al,1; आपको आमतौर पर वह मुफ्त में नहीं मिलता है। (या and al,1विषम / सम के आधार पर पूर्णांक 0/1 बनाने के लिए।)
पीटर कॉर्डेस

वैसे भी, अगर इस जवाब में कहा गया है "बचने के लिए पहले से ही अन्य निर्देशों द्वारा निर्धारित झंडे का उपयोग करें test/ cmp", तो यह बहुत बुनियादी शुरुआती x86 होगा, लेकिन फिर भी एक मूल्य के लायक है।
पीटर कॉर्डेस

@PeterCordes हुह, मुझे लगता है कि समता ध्वज को गलत समझा गया था। मैं अभी भी अपने दूसरे जवाब पर काम कर रहा हूं। मैं उत्तर संपादित करूँगा। और जैसा कि आप शायद बता सकते हैं, मैं एक शुरुआती हूं इसलिए बुनियादी सुझाव मदद करते हैं।
क्यूर

2

जबकि छोरों के बजाय करते-करते छोरों का उपयोग करें

यह x86 विशिष्ट नहीं है, लेकिन व्यापक रूप से लागू शुरुआती विधानसभा टिप है। यदि आप जानते हैं कि एक लूप कम से कम एक बार चलेगा, तो लूप को डू-टाइम लूप के रूप में फिर से लिखना होगा, अंत में लूप कंडीशन चेक करने के साथ, अक्सर एक 2 बाइट जंप इंस्ट्रक्शन सेव करता है। एक विशेष मामले में आप भी उपयोग करने में सक्षम हो सकते हैं loop


2
संबंधित: छोरों को हमेशा इस तरह क्यों संकलित किया जाता है? बताते हैं कि do{}while()विधानसभा में प्राकृतिक लूपिंग मुहावरे (विशेष रूप से दक्षता के लिए) क्यों है। यह भी ध्यान दें कि एक लूप से पहले 2-बाइट्स jecxz/ "शून्य समय चलने की आवश्यकता" केस को "कुशलता से" संभालने के लिए jrcxzबहुत अच्छी तरह से काम करता है loop(दुर्लभ सीपीयू पर जहां loopधीमा नहीं है)। लूप के अंदरjecxz भी प्रयोग करने योग्य हैwhile(ecx){} , एकjmp तल पर लागू करने के लिए
पीटर कॉर्डेस

@PeterCordes जो एक बहुत अच्छी तरह से लिखित उत्तर है। मैं एक कोड गोल्फ कार्यक्रम में एक लूप के बीच में कूदने के लिए एक उपयोग खोजना चाहता हूं।
क्यूर

गोटो जेएमपी और इंडेंटेशन का उपयोग करें ... लूप का पालन करें
रोजलूपी

2

जो भी कॉलिंग कन्वेंशन सुविधाजनक हैं, उसका उपयोग करें

सिस्टम वी 86 ढेर का उपयोग करता है और सिस्टम वी x86-64 का उपयोग करता है rdi, rsi, rdx, rcx, आदि इनपुट पैरामीटर के लिए, और raxवापसी मान के रूप में है, लेकिन यह पूरी तरह से अपने खुद के फोन करने के सम्मेलन उपयोग करने के लिए उचित है। __fastcall का उपयोग करता है ecxऔर edxइनपुट पैरामीटर, और जैसा कि अन्य compilers / OSes अपने स्वयं परंपराओं का उपयोग । सुविधाजनक होने पर स्टैक और जो भी रजिस्टर / आउटपुट के रूप में उपयोग करें।

उदाहरण: दोहरावदार बाइट काउंटर , 1 बाइट समाधान के लिए एक चतुर कॉलिंग कन्वेंशन का उपयोग करना।

मेटा: रजिस्टरों को इनपुट लिखना , रजिस्टरों को आउटपुट लिखना

अन्य संसाधन: कन्वेंशन बुलाने पर एग्नर फॉग के नोट्स


1
मैं आखिरकार कॉलिंग कन्वेंशन बनाने के बारे में इस सवाल पर अपना खुद का जवाब पोस्ट करने के लिए चारों ओर हो गया , और क्या अनुचित बनाम अनुचित है।
पीटर कॉर्ड्स

@PeterCordes असंबंधित, x86 में प्रिंट करने का सबसे अच्छा तरीका क्या है? अब तक मैं उन चुनौतियों से बचता रहा हूं जिनमें मुद्रण की आवश्यकता होती है। डॉस ऐसा लगता है कि इसमें I / O के लिए उपयोगी व्यवधान है लेकिन मैं केवल 32/64 बिट उत्तर लिखने की योजना बना रहा हूं। एकमात्र तरीका मुझे पता है int 0x80कि सेटअप की एक गुच्छा की आवश्यकता है।
qwr

हाँ, int 0x8032-बिट कोड में, या syscall64-बिट कोड में, इनवॉइस करने sys_writeका एकमात्र तरीका है। यह वही है जो मैंने चरम फाइबोनैचि के लिए उपयोग किया था । 64-बिट कोड में __NR_write = 1 = STDOUT_FILENO, ताकि आप कर सकें mov eax, edi। या यदि EAX की ऊपरी बाइट्स mov al, 432-बिट कोड में शून्य हैं । आप भी call printfकर सकते हैं या puts, मुझे लगता है, और "x86 asm Linux के लिए glibc" उत्तर लिखें। मुझे लगता है कि पीएलटी या जीओटी प्रविष्टि स्थान, या पुस्तकालय कोड की गणना न करना उचित है।
पीटर कॉर्ड्स

1
मैं फोन करने वाले को पास करने के लिए इच्छुक हूं और उसमें char*bufस्ट्रिंग का उत्पादन करूंगा , जिसमें मैन्युअल स्वरूपण होगा। इस तरह से (गति के लिए अजीब तरह से अनुकूलित) asm FizzBuzz , जहां मुझे रजिस्टर में स्ट्रिंग डेटा मिला और फिर इसे संग्रहीत किया गया mov, क्योंकि तार छोटे और निश्चित लंबाई के थे।
पीटर कॉर्ड्स

1

सशर्त चाल CMOVccऔर सेट का उपयोग करेंSETcc

यह खुद के लिए एक अनुस्मारक है, लेकिन सशर्त सेट निर्देश मौजूद हैं और प्रोसेसर पी 6 (पेंटियम प्रो) या नए पर सशर्त चाल निर्देश मौजूद हैं। ऐसे कई निर्देश हैं जो EFLAGS में स्थापित एक या अधिक झंडे पर आधारित हैं।


1
मैंने पाया है कि आमतौर पर ब्रांचिंग छोटी होती है। कुछ मामले हैं जहां यह एक प्राकृतिक फिट है, लेकिन cmovइसमें 2-बाइट ओपकोड ( 0F 4x +ModR/M) है, इसलिए यह 3 बाइट न्यूनतम है। लेकिन स्रोत r / m32 है, इसलिए आप सशर्त रूप से 3 बाइट्स में लोड कर सकते हैं। ब्रांचिंग के अलावा, setccसे अधिक मामलों में उपयोगी है cmovcc। फिर भी, पूरे निर्देश सेट पर विचार करें, न कि केवल आधारभूत 386 निर्देश। (हालांकि SSE2 और BMI / BMI2 निर्देश इतने बड़े हैं कि वे शायद ही कभी उपयोगी होते हैं। rorx eax, ecx, 326 बाइट्स, लंबे + रोर की तुलना में अधिक अच्छा है। प्रदर्शन के लिए अच्छा है, जब तक कि POPCNT या PDEP बहुत से आईएस को बचाता नहीं है)
पीटर कॉर्ड

@PeterCordes धन्यवाद, मैंने जोड़ा है setcc
क्यूर

1

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, %alNo-modrm 2-बाइट एन्कोडिंग का लाभ लेने के लिए EAX का उपयोग न करें op $imm, %al
पीटर कॉर्ड्स

इसके अलावा, आप कर के खत्म कर सकते cmpहैं sub $'A'-10, %al; jae .was_alpha; add $('A'-10)-'0'। (मुझे लगता है कि मुझे तर्क सही लगा)। ध्यान दें कि 'A'-10 > '9'कोई अस्पष्टता नहीं है। एक पत्र के लिए सुधार को घटाना एक दशमलव अंक लपेटेगा। तो यह सुरक्षित है अगर हम मान रहे हैं कि हमारा इनपुट वैसा ही है, जैसा आपका है।
पीटर कॉर्ड्स

0

आप एसआईआई से एसआईएफ की स्थापना करके और लॉस्ड / एक्सचग रेज, ईएएनएक्स के अनुक्रम का प्रदर्शन करके स्टैक से अनुक्रमिक ऑब्जेक्ट ला सकते हैं।


यह pop eax/ pop edx/ से बेहतर क्यों है ...? यदि आपको उन्हें स्टैक पर छोड़ने की आवश्यकता है, तो आप pushईएसपी को पुनर्स्थापित करने के बाद सभी को वापस कर सकते हैं, फिर भी बिना किसी आवश्यकता के साथ प्रति ऑब्जेक्ट 2 बाइट्स mov esi,esp। या आप 64-बिट कोड में 4-बाइट ऑब्जेक्ट्स के लिए क्या मतलब है जहां pop8 बाइट्स मिलेगा? BTW, तुम भी popबेहतर प्रदर्शन के साथ एक बफर पर पाश का उपयोग कर सकते हैं lodsd, उदाहरण के लिए एक्सट्रीम फाइबोनैचि में विस्तारित सटीक परिशुद्धता के
पीटर कॉर्ड्स

"लीक एसआई, [रिट पते का आकार +]] के बाद यह अधिक सही ढंग से उपयोगी है, जो तब तक पॉप का उपयोग करने से रोकता है जब तक कि आपके पास एक अतिरिक्त रजिस्टर न हो।
पीटर फेर्री

ओह, फंक्शन आर्ग्स के लिए? बहुत कम आप चाहते हैं कि रजिस्टरों की तुलना में अधिक args हो, या कि आप चाहते हैं कि कॉल करने वाले को सभी रजिस्टरों में पास करने के बजाय स्मृति में छोड़ दें। (मेरे पास कस्टम कॉलिंग सम्मेलनों का उपयोग करने के बारे में एक आधा-समाप्त उत्तर है, अगर मानक रजिस्टर-कॉल
पीटर कॉर्ड्स

Fastcall के बजाय cdecl स्टैक पर मापदंडों को छोड़ देगा, और इसमें बहुत सारे पैरामीटर होना आसान है। उदाहरण के लिए github.com/peterferrie/tinycrypt देखें।
पीटर फेर्री

0

कोडगुल्फ़ और एएसएम के लिए: निर्देशों का उपयोग केवल रजिस्टर, पुश पॉप का उपयोग करें, रजिस्टर मेमोरी या मेमोरी को कम से कम करें


0

64-बिट रजिस्टर की प्रतिलिपि बनाने के लिए, उपयोग करें push rcx; pop rdx3-बाइट के बजाय 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 में मान प्राप्त करने की आवश्यकता होती है और पुरानी प्रति को संरक्षित करने की परवाह नहीं करते हैं। पुश / पॉप वास्तव में आपको विनिमय करने में मदद नहीं करता है, हालांकि।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.