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

c_uglify -- семантический фильтр GNU C для компиляции программ с помощью не-GCC

Ivan Zakharyaschev, Moscow, Russia

LVEE Winter 2016

A converter of C code is presented, which overwrites some GNU extensions, making it possible to compile gcc-oriented FOSS software with othe compilers - clang, etc. Converter is based on "language-c":http://hackage.haskell.org/package/language-c Haskell library. Draft GNU extensions classification and tasks of the distribution porting to an alien platform are reviewed as far as stage of the project and current results of its usage.

Темой представленной работы является преобразователь C-кода, который бы переписывал некоторые GNU extensions. Это необходимо для того, чтобы такие программы можно было компилировать компилятором, который GNU extensions не поддерживает – например, clang или др. Преобразователь реализуется на Haskell-библиотеке language-c.

Введение. Мы за или против GNU extensions?

Автор считает, что использование GNU extensions при программировании на C — это не плохо. На это же намекает и название проекта “C uglify”: захочет ли человек видеть обезображенный код (ugly), который получается в результате избавления от GNU extensions, т.е. переписывания программы с языка чуть более высокого уровня на язык чуть более низкого уровня?

Пример из ALT-овского rpm:

// prune src dups from pkg1 and add dependency on pkg2
static
void pruneSrc1(struct Req *r, Package pkg1, Package pkg2)
{
    TFI_t fi1 = pkg1->cpioList;
    const TFI_t fi2 = pkg2->cpioList;
    if (!fi1 || !fi2) return;
    struct {
	    unsigned int n;
	    char list[fi1->fc];
    } pruned;
    bzero(&pruned, sizeof(pruned));
    fiIntersect(fi1, fi2, fiIntersect_cb, &pruned);
    if (pruned.n == 0)
	return;
    addDeps1(r, pkg1, pkg2);
    rpmMessage(RPMMESS_NORMAL, "Removing from %s %d sources provided by %s\n",
	    pkgName(pkg1), pruned.n, pkgName(pkg2));
    fiPrune(fi1, pruned.list);
}

Здесь было использовано GNU extension “variable-length array at the end of struct”. А после избавления от него вручную стало так:

// prune src dups from pkg1 and add dependency on pkg2
static
void pruneSrc1(struct Req *r, Package pkg1, Package pkg2)
{
    TFI_t fi1 = pkg1->cpioList;
    const TFI_t fi2 = pkg2->cpioList;
    if (!fi1 || !fi2) return;
    struct {
	    unsigned int n;
	    char *list;
    } pruned;
    pruned.n = 0;
    pruned.list = alloca(fi1->fc);
    memset(pruned.list, 0, fi1->fc);
    fiIntersect(fi1, fi2, fiIntersect_cb, &pruned);
    if (pruned.n == 0)
	return;
    addDeps1(r, pkg1, pkg2);
    rpmMessage(RPMMESS_NORMAL, "Removing from %s %d sources provided by %s\n",
	    pkgName(pkg1), pruned.n, pkgName(pkg2));
    fiPrune(fi1, pruned.list);
}

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

(Любопытно, что использование VLAs at the end of struct появилось в свою очередь в этом коде в результате избавления от другого GNU C extension “nested functions”; оно в ту пору было произведено1 ещё не столько в целях портируемости кода, сколько по отдельным причинам2).

Таким образом, cuglify предполагается использовать не для того, чтобы контрибьютить некрасивые патчи в проекты, которые не удаётся скомпилировать компилятором, отличным от GCC (например, clang), а чтобы сделать возможной выполнение такой компиляции гладко и незаметно для компилирующего (продолжение этой темы — в разделе “Часть большей задачи”).

Существуют и другие примеры “некрасивых” и не всегда компактных патчей, которые были предложены по этой причине в некоторые FOSS-проекты. Можно предположить, что их написание (помимо того, что они делают код менее понятным) потребовало немало человеческих сил (например, патчи на elfutils были растянуты во времени), а проверка на отсутствие ошибок должна была потребовать много человеческого внимания:

Также в случае, если патч не принят в upstream, придётся регулярно повторять эти человеческие усилия для переноса на новые версии. (Ср. отставание патча на elfutils от текущей версии upstream-а: 0.165.)

С чем надо справляться (рабочая классификация GNU extensions)

Одним из заметных GNU C extensions являются nested functions. (С полным списком классов проблем, обнаруженных при пересборке Debian clang-ом, можно ознакомиться в 3). Они были замечены в исходном коде базовых инструментов для сборки дистрибутивов ALT (упомянутый выше случай elfutils и неупомянутые ещё случаи nested functions в rpm).

В первую очередь в cuglify мы занялись переписыванием nested functions.

Набросок классификации GNU C extensions в наших рабочих планах на текущий момент выглядит так:

  • GNU C extensions:
    • VLAs;
    • nested functions:
      • на которые берётся указатель;
      • r/w;
      • reader-only;
      • “pure”;
    • другие;

