2015-01-05 3 views
2

я начал изучать язык ARM сборки с компилированными инструментами GNU и создать очень простой пример функции в C с помощью следующего кода:Анализа простого ARM коды сборки

#include <stdint.h> 

    uint32_t *a; 
    uint32_t *b; 
    uint32_t *c; 

    __attribute__((naked)) void f() { 

      *a += *c; 
      *b += *c; 
     } 

После того как я использовал эти команды в терминале чтобы увидеть код сборки:

arm-none-eabi-gcc -O1 -S -std=c99 example.c -o - 

И вот результат:

@ Function supports interworking. 
    @ Naked Function: prologue and epilogue provided by programmer. 
    @ args = 0, pretend = 0, frame = 0 
    @ frame_needed = 0, uses_anonymous_args = 0 
    ldr r3, .L2 
    ldr r2, .L2+4 
    ldr r3, [r3] 
    ldr r2, [r2] ; r2 <- &(*c) 
    ldr ip, [r3] 
    ldr r0, [r2] ; r0 <- *c 
    ldr r1, .L2+8 
    add r0, ip, r0 
    str r0, [r3] 
    ldr r3, [r1] 
    ldr r2, [r2] ; why make the same thing 
    ldr r1, [r3] 
    add r2, r1, r2 
    str r2, [r3] 
.L3: 
    .align 2 
.L2: 
    .word a 
    .word c 
    .word b 
    .size f, .-f 
    .comm c,4,4 
    .comm b,4,4 
    .comm a,4,4 

Мой вопрос, почему нагрузка составителя два раза адрес указатель с, если я хорошо понимаю, что это линия

ldr r2, [r2] 

Я не могу найти хороший повод для компилятора повторности этого кода. Спасибо заранее.

+2

Вы должны попробовать более высокий уровень оптимизации, '-O2' или' -O3'. – Jester

+0

