Одной из крайне интересных фич, внедрённых в gcc 4.5 стала возможность создания плагинов для компилятора. К сожалению, в сети пока очень мало информации, описывающей данную возможность, а на русском языке её нет совсем. Что ж, постараюсь исправить эту ситуацию.
Перед началом хотелось бы указать небольшой (больше просто не нашёл) список статей, касающихся данной темы:
Для начала уточним, где на конкретной системе находятся заголовочные файлы с плагинами:
круто, в нашей системе у gcc реально есть плагины ;) Каждый плагин должен подключать заголовочный файл gcc-plugin.h. В нём описаны структуры, содержащие информацию о плагине, а также объявлены все основные функции.
Обязательной для любого плагина является функция plugin_init. Она вызывается сразу после загрузки плагина. Эта функция отвечает за инициализацию данных плагина, а также за регистрацию всех callback вызовов, которые будут применены в ходе его работы.
Ещё одним обязательным условием работы плагина является его совместимость с лицензией GPL. Да, да, именно обязательным условием, без этого gcc просто откажется с ним работать. Чтобы подтвердить свою приверженность идеям свободного ПО, нужно в плагине объявить глобальную переменную plugin_is_GPL_compatible. Теперь у нас есть всё что необходимо для создания минимального плагина. Вот его код:
Как видим, всё не так страшно. Осталось скомпилировать и применить. Делается это очень просто. Сначала создаём разделяемую библиотеку:
Затем при компиляции программы показываем gcc, что стоит использовать данный плагин:
Программа main.c вполне корректно компилируется и работает. Но всё таки осталось ощущение неуверенности. А вдруг плагин не отработал? Что ж, справедливый вопрос. Чтобы развеять эти сомнения, надо добавить вывод какой-нибудь информации. Как Вы могли заметить, на вход инициализирующей функции подаётся 2 аргумента с информацией о плагине и о самом gcc. Как раз то, что нужно. Добавим в нашу функцию следущие строки:
Перекомпиливаем:
Да, оно действительно работает! Осталось только нанести последние штрихи. А именно заполнить информацию о версии и справку:
Теперь, если перекомпилировать плагин, он будет выводить всё корректно. Вот его итоговый код:
Перед началом хотелось бы указать небольшой (больше просто не нашёл) список статей, касающихся данной темы:
- документация gcc, описывающая построение плагинов
- цикл статей по парсингу C++ при помощи плагинов gcc
- введение в создание плагинов gcc
Для начала уточним, где на конкретной системе находятся заголовочные файлы с плагинами:
$ gcc -print-file-name=plugin
/usr/lib/gcc/i686-pc-linux-gnu/4.5.3/plugin
круто, в нашей системе у gcc реально есть плагины ;) Каждый плагин должен подключать заголовочный файл gcc-plugin.h. В нём описаны структуры, содержащие информацию о плагине, а также объявлены все основные функции.
Обязательной для любого плагина является функция plugin_init. Она вызывается сразу после загрузки плагина. Эта функция отвечает за инициализацию данных плагина, а также за регистрацию всех callback вызовов, которые будут применены в ходе его работы.
Ещё одним обязательным условием работы плагина является его совместимость с лицензией GPL. Да, да, именно обязательным условием, без этого gcc просто откажется с ним работать. Чтобы подтвердить свою приверженность идеям свободного ПО, нужно в плагине объявить глобальную переменную plugin_is_GPL_compatible. Теперь у нас есть всё что необходимо для создания минимального плагина. Вот его код:
#include "gcc-plugin.h"
int plugin_is_GPL_compatible;
int plugin_init (struct plugin_name_args *plugin_info,
struct plugin_gcc_version *version )
{
return 0;
}
Как видим, всё не так страшно. Осталось скомпилировать и применить. Делается это очень просто. Сначала создаём разделяемую библиотеку:
gcc -I`gcc -print-file-name=plugin`/include -fPIC -shared plugin.c -o plugin.so
Затем при компиляции программы показываем gcc, что стоит использовать данный плагин:
gcc -fplugin=./plugin.so main.c
$ ./a.out
Hello, world
Программа main.c вполне корректно компилируется и работает. Но всё таки осталось ощущение неуверенности. А вдруг плагин не отработал? Что ж, справедливый вопрос. Чтобы развеять эти сомнения, надо добавить вывод какой-нибудь информации. Как Вы могли заметить, на вход инициализирующей функции подаётся 2 аргумента с информацией о плагине и о самом gcc. Как раз то, что нужно. Добавим в нашу функцию следущие строки:
printf("Plugin info:\n");
printf("Plugin short name (filename without .so suffix): %s\n", plugin_info->base_name);
printf("Path to the plugin as specified with -fplugin=: %s\n", plugin_info->full_name);
printf("Number of arguments specified with -fplugin-arg-...: %d\n", plugin_info->argc);
printf("Version string provided by plugin: %s\n", plugin_info->version);
printf("Help string provided by plugin: %s\n", plugin_info->help);
printf("\nGCC info:\n");
printf("Base version: %s\n", version->basever);
printf("Datestamp: %s\n", version->datestamp);
printf("Devphase: %s\n", version->devphase);
printf("Revision: %s\n", version->revision);
printf("Configuration arguments: %s\n", version->configuration_arguments);
Перекомпиливаем:
$ gcc -I`gcc -print-file-name=plugin`/include -fPIC -shared plugin.c -o plugin.so
$ gcc -fplugin=./plugin.so main.cpp
Plugin info:
Plugin short name (filename without .so suffix): plugin
Path to the plugin as specified with -fplugin=: ./plugin.so
Number of arguments specified with -fplugin-arg-...: 0
Version string provided by plugin: (null)
Help string provided by plugin: (null)
GCC info:
Base version: 4.5.3
Datestamp: 20110428
Devphase:
Revision:
Configuration arguments: /var/tmp/portage/sys-devel/gcc-4.5.3-r2/work/gcc-4.5.3/configure --prefix=/usr --bindir=/usr/i686-pc-linux-gnu/gcc-bin/4.5.3 --includedir=/usr/lib/gcc/i686-pc-linux-gnu/4.5.3/include --datadir=/usr/share/gcc-data/i686-pc-linux-gnu/4.5.3 --mandir=/usr/share/gcc-data/i686-pc-linux-gnu/4.5.3/man --infodir=/usr/share/gcc-data/i686-pc-linux-gnu/4.5.3/info --with-gxx-include-dir=/usr/lib/gcc/i686-pc-linux-gnu/4.5.3/include/g++-v4 --host=i686-pc-linux-gnu --build=i686-pc-linux-gnu --disable-altivec --disable-fixed-point --with-ppl --with-cloog --disable-ppl-version-check --with-cloog-include=/usr/include/cloog-ppl --disable-lto --enable-nls --without-included-gettext --with-system-zlib --disable-werror --enable-secureplt --disable-multilib --enable-libmudflap --disable-libssp --enable-libgomp --with-python-dir=/share/gcc-data/i686-pc-linux-gnu/4.5.3/python --enable-checking=release --disable-libgcj --with-arch=i686 --enable-languages=c,c++,fortran --enable-shared --enable-threads=posix --enable-__cxa_atexit --enable-clocale=gnu --enable-targets=all --with-bugurl=http://bugs.gentoo.org/ --with-pkgversion='Gentoo 4.5.3-r2 p1.0, pie-0.4.6'
Да, оно действительно работает! Осталось только нанести последние штрихи. А именно заполнить информацию о версии и справку:
plugin_info->version = "0.01";
plugin_info->help = "This plugin does nothing\n";
Теперь, если перекомпилировать плагин, он будет выводить всё корректно. Вот его итоговый код:
#include "gcc-plugin.h"
#include <stdio.h>
int plugin_is_GPL_compatible;
int plugin_init (struct plugin_name_args *plugin_info,
struct plugin_gcc_version *version )
{
plugin_info->version = "0.01";
plugin_info->help = "This plugin does nothing\n";
printf("Plugin info:\n");
printf("Plugin short name (filename without .so suffix): %s\n", plugin_info->base_name);
printf("Path to the plugin as specified with -fplugin=: %s\n", plugin_info->full_name);
printf("Number of arguments specified with -fplugin-arg-...: %d\n", plugin_info->argc);
printf("Version string provided by plugin: %s\n", plugin_info->version);
printf("Help string provided by plugin: %s\n", plugin_info->help);
printf("\nGCC info:\n");
printf("Base version: %s\n", version->basever);
printf("Datestamp: %s\n", version->datestamp);
printf("Devphase: %s\n", version->devphase);
printf("Revision: %s\n", version->revision);
printf("Configuration arguments: %s\n", version->configuration_arguments);
return 0;
}
Я не очень понял зачем это может быть полезно.
ОтветитьУдалитьТак и свои ключевые слова вводить можно и AST менять?
Т.е. можно будет в С сделать мемоизацию, кеширование, декораторы, карирование, асинхронный вызов и прочее?
В принципе - да. Хоть под jvm его портировать. Например, есть DragonEgg (http://dragonegg.llvm.org/), который под LLVM байт-код генерит. Часто используется для получения AST с целью последующего анализа или оптимизации. Если порыться в расширениях gcc, то там много интересного найти можно, например объявление вложенных функций :)
УдалитьА в промышленных масштабах это смысл делать имеет?
ОтветитьУдалитьДля erlang есть parse_transform. Он просто позволяет менять AST.
http://erlanger.ru/page/1574/monady-i-srezy-v-erlang-e-erlando и https://github.com/nbowe/erlang_decorators
хочется использовать для больших проектов, ибо сильно уменьшают размер кода и добавляют всяких удобств. Несколько пугают сами изменения AST, и то что это таки хак компилятора, а не стандарт языка.
Я бы назвал это не хаком, а штатной возможностью. Если тебе просто неудобен синтаксис языка, то наверное нет, не стоит. А если тебе нужно скомпилировать код под собственную платформу, или ты считаешь, что придумал крутую оптимизацию, то других вариантов нет. Более того это довольно портабельный способ, т.к. оригинальный код будет работать и без плагина.
УдалитьЛично я планировал использовать плагин для получения AST и его анализа во внешней среде. Но как-то пока что забил.
Привет! Как, руки дошли у Вас до получения AST при помощи плагинов к GCC или CLang? Может, порекомендуете по этой теме, что можно почитать для обучения? Хотел попробовать поэкспериментировать с расширением возможностей С++. Может, проекты какие знаете на эту тему (кроме Qt)?
УдалитьПривет.
УдалитьРуки не дошли, и на вряд ли дойдут. Но вообще по gcc в посте есть ссылка сюда: http://codesynthesis.com/~boris/blog/2010/05/10/parsing-cxx-with-gcc-plugin-part-2/. Данный пост писался на основе первой статьи из цикла. Там тупым копированием исходников скорей всего не получится - в новых версиях gcc могли интерфейсы поменяться, но в целом по ней можно что-нибудь сделать.
Если Вам просто "поиграться", о думаю можно посмотреть в MELT - он (судя по описанию) работает уже не с AST, а с GIMPLE, но оно тоже полезно будет. Ещё можете посмотреть в плагин визуализации аналитических структур gcc: https://code.google.com/p/gcc-vcg-plugin/.
Вообще лучше, конечно, начать с clang: http://clang.llvm.org/docs/ClangPlugins.html
А какую задачу Вы хотите решить?
Задача - чисто "поиграться". Посетила мысль, что С++ же мощный язык... ...Его пытаются "расширять", активно используя макросы (в Boost'е том же) - это не есть хорошее решение... ...А вот добавить нужные фичи, имея возможность получать AST кода, это вроде как, очевидно, но что-то люди всё пытаются с нуля делать даже языки с C-подобным синтаксисом, а не на базе C++. Это очень странно.
УдалитьЗа ссылки спасибо.
Что, если после команды gcc -print-file-name=plugin выводит только слово plugin? Как дальше поступать?
ОтветитьУдалитьА можно по подробнее? Какая версия gcc? Сами собирали? Дистрибутивная?
УдалитьУ меня на Arch'е, например, плагины лежат здесь: /usr/lib/gcc/i686-pc-linux-gnu/4.9.1/plugin. Думаю путь более или менее стандартный.
Вопрос исчерпан
Удалить