GCC может распечатать промежуточные результаты?

Проверьте код ниже:

#include <avr/io.h>

const uint16_t baudrate = 9600;

void setupUART( void ) {
uint16_t ubrr = ( ( F_CPU / ( 16 * (float) baudrate ) ) - 1 + .5 );
UBRRH = ubrr >> 8;
UBRRL = ubrr & 0xff;
}

int main( void ) {
setupUART();
}

Эта команда используется для компиляции кода:

avr-gcc -g -DF_CPU=4000000       -Wall -Os -Werror -Wextra -mmcu=attiny2313 -Wa,-ahlmns=project.lst -c -o project.o project.cpp

ubrr рассчитывается компилятором как 25, пока все хорошо. Однако, чтобы проверить, что рассчитал компилятор, я заглянул в лист разборки.

000000ae <setupUART()>:
ae:   12 b8           out     UBRRH, r1       ; 0x02
b0:   89 e1           ldi     r24, 0x19       ; 25
b2:   89 b9           out     UBRRL, r24      ; 0x09
b4:   08 95           ret

Можно ли сделать avr-gcc распечатать промежуточный результат во время компиляции (или извлеките информацию из файла .o), поэтому, когда я компилирую код, он печатает строку вроде (uint16_t) ubbr = 25 или похожие? Таким образом, я могу быстро проверить правильность вычислений и настроек.

7

Решение

GCC имеет параметры командной строки, чтобы запросить вывод промежуточного представления после любого этапа компиляции. Дампы «дерева» имеют синтаксис псевдо-C и содержат необходимую информацию. За то, что вы пытаетесь сделать, -fdump-tree-original а также -fdump-tree-optimized дампы происходят в полезных точках в конвейере оптимизации. У меня нет компилятора AVR, поэтому я изменил ваш тестовый пример, чтобы он был автономным и компилируемым с моим компилятором:

typedef unsigned short uint16_t;
const int F_CPU = 4000000;
const uint16_t baudrate = 9600;
extern uint16_t UBRRH, UBRRL;

void
setupUART(void)
{
uint16_t ubrr = ((F_CPU / (16 * (float) baudrate)) - 1 + .5);
UBRRH = ubrr >> 8;
UBRRL = ubrr & 0xff;
}

а потом

$ gcc -O2 -S -fdump-tree-original -fdump-tree-optimized test.c
$ cat test.c.003t.original
;; Function setupUART (null)
;; enabled by -tree-original{
uint16_t ubrr = 25;

uint16_t ubrr = 25;
UBRRH = (uint16_t) ((short unsigned int) ubrr >> 8);
UBRRL = ubrr & 255;
}

$ cat test.c.149t.optimized
;; Function setupUART (setupUART, funcdef_no=0, decl_uid=1728, cgraph_uid=0)

setupUART ()
{
<bb 2>:
UBRRH = 0;
UBRRL = 25;
return;
}

Вы можете видеть, что свертывание константных выражений выполняется настолько рано, что это уже произошло в «оригинальном» дампе (который является самым ранним доступным дампом, который вы можете иметь), и что оптимизация еще больше свела операции сдвига и маски в операторы, записывающие в UBRRH и UBRRL.

Числа в именах файлов (003t и 149t), вероятно, будут отличаться для вас. Если вы хотите увидеть все свалки «дерева», используйте -fdump-tree-all, Есть также дампы «RTL», которые не похожи на C и, вероятно, бесполезны для вас. Если вам интересно, -fdump-rtl-all включит их. Всего существует около 100 древовидных дампов и 60 дампов RTL, так что это хорошая идея сделать это в чистом каталоге.

(Psssst: Каждый раз, когда вы ставите пробелы внутри скобок, Бог убивает котенка.)

3

Другие решения

Может быть решение для печати промежуточных результатов, но для его реализации потребуется некоторое время. Так что это стоит только для довольно большой базы исходного кода.

Вы можете настроить свой компилятор GCC; либо через плагин (болезненно закодированный в C или C ++) или через ПЛАВИТЬСЯ расширение. MELT — это высокоуровневый, похожий на Lisp, предметно-ориентированный язык для расширения GCC. (Он реализован как [meta-] плагин для GCC и переведен на C ++ код, подходящий для GCC).

