[devel] re-writing GNU C extensions (part0)

Ivan Zakharyaschev imz на altlinux.org
Пн Янв 11 11:48:36 MSK 2016


Пишу преобразователь C-кода, который бы переписывал некоторые GNU
extensions. Это делается для того, чтобы такие программы можно было
компилировать компилятором, который не поддерживает GNU extensions.
Например, clang-ом. (Реализуется на библиотеке
<http://hackage.haskell.org/package/language-c>.)

Сейчас хочу предварительно, пока преобразователь ещё не полностью
готов, показать заготовку, т.е. интерфейс этой программы. В следующих
сообщениях будут описываться работающие преобразования.

(Это сообщение сохранено как
[ann0.md](http://hub.darcs.net/imz/cuglify/browse/ann0.md).)

Вопросы:
========

Может быть, кто-нибудь может быстро дать совет, который пригодится в
этой работе:

как встроить её в пересборку Sisyphus? (На данном этапе это ценно
только для тестирования возможностей language-c на реальном коде из
Sisyphus -- не ломается ли на чём-то, т.е. не очень интересно для
широкого круга разработчиков. До проведения такой проверки у меня пока
руки не дошли. Думаю, это реализовать будет несложно и проверить
пересборку по крайней мере избранных пакетов.)

Это поспособствует потенциальной переносимости Sisyphus на платформы
без gcc.

Ещё интересуют любые замеченные ошибки в работе, если вдруг кто-то
будет пробовать.

Описание
========

Разные варианты программы-заготовки живут в файле examples/Process.hs
в разных ветках изменённой language-c:

1. <http://hub.darcs.net/imz/language-c_process_analyze-silently>,
2. <http://hub.darcs.net/imz/language-c_process_analyze-printAST>,
3. <http://hub.darcs.net/imz/language-c_process_analyze-generate>.

[Вот](http://hub.darcs.net/imz/cuglify/browse/run-tests.sh) скриптик
для тестирования Process.hs, который демонстрирует идею:

если запустить программу с теми же опциями, что cpp/gcc, то её
поведение должно быть в некотором смысле эквивалентно cpp/gcc.
(Сравниваются коды возврата и демонстрируется diff между
выводом `gcc -E` и нашего `examples/Process`.)

Что умеют эти варианты examples/Process:
----------------------------------------

1. молча парсит и анализирует входной C-файл и завершается с успехом
или неуспехом, т.е. грубо говоря (не)успех ожидается в тех же случаях,
что у `gcc -c`

2. парсит и анализирует входной C-файл, а потом в случае успеха
анализа печатает C-код по своему внутреннему AST, т.е. должен быть
заменой `cpp`/`gcc -E`

3. парсит и анализирует входной C-файл, а потом (в случае успеха,
конечно) генерирует нечто похожее на C-код по своему внутреннему
семантическому представлению

(Может быть, где-то реальное поведение не соответствует заявленному
выше. Тогда это ошибка, которая требует исправления. Я пока ещё
внимательно не проверял все случаи.)

Как массово проверять разные варианты examples/Process и зачем:
---------------------------------------------------------------

1 интересно встраивать в процесс пересборки так: (если пакет успешно
пересобирается в Sisyphus, то) перед всяким вызовом gcc молча вызывать
Process и отваливаться в случае неуспеха. (Подменять cpp и gcc для
других языков не надо, потому что cpp может натравливаться на разный
код, а Process рассчитан на код, который C семантически.)

2 интересно встроить вместо этапа препроцессинга. (Чтобы проверить
печаталку Process на годность.)

3 в таком виде никуда встраивать не планируется, потому что генератор
просто вываливает код для всех встреченных глобальных семантических
объектов неважно в каком порядке. Это интересно для изучения работы
генератора человеком. Использовать генератор всё равно планируется,
чтобы сгенерировать только кусочки переписанного кода, а не всю
программу.

Примеры работы на ex-nested_undef.c
-----------------------------------

(на других примерах -- см. в конце письма):

### 1.

     PROCESSING ex-nested_undef.c...
     * with gcc -c:
     0
     * with ../language-c_process_analyze-silently/examples/Process:
     ex-nested_undef.c:7: (column 3) [WARNING]  >>> AST invariant violated
       unknown function: h

     0
     * diff against gcc -E:
     ex-nested_undef.c:7: (column 3) [WARNING]  >>> AST invariant violated
       unknown function: h

     --- /dev/fd/63	2016-01-09 05:35:09.788220253 +0300
     +++ /dev/fd/62	2016-01-09 05:35:09.789220179 +0300
     @@ -1,12 +0,0 @@
     -# 1 "ex-nested_undef.c"
     -# 1 "<command-line>"
     -# 1 "ex-nested_undef.c"
     -void f(int a) {
     -  int b = 5;
     -  int g(int x) {
     -    return b + x + a;
     -  }
     -  g(0);
     -  h(1);
     -  g(2);
     -}
     TEST OK on ex-nested_undef.c

### 2.

     PROCESSING ex-nested_undef.c...
     * with gcc -c:
     0
     * with ../language-c_process_analyze-printAST/examples/Process:
     ex-nested_undef.c:7: (column 3) [WARNING]  >>> AST invariant violated
       unknown function: h

     void f(int a)
     {
         int b = 5;
         int g(int x)
         {
             return b + x + a;
         }
         g(0);
         h(1);
         g(2);
     }
     0
     * diff against gcc -E:
     ex-nested_undef.c:7: (column 3) [WARNING]  >>> AST invariant violated
       unknown function: h

     --- /dev/fd/63	2016-01-09 05:23:34.198134568 +0300
     +++ /dev/fd/62	2016-01-09 05:23:34.199134493 +0300
     @@ -1,9 +1,8 @@
     -# 1 "ex-nested_undef.c"
     -# 1 "<command-line>"
     -# 1 "ex-nested_undef.c"
     -void f(int a) {
     +void f(int a)
     +{
        int b = 5;
     -  int g(int x) {
     +    int g(int x)
     +    {
          return b + x + a;
        }
        g(0);
     TEST OK on ex-nested_undef.c

### 3.

     PROCESSING ex-nested_undef.c...
     * with gcc -c:
     0
     * with ../language-c_process_analyze-generate/examples/Process:
     ex-nested_undef.c:7: (column 3) [WARNING]  >>> AST invariant violated
       unknown function: h

     void f(int a)
     {
         int b = 5;
         int g(int x)
         {
             return b + x + a;
         }
         g(0);
         h(1);
         g(2);
     }
     void * __builtin_extract_return_addr(void *);
     static const char __FUNCTION__[];
     int __builtin___snprintf_chk(char *,
                                  int,
                                  int,
                                  int,
                                  char * const, ...);
     int __builtin___vsprintf_chk(char *,
                                  int,
                                  int,
                                  char * const,
                                  va_list);
     void * __builtin___memcpy_chk(void *, void * const, int, int);
     char * __builtin___stpcpy_chk(char * const, char * const, int);
     char * __builtin___strcat_chk(char * const, char * const, int);
     char * __builtin___strcpy_chk(char * const, char * const, int);
     int __builtin___sprintf_chk(char *, int, int, char * const, ...);
     void * __builtin_return_address(unsigned int);
     int __builtin_va_arg_pack();
     void * __builtin___memmove_chk(void *, void * const, int, int);
     int __builtin___vsnprintf_chk(char *,
                                   int,
                                   int,
                                   int,
                                   char * const,
                                   va_list);
     char * __builtin___strncat_chk(char * const,
                                    char * const,
                                    int,
                                    int);
     char * __builtin___strncpy_chk(char * const,
                                    char * const,
                                    int,
                                    int);
     void * __builtin___mempcpy_chk(void *, void * const, int, int);
     void * __builtin___memset_chk(void *, int, int, int);
     int __builtin_constant_p(__ty_any);
     void __builtin_va_start(va_list, void *);
     void * __builtin_frame_address(unsigned int);
     void __builtin_va_end(va_list);
     void * __builtin_alloca(int);
     int __builtin_object_size(void *, int);
     void __builtin_va_copy(va_list, va_list);
     char * __builtin_strncat(char * const, char * const, int);
     double __builtin_copysign(double, double);
     void * __builtin_memcpy(void *, void * const, int);
     double __builtin_fabs(double);
     float __builtin_fabsf(float);
     long double __builtin_fabsl(long double);
     int __builtin_strspn(char * const, char * const);
     char * __builtin_strncpy(char * const, char * const, int);
     int __builtin_strcmp(char * const, char * const);
     int __builtin_strcspn(char * const, char * const);
     char * __builtin_strpbrk(char * const, char * const);
     void __builtin_prefetch(void * const);
     char * __builtin_strchr(char * const, int);
     static const char __PRETTY_FUNCTION__[];
     double __builtin_huge_val();
     int __builtin_clz(unsigned int);
     float __builtin_huge_valf();
     long double __builtin_huge_vall();
     long __builtin_expect(long, long);
     double __builtin_inf();
     float __builtin_inff();
     long double __builtin_infl();
     static const char __func__[];
     void __builtin_bzero(void *, int);
     int __builtin_va_arg_pack_len();
     0
     * diff against gcc -E:
     ex-nested_undef.c:7: (column 3) [WARNING]  >>> AST invariant violated
       unknown function: h

     --- /dev/fd/63	2016-01-09 05:33:00.477902543 +0300
     +++ /dev/fd/62	2016-01-09 05:33:00.477902543 +0300
     @@ -1,12 +1,79 @@
     -# 1 "ex-nested_undef.c"
     -# 1 "<command-line>"
     -# 1 "ex-nested_undef.c"
     -void f(int a) {
     +void f(int a)
     +{
        int b = 5;
     -  int g(int x) {
     +    int g(int x)
     +    {
          return b + x + a;
        }
        g(0);
        h(1);
        g(2);
      }
     +void * __builtin_extract_return_addr(void *);
     +static const char __FUNCTION__[];
     +int __builtin___snprintf_chk(char *,
     +                             int,
     +                             int,
     +                             int,
     +                             char * const, ...);
     +int __builtin___vsprintf_chk(char *,
     +                             int,
     +                             int,
     +                             char * const,
     +                             va_list);
     +void * __builtin___memcpy_chk(void *, void * const, int, int);
     +char * __builtin___stpcpy_chk(char * const, char * const, int);
     +char * __builtin___strcat_chk(char * const, char * const, int);
     +char * __builtin___strcpy_chk(char * const, char * const, int);
     +int __builtin___sprintf_chk(char *, int, int, char * const, ...);
     +void * __builtin_return_address(unsigned int);
     +int __builtin_va_arg_pack();
     +void * __builtin___memmove_chk(void *, void * const, int, int);
     +int __builtin___vsnprintf_chk(char *,
     +                              int,
     +                              int,
     +                              int,
     +                              char * const,
     +                              va_list);
     +char * __builtin___strncat_chk(char * const,
     +                               char * const,
     +                               int,
     +                               int);
     +char * __builtin___strncpy_chk(char * const,
     +                               char * const,
     +                               int,
     +                               int);
     +void * __builtin___mempcpy_chk(void *, void * const, int, int);
     +void * __builtin___memset_chk(void *, int, int, int);
     +int __builtin_constant_p(__ty_any);
     +void __builtin_va_start(va_list, void *);
     +void * __builtin_frame_address(unsigned int);
     +void __builtin_va_end(va_list);
     +void * __builtin_alloca(int);
     +int __builtin_object_size(void *, int);
     +void __builtin_va_copy(va_list, va_list);
     +char * __builtin_strncat(char * const, char * const, int);
     +double __builtin_copysign(double, double);
     +void * __builtin_memcpy(void *, void * const, int);
     +double __builtin_fabs(double);
     +float __builtin_fabsf(float);
     +long double __builtin_fabsl(long double);
     +int __builtin_strspn(char * const, char * const);
     +char * __builtin_strncpy(char * const, char * const, int);
     +int __builtin_strcmp(char * const, char * const);
     +int __builtin_strcspn(char * const, char * const);
     +char * __builtin_strpbrk(char * const, char * const);
     +void __builtin_prefetch(void * const);
     +char * __builtin_strchr(char * const, int);
     +static const char __PRETTY_FUNCTION__[];
     +double __builtin_huge_val();
     +int __builtin_clz(unsigned int);
     +float __builtin_huge_valf();
     +long double __builtin_huge_vall();
     +long __builtin_expect(long, long);
     +double __builtin_inf();
     +float __builtin_inff();
     +long double __builtin_infl();
     +static const char __func__[];
     +void __builtin_bzero(void *, int);
     +int __builtin_va_arg_pack_len();
     TEST OK on ex-nested_undef.c

Appendix. Как воспроизвести:
============================

(Последний раздел. Дальше можно не читать. Кстати, готовые варианты
исполняемого файла examples/Process кто-то может взять, чтобы не
воспроизводить, в vb2:/home/imz/public/cuglify-WIP/ .)


Инструменты и зависимости:
--------------------------

Побольше всего, чтобы поменьше пересобирать cabal-ом:

     # apt-get install ghc7.6.1-cabal-install ghc7.6.1 ghc7.6.1-darcs 
ghc7.6.1-language-c ghc7.6.1-happy ghc7.6.1-alex ghc7.6.1-haskell-src 
ghc7.6.1-mtl

Устанавливаем новую версию cabal-install с поддержкой sandboxes:
(TODO: упаковать в Sisyphus. См. также <http://altlinux.org/Haskell>)

     $ cabal update
     $ cabal install cabal-install
     $ ln -s ~/.cabal/bin/cabal ~/bin -v
     «/home/imz/bin/cabal» -> «/home/imz/.cabal/bin/cabal»
     $

После этого нужно, чтобы shell не вызывал старый cabal (можно
по-простому заново зайти).

Скачиваем несколько вариантов кода
----------------------------------

(Чтобы в итоге собрать разные варианты, делаем, например, так:)

     $ mkdir cuglify-WIP
     $ cd cuglify-WIP
     $ darcs clone 
http://hub.darcs.net/imz/language-c_process_analyze-silently
     $ darcs clone 
http://hub.darcs.net/imz/language-c_process_analyze-printAST
     $ darcs clone 
http://hub.darcs.net/imz/language-c_process_analyze-generate

Собираем библиотеку
-------------------

Несовместимых изменений в разных вариантах библиотеки нет, поэтому
можно скомпилировать и установить в sandbox одну на всех (самую полную):

     [imz на vb2 cuglify-WIP]$ cd language-c_process_analyze-generate/
     [imz на vb2 language-c_process_analyze-generate]$ darcs pull 
http://hub.darcs.net/imz/language-c_WIP -p tmp
     [imz на vb2 language-c_process_analyze-generate]$ cabal sandbox 
--sandbox=/storage/imz/CABAL-SANDBOX-cuglify init
     [imz на vb2 language-c_process_analyze-generate]$ cabal install
     [imz на vb2 language-c_process_analyze-generate]$ cd ..

### Замечание (о проверке компилятором полноты реализации):

При компиляции Export.hs из библиотеки предупреждения
компилятора рассказывают нам, экспорт каких конструкций ещё не
реализован. (Я пока не думал толком, можно ли это требование закодировать 
более
явно на Haskell, а не полагаться на определённый стиль написания
функций и предупреждения компилятора.) Вот например:

     [28 of 39] Compiling Language.C.Analysis.Export ( 
src/Language/C/Analysis/Export.hs, 
dist/dist-sandbox-36d68e1d/build/Language/C/Analysis/Export.o )

     src/Language/C/Analysis/Export.hs:236:39: Warning:
         Defined but not used: `g_tags'

     src/Language/C/Analysis/Export.hs:236:46: Warning:
         Defined but not used: `g_typedefs'

     src/Language/C/Analysis/Export.hs:241:1: Warning:
         Pattern match(es) are non-exhaustive
         In an equation for `exportIdentDecl':
             Patterns not matched: EnumeratorDef _

Компилируем все варианты нашего examples/Process
------------------------------------------------

### 1.

     [imz на vb2 cuglify-WIP]$ cd language-c_process_analyze-silently/
     [imz на vb2 language-c_process_analyze-silently]$ darcs pull 
http://hub.darcs.net/imz/language-c_WIP -p tmp
     [imz на vb2 language-c_process_analyze-silently]$ cabal sandbox 
--sandbox=/storage/imz/CABAL-SANDBOX-cuglify init
     [imz на vb2 language-c_process_analyze-silently]$ cabal exec -- make -C 
examples -j Process
     [imz на vb2 language-c_process_analyze-silently]$ cd ..

### 2.

     [imz на vb2 cuglify-WIP]$ cd language-c_process_analyze-printAST/
     [imz на vb2 language-c_process_analyze-printAST]$ darcs pull 
http://hub.darcs.net/imz/language-c_WIP -p tmp
     [imz на vb2 language-c_process_analyze-printAST]$ cabal sandbox 
--sandbox=/storage/imz/CABAL-SANDBOX-cuglify init
     [imz на vb2 language-c_process_analyze-printAST]$ cabal exec -- make -C 
examples -j Process
     make: Вход в каталог 
`/home/imz/public/cuglify-WIP/language-c_process_analyze-printAST/examples'
     ghc -Wall -package language-c-0.4.8 --make -O Process.hs
     [1 of 1] Compiling Main             ( Process.hs, Process.o )

     Process.hs:3:1: Warning:
         The import of `Data.List' is redundant
           except perhaps to import instances from `Data.List'
         To import instances alone, use: import Data.List()

     Process.hs:14:1: Warning:
         The import of `System.Console.GetOpt' is redundant
           except perhaps to import instances from `System.Console.GetOpt'
         To import instances alone, use: import System.Console.GetOpt()

     Process.hs:16:1: Warning:
         The import of `System.Exit' is redundant
           except perhaps to import instances from `System.Exit'
         To import instances alone, use: import System.Exit()
     Linking Process ...
     make: Выход из каталога 
`/home/imz/public/cuglify-WIP/language-c_process_analyze-printAST/examples'
     [imz на vb2 language-c_process_analyze-printAST]$ cd ..

### 3.

     [imz на vb2 cuglify-WIP]$ cd language-c_process_analyze-generate/
     [imz на vb2 language-c_process_analyze-generate]$ darcs pull 
http://hub.darcs.net/imz/language-c_WIP -p tmp
     [imz на vb2 language-c_process_analyze-generate]$ cabal sandbox 
--sandbox=/storage/imz/CABAL-SANDBOX-cuglify init
     [imz на vb2 language-c_process_analyze-generate]$ cabal exec -- make -C 
examples -j Process
     make: Вход в каталог 
`/home/imz/public/cuglify-WIP/language-c_process_analyze-generate/examples'
     ghc -Wall -package language-c-0.4.8 --make -O Process.hs
     [1 of 1] Compiling Main             ( Process.hs, Process.o )

     Process.hs:3:1: Warning:
         The import of `Data.List' is redundant
           except perhaps to import instances from `Data.List'
         To import instances alone, use: import Data.List()

     Process.hs:15:1: Warning:
         The import of `System.Console.GetOpt' is redundant
           except perhaps to import instances from `System.Console.GetOpt'
         To import instances alone, use: import System.Console.GetOpt()

     Process.hs:17:1: Warning:
         The import of `System.Exit' is redundant
           except perhaps to import instances from `System.Exit'
         To import instances alone, use: import System.Exit()
     Linking Process ...
     make: Выход из каталога 
`/home/imz/public/cuglify-WIP/language-c_process_analyze-generate/examples'
     [imz на vb2 language-c_process_analyze-generate]$ cd ..

Тестируем разные варианты
-------------------------

     [imz на vb2 cuglify-WIP]$ darcs clone http://hub.darcs.net/imz/cuglify 
--set-scripts-executable
     [imz на vb2 cuglify-WIP]$ cd cuglify/

### Смотрим прохождение тестов (коротко):

     [imz на vb2 cuglify]$ ./run-tests.sh 
../language-c_process_analyze-silently/examples/Process 2> /dev/null
     TEST OK on ex-nested.c
     TEST OK on ex-nested_typemismatch.c
     TEST OK on ex-nested_undef.c
     TEST OK on ex-nested-with-id-collisions.c
     [imz на vb2 cuglify]$ ./run-tests.sh 
../language-c_process_analyze-printAST/examples/Process 2> /dev/null
     TEST OK on ex-nested.c
     TEST OK on ex-nested_typemismatch.c
     TEST OK on ex-nested_undef.c
     TEST OK on ex-nested-with-id-collisions.c
     [imz на vb2 cuglify]$ ./run-tests.sh 
../language-c_process_analyze-generate/examples/Process 2> /dev/null
     TEST OK on ex-nested.c
     TEST OK on ex-nested_typemismatch.c
     TEST OK on ex-nested_undef.c
     TEST OK on ex-nested-with-id-collisions.c

### Пример того, что происходит (за кулисами):

     [imz на vb2 cuglify]$ ./run-tests.sh 
../language-c_process_analyze-printAST/examples/Process
     PROCESSING ex-nested.c...
     * with gcc -c:
     0
     * with ../language-c_process_analyze-printAST/examples/Process:
     void f(int a)
     {
         int b = 5;
         int g(int x)
         {
             return b + x + a;
         }
         g(0);
         g(1);
         g(2);
     }
     0
     * diff against gcc -E:
     --- /dev/fd/63	2016-01-09 05:23:34.041146328 +0300
     +++ /dev/fd/62	2016-01-09 05:23:34.042146253 +0300
     @@ -1,9 +1,8 @@
     -# 1 "ex-nested.c"
     -# 1 "<command-line>"
     -# 1 "ex-nested.c"
     -void f(int a) {
     +void f(int a)
     +{
        int b = 5;
     -  int g(int x) {
     +    int g(int x)
     +    {
          return b + x + a;
        }
        g(0);
     TEST OK on ex-nested.c
     PROCESSING ex-nested_typemismatch.c...
     * with gcc -c:
     ex-nested_typemismatch.c: In function ‘f’:
     ex-nested_typemismatch.c:7:4: error: called object ‘b’ is not a 
function
     1
     * with ../language-c_process_analyze-printAST/examples/Process:
     Process: user error (ex-nested_typemismatch.c:7: (column 3) [ERROR] 
>>> AST invariant violated
       attempt to call non-function of type int
     )
     1
     TEST OK on ex-nested_typemismatch.c
     PROCESSING ex-nested_undef.c...
     * with gcc -c:
     0
     * with ../language-c_process_analyze-printAST/examples/Process:
     ex-nested_undef.c:7: (column 3) [WARNING]  >>> AST invariant violated
       unknown function: h

     void f(int a)
     {
         int b = 5;
         int g(int x)
         {
             return b + x + a;
         }
         g(0);
         h(1);
         g(2);
     }
     0
     * diff against gcc -E:
     ex-nested_undef.c:7: (column 3) [WARNING]  >>> AST invariant violated
       unknown function: h

     --- /dev/fd/63	2016-01-09 05:23:34.198134568 +0300
     +++ /dev/fd/62	2016-01-09 05:23:34.199134493 +0300
     @@ -1,9 +1,8 @@
     -# 1 "ex-nested_undef.c"
     -# 1 "<command-line>"
     -# 1 "ex-nested_undef.c"
     -void f(int a) {
     +void f(int a)
     +{
        int b = 5;
     -  int g(int x) {
     +    int g(int x)
     +    {
          return b + x + a;
        }
        g(0);
     TEST OK on ex-nested_undef.c
     PROCESSING ex-nested-with-id-collisions.c...
     * with gcc -c:
     ex-nested-with-id-collisions.c: In function ‘g’:
     ex-nested-with-id-collisions.c:10:5: warning: return makes integer 
from pointer without a cast [enabled by default]
     0
     * with ../language-c_process_analyze-printAST/examples/Process:
     void f(int x);
     void g(int x);
     void h(int x);
     int b = 11;
     void f(int a)
     {
         int b = 5;
         int g(int x)
         {
             return f + x + a;
         }
         g(0);
         g(1);
         g(2);
     }
     void f(int y);
     void g(int y);
     void h(int y);
     0
     * diff against gcc -E:
     --- /dev/fd/63	2016-01-09 05:23:34.309126253 +0300
     +++ /dev/fd/62	2016-01-09 05:23:34.310126178 +0300
     @@ -1,23 +1,18 @@
     -# 1 "ex-nested-with-id-collisions.c"
     -# 1 "<command-line>"
     -# 1 "ex-nested-with-id-collisions.c"
      void f(int x);
      void g(int x);
      void h(int x);
     -
      int b = 11;
     -
     -void f(int a) {
     +void f(int a)
     +{
        int b = 5;
     -  int g(int x) {
     +    int g(int x)
     +    {
          return f + x + a;
        }
     -
        g(0);
        g(1);
        g(2);
      }
     -
      void f(int y);
      void g(int y);
      void h(int y);
     TEST OK on ex-nested-with-id-collisions.c
     [imz на vb2 cuglify]$

Best regards.
Ivan


Подробная информация о списке рассылки Devel