Пояснение названий приведенных классов вложенных функций: “reader-only” — те, которые только читают переменные из local scope функции, в которую они вложены, но не пишут в них; “r/w” — и пишут, и читают; “pure” — ни то, ни другое.

Классификация nested functions сделана в порядке убывания сложности задачи их переписывания.

Вложенными функциями, на которые берётся указатель, было решено пока не заниматься. Это принципиально иной, более сложный случай, при этом предположительно нечастый2.

Последние три класса не отличаются на принципиальном уровне, но ожидается, что технически реализация переписывания “r/w” вложенных функций займёт больше времени, т.е. появится в более поздней версии cuglify.

Часть большей задачи. Обобщение задачи семантического фильтра

На самом деле, разработка cuglify — часть большей практической задачи. Мы хотим научиться применять cuglify в работе с некой “инородной” платформой FOO/Linux, а точнее, в работе по созданию порта Sisyphus на эту платформу (т.е. порта репозитория пакетов свободного ПО, из которого создаются дистрибутивы ОС). Здесь FOO/Linux — какая-то платформа без GCC, где есть свой foo-cc, в чём-то похожий на GCC, а в чём-то нет (и foo-cc патчить мы не можем).

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

cuglify является фильтром, цель которого – поддержка задуманной программистом (и мейнтейнером пакета) семантики C-кода с использованием в качестве backend-а не GCC, а foo-cc.

Задуманная семантика может выражаться не только в C-коде (со своими особыми конструкциями вроде nested functions, которые мы переписываем из-за ограничений в backend), но и в опциях gcc. Опции, работа которых в foo-cc нас не устраивает, мы будем исполнять по возможности сами, а опции для backend-а переписывать (хотя в простом случае всего лишь передаём их без изменений). Знание о семантике некоторых опций GCC и особенностях их реализации в foo-cc закладывается в cuglify.

В результате незаметно для собирающего под FOO/Linux убирается часть массовых проблем со сборкой пакетов. Не потребуется в них влезать.

Особенности текущей реализации

Исходники на текущий момент расположены по адресу hub.darcs.net/imz/language-c_WIP. Свежесобранный cuglify под платформу x86 с последним набором работающего функционала может быть взят у автора лично или по инструкции в списке рассылки devel ALTLinux6.

Есть несколько вариантов, как начать применять cuglify (на инородной платформе). Один вариант – “автоматический нативный” – более основательный и ценный для будущего; второй вариант – “ручной” – уже опробован на практике (при компиляции ALT-ового пакета rpm на Эльбрусе), проще и позволяет быстрее увидеть результат компиляции.

И третий вариант, который прорабатывается в данный момент и который достижим быстрее, чем первый основательный – основан на клиент-серверной модели взаимодействия между сборочницей на FOO/Linux и cuglify на x86. Эта схема использования напоминает ccache или ещё больше distcc, с тем небольшим техническим усложнением, что cuglify должна решать на основании переданных ей C-исходников и GCC-подобных опций, какую команду foo-cc надо выполнить, и попросить сделать это сборочницу.

Благодарности

Автор выражает благодарность за обсуждения в ходе этого проекта участникам ALT Linux Team: Г.Фонтенгауэру-Малиновскому, Д.Левину, Г.Курячему, А.Новодворскому, М.Шигорину, А.Турбину, А.Гладкову. Отдельная благодарность за написание вручную и отыскивание патчей, избавляющих от GNU C extensions в пакетах из Sisyphus, Г.Фонтенгауэру-Малиновскому, Д.Левину, М.Шигорину (и тем самым предоставление примеров, а также за обнаружение на практике опций GCC, которые не так понимаются “другим компилятором”, а именно lcc на Эльбрусе). И, конечно же, энтузиасту сборки Debian clang-ом Sylvestre Ledru. А также всем участникам создания свободного ПО.

Автор благодарен редакторам и организаторам LVEE2016 Winter за замечания и помощь в редактировании этих тезисов.

Ссылки и примечания

1 git.altlinux.org/…

2 Вложенными функциями, на которые берётся указатель, мы вообще пока заниматься не будем. Во-первых, потому что избавление от них compile-time потребует совсем иного подхода, чем в cuglify, если вообще возможно, а именно более глобального, затрагивающего весь дистрибутив, а не отдельную программу; во-вторых, из-за того, что их run-time реализация через executable stack считается нежелательной хотя бы с точки зрения безопасности, есть надежда, что их и так стараются избегать. Среди пакетов ALT Sisyphus есть около 30 специфических пакетов с executable stack.

3 clang.debian.net

4 1849 insertions(+), 1608 deletions(-) in elfutils

5 38 insertions(+), 31 deletions(-) in gpm

6 Вот последняя на момент публикации инструкция, отправленная в devel. (Её копия сохранена как ann1_4.)

Abstract licensed under Creative Commons Attribution-ShareAlike 3.0 license

Назад