Однако такой подход требует, чтобы вы поняли внутреннюю часть GCC, а затем добавили свой собственный «оптимизационный» проход, чтобы сделать аспектно-ориентированное программирование (например, используя MELT), чтобы распечатать Соответствующий промежуточные результаты.

Вы также можете посмотреть не только сгенерированную сборку (и использовать -fverbose-asm -S в качестве опций для GCC), но также, возможно, в сгенерированных представлениях Gimple (возможно, с -fdump-tree-gimple). Для некоторого интерактивного инструмента, рассмотрите графический MELT зонд.

Возможно добавление вашего собственного встроенного (с расширением MELT), как __builtin_display_compile_time_constant может быть актуальным.

2

Я сомневаюсь, что есть простой способ определить, что делает компилятор. В gcc могут быть некоторые инструменты, специально предназначенные для вывода промежуточной формы языка, но это определенно будет непросто прочитать, и если вы ДЕЙСТВИТЕЛЬНО не подозреваете, что компилятор делает что-то не так (и имеете ОЧЕНЬ маленький пример, чтобы показать это) Маловероятно, что вы можете использовать его для чего-то значимого — просто потому, что слишком много работы, чтобы следить за тем, что происходит.

Лучше всего добавить временные переменные (и, возможно, распечатать) в ваш код, если вы беспокоитесь о его корректности:

    uint16_t ubrr = ( ( F_CPU / ( 16 * (float) baudrate ) ) - 1 + .5 );
uint8_t ubrr_high = ubrr >> 8
uint8_t ubrr_low = ubrr & 0xff;
UBRRH = ubrr_high;
UBRRL = ubrr_low;

Теперь, если у вас есть неоптимизированная сборка и вы пройдете через нее в GDB, вы сможете увидеть, что она делает. В противном случае добавление каких-либо распечаток в код для отображения значений …

Если вы не можете распечатать его в целевой системе, потому что вы находитесь в процессе настройки UART, который вы будете использовать для печати, скопируйте код на локальную хост-систему и отладьте его там. Если компилятор не очень глючит, вы должны получить те же значения из одной и той же компиляции.

1

Вот хак: просто автоматизируйте то, что вы делаете сейчас, вручную.

  • В вашем make-файле убедитесь, что avr-gcc производит дизассемблирование (-ahlms=output.lst). В качестве альтернативы, используйте свой собственный метод разборки в качестве шага после компиляции в вашем make-файле.
  • В качестве шага после компиляции обработайте файл листинга, используя ваш любимый язык сценариев для поиска out UBRRH а также out UBRRL линий. Они будут загружены из регистров, поэтому ваш скрипт может извлекать непосредственно предшествующие назначения для регистров, которые будут загружены в UBRRH а также UBRRL, Затем сценарий может собрать UBRR значение из значения, загруженного в регистры общего назначения, которые используются для установки UBRRH а также UBRRL,

Это звучит проще, чем Василий Старынкевичочень полезное предложение ПЛАВИТЬСЯ расширение. Теперь, учитывая, что это решение кажется хрупкий, на первый взгляд, давайте рассмотрим эту проблему:

  • Мы знать что (по крайней мере на вашем процессоре) линии out UBRR_, r__ появится в листинге разборки: просто нет другого способа установить регистры / записать данные в порт. Одна вещь, которая может измениться, — это расстояние между этими строками, но это может быть легко обработано вашим скриптом
  • Мы также знаем, что out инструкции могут поступать только из регистров общего назначения, поэтому мы знаем, что в качестве второго аргумента out строка инструкции, так что это не должно быть проблемой.
  • Наконец, мы также знаем, что этот регистр будет установлен до out инструкция. Здесь мы должны учесть некоторую изменчивость: вместо LDI (загрузить немедленно), avr-gcc может выдать какой-то другой набор инструкций для установки значения регистра. Я думаю, что в качестве первого прохода скрипт должен быть в состоянии проанализировать немедленную загрузку и в противном случае вывести любую последнюю найденную инструкцию, включающую регистр, в который будет записана запись. UBRR_ порты.

Сценарий, возможно, придется изменить, если вы меняете платформы (некоторые процессоры имеют UBRRH1/2 регистрирует на месте UBRRHОднако в этом случае ваш код в бодах должен будет измениться. Если скрипт жалуется, что не может разобрать разборку, то вы по крайней мере узнаете, что ваша проверка не была выполнена.

1
По вопросам рекламы [email protected]