2009-02-01 17 views
5

जीसीसी compiles (gcc --omit-frame-pointer -s उपयोग करते हुए):मेरे जीसीसी आउटपुट में अतिरिक्त निर्देश क्यों हैं?

int the_answer() { return 42; } 

  .Text 
    .globl _the_answer 
    _the_answer: 
     subl $12, %esp 
     movl $42, %eax 
     addl $12, %esp 
     ret 
     .subsections_via_symbols 

'$ 12' निरंतर यहाँ क्या कर रहा है, और क्या '% esp' रजिस्टर है में?

उत्तर

8

संक्षिप्त उत्तर: ढेर फ्रेम।

लंबा उत्तर: जब आप कोई फ़ंक्शन कॉल करते हैं, तो कंपाइलर फ़ंक्शन वेरिएबल जैसे स्थानीय डेटा की अनुमति देने के लिए स्टैक पॉइंटर में हेरफेर करेंगे। चूंकि आपका कोड esp बदल रहा है, स्टैक पॉइंटर, जो मुझे लगता है वह यहां हो रहा है। मैंने सोचा होगा कि जीसीसी स्मार्ट इसे इस ऑप्टिमाइज़ करने के लिए पर्याप्त है जहां वास्तव में इसकी आवश्यकता नहीं है, लेकिन आप अनुकूलन का उपयोग नहीं कर रहे हैं।

1

जीसीसी 4.3.2 का उपयोग करते हुए मैं समारोह के लिए इस मिल:

the_answer: 
movl $42, %eax 
ret 

... प्लस जंक आसपास के निम्न आदेश पंक्ति का उपयोग करके,: echo 'int the_answer() { return 42; }' | gcc --omit-frame-pointer -S -x c -o - -

कौन सा संस्करण का उपयोग कर रहे हैं?

+0

4.0.1 (ऐप्पल इंक 5488 का निर्माण)। मान लीजिए कि यह एक बग है। –

+1

@ माइक, एक बग नहीं। कोड ठीक काम करता है क्योंकि एसयूएल को एडीएल द्वारा उलट दिया जाता है। यह अक्षम है लेकिन निश्चित रूप से एक बग नहीं है। – paxdiablo

+0

कोई बग नहीं हो सकता है, यह हो सकता है कि 4.3 यह समझने में स्मार्ट हो कि कौन से निर्देश निकालने के लिए सुरक्षित हैं। – flussence

4
_the_answer: 
    subl $12, %esp 
    movl $42, %eax 
    addl $12, %esp 
    ret 

पहला उपन्यास आपके कार्य में उपयोग किए जा सकने वाले चर के लिए जगह बनाने के लिए स्टैक-पॉइंटर को कम करता है। फ़्रेम पॉइंटर के लिए एक स्लॉट का उपयोग किया जा सकता है, उदाहरण के लिए, रिटर्न पता रखने के लिए दूसरा। आपने कहा कि इसे फ्रेम पॉइंटर को छोड़ देना चाहिए। इसका आमतौर पर मतलब है कि फ्रेम फ्रेम को सहेजने/पुनर्स्थापित करने के लिए यह लोड/स्टोर को छोड़ देता है। लेकिन अक्सर कोड इसके लिए स्मृति आरक्षित करेगा। कारण यह है कि यह कोड बनाता है जो ढेर का विश्लेषण बहुत आसान है। स्टैक की ऑफसेट को न्यूनतम चौड़ाई देना आसान है और इसलिए आप जानते हैं कि आप पहले स्थानीय चर स्लॉट पर पहुंचने के लिए हमेशा एफपी + 0x12 तक पहुंच सकते हैं, भले ही आप फ्रेम पॉइंटर को सहेजना छोड़ दें।

ठीक है, eax x86 पर कॉलर को वापसी मूल्य को संभालने के लिए उपयोग किया जाता है, जहां तक ​​मुझे पता है। और अंतिम addl बस आपके फ़ंक्शन के लिए पहले बनाए गए फ्रेम को नष्ट कर देता है।

कोड जो कार्यों के प्रारंभ और अंत में निर्देश उत्पन्न करता है उसे फ़ंक्शन के "एपिलॉग" और "प्रस्तावना" कहा जाता है। यहाँ क्या मेरी बंदरगाह करता है जब यह जीसीसी में एक समारोह की प्रस्तावना बनाने के लिए किया गया है (यह वास्तविक दुनिया पोर्ट के रूप में तेजी से और संभव के रूप में बहुमुखी होना करने का इरादा के लिए जिस तरह से और अधिक जटिल है):

