Satır içi derleme yorumları eklemek neden GCC'nin ürettiği kodda bu kadar köklü değişikliğe neden oluyor?


82

Yani, bu koda sahiptim:

constexpr unsigned N = 1000;
void f1(char* sum, char* a, char* b) {
    for(int i = 0; i < N; ++i) {
        sum[i] = a[i] + b[i];
    }
}

void f2(char* sum, char* a, char* b) {
    char* end = sum + N;
    while(sum != end) {
        *sum++ = *a++ + *b++;
    }
}

GCC 4.7.2'nin oluşturacağı kodu görmek istedim. Bu yüzden koştum g++ -march=native -O3 -masm=intel -S a.c++ -std=c++11ve aşağıdaki çıktıyı aldım:

        .file   "a.c++"
        .intel_syntax noprefix
        .text
        .p2align 4,,15
        .globl  _Z2f1PcS_S_
        .type   _Z2f1PcS_S_, @function
_Z2f1PcS_S_:
.LFB0:
        .cfi_startproc
        lea     rcx, [rdx+16]
        lea     rax, [rdi+16]
        cmp     rdi, rcx
        setae   r8b
        cmp     rdx, rax
        setae   cl
        or      cl, r8b
        je      .L5
        lea     rcx, [rsi+16]
        cmp     rdi, rcx
        setae   cl
        cmp     rsi, rax
        setae   al
        or      cl, al
        je      .L5
        xor     eax, eax
        .p2align 4,,10
        .p2align 3
.L3:
        movdqu  xmm0, XMMWORD PTR [rdx+rax]
        movdqu  xmm1, XMMWORD PTR [rsi+rax]
        paddb   xmm0, xmm1
        movdqu  XMMWORD PTR [rdi+rax], xmm0
        add     rax, 16
        cmp     rax, 992
        jne     .L3
        mov     ax, 8
        mov     r9d, 992
.L2:
        sub     eax, 1
        lea     rcx, [rdx+r9]
        add     rdi, r9
        lea     r8, [rax+1]
        add     rsi, r9
        xor     eax, eax
        .p2align 4,,10
        .p2align 3
.L4:
        movzx   edx, BYTE PTR [rcx+rax]
        add     dl, BYTE PTR [rsi+rax]
        mov     BYTE PTR [rdi+rax], dl
        add     rax, 1
        cmp     rax, r8
        jne     .L4
        rep
        ret
.L5:
        mov     eax, 1000
        xor     r9d, r9d
        jmp     .L2
        .cfi_endproc
.LFE0:
        .size   _Z2f1PcS_S_, .-_Z2f1PcS_S_
        .p2align 4,,15
        .globl  _Z2f2PcS_S_
        .type   _Z2f2PcS_S_, @function
_Z2f2PcS_S_:
.LFB1:
        .cfi_startproc
        lea     rcx, [rdx+16]
        lea     rax, [rdi+16]
        cmp     rdi, rcx
        setae   r8b
        cmp     rdx, rax
        setae   cl
        or      cl, r8b
        je      .L19
        lea     rcx, [rsi+16]
        cmp     rdi, rcx
        setae   cl
        cmp     rsi, rax
        setae   al
        or      cl, al
        je      .L19
        xor     eax, eax
        .p2align 4,,10
        .p2align 3
.L17:
        movdqu  xmm0, XMMWORD PTR [rdx+rax]
        movdqu  xmm1, XMMWORD PTR [rsi+rax]
        paddb   xmm0, xmm1
        movdqu  XMMWORD PTR [rdi+rax], xmm0
        add     rax, 16
        cmp     rax, 992
        jne     .L17
        add     rdi, 992
        add     rsi, 992
        add     rdx, 992
        mov     r8d, 8
.L16:
        xor     eax, eax
        .p2align 4,,10
        .p2align 3
.L18:
        movzx   ecx, BYTE PTR [rdx+rax]
        add     cl, BYTE PTR [rsi+rax]
        mov     BYTE PTR [rdi+rax], cl
        add     rax, 1
        cmp     rax, r8
        jne     .L18
        rep
        ret
.L19:
        mov     r8d, 1000
        jmp     .L16
        .cfi_endproc
.LFE1:
        .size   _Z2f2PcS_S_, .-_Z2f2PcS_S_
        .ident  "GCC: (GNU) 4.7.2"
        .section        .note.GNU-stack,"",@progbits

Montajı okurken berbatım, bu yüzden döngülerin gövdelerinin nereye gittiğini bilmek için bazı işaretçiler eklemeye karar verdim:

constexpr unsigned N = 1000;
void f1(char* sum, char* a, char* b) {
    for(int i = 0; i < N; ++i) {
        asm("# im in ur loop");
        sum[i] = a[i] + b[i];
    }
}

