2016-06-28 2 views
1

Cette question est une question de suivi sur Can the C compiler optimizer violate short-circuiting and reorder memory accesses for operands in a logical-AND expression?.Comment une erreur spéculative due à l'optimisation du compilateur est-elle implémentée sous le capot?

Considérons le code suivant.

if (*p && *q) { 
    /* do something */ 
} 

Maintenant que par la discussion à Can the C compiler optimizer violate short-circuiting and reorder memory accesses for operands in a logical-AND expression? (en particulier David commentaire Schwartz et réponse), il est possible pour l'optimiseur d'un compilateur standard conforme C pour émettre des instructions CPU qui accède à *q avant *p tout en conservant le comportement observable du point de séquence établi avec l'opérateur &&.

Par conséquent, bien que l'optimiseur peut émettre le code qui accède *q avant *p, il doit encore faire en sorte que tous les effets secondaires des *q (comme erreur de segmentation) est observable uniquement si *p est non nul. Si *p est zéro, alors une erreur due à *q ne devrait pas être observable, c'est-à-dire qu'une erreur spéculative se produirait d'abord parce que *q était exécutée en premier sur la CPU mais la faute spéculative serait ignorée une fois que *p serait exécutée et trouvée 0.

Ma question: Comment cette faille spéculative est-elle mise en œuvre sous le capot?

J'apprécierais que vous puissiez jeter plus de lumière sur les points suivants en répondant à cette question.

  1. Pour autant que je sache, lorsque le CPU détecte un défaut, il génère un piège, que le noyau doit gérer (soit prendre des mesures de recouvrement telles que swap de page ou signaler la faute comme SIGSEGV au processus) . Ai-je raison?
  2. Donc si le compilateur doit émettre du code pour effectuer un défaut spéculatif, il me semble que le noyau et le compilateur (et peut-être aussi le CPU) doivent tous coopérer les uns avec les autres pour implémenter un défaut spéculatif. Comment le compilateur émet-il des instructions qui indiqueraient au noyau ou au CPU qu'une faute générée par le code devrait être considérée comme spéculative?
+1

Si on accède * inconditionnellement à * q avant ou après le 'if (* p && * q)', ​​le compilateur peut être capable de conclure que l'accès ne peut pas être défectueux dans un programme conforme, et ainsi pouvoir réorganiser les accès. – EOF

Répondre

3

Il est implémenté dans le cadre du processus normal de récupération spéculative. Le résultat d'un fetch spéculatif, qu'il s'agisse d'un résultat numérique ou d'un défaut, est spéculatif. Il est utilisé si, et seulement si, il est nécessaire plus tard.

Pour autant que je sache, lorsque le CPU détecte un défaut, il génère un piège, que le noyau doit gérer (soit prendre des mesures de recouvrement telles que swap de page ou signaler la faute comme SIGSEGV au processus) . Ai-je raison?

Le résultat de l'exécution non-spéculative d'une extraction produisant un défaut est une interruption. Le résultat de l'exécution d'une extraction produit une erreur spéculative est un piège spéculatif qui ne se produira réellement que si le résultat de l'extraction spéculative est utilisé. Si vous y pensez, les extractions spéculatives seraient impossibles sans ce mécanisme.

Donc, si le compilateur doit émettre un code pour effectuer la faute spéculative, il me semble que le noyau et le compilateur (et peut-être la CPU aussi) doivent tous coopérer entre eux pour mettre en œuvre faute spéculative. Comment le compilateur émet-il des instructions qui indiqueraient au noyau ou au CPU qu'une faute générée par le code devrait être considérée comme spéculative?

Le compilateur qu'il fait en plaçant le chercher pour *q après un test sur le résultat de *p. Cela signale au CPU que le fetch est spéculatif et qu'il ne peut utiliser les résultats que lorsque le résultat du test sur le résultat de *p est connu.

Le processeur peut et effectue l'extraction de *q avant de savoir s'il en a besoin ou non. Ceci est presque essentiel car un fetch peut nécessiter des opérations inter-core qui sont lentes - vous ne voudriez pas attendre plus longtemps que nécessaire. Les processeurs multi-cœurs modernes mettent donc en œuvre une extraction spéculative agressive.

C'est ce que font les processeurs modernes. (La réponse pour les CPU avec des opérations d'extraction spéculative explicite est différente.)

+0

'Le compilateur le fait en plaçant le fetch pour * q après un test sur le résultat de * p.' - Mais la prémisse de ma question est un scénario où l'optimiseur du compilateur émet du code pour récupérer' * q' avant d'aller chercher '* p'. Je veux dire: Ma question ne concerne pas l'ordre d'exécution des instructions du CPU. Ma question est de savoir comment le compilateur maintient un comportement observable correct tout en réordonnant les instructions (en plaçant '* q' avant' * p'). –

+0

Votre prémisse est un non-sens. S'il y a une différence détectable, alors le compilateur est cassé. – gnasher729

+0

@LoneLearner Oui. Le compilateur émet un code pour récupérer '* q' avant' * p' en émettant les instructions de récupération dans l'autre ordre. Le compilateur conserve un comportement observable correct car le processeur implémente correctement la récupération spéculative. L'auteur du compilateur comprend comment le processeur fonctionne et émet des instructions qui font que le processeur fait ce qu'il fait quand il reçoit ces instructions. Ils ne sont pas en concurrence, ils coopèrent à 100%. –

2

En C et C++, vous avez la règle "as-if", ce qui signifie que le compilateur peut faire ce qu'il veut tant que le comportement observable est ce que la langue promet.

Si le compilateur génère du code pour un processeur ancien sans protection de la mémoire, où la lecture * q lira quelque chose (une valeur non spécifiée), sans effets secondaires, il est clair qu'il est autorisé à lire * q, et même échanger les ordre des tests. Tout comme tout compilateur peut permuter les opérandes dans (x> 0 || y> 0), à condition que y ait une valeur définie ou lire y avec une valeur indéfinie n'a pas d'effet secondaire.

Mais vous posez des questions sur l'exécution spéculative dans le processeur. Eh bien, les processeurs font exécuter des instructions après les branches conditionnelles avant de savoir si la branche conditionnelle a été prise ou non, mais ils sont sûrs à 100% que cela n'entraîne aucun effet secondaire visible visible. Il n'y a aucun code pour cela, tout est dans le CPU. Si l'exécution conditionnelle fait quelque chose qui devrait générer un piège, alors le CPU attend jusqu'à ce qu'il sache avec certitude si la branche a été prise ou non, puis il prend le piège ou pas. Votre code ne le voit pas, et même le système d'exploitation ne le voit pas.

+0

"* Il n'y a jamais de code pour cela, tout est dans la CPU *" C'est sans signification. Si du code fait que le CPU fait quelque chose, alors ce code est le code pour ce que le CPU a fait. –