void eco32_prologue(void) { 
    int i, j; 
    /* reserve space for all callee saved registers, and 2 additional ones: 
    * for the frame pointer and return address */ 
    int regs_saved = registers_to_be_saved() + 2; 
    int stackptr_off = (regs_saved * 4 + get_frame_size()); 

    /* decrement the stack pointer */ 
    emit_move_insn(stack_pointer_rtx, 
        gen_rtx_MINUS(SImode, stack_pointer_rtx, 
           GEN_INT(stackptr_off))); 

    /* save return adress, if we need to */ 
    if(eco32_ra_ever_killed()) { 
     /* note: reg 31 is return address register */ 
     emit_move_insn(gen_rtx_MEM(SImode, 
          plus_constant(stack_pointer_rtx, 
             -4 + stackptr_off)), 
         gen_rtx_REG(SImode, 31)); 
    } 

    /* save the frame pointer, if it is needed */ 
    if(frame_pointer_needed) { 
     emit_move_insn(gen_rtx_MEM(SImode, 
          plus_constant(stack_pointer_rtx, 
             -8 + stackptr_off)), 
         hard_frame_pointer_rtx); 
    } 

    /* save callee save registers */ 
    for(i=0, j=3; i<FIRST_PSEUDO_REGISTER; i++) { 
     /* if we ever use the register, and if it's not used in calls 
     * (would be saved already) and it's not a special register */ 
     if(df_regs_ever_live_p(i) && 
      !call_used_regs[i] && !fixed_regs[i]) { 
      emit_move_insn(gen_rtx_MEM(SImode, 
           plus_constant(stack_pointer_rtx, 
              -4 * j + stackptr_off)), 
          gen_rtx_REG(SImode, i)); 
      j++; 
     } 
    } 

    /* set the new frame pointer, if it is needed now */ 
    if(frame_pointer_needed) { 
     emit_move_insn(hard_frame_pointer_rtx, 
         plus_constant(stack_pointer_rtx, stackptr_off)); 
    } 
} 

मैं कुछ छोड़े गए कोड जो अन्य मुद्दों से संबंधित है, मुख्य रूप से जीसीसी को बताते हुए अपवाद हैंडलिंग के लिए महत्वपूर्ण निर्देश क्या हैं (यानी जहां फ्रेम पॉइंटर संग्रहीत किया जाता है)। खैर, कैली सहेजे गए रजिस्टर्स वे हैं जिन्हें कॉलर को कॉल से पहले सहेजने की आवश्यकता नहीं होती है। बुलाया गया फ़ंक्शन आवश्यकतानुसार उन्हें सहेजने/पुनर्स्थापित करने की परवाह करता है। जैसा कि आप पहली पंक्तियों में देखते हैं, हम हमेशा वापसी पते और फ्रेम सूचक के लिए स्थान आवंटित करते हैं। वह जगह केवल कुछ बाइट्स है और इससे कोई फर्क नहीं पड़ता। लेकिन जब आवश्यक हो तो हम केवल स्टोर/भार उत्पन्न करते हैं। अंत में "हार्ड" फ्रेम सूचक "असली" फ्रेम सूचक रजिस्टर है नोट करें। कुछ जीसीसी आंतरिक कारणों से यह आवश्यक है। "Frame_pointer_needed" ध्वज जीसीसी द्वारा निर्धारित किया जाता है, जब भी मैं फ्रेम-पॉइंटर संग्रहीत नहीं कर सकता। कुछ मामलों के लिए, इसे संग्रहीत किया जाना चाहिए, उदाहरण के लिए जब alloca (यह गतिशील रूप से स्टैकपोइंटर बदलता है) का उपयोग किया जाता है। जीसीसी सभी के बारे में परवाह करता है।ध्यान दें कि मैंने उस कोड को लिखने के बाद कुछ समय दिया है, इसलिए मुझे उम्मीद है कि ऊपर दी गई अतिरिक्त टिप्पणियां सभी गलत नहीं हैं :)

3

स्टैक संरेखण। फ़ंक्शन एंट्री पर, esp is -4 mod 16 है, वापसी पते को call द्वारा धक्का दिया गया है। 12 घटाना इसे फिर से संरेखित करता है। एमएमएक्स/एसएसई/इत्यादि का उपयोग कर रहे मल्टीमीडिया कोड को छोड़कर x86 पर 16 बाइट्स पर स्टैक को गठबंधन करने का कोई अच्छा कारण नहीं है, लेकिन कहीं 3.x युग में, जीसीसी डेवलपर्स ने तय किया कि स्टैक को हमेशा किसी भी तरह से गठबंधन किया जाना चाहिए प्रस्तावना/उपद्रव उपरांत, ढेर के आकार में वृद्धि हुई, और इसके परिणामस्वरूप कुछ विशेष प्रयोजनों के हितों के लिए सभी कार्यक्रमों पर कैश थ्रैशिंग बढ़ गई (जो संयोग से मेरे कुछ क्षेत्रों में से कुछ होने के कारण होता है, लेकिन मुझे अभी भी लगता है कि यह अनुचित और बुरा था फेसला)।

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

आप इस समस्या को -mpreferred-stack-boundary=2 के साथ भी ठीक कर सकते हैं।

+0

+1 यह स्वीकार्य उत्तर होना चाहिए! मैंने जीसीसी 4.8.एक्स (मिनजीडब्लू) का उपयोग करके एक समान उदाहरण की कोशिश की, और '_main' प्रक्रिया में डिफ़ॉल्ट रूप से यह प्रस्तावना के हिस्से के रूप में 'andl $ -16,% esp' उत्सर्जित करता है, जो प्रभावी रूप से स्टैक पॉइंटर को 16 बाइट्स तक संरेखित करता है । हालांकि इस तरह के निर्देश को '_the_answer' नामक फ़ंक्शन में दिखाई नहीं देता है – Amro

संबंधित मुद्दे