void f2(char* sum, char* a, char* b) {
    char* end = sum + N;
    while(sum != end) {
        asm("# im in ur loop");
        *sum++ = *a++ + *b++;
    }
}

Ve GCC bunu tükürdü:

    .file   "a.c++"
    .intel_syntax noprefix
    .text
    .p2align 4,,15
    .globl  _Z2f1PcS_S_
    .type   _Z2f1PcS_S_, @function
_Z2f1PcS_S_:
.LFB0:
    .cfi_startproc
    xor eax, eax
    .p2align 4,,10
    .p2align 3
.L2:
#APP
# 4 "a.c++" 1
    # im in ur loop
# 0 "" 2
#NO_APP
    movzx   ecx, BYTE PTR [rdx+rax]
    add cl, BYTE PTR [rsi+rax]
    mov BYTE PTR [rdi+rax], cl
    add rax, 1
    cmp rax, 1000
    jne .L2
    rep
    ret
    .cfi_endproc
.LFE0:
    .size   _Z2f1PcS_S_, .-_Z2f1PcS_S_
    .p2align 4,,15
    .globl  _Z2f2PcS_S_
    .type   _Z2f2PcS_S_, @function
_Z2f2PcS_S_:
.LFB1:
    .cfi_startproc
    xor eax, eax
    .p2align 4,,10
    .p2align 3
.L6:
#APP
# 12 "a.c++" 1
    # im in ur loop
# 0 "" 2
#NO_APP
    movzx   ecx, BYTE PTR [rdx+rax]
    add cl, BYTE PTR [rsi+rax]
    mov BYTE PTR [rdi+rax], cl
    add rax, 1
    cmp rax, 1000
    jne .L6
    rep
    ret
    .cfi_endproc
.LFE1:
    .size   _Z2f2PcS_S_, .-_Z2f2PcS_S_
    .ident  "GCC: (GNU) 4.7.2"
    .section    .note.GNU-stack,"",@progbits

Bu oldukça kısadır ve SIMD talimatlarının olmaması gibi bazı önemli farklılıkları vardır. Ortasında bir yerde bazı yorumlarla aynı çıktıyı bekliyordum. Burada yanlış bir varsayımda bulunuyor muyum? GCC'nin optimize edicisi asm yorumlarıyla engelleniyor mu?


28
GCC'nin (ve çoğu derleyicinin) ASM yapısına blok kutuları gibi davranmasını beklerdim. Bu yüzden böyle bir kutudan ne olacağı konusunda akıl yürütemezler. Ve bu, özellikle döngü sınırları boyunca taşınan birçok optimizasyonu engeller.
Ira Baxter

10
Genişletilmiş asmformu boş çıktı ve patlatma listeleri ile deneyin .
Kerrek SB

4
@ R.MartinhoFernandes: asm("# im in ur loop" : : );( belgelere bakın )
Mike Seymour

16
Oluşturulan derlemeye bakarken, -fverbose-asmişlerin kayıtlar arasında nasıl hareket ettiğini belirlemeye yardımcı olacak bazı ek açıklamalar ekleyen bayrağı ekleyerek biraz daha fazla yardım alabileceğinizi unutmayın .
Matthew Slattery

1
Çok ilginç. Döngülerde optimizasyonu seçerek önlemek için kullanılabilir mi?
SChepurin

Yanıtlar:


62

İyileştirmelerle etkileşimler , dokümantasyondaki "C İfade İşlemcileri ile Assembler Talimatları" sayfasının yarısında açıklanmıştır .

GCC asm; içindeki gerçek derlemelerin hiçbirini anlamaya çalışmaz ; içerik hakkında bildiği tek şey, (isteğe bağlı olarak) ona çıktı ve giriş işlenen belirtiminde ve yazmaç bozucu listesinde söyledikleridir.

Özellikle şunları unutmayın:

asmÇıkış işlenenleri olmayan bir komut, uçucu bir asmkomutla aynı şekilde ele alınacaktır .

ve

volatileAnahtar kelime talimat önemli yan etkiye sahip olmadığını göstermektedir [...]

Dolayısıyla, asmdöngünüzün iç kısmının varlığı vektörleştirme optimizasyonunu engelledi, çünkü GCC bunun yan etkileri olduğunu varsayar.


1
Bir Basic Asm ifadesinin yan etkilerinin, kayıtların değiştirilmesini veya C ++ kodunuzun okuduğu / yazdığı herhangi bir belleği içermemesi gerektiğini unutmayın. Ancak evet, asmifade C ++ soyut makinede her seferinde bir kez çalıştırılmalıdır ve GCC vektörleştirmemeyi seçer ve ardından asm'yi satır başına 16 kez yayar paddb. Bunun yasal olduğunu düşünürdüm, çünkü karakter erişimi değil volatile. (A sahip uzatılmış asm ifadeye aksine "memory"clobber)
Peter Cordes