Я пробовал, это тот же код = ( – Mike

+0

Тогда это просто пропущенная оптимизация. Компиляторы не идеальны. Один на [gcc.godbolt.org] (http://gcc.godbolt.org) создает другой код. – Jester

ответ

5

Если ваш псевдоним указателей, необходимы две разницы. Подумайте, что делает ваш алгоритм, если у вас есть a == c. Если они не могут быть псевдонимом, вам нужно добавить несколько ключевых слов restrict. Вот пример, который оптимизирует, как вы ожидаете:

#include <stdint.h> 

void f(uint32_t * restrict a, uint32_t * restrict b, uint32_t * restrict c) 
{ 
    *a += *c; 
    *b += *c; 
} 

И выход в сборе (комментарии шахтное):

00000000 <f>: 
    0: e5922000 ldr r2, [r2]  // r2 = *c 
    4: e5903000 ldr r3, [r0]  // r3 = *a 
    8: e0833002 add r3, r3, r2 // r3 = r3 + r2 = *a + *c 
    c: e5803000 str r3, [r0]  // *a = r3 = *a + *c 
    10: e5910000 ldr r0, [r1]  // r0 = *b 
    14: e0800002 add r0, r0, r2 // r0 = r0 + r2 = *b + *c 
    18: e5810000 str r0, [r1]  // *b = r0 = *b + *c 
    1c: e12fff1e bx lr 

Edit: Вот пример более, как ваш оригинал, первый без restrict ключевых слов и во-вторых, в формате вывода GCC на этот раз.

Пример один (без restrict ключевые слова) Код:

#include <stdint.h> 

__attribute__((naked)) 
void f(uint32_t *a, uint32_t *b, uint32_t *c) 
{ 
    *a += *c; 
    *b += *c; 
} 

Выход:

f: 
    ldr ip, [r0, #0] 
    ldr r3, [r2, #0] 
    add r3, ip, r3 
    str r3, [r0, #0] 
    ldr r0, [r1, #0] 
    ldr r3, [r2, #0] 
    add r3, r0, r3 
    str r3, [r1, #0] 

Пример два (с restrict ключевые слова) Код:

#include <stdint.h> 

__attribute__((naked)) 
void f(uint32_t * restrict a, uint32_t * restrict b, uint32_t * restrict c) 
{ 
    *a += *c; 
    *b += *c; 
} 

Выход:

f: 
    ldr r3, [r2, #0] 
    ldr ip, [r1, #0] 
    ldr r2, [r0, #0] 
    add r2, r2, r3 
    add r3, ip, r3 
    str r2, [r0, #0] 
    str r3, [r1, #0] 

Второе разыменование c не во второй программе, сокращая его на одну инструкцию.

+0

очень хорошо объяснил, большое вам спасибо. – Mike

0

Последовательное выполнение ldr rX, [rX] будет означать двойное разыменование любого rX указывает.

Если бы я получил свой вопрос прямо, первый, как вы говорите:

ldr r2, [r2] ; r2 <- &(*c) 

затем второй один становится

ldr r2, [r2] ; r2 <- *(r2) 

Если это не вопрос, то из GCC docs (см смелой части):

голый

Этот атрибут доступен на портах ARM, AVR, MCORE, MSP430, NDS32, RL78, RX и SPU. Он позволяет компилятору построить объявление функции обязательного объявления , а тело - это код сборки. Указанная функция не будет иметь последовательности пролога/эпилога, сгенерированные компилятором. Операции Basic asm могут безопасно включаться в голые функции (см. Basic Asm). При использовании Extended asm или смеси Basic asm и кода «C» могут работать , они не могут зависеть от надежной работы и не поддерживаются .

+0

На что вы пытаетесь добраться? –

+0

@CarlNorum Что? – auselen

+0

Я не понимаю, что должен подразумевать этот ответ. Код разыменования «c» дважды требуется кодом OP. –

0

надстройку разрушает r0 поэтому мы теряем значение с и должны перезагрузить его

ldr r2, .L2+4 get address of .data location of *c from .text 
... 
ldr r2, [r2] ; r2 = pointer to c 
... 
ldr r0, [r2] ; r0 = c 
... 
add r0, ip, r0 ; this destroys r0 it no longer holds the value of c 
... 
ldr r2, [r2] ; need the value of c again to add to b 

Интересные да, что различные версии GCC и/или различных оптимизаций выбрать различные сочетания регистров. Но такая же последовательность с дополнительной нагрузкой. Главное здесь почему это сделать:

add r0, ip, r0 
str r0, [r3] 

вместо

add ip, ip, r0 
str ip, [r3] 

, а затем не нужно повторно нагрузки С?

Нюанс оптимизатора глазок - это мое предположение. Другой связанный с этим вопрос заключается в том, зачем начинать возиться с ** b до того, как закончите с сохранением? Если бы он не сделал этого, у него был бы еще один бесплатный реестр. (Без сомнения, другая оптимизация)

Еще один интересный момент, по крайней мере один из моих GCC компиляторов производит это:

00001000 <_start>: 
    1000: eaffffff b 1004 <fun> 

00001004 <fun>: 
    1004: e59f2034 ldr r2, [pc, #52] ; 1040 <fun+0x3c> 
    1008: e59f3034 ldr r3, [pc, #52] ; 1044 <fun+0x40> 
    100c: e5921000 ldr r1, [r2] 
    1010: e5932000 ldr r2, [r3] 
    1014: e591c000 ldr ip, [r1] 
    1018: e5920000 ldr r0, [r2] 
    101c: e59f3024 ldr r3, [pc, #36] ; 1048 <fun+0x44> 
    1020: e08c0000 add r0, ip, r0 
    1024: e5933000 ldr r3, [r3] 
    1028: e5810000 str r0, [r1] 
    102c: e5922000 ldr r2, [r2] 
    1030: e5931000 ldr r1, [r3] 
    1034: e0812002 add r2, r1, r2 
    1038: e5832000 str r2, [r3] 
    103c: e12fff1e bx lr 
    1040: 00009054 andeq r9, r0, r4, asr r0 
    1044: 00009050 andeq r9, r0, r0, asr r0 
    1048: 0000904c andeq r9, r0, ip, asr #32 

Disassembly of section .bss: 

0000904c <__bss_start>: 
    904c: 00000000 andeq r0, r0, r0 

00009050 <c>: 
    9050: 00000000 andeq r0, r0, r0 

00009054 <a>: 
    9054: 00000000 andeq r0, r0, r0 

С или без обнаженный вы получите то же самое, почему НКУ так отчаянно использовать каждый одноразовый регистр и не использовать стек, например. Обратите внимание, что в вашей компиляции он добавляет, а затем сохраняет его в моей, он добавляет затем нагрузки * b, затем сохраняет. Он не только перемещал нагрузку ** b в последовательности, но также загружал * b вверх до завершения результата a.

так что голая вещь не помогла здесь, кроме как удалить bx lr в конце функции. то, что вы можете/должны попробовать, это -fdump-rtl-all в командной строке gcc (делает много файлов) и прокладывать путь через них, чтобы увидеть, где начался gcc и где он изменил ситуацию, и, возможно, это определит вывод или если а не в компиляторе, а затем в бэкэнде оптимизатор подглядывает переделанные вещи и не уверен, что командная строка должна сбросить.

Итог заключается в том, что в то время как в течение длительного времени (десятки тысяч, сотни тысяч, миллионы строк кода) компилятор/optmizer будет превосходить человека, но очень легко поймать изолированные части оптимизированного код, который можно настроить вручную, чтобы быть немного «лучше» в зависимости от вашего определения лучше. Обратите внимание, что меньшее количество инструкций не всегда лучше.

Смежные вопросы