Pull to refresh

К вопросу о gcc в разрезе RISCV

Level of difficultyEasy
Reading time3 min
Views3.2K

"Удивительное рядом, но оно запрещено"

Разбирался тут на досуге с (относительно) новыми МК фирмы WCH с ядром RISCV (CH32V307FBP6) с целью осветить данный прибор, скачал рекомендованную среду разработки (mounriver) и на первом же примере наткнулся на совершенно неожиданную вещь. Пример совершенно классический (нет, не мигание светодиодом и даже не приветствие миру) и посвящен использованию функции printf, вот его ключевая часть:

USART_Printf_Init(115200);
printf("SystemClk:%d\r\n"),SystemCoreClоck);
printf("This is printf example\r\n");

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

Файл листинга в дереве проекта был обнаружен довольно-таки быстро, поскольку китайская IDE основана на Eclipse, с которой я ранее неоднократно имел дело. Какого же было мое удивление, когда я обнаружил следующий фрагмент ассемблерного кода:

jal <Usart_Printf_Init>
....
jal <iprintf>
....
jal <puts>

Здесь оставлено самое интересное для данного поста для первого вызова демонстрируемой функции в строке 3 подставлен адрес функции iprintf (скорее всего, печать целого числа), а для второго вызова подставлен адрес функции puts (скорее всего, печать строки). Пнп: на самом деле в ассемблере есть еще много интересного в плане формирования параметров вызова, но об этом в другой раз. Хм, мне всегда казалось, что в С такие трюки невозможны. В С++ к нашим услугам и перегрузка и шаблоны и, наверное, что-нибудь еще (автор не считает себя знатоком данного языка), но в "чистом С" ... странно как то.

Необходимое пояснение: на самом деле ничего экстраординарного не происходит, и обычная реализация компилятора, во-первых, проверит соответствие параметров функции форматной строке, во-вторых, передаст указатель на строку формата стандартной функции, и та выведет все требуемые символы в устройство вывода. Но в данном конкретном случае часть работы выполнятся в компиляторе, что позволяет (не так, чтобы очень сильно, но тем не менее) сократить время на этапе исполнения.

Гипотеза 1 - у нас на самом дела С++, легко опровергается - попытка создать описание класса отвергнута компилятором.

Гипотеза 2 - мы имеем дело с каким то собственным компилятором, опровергается путем изучения ключей компиляции и сообщений - имеется он выглядит, как обычный gcc. Конечно, остается возможность того, что кто-то замаскировал свой компилятор под стандартный, но вероятность такого события будем считать низкой.

Гипотеза 3 (по настоящему интересная и с нее я начал) - у нас какой то офигенно хитрый макрос вместо настоящей функции (хоть и с именем, записанным маленькими буквами), вызвала у меня нездоровое оживления Пнп: я всегда хотел делать именно так, причем исключительно средствами языка. Но в сырках лежит честный хидер функции, правда с добавочным атрибутом, который не производит впечатление установки перегрузки имени функции.

int printf (const char *_restrict, ...) _ATTRIBUTE ((__format__(__printf__, 1, 2))) 


Начинаю эксперименты и быстро устанавливаю, что iprintf - это просто алиас для собственно printf, а вовсе не функция печати исключительно целых чисел, и во всех случаях, кроме печати одиночной строки формата, подставляется вызов именно этой функции (независимо от типа выводимых данных, в том числе и для печати одинокой строки через формат %s). Ладно, смотрю, что это за атрибут и ничего, что бы намекало на полиморфизм (а ведь очень похоже) не нахожу, так что гипотеза опровергнута.

Есть еще гипотеза 4 - вариант с хитрыми ключами компиляции, хотя я их не вижу в строке вызова компилятора. Пытаюсь обнаружить данную оптимизацию (для архитектуры RISCV) на любимом сайте godbolt и не наблюдаю ее. Значит, или ключи как-то хитро скрыты в недрах среды программирования, или их просто нет, склоняюсь к последнему.

Вроде как получается по Холмсу - отбросьте все невозможные объяснения и оставшееся будет истинным, каким бы малоправдоподобным оно ни выглядело. В данном случае это вариант гипотезы 2 - модификация компилятора с целью локальной оптимизации. Возможно, это сделано только для одной архитектуры и тогда логично искать оптимизацию в генераторе кода, но, возможно, что оптимизация проводится на этапе генерации RTL кода и тогда она будет наличествовать всегда.

Поскольку, в любом из рассмотренных вариантов, для меня решение бесполезно, воспроизвести его я не смогу (в отличие от хитрого макроса, а он однозначно исключен), то дальнейших исследований не проводил, оставлю это на долю пытливого читателя.

Tags:
Hubs:
Total votes 10: ↑6 and ↓4+2
Comments11

Articles