Mengapa kompiler GCC menghilangkan beberapa kode?

9

Saya tidak bisa mengerti mengapa kompiler GCC memotong bagian dari kode saya sementara itu benar-benar menjaga yang sama di lingkungan?

Kode C:

#define setb_SYNCO do{(PORTA|= (1<<0));} while(0);

ISR(INT0_vect){
    unsigned char i;

    i = 10;
    while(i>0)i--;   // first pause - omitted

    setb_SYNCO;
    setb_GATE;
    i=30;
    clrb_SYNCO;
    while(i>0)i--;  // second pause - preserved
    clrb_GATE;
}

Bagian LSS yang sesuai (file assembler, dibuat oleh kompiler):

ISR(INT0_vect){
  a4:   1f 92           push    r1
  a6:   0f 92           push    r0
  a8:   0f b6           in  r0, 0x3f    ; 63
  aa:   0f 92           push    r0
  ac:   11 24           eor r1, r1
  ae:   8f 93           push    r24
    unsigned char i;

    i = 10;
    while(i>0)i--;

    setb_SYNCO;
  b0:   d8 9a           sbi 0x1b, 0 ; 27
    setb_GATE;
  b2:   d9 9a           sbi 0x1b, 1 ; 27
    i=30;
    clrb_SYNCO;
  b4:   d8 98           cbi 0x1b, 0 ; 27
  b6:   8e e1           ldi r24, 0x1E   ; 30
  b8:   81 50           subi    r24, 0x01   ; 1
    while(i>0)i--;
  ba:   f1 f7           brne    .-4         ; 0xb8 <__vector_1+0x14>
    clrb_GATE;
  bc:   d9 98           cbi 0x1b, 1 ; 27
}
  be:   8f 91           pop r24
  c0:   0f 90           pop r0
  c2:   0f be           out 0x3f, r0    ; 63
  c4:   0f 90           pop r0
  c6:   1f 90           pop r1
  c8:   18 95           reti

Saya bisa berasumsi bahwa kompiler mengetahui bahwa kode seperti itu adalah dummy dan memotongnya tetapi mengapa ia mempertahankan kode yang sama di akhir kode?

Apakah ada instruksi penyusun untuk mencegah optimasi tersebut?

Roman Matveev
sumber
1
Anda juga dapat memberitahu kompiler untuk tidak mengoptimalkan satu fungsi, mungkin ada baiknya untuk mencoba metode ini dengan ISR Anda. Lihat pertanyaan ini di stackoverflow.
Vladimir Cravero
2
Hai Roman, saya menambahkan tag "c" ke pertanyaan Anda menghapus atmega. Saya harus menghapus satu tag karena ada batasan (lima), dan ketika mengajukan pertanyaan terkait kode, menambahkan nama bahasa sebagai tag sangat bagus karena semua kode (Tanya Jawab) disorot.
Vladimir Cravero
5
Secara umum, bahasa tingkat yang lebih tinggi (seperti C) secara eksplisit dirancang untuk tidak terikat pada hubungan 1: 1 dengan perakitan yang dihasilkan. Jika Anda perlu menghitung instruksi untuk mendapatkan pengaturan waktu yang tepat, Anda harus selalu mengandalkan perakitan (seperti yang dilakukan beberapa jawaban). Inti dari bahasa tingkat tinggi adalah bahwa kompiler memiliki beberapa kebebasan untuk membantu membuat kode Anda lebih cepat-lebih kuat-lebih baik dari sebelumnya. Detail seperti alokasi register dan prediksi cabang jauh lebih baik diserahkan kepada kompiler ... kecuali pada saat-saat seperti ini di mana Anda, programmer, tahu persis instruksi yang Anda inginkan.
Cort Ammon
5
Pertanyaan yang lebih baik adalah, mengapa GCC tidak mengoptimalkan kedua loop tersebut?
Ilmari Karonen
5
Gcc pertama membuka gulungannya dan baru kemudian memperhatikan bahwa kode yang sesuai tidak berguna. Dengan ukuran lingkaran 30, membuka gulungan akan menjadi bodoh dan gcc tidak melakukannya. Pada tingkat optimasi yang lebih tinggi keduanya dioptimalkan jauh.
Marc Glisse

Jawaban:

9

Karena dalam satu komentar Anda menyatakan bahwa "setiap centang CPU layak" Saya sarankan menggunakan beberapa perakitan inline untuk membuat loop penundaan Anda seperti yang Anda inginkan. Solusi ini lebih unggul daripada berbagai volatileatau -O0karena itu memperjelas maksud Anda.

unsigned char i = 10;
__asm__ volatile ( "loop: subi    %0, 0x01\n\t"
                   "      brne    loop"
                   : "+rm" (i)
                   : /* no inputs */
                   : /* no dirty registers to decleare*/);

Itu harus melakukan trik. Hal yang mudah menguap ada di sana untuk memberitahu kompiler "Saya tahu ini tidak melakukan apa-apa, simpan saja dan percayalah pada saya". Tiga "pernyataan" asm cukup jelas, Anda dapat menggunakan register apa pun alih-alih r24, saya percaya kompiler menyukai register yang lebih rendah sehingga Anda mungkin ingin menggunakan yang tinggi. Setelah yang pertama :Anda harus mencantumkan output (baca dan tulis) variabel c, dan tidak ada, setelah yang kedua :Anda harus mencantumkan input (ronly) variabel c, sekali lagi, tidak ada, dan parameter ketiga adalah daftar register modifikasi yang dipisahkan dengan koma , dalam hal ini r24. Saya tidak yakin apakah Anda harus memasukkan juga register status karena ZEROflagnya berubah tentu saja, saya tidak memasukkannya.

