17 votes

Folie du préprocesseur (stringification d'un #include)

Remarque : cette question n'a rien à voir avec OpenCL en soi... consultez le dernier paragraphe pour un exposé succinct de ma question. Mais pour fournir un peu de contexte :

Je suis en train d'écrire un code C++ qui utilise OpenCL. J'aime garder les sources de mes noyaux OpenCL dans leurs propres fichiers, afin de faciliter le codage et la maintenance (par opposition à l'intégration des sources directement en tant que constantes de chaîne dans le code C++ associé). Cela conduit inévitablement à la question de savoir comment les charger dans le runtime OpenCL une fois que le moment est venu de distribuer les binaires---idéalement, la source OpenCL est incluse dans le binaire, de sorte que le binaire n'a pas besoin d'être à un endroit spécifique dans une structure de répertoire pour savoir où se trouve le code source OpenCL.

J'aimerais inclure les fichiers OpenCL en tant que constantes de chaîne quelque part, et de préférence sans utiliser d'étapes de compilation supplémentaires ou d'outils externes (pour une facilité d'utilisation entre compilateurs et plates-formes... c'est-à-dire, pas de xxd et autres). J'ai pensé être tombé sur une technique basée sur la deuxième réponse dans ce fil, comme ça :

#define STRINGIFY(src) #src

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

Notez que je préférerais ne pas intégrer l'élément STRINGIFY dans mon code OpenCL si cela est possible (comme cela a été fait dans la question SO référencée ci-dessus). Maintenant, cela fonctionne à merveille avec le compilateur Clang/LLVM, mais GCC meurt d'une mort horrible ("Unterminated argument list invoking macro STRINGIFY" et diverses "erreurs" de syntaxe liées au contenu des fichiers .cl apparaissent). Donc, il est clair que cette technique exacte n'est pas utilisable par tous les compilateurs (je n'ai pas essayé MSVC, mais j'aimerais qu'elle fonctionne là aussi)... Comment pourrais-je la masser un minimum pour qu'elle fonctionne sur tous les compilateurs ?

En résumé, j'aimerais disposer d'une technique conforme aux normes pour inclure le contenu d'un fichier en tant que constante de chaîne C/C++ sans faire appel à des outils externes ou polluer les fichiers avec du code étranger. Des idées ?

EDITAR : Comme Potatoswatter l'a fait remarquer, le comportement de ce qui précède n'est pas défini, donc une technique de préprocesseur vraiment inter-compilateur qui n'implique pas de toucher aux fichiers-à-stringifier n'est probablement pas possible (la première personne à comprendre un hack odieux qui fait fonctionne pour la plupart/toutes les compilations obtient les points de réponse). Pour les curieux, j'ai fini par faire ce qui était suggéré dans la deuxième réponse ici ... c'est-à-dire que j'ai ajouté le STRINGIFY directement aux fichiers OpenCL que j'incluais :

En somefile.cl :

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

En somefile.cpp :

#define STRINGIFY(src) #src

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

Cela fonctionne dans les compilateurs dans lesquels je l'ai essayé (Clang et GCC également, puisqu'il n'y a pas de directives de préprocesseur à l'intérieur de la macro), et n'est pas une charge trop importante, du moins dans mon contexte (c'est-à-dire que cela n'interfère pas avec la coloration syntaxique/l'édition des fichiers OpenCL). Une caractéristique des approches de préprocesseur comme celle-ci est que, puisque les chaînes adjacentes sont concaténées, vous pouvez écrire

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

et tant que la macro STRINGIFY est dans les deux .cl les chaînes sont concaténées, ce qui vous permet de modulariser votre code OpenCL.

4voto

Potatoswatter Points 70305

La partie la plus pertinente de la norme est le §16.3/10 :

La séquence d'éléments de prétraitement délimitée par les parenthèses correspondantes les plus extérieures forme la liste des arguments de la macro de type fonction. Les arguments individuels dans la liste sont séparés par des jetons de prétraitement de type virgule, mais les jetons de prétraitement de type virgule entre les parenthèses intérieures correspondantes ne séparent pas les arguments. Si (avant la substitution d'argument) un argument quelconque ne consiste en aucun jeton de prétraitement, le comportement est indéfini. S'il y a des séquences de marqueurs de prétraitement dans la liste des arguments qui agiraient autrement comme des directives de prétraitement, le comportement est indéfini.

Extraire les points clés :

  • Vous devez placer les fichiers d'en-tête entre parenthèses pour que la macro ne pense pas que chaque virgule du fichier introduit un autre argument. Ces parenthèses seront également transformées en chaînes de caractères, mais il ne devrait pas être difficile de les contourner.
  • Poniendo #include dans une liste d'arguments est officiellement un comportement non défini, donc cela va être impossible à porter. Le compilateur ne sait officiellement pas si vous voulez que la chaîne de caractères résultante soit "#include \"kernels/util.cl\"" .

1voto

Yann Vernier Points 3170

La technique classique consiste à utiliser un programme comme bin2c, généralement écrit à la hâte. Une autre méthode consiste à utiliser objcopy de 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

Les drapeaux -O et -B doivent correspondre à la sortie d'objdump pour l'un de vos fichiers objets compilés, afin de satisfaire l'éditeur de liens, tandis que le renommage des sections est juste pour informer l'éditeur de liens d'exécution que ces données sont en lecture seule. Notez les symboles, qui correspondent à l'adresse de début, l'adresse de fin et la taille des données. Ils comptent tous comme des adresses, donc en C vous les utiliseriez avec quelque chose comme :

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);

Je me rends compte que ni l'un ni l'autre n'est le préprocesseur que vous demandez, mais il n'a tout simplement pas été conçu pour cela. Néanmoins, je serais intéressé de savoir s'il le peut.

0voto

Nicol Bolas Points 133791

Vous ne pouvez pas le faire de cette façon ; je suis surpris que cela ait fonctionné dans clang. Si vous voulez inclure des fichiers texte directement dans votre binaire, vous avez quelques options :

1 : Un préprocesseur qui s'exécute avant la compilation et qui convertit vos fichiers .cl en un fichier .cpp qui définit la chaîne.

2 : Stocker les données de la chaîne via un stockage spécifique au compilateur dans votre exécutable compilé. C'est ainsi que des outils comme Visual Studio incluent des fichiers .rc, .ico et d'autres fichiers utilisés pour créer des applications Windows. Bien sûr, comme indiqué, ces fichiers sont spécifiques au compilateur.

Le moyen le plus sûr est l'option 1.

-2voto

Photovore Points 71

Voici ce que je fais en 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);

... ce n'est pas du voodoo de préprocesseur, mais cela fonctionne pour moi, je peux voir la syntaxe mise en évidence dans mon fichier .cl, et je peux même copier mon .cl dans un .c, changer un #define, et il s'exécute comme xcode C.....

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X