Препроцессор дурачества (строковый #include) - PullRequest
16 голосов
/ 28 июня 2011

Примечание: этот вопрос не имеет ничего общего с OpenCL как таковым ... проверьте последний абзац для краткого изложения моего вопроса.Но, чтобы обеспечить некоторую предысторию:

Я пишу некоторый код C ++, который использует OpenCL.Мне нравится хранить исходные тексты для моих ядер OpenCL в их собственных файлах, чтобы облегчить кодирование и сопровождение (в отличие от непосредственного встраивания исходных текстов в виде строковых констант в связанный код C ++).Это неизбежно приводит к вопросу о том, как загрузить их в среду выполнения OpenCL, как только наступит время для распространения двоичных файлов - в идеале, источник OpenCL включен в двоичный файл, так что двоичный файл не должен находиться в определенном месте.в некоторой структуре каталогов, чтобы знать, где находится исходный код OpenCL.

Я хотел бы включить файлы OpenCL в качестве строковых констант где-нибудь, и желательно без использования дополнительных шагов сборки или внешних инструментов (для кросс-компилятора/ кроссплатформенность простота использования ... т.е. нет до xxd и тому подобное).Я думал, что наткнулся на технику, основанную на втором ответе в этом обсуждении, например:

#define STRINGIFY(src) #src

inline const char* Kernels() {
  static const char* kernels = STRINGIFY(
    #include "kernels/util.cl"
    #include "kernels/basic.cl"
  );
  return kernels;
}

Обратите внимание, что я предпочел бы не вставлять макрос STRINGIFYв моем коде OpenCL, если это вообще возможно (как это было сделано в вышеупомянутом вопросе SO).Теперь это прекрасно работает на компиляторе Clang / LLVM, но GCC умирает ужасной смертью (появляется «Неопределенный список аргументов, вызывающий макрос STRINGIFY» и различные синтаксические «ошибки», связанные с содержимым файлов .cl).Итак, очевидно, что этот точный метод не применим в компиляторах (я не пробовал MSVC, но я бы хотел, чтобы он там тоже работал) ... Как я мог бы минимально помассировать его, чтобы он работал в компиляторах?1013 * Подводя итог, хотелось бы, чтобы стандартная техника включала содержимое файла в виде строковой константы C / C ++ без вызова внешних инструментов или загрязнения файлов посторонним кодом.Идеи?

РЕДАКТИРОВАТЬ : Как указал Potatoswatter, поведение вышеописанного не определено, поэтому действительно метод препроцессора кросс-компиляции, который не включает в себя касание файлов, которые будутСтроковый код, вероятно, не возможен (первый человек, который выяснит отвратительный хак, который выполняет для большинства / всех компиляторов, получает очки ответа).Для любопытных я закончил делать то, что было предложено во втором ответе здесь ... то есть я добавил макрос STRINGIFY непосредственно в файлы OpenCL, которые я включал:

В somefile.cl:

STRINGIFY(
  ... // Lots of OpenCL code
)

В somefile.cpp:

#define STRINGIFY(src) #src

inline const char* Kernels() {
  static const char* kernels =
    #include "somefile.cl"
    ;
  return kernels;
}

Это работает в компиляторах, в которых я пробовал (Clang и GCC, так каку него нет директив препроцессора внутри макроса), и это не слишком большая нагрузка, по крайней мере, в моем контексте (т. е. это не мешает подсветке синтаксиса / редактированию файлов OpenCL).Одной из особенностей подходов препроцессора, подобных этому, является то, что, поскольку смежные строки объединяются, вы можете записать

inline const char* Kernels() {
  static const char* kernels =
    #include "utility_functions.cl"
    #include "somefile.cl"
    ;
  return kernels;
}

, и пока макрос STRINGIFY находится в обоих файлах .cl, строки объединяются, что позволяетвам, чтобы модулировать свой код OpenCL.

Ответы [ 4 ]

4 голосов
/ 28 июня 2011

Наиболее важной частью Стандарта является §16.3 / 10:

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

Извлечение ключевых точек:

  • Вам нужнозаключить заголовочные файлы в пару круглых скобок, чтобы макрос не думал, что каждый символ запятой в файле вводит другой аргумент.Эти круглые скобки также будут заштрихованы, но их не сложно обойти.
  • Помещение #include в список аргументов вообще является официально неопределенным поведением, так что это будет непереносимым.Официально компилятор не знает, хотите ли вы, чтобы результирующая строка была "#include \"kernels/util.cl\"".
1 голос
/ 28 июня 2011

Обычная техника использует программу наподобие bin2c, обычно написанную на скорую руку.Другой метод использует objcopy из GNU binutils:

$ objcopy -I binary extensions.cfg -O elf32-little -B i386 --rename-section .data=.rodata extensions.o
$ objdump -x extensions.o

extensions.o:     file format elf32-i386
extensions.o
architecture: i386, flags 0x00000010:
HAS_SYMS
start address 0x00000000

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .rodata       00000447  00000000  00000000  00000034  2**0
                  CONTENTS, ALLOC, LOAD, DATA
SYMBOL TABLE:
00000000 l    d  .rodata        00000000 .rodata
00000000 g       .rodata        00000000 _binary_extensions_cfg_start
00000447 g       .rodata        00000000 _binary_extensions_cfg_end
00000447 g       *ABS*  00000000 _binary_extensions_cfg_size

Флаги -O и -B должны соответствовать выводу objdump для одного из ваших скомпилированных объектных файлов, чтобы удовлетворить компоновщик, тогда как переименование раздела просточтобы сообщить компоновщику времени выполнения, эти данные доступны только для чтения.Обратите внимание на символы, отображающие начальный адрес, конечный адрес и размер данных.Каждый из них считается как адрес, поэтому в C вы могли бы использовать их с чем-то вроде:

extern const char _binary_extensions_cfg_start, _binary_extensions_cfg_end;
extern const char _binary_extensions_cfg_size;
for (const char *p=&_binary_extensions_cfg_start; p<&_binary_extensions_cfg_end; p++)
    do_something(p);
memcpy(somewhere, &_binary_extensions_cfg_start, (intptr_t)&_binary_extensions_cfg_size);

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

0 голосов
/ 22 июля 2011

Вот что я делаю в xcode C:

const char oursource[60000];
const char * oursourceptr = oursource;
const char * * oursourceptrptr = & oursourceptr;

// in function "readfileintostring":

char *fylnm = "/Developer/projs/myproj/mykernel.cl";

long enby; short pathref;

FSRef dink; FSPathMakeRef( (const UInt8 *) &fylnm, &dink, NULL );
SInt16 forkRefNum;  HFSUniStr255 dataForkName;  FSGetDataForkName(&dataForkName);
FSOpenFork( &dink, dataForkName.length, dataForkName.unicode, fsRdPerm, (FSIORefNum *) &pathref );

enby = 100000;  FSRead( pathref, &enby, (void *) oursourceptr );

// .. then later ..

program = clCreateProgramWithSource(context, 1, (const char **) oursourceptrptr, NULL, &err);

... это не препроцессор voodoo, но он работает для меня, я вижу выделенный синтаксис в моем файле .cl, и я даже могу скопировать свой .cl в .c, изменить один #define и запустить как xcode C ....

0 голосов
/ 28 июня 2011

Вы не можете сделать это таким образом; Я удивлен, что это сработало в Clang. Если вы хотите включить текстовые файлы непосредственно в ваш двоичный файл, у вас есть несколько вариантов:

1: препроцесс, выполняемый перед компиляцией, который преобразует ваши файлы .cl в файл .cpp, который определяет строку.

2: Хранение строковых данных через специфичное для компилятора хранилище в вашем скомпилированном исполняемом файле. Вот как такие инструменты, как Visual Studio, включают .rc, .ico и другие файлы, которые используются для создания приложений Windows. Конечно, как уже говорилось, это зависит от компилятора.

Самый безопасный способ - вариант 1.

...