sunting jawaban yang diedit sesuai permintaan OP. Beberapa catatan.

The "+rm"sebelum (i)berarti bahwa Anda membiarkan compiler memutuskan untuk menempatkan saya di m Emory atau di r egister. Itu hal yang baik dalam banyak kasus karena kompiler dapat mengoptimalkan dengan lebih baik jika gratis. Dalam kasus Anda, saya yakin Anda hanya ingin mempertahankan batasan r untuk memaksa saya menjadi register.

Vladimir Cravero
sumber
Sepertinya ini adalah hal yang sangat saya butuhkan. Tetapi bisakah Anda memodifikasi jawaban Anda untuk menerima cvariabel apa pun alih-alih secara literal yang 10saya sebutkan dalam jawaban asli? Saya mencoba membaca manual GCC tentang penggunaan yang tepat dari konstruksi asm tapi agak dikaburkan bagi saya sekarang. Saya akan sangat menghargai!
Roman Matveev
1
@RomanMatveev diedit seperti yang Anda minta
Vladimir Cravero
13

Anda dapat mencoba membuat loop benar-benar melakukan sesuatu. Saat berdiri, kompiler mengatakan "Lingkaran ini tidak melakukan apa-apa - saya akan menyingkirkannya".

Jadi Anda bisa mencoba konstruk yang sering saya gunakan:

int i;
for (i = 0; i < 10; i++) {
    asm volatile ("nop");
}

Catatan: tidak semua target untuk kompiler gcc menggunakan sintaks majelis inline yang sama - Anda mungkin perlu menyesuaikannya untuk target Anda.

Majenko
sumber
Solusi Anda tampaknya jauh lebih elegan daripada milik saya ... mungkin milik saya lebih baik ketika hitungan siklus PRECISE diperlukan? Maksud saya, keseluruhan hal tidak dijamin untuk dikompilasi dengan cara tertentu, bukan?
Vladimir Cravero
8
Fakta sederhana menggunakan C berarti Anda tidak dapat menjamin siklus. Jika Anda membutuhkan penghitungan siklus yang tepat maka ASM adalah satu-satunya cara untuk melakukannya. Maksud saya, Anda mendapatkan pengaturan waktu yang berbeda dengan loop C jika Anda memiliki -funroll-loop diaktifkan daripada jika Anda tidak melakukannya, dll.
Majenko
Ya itulah yang saya pikirkan. Ketika melakukan penundaan HW dengan nilai i yang cukup tinggi (100 atau lebih), saya kira solusi Anda menghasilkan hasil yang hampir sama sambil meningkatkan keterbacaan.
Vladimir Cravero
6

Ya, Anda bisa berasumsi itu. Jika Anda mendeklarasikan variabel i sebagai volatile, Anda memberi tahu kompiler untuk tidak mengoptimalkan pada i.

Ambiorix
sumber
1
Itu tidak sepenuhnya benar menurut saya.
Vladimir Cravero
1
@VladimirCravero, apa maksud Anda dengan mengatakan itu? Bisakah Anda membuatnya lebih jelas?
Roman Matveev
2
Yang saya maksudkan adalah bahwa saya tidak akan begitu yakin tentang apa yang dilakukan oleh kompiler. Mendeklarasikan variabel volatile memberi tahu kompiler bahwa ia mungkin berubah di tempat lain sehingga benar-benar harus membuatnya sementara.
Vladimir Cravero
1
@ Roman Matveev register unsigned char volatile i __asm__("r1");mungkin?
a3f
2
Mendeklarasikan isebagai volatile memecahkan segalanya. Ini dijamin oleh standar C 5.1.2.3. Compiler yang menyesuaikan harus tidak mengoptimalkan loop tersebut jika ivolatile. Untungnya, GCC adalah kompiler yang sesuai. Sayangnya ada banyak calon C kompiler yang tidak sesuai dengan standar, tetapi itu tidak relevan untuk pertanyaan khusus ini. Sama sekali tidak perlu untuk assembler inline.
Lundin
1

Setelah loop pertama iadalah sebuah konstanta. Inisialisasi idan loop tidak menghasilkan apa-apa selain menghasilkan nilai yang konstan. Tidak ada dalam standar yang menetapkan bahwa loop ini harus dikompilasi apa adanya. Standar juga tidak mengatakan apa pun tentang waktu. Kode yang dikompilasi harus berperilaku seolah-olah ada loop dan itu terjadi. Anda tidak dapat memastikan bahwa pengoptimalan ini dilakukan di bawah standar (waktu tidak dihitung).

Loop kedua juga harus dihapus. Saya menganggapnya sebagai bug (atau optimasi yang hilang) yang bukan. Setelah loop iadalah nol konstan. Kode harus diganti dengan pengaturan ike nol.

Saya pikir GCC menjaga imurni karena alasan Anda melakukan akses port (buram) mungkin mempengaruhi i.

Menggunakan

asm volatile ("nop");

untuk menipu GCC agar percaya bahwa loop melakukan sesuatu.

usr
sumber