1
Genel olarak GNU C Temel Asm deyimlerini kullanmama nedenleri için gcc.gnu.org/wiki/ConvertBasicAsmToExtended'e bakın . Bu kullanım durumu (sadece bir yorum işaretçisi), denemenin mantıksız olmadığı birkaç durumdan biridir.
Peter Cordes

23

Gcc'nin kodu vektörize ettiğini, döngü gövdesini iki parçaya böldüğünü, ilkinin bir seferde 16 öğeyi işlediğini ve ikincisinin kalanı daha sonra yaptığını unutmayın.

Ira'nın dediği gibi, derleyici asm bloğunu ayrıştırmaz, bu yüzden bunun sadece bir yorum olduğunu bilmiyor. Olsa bile, ne istediğini bilmenin bir yolu yok. Optimize edilmiş döngüler bedeni ikiye katladı, her birine asminizi koymalı mı? 1000 defa infaz edilmemesini ister misiniz? Bilmiyor, bu yüzden güvenli rotaya gidiyor ve basit tek döngüye geri dönüyor.


3

"Gcc asm()blokta ne olduğunu anlamıyor" fikrine katılmıyorum . Örneğin, gcc, parametreleri optimize etmekle ve hatta asm()blokları, üretilen C koduyla karışacak şekilde yeniden düzenlemekle oldukça iyi başa çıkabilir . Bu nedenle, örneğin Linux çekirdeğindeki satır içi assembler'a bakarsanız __volatile__, derleyicinin "kodu hareket ettirmediğinden" emin olmak için hemen hemen her zaman öneki ile gösterilir . Gcc'nin "rdtsc" yi hareket ettirmesini sağladım, bu da belirli bir şeyi yapmak için geçen süreyi ölçtü.

Belgelendiği gibi, gcc belirli asm()blok türlerini "özel" olarak ele alır ve bu nedenle bloğun her iki tarafındaki kodu optimize etmez.

Bu, gcc'nin bazen satır içi derleyici blokları tarafından karıştırılmayacağı veya yalnızca assembler kodunun vb. Sonuçlarını takip edemediği için belirli bir optimizasyondan vazgeçmeye karar vermeyeceği anlamına gelmez. Daha da önemlisi, Clobber etiketlerinin eksik olması genellikle kafanız karışabilir - bu nedenle,cpuidBu, EAX-EDX'in değerini değiştirir, ancak kodu yalnızca EAX'i kullanacak şekilde yazdınız, derleyici şeyleri EBX, ECX ve EDX'te depolayabilir ve bu kayıtların üzerine yazıldığında kodunuz çok garip davranır ... şanslısın, hemen çöküyor - sonra ne olduğunu anlamak kolay. Ama şanssızsanız, hattın aşağısına çöker ... Bir diğer zor olan ise edx'te ikinci bir sonuç veren bölme talimatıdır. Modülo umurunuzda değilse, EDX'in değiştirildiğini unutmak kolaydır.


1
gcc, asm bloğunun içinde ne olduğunu gerçekten anlamıyor - bunu genişletilmiş bir asm ifadesiyle söylemelisiniz. bu ekstra bilgi olmadan gcc bu tür bloklar arasında hareket etmeyecektir. gcc de belirttiğiniz durumlarda kafanız karışmaz - gcc'ye bu yazmaçları kullanabileceğini söyleyerek bir programlama hatası yaptınız, aslında kodunuz onları tıkıyor.
Monica'yı

Geç cevap, ama söylemeye değer olduğunu düşünüyorum. volatile asmGCC'ye kodun 'önemli yan etkileri' olabileceğini ve bununla daha özel bir dikkatle ilgileneceğini söyler. Bu olabilir hala ölü kod optimizasyonu bir parçası olarak silinmiş veya dışarı taşınacak. C kodu ile etkileşim böyle (nadir) bir durumu varsaymalı ve katı ardışık değerlendirmeyi dayatmalıdır (örneğin, asm içinde bağımlılıklar oluşturarak).
edmz

