2016-02-04 1 views
2

Est-il possible de réécrire ou d'améliorer cette fonction pour ne pas avoir besoin de volatile ou d'un fragment de mémoire générique dans son assemblage en ligne?Réécrire l'assemblage en ligne GCC pour qu'il ne nécessite pas de volatile ou de fragmentation de mémoire

// do stuff with the input Foo structure and write the result to the 
// output Bar structure. 
static inline void MemFrob(const struct Foo* input, struct Bar* output) { 
    register const Foo* r0 asm("r0") = input; 
    register Bar* r1 asm("r1") = output; 

    __asm__ __volatile__(
     "svC#0x0f0000 \n\t" 
     : "+r" (r0), "+r" (r1) 
     : 
     : "r2", "r3", "cc", "memory" 
     ); 
} 

Pour cette situation particulière, la plate-forme cible est un système ARM7 et le code est compilé avec GCC 5.3.0. L'appel système en cours a la même convention d'appel qu'un appel de fonction C. Après quelques essais et erreurs, je suis arrivé à ce qui précède "qui fonctionne" mais je ne suis pas encore convaincu qu'il est correct et fonctionnera toujours, sous réserve des caprices et des fantaisies du compilateur d'optimisation. Je souhaite pouvoir supprimer le clobber "memory" et dire à GCC quelle mémoire sera modifiée, mais la documentation GCC Extended Asm explique comment affecter des valeurs à des registres spécifiques, puis à des contraintes de mémoire, mais pas si les deux peuvent être combinés. À partir de maintenant, en supprimant le clobber "mémoire" de l'exemple ci-dessus, GCC ne peut pas utiliser la sortie dans le code précédent.

Je voudrais aussi pouvoir supprimer le volatile dans les cas où la sortie n'est pas utilisée. Mais à partir de maintenant, en supprimant volatile de l'exemple ci-dessus, GCC n'émettra pas du tout l'assembly. Ajouter un assemblage en ligne supplémentaire pour déplacer manuellement les paramètres d'appel système dans r0/r1 ou les désincruster en déplaçant le code vers une unité de compilation externe sont des solutions de contournement inutiles que je préfère éviter.

+0

1) Oui, vous pouvez avoir 1 contrainte soit un registre et une autre soit une mémoire, et oui, deux contraintes peuvent se chevaucher. 2) Sans le volatile, gcc voit l'asm comme le moyen de calculer les nouvelles valeurs de 'r0' et' r1'. Mais comme les optimiseurs peuvent voir que les variables 'r0' et' r1' ne sont plus utilisées après l'asm avant qu'elles ne soient hors de portée, l'asm est rejeté. –

Répondre

2

Petite histoire courte: c'est ce que la contrainte "m" est pour. Habituellement, si vous utilisez volatile ou __volatile__, avec asm, c'est parce qu'il y a une erreur dans votre code. L'un des principaux travaux du compilateur est l'analyse de flux, aussi longtemps que vous lui donnez suffisamment d'informations pour effectuer l'analyse de flux correcte, tout fonctionne correctement.

Voici une version fixe:

void MemFrob(const struct Foo* input, struct Bar* output) { 
    register const Foo* r0 asm("r0") = input; 
    register Bar* r1 asm("r1") = output; 
    __asm__ (
     "svC#0x0f0000" 
     : "=m"(*r1) // writes data to *output (but does not read) 
     : "m"(*r0), // reads data in *input 
      "l"(r0), "l"(r1) // This is necessary to ensure correct register 
     : "r2", "r3", "cc" 
     ); 
} 

Vous pouvez le tester sur https://gcc.godbolt.org/ (-O2 options du compilateur recommandé). La sortie est la suivante:

svC#0x0f0000 
bx lr 

De toute évidence, lorsque inline, il devrait être réduit à une seule instruction.

Malheureusement, je ne pouvais pas comprendre comment spécifier des registres spécifiques lors de l'utilisation de l'assemblage ARM en ligne, autre que la méthode ci-dessus qui est un peu maladroite.