Почему компилятор GCC пропускает некоторый код?

9

Я не могу понять, почему компилятор GCC вырезал часть моего кода, в то время как он сохранил абсолютно тот же самый по соседству?

Код 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;
}

Соответствующая часть LSS (файл ассемблера, созданный компилятором):

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

Я мог бы предположить, что компилятор выяснит, что такой код является фиктивным, и вырезает его, но почему он сохраняет тот же самый код в конце кода?

Есть ли какие-либо инструкции компилятора, чтобы предотвратить такую ​​оптимизацию?

Роман Матвеев
источник
1
Вы также можете указать компилятору не оптимизировать ни одну функцию, возможно, стоит попробовать этот метод с ISR. Смотрите этот вопрос на stackoverflow.
Владимир Краверо
2
Привет, Роман, я добавил тэг "c" к твоему вопросу, удалив atmega. Мне пришлось удалить один тег, поскольку существует ограничение (пять), и при задании вопроса, связанного с кодом, добавление имени языка в качестве тега - это замечательно, поскольку весь код (вопросы и ответы) выделяется.
Владимир Краверо
5
Вообще говоря, языки более высокого уровня (такие как C) явно предназначены для того, чтобы не связываться с отношением 1: 1 с их результирующей сборкой. Если вам нужно посчитать инструкции для правильного выбора времени, вы всегда должны полагаться на сборку (как это делали некоторые ответы). Смысл языков высокого уровня в том, что у компилятора есть некоторая свобода, чтобы сделать ваш код быстрее, сильнее, лучше, чем раньше. Такие детали, как распределение регистров и предсказания ветвлений, лучше оставить для компилятора ... за исключением случаев, когда вы, программист, точно знаете инструкции, которые вы хотите.
Корт Аммон
5
Лучший вопрос: почему GCC не оптимизирует обе эти петли?
Ильмари Каронен
5
Gcc сначала развертывает цикл и только потом замечает, что соответствующий код бесполезен. С размером петли 30 развертывание было бы глупо, и gcc этого не делает. На более высоком уровне оптимизации оба оптимизируются.
Марк Глисс,

Ответы:

9

Поскольку в одном комментарии вы утверждаете, что «каждый такт процессора достоин», я предлагаю использовать некоторую встроенную сборку, чтобы сделать цикл задержек именно таким, как вы хотите. Это решение превосходит различные volatileили -O0потому, что оно проясняет ваши намерения.

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

Это должно делать свое дело. Изменчивая вещь заключается в том, чтобы сказать компилятору: «Я знаю, что это ничего не делает, просто держи это и поверь мне». Три ассемблерных «утверждения» вполне понятны, вы можете использовать любой регистр вместо r24, я считаю, что компилятору нравятся младшие регистры, поэтому вы можете захотеть использовать старшие. После первого :вы должны перечислить выходные (чтение и запись) переменные c, а их нет, после второго :вы должны перечислить входные (ronly) переменные c, опять же, их нет, а третий параметр - разделенный запятыми список модифицированных регистров. в данном случае р24. Я не уверен, стоит ли вам включать регистр статуса, поскольку ZEROфлаг, конечно, меняется, я его не включал.

отредактируйте отредактированный ответ как запрос OP Некоторые заметки.

"+rm"Перед тем (i)означает , что вы выпускающих компилятор решил место я в м Эмори или в г egister. В большинстве случаев это хорошо, так как компилятор может оптимизировать лучше, если он бесплатный. В вашем случае я считаю, что вы хотите оставить только ограничение r, чтобы заставить i быть регистром.

Владимир Краверо
источник
Похоже, это то, что мне действительно нужно. Но не могли бы вы изменить свой ответ так, чтобы он принимал любую cпеременную вместо литерала, который 10я упоминал в исходном ответе? Я пытаюсь прочитать руководства GCC, касающиеся правильного использования конструкции asm, но сейчас она для меня немного неясна. Я был бы очень признателен!
Роман Матвеев
1
@RomanMatveev отредактировал, как вы просили
Владимир Краверо
13

Вы можете попробовать сделать цикл на самом деле сделать что-то. В его нынешнем виде компилятор совершенно правильно говорит: «Этот цикл ничего не делает - я от него избавлюсь».

Таким образом, вы можете попробовать конструкцию, которую я часто использую:

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

Примечание: не все цели для компилятора gcc используют один и тот же синтаксис встроенной сборки - вам может потребоваться настроить его для своей цели.

Majenko
источник
Ваше решение кажется намного более элегантным, чем мое ... может быть, мое лучше, когда требуется ТОЧНОЕ подсчет циклов? Я имею в виду, что целые вещи не гарантированно компилируются определенным образом, не так ли?
Владимир Краверо
8
Тот факт, что он использует C, означает, что вы не можете гарантировать циклы. Если вам нужен точный подсчет циклов, тогда ASM - единственный путь. Я имею в виду, что вы получите другое время с циклом C, если у вас включена опция -funroll-loops, чем если вы этого не сделаете и т. Д.
Majenko
Да, я так и думал. При выполнении задержек HW с достаточно высокими значениями i (100 или более), я думаю, ваше решение дает практически те же результаты, улучшая читабельность.
Владимир Краверо
6

Да, вы могли бы предположить это. Если вы объявляете переменную i как volatile, вы говорите компилятору не оптимизировать i.

Ambiorix
источник
1
Это не совсем так, по моему мнению.
Владимир Краверо
1
@VladimirCravero, что ты имеешь в виду, говоря это? Не могли бы вы сделать более ясным?
Роман Матвеев
2
Я имел в виду, что не уверен в том, что делает компилятор. Объявление переменной volatile говорит компилятору о том, что она может измениться где-то еще, поэтому она должна сделать это в то время.
Владимир Краверо
1
@ Роман Матвеев register unsigned char volatile i __asm__("r1");может быть?
a3f
2
Объявление iкак изменчивое решает все. Это гарантируется стандартом C 5.1.2.3. Соответствующий компилятор не должен оптимизировать эти циклы, если iон изменчив. К счастью, GCC - соответствующий компилятор. К сожалению, есть много потенциальных C-компиляторов, которые не соответствуют стандарту, но это не имеет отношения к этому конкретному вопросу. Абсолютно не нужен встроенный ассемблер.
Лундин
1

После первого цикла iэто константа. Инициализация iи цикл ничего не делают, кроме как выдают постоянное значение. Ничто в стандарте не указывает, что этот цикл должен быть скомпилирован как есть. Стандарт также ничего не говорит о сроках. Скомпилированный код должен вести себя так, как если бы цикл присутствовал, и это так. Вы не можете достоверно сказать, что эта оптимизация была выполнена по стандарту (время не учитывается).

Второй цикл также должен быть удален. Я считаю это ошибкой (или отсутствующей оптимизацией), что это не так. После цикла iпостоянный ноль. Код должен быть заменен установкой iна ноль.

Я думаю, что GCC хранится iисключительно по той причине, что выполнение (непрозрачного) доступа к порту может повлиять i.

использование

asm volatile ("nop");

чтобы заставить GCC поверить, что цикл что-то делает.

USR
источник