GNU C Basic asm (OP'ler gibi işlenen kısıtlamaları yoktur asm("")), çıktı işlenenleri olmayan Extended asm gibi örtük olarak uçucudur. GCC asm şablon dizesini anlamaz, yalnızca kısıtlamaları anlar; bu nedenle asm'inizi kısıtlamaları kullanarak derleyiciye doğru ve tam olarak tanımlamanız önemlidir . İşlenenleri şablon dizesine değiştirmek, printfbir biçim dizesi kullanmaktan daha fazla anlama gerektirmez . TL: DR: GNU C Basic asm'i herhangi bir şey için kullanmayın, belki bunun gibi saf yorumlarla kullanım durumları dışında.
Peter Cordes

-2

Bu cevap şimdi değiştirildi: Başlangıçta, inline Basic Asm'i oldukça güçlü bir şekilde belirlenmiş bir araç olarak düşünen bir zihniyetle yazılmıştı, ancak GCC'de böyle bir şey değil. Temel Asm zayıf olduğu için yanıt düzenlendi.

Her bir montaj açıklaması bir kesme noktası görevi görür.

DÜZENLEME: Ama Temel Asm'ı kullandığınız için bozuk bir tane. Açık bozucu listesi olmayan satır içi asm( asmişlev gövdesi içindeki bir ifade), GCC'de zayıf bir şekilde belirtilmiş bir özelliktir ve davranışını tanımlamak zordur. Özellikle herhangi bir şeye bağlı görünmüyor (garantilerini tam olarak anlamıyorum), bu nedenle, işlev çalıştırılırsa derleme kodunun bir noktada çalıştırılması gerekirken, herhangi bir önemsiz optimizasyon düzeyi . Komşu talimatla yeniden sıralanabilen bir kesme noktası, çok kullanışlı bir "kesme noktası" değildir. DÜZENLEMEYİ SONLANDIR

Programınızı, her açıklamada kırılan ve her değişkenin durumunu yazdıran (hata ayıklama bilgilerini kullanarak) bir yorumlayıcıda çalıştırabilirsiniz. Çevreyi gözlemleyebilmeniz için bu noktalar mevcut olmalıdır (kayıtların ve hafızanın durumu).

Yorum olmadan gözlem noktası yoktur ve döngü, bir ortamı alıp değiştirilmiş bir ortam üreten tek bir matematiksel fonksiyon olarak derlenir.

Anlamsız bir sorunun cevabını bilmek istiyorsunuz: her bir talimatın (veya belki blok veya belki talimat aralığının) nasıl derlendiğini bilmek istiyorsunuz, ancak tek bir izole talimat (veya blok) derlenmiyor; tüm şeyler bir bütün olarak derlenmiştir.

Daha iyi bir soru şu olabilir:

Merhaba GCC. Neden bu asm çıktısının kaynak kodunu uyguladığına inanıyorsunuz? Lütfen her varsayımla adım adım açıklayın.

Ancak o zaman, GCC dahili temsili terimiyle yazılmış asm çıktısından daha uzun bir ispatı okumak istemezsiniz.


1
Çevreyi gözlemleyebilmeniz için bu noktalar mevcut olmalıdır (kayıtların durumu ve hafıza). - bu, optimize edilmemiş kod için doğru olabilir. Optimizasyonlar etkinleştirildiğinde, tüm işlevler ikili dosyadan kaybolabilir. Burada optimize edilmiş koddan bahsediyoruz.
Bartek Banachewicz

1
Optimizasyonların etkinleştirildiği derlemenin bir sonucu olarak oluşturulan derlemeden bahsediyoruz. Bu nedenle bir şeyin var olması gerektiğini söylerken yanılıyorsunuz .
Bartek Banachewicz

1
Evet, IDK neden kimse yapsın ve kimsenin yapmaması konusunda hemfikir. Son yorumumdaki bağlantıdan da anlaşılacağı gibi, hiç kimse yapmamalı ve "memory"kesinlikle var olan mevcut buggy kodu için bir bandaid olarak onu güçlendirmek (ör. Örtük bir patlayıcı ile) hakkında tartışmalar olmuştur . Bu tür talimatlar için bile asm("cli"), mimari durumun yalnızca derleyici tarafından üretilen kodun dokunmadığı bir kısmını etkilemek için bile , buna yine de wrt sipariş etmeniz gerekir. derleyici tarafından oluşturulan yükler / depolar (örneğin, kritik bir bölümdeki kesintileri devre dışı bırakıyorsanız).
Peter Cordes

1
Kırmızı bölgeyi bozmak güvenli olmadığından, asm ifadesi içindeki kayıtların (push / pop ile) verimsiz manuel olarak kaydedilmesi / geri yüklenmesi bile, add rsp, -128ilk siz olmadığınız sürece güvenli değildir . Ama bunu yapmak kesinlikle beyinsizdir.
Peter Cordes

1
Şu anda GCC, Temel Asm'yi tam olarak eşdeğer olarak kabul eder asm("" :::)(hiçbir çıktıya sahip olmadığı için örtük olarak uçucudur, ancak girdi veya çıktı bağımlılıkları ile kodun geri kalanına bağlı değildir. Ve "memory"bozucu değildir). Ve tabii ki yapmaz %operandşablon dizesi değiştirme, yani edebi %olarak öncelenmelidir zorunda değildir %%. Yani evet, kabul __attribute__((naked))ediyorum , Temel Asm'ı işlevlerin ve genel kapsamın dışında bırakmak iyi bir fikir olacaktır.
Peter Cordes
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.