Quand on regarde l’histoire des générations ARM utilisées par le Raspberry Pi, on est surpris de voir à quel point la puce était ancienne
Même en 2014, quand le Raspberry Pi B+ est sorti, le cœur ARM1176 utilisé datait de 2003, il avait donc déjà 11 ans
Il n’est donc pas étonnant que, lorsqu’on compile sur une autre plateforme comme un Raspberry Pi plus récent, il faille éventuellement préciser les flags d’architecture pour produire du code compatible
En revanche, si l’architecture correcte n’est pas sélectionnée par défaut même lorsqu’on compile sur le Raspberry Pi B+ lui-même, cela ressemble à une erreur de configuration dans les valeurs par défaut de la distribution
Dans mon souvenir, le Pi original utilisait des puces restantes destinées à des boîtiers TV
Pour ce genre de produit, pour des raisons de coût, on met rarement plus de puissance de calcul que nécessaire
À la base, c’était un produit conçu comme un ordinateur bon marché
En réalité, la compilation n’a pas été faite sur un B+
L’article dit : « j’ai pris un binaire depuis l’hôte de build, un Pi 4B bien plus rapide, et j’ai essayé de l’exécuter sur cette vieille machine, ce qui a provoqué une illegal instruction »
C’est un peu comme essayer d’exécuter sur un vieux PC avec Windows XP un .EXE compilé avec un MSVC récent sous Windows 11
Il est même assez probable que toute la distribution Pi qui tourne sur le Pi 4B ne fonctionne pas sur le B+, et que le noyau ait été compilé de la même manière
Il me semble que le billet ou l’article ne le traite pas clairement, mais est-ce que ce n’est pas un bug ?
En cherchant dans les bugs LLVM, j’ai trouvé une entrée qui ressemblait beaucoup au même problème, mais elle datait de 2012 et était fermée. À lire les derniers commentaires, on pouvait avoir l’impression que ce n’était peut-être pas vraiment corrigé, mais je n’ai fait que survoler, donc j’ai peut-être mal compris https://github.com/llvm/llvm-project/issues/13989
En relisant, l’article dit à la fin que si l’on passe explicitement la cible, on obtient un programme qui fonctionne. Dans ce cas, ça ressemble à une sorte de bug de configuration ; sous Unix, j’aurais tendance à penser que la cible par défaut est le processeur courant, mais je n’en suis pas sûr
Le bug que j’ai lié semblait être un problème où du code incorrect était généré même lorsque la cible était correctement définie ; heureusement, cela ne semble plus être le cas aujourd’hui
Oui. Le bug lié concernait un compilateur à qui l’on demandait de cibler armv6, mais qui émettait quand même des instructions armv7
Le problème de Rachel a été résolu en indiquant au compilateur de cibler armv6 ; ce bug a donc déjà été corrigé et semble distinct de ce problème
C’est évidemment un bug, mais l’auteur semble avoir préféré écrire un billet de blog au titre un peu accrocheur et conclure par « c’est trop bizarre » plutôt que de le signaler
ClickHouse, la base de données sur laquelle je travaille, fait pas mal d’efforts pour rester compatible avec du matériel très ancien
Le binaire ARM standard exige Armv8.2, datant de 2016, et peut être utilisé à partir du Raspberry Pi 2. Le binaire x86 tourne sur du matériel d’environ 2010 disposant de SSE4.2 et des instructions pclmul* pour le CRC rapide
Nous construisons aussi des binaires pour les systèmes n’ayant que Armv8.0 et SSE2, mais ils ne sont pas testés en CI. Le script d’installation rapide télécharge et décompresse le binaire adapté à l’hôte cible
Je trouve qu’il est globalement difficile de trouver le bon équilibre entre la rétrocompatibilité et l’exploitation des fonctionnalités CPU des générations AArch64 récentes https://en.wikipedia.org/wiki/AArch64
Il y a eu un nombre étonnamment élevé d’institutions aux budgets serrés — par exemple des universités de pays émergents — ou d’utilisateurs amateurs qui n’ont pas les moyens de mettre leur matériel à niveau
Techniquement, ce qui a été assez pénible, c’est que les flags CPU de /proc/cpuinfo ne correspondent pas toujours aux flags -march= passés au compilateur. Par exemple, ils apparaissent différemment, comme "lrcpc" et "rcpc"
Pour que cela fonctionne correctement, il faut en pratique maintenir deux ensembles de flags
Dans ce genre de cas, je pense qu’il est préférable pour tout le monde de fournir plusieurs builds, afin que les clients puissent choisir celui qui correspond le mieux à leur architecture
Le problème semble très probablement être que, dans le paquet clang-13 actuel de bookworm, la cible configurée a changé
Plus précisément, avec bullseye et clang-11, la cible par défaut est armv6k-unknown-linux-gnueabihf, tandis qu’avec bookworm et clang-13, c’est arm-unknown-linux-gnueabihf
Ou alors il se peut que la valeur par défaut de cette configuration de build ait changé côté LLVM
J’aimerais mieux comprendre le processus de gestion des changements chez Debian. Je ne sais même pas vraiment si Raspbian est effectivement maintenu dans Debian
Cela dit, en comparant [1] et [2], on voit dans le fichier rules un test propre du type « si DEB_HOST_ARCH vaut armhf, définir LLVM_HOST_TRIPLE sur armv6k », ce qui semble confirmer un changement de configuration de build
[1] http://raspbian.raspberrypi.org/raspbian/pool/main/l/llvm-to...
[2] http://raspbian.raspberrypi.org/raspbian/pool/main/l/llvm-to...
Il ne s’agit probablement pas d’un changement intentionnel
Comme des commentaires voisins l’ont mentionné à propos de /etc/env.d/gcc, il est assez possible que cela vienne de la façon dont les informations sont lues depuis l’environnement
Le triplet par défaut devrait être quelque chose comme arm-unknown-linux si clang ne trouve pas, ou ne reçoit pas, d’informations plus précises, mais le mécanisme qui fournit une cible plus spécifique semble cassé
Cela peut vouloir dire qu’il n’y a pas de buildbot ARMv6, ou bien qu’il y en a un mais que, là-bas, la configuration implicite fonctionne encore correctement
LLVM est un très bon cross-compilateur. On peut compiler de n’importe quelle cible vers n’importe quelle autre sans gros problème
Clang est un peu moins séduisant sur ce point. S’il a été compilé avec la prise en charge de la cible et qu’on peut lui indiquer correctement pour quelle cible compiler, il fera probablement ce qu’il faut. Dans cet article aussi, la supposition était fausse, mais une fois des informations supplémentaires fournies, il a fonctionné correctement
La situation des bibliothèques d’exécution est encore pire. Même si l’on compile pour une cible comme armv4, il faut trouver la libc correspondante, entre autres, et il peut être nécessaire d’indiquer au compilateur l’emplacement de ces bibliothèques et en-têtes ; sur ce point, les détails restent encore flous
La plupart des distributions et des compilateurs ont, de fait, abandonné la prise en charge d’ARMv6 il y a quelques années
J’ai rencontré un problème similaire en compilant des binaires pour un ancien NAS Synology
Pourquoi Clang devrait-il lire des informations depuis /etc/env.d/gcc ?
clang/clang++ lisent les flags de cible et les profils dans /etc/env.d/gcc, et c’est au système d’exploitation de les maintenir correctement
Sur ce système d’exploitation, cette gestion ne semble pas avoir été faite correctement
Mon SBC Gentoo ARM basé sur une architecture armv4 plus ancienne continue de fonctionner correctement, même avec les dernières mises à jour de gcc/clang grep CTARGET /etc/env.d/gcc -r /etc/env.d/gcc/armv4tl-softfloat-linux-gnueabi-11.3.0:CTARGET="armv4tl-softfloat-linux-gnueabi"
/etc/env.d est un répertoire propre à Gentoo qui définit les variables d’environnement par défaut des sessions utilisateur
Clang n’a pas de fonctionnalité qui lit ce répertoire, il ne faut donc pas supposer qu’il existe aussi sur d’autres distributions
C’est simplement que la configuration des compilateurs de Gentoo lit la variable d’environnement CTARGET pour choisir la cible, et que Gentoo utilise /etc/env.d pour définir cette valeur
L’article ne précise pas s’il s’agit de Debian ou de Raspbian, ni, si c’est Debian, du port armel ou du port armhf
Sans cette information, discuter de l’ensemble d’instructions pour lequel LLVM compile n’a pas beaucoup de sens. Cela dépend de la configuration de cible native de LLVM
Pour référence, llvm-toolchaim-snapshot de Debian prend toujours en charge armel, qui utilise encore ARMv5T comme base. En revanche, il y a actuellement un bug distinct dans la bibliothèque OpenMP de LLVM, ce qui empêche la compilation de réussir
Ce qui est étrange, c’est que le binaire Clang lui-même est compilé avec un jeu d’instructions compatible avec le Pi B+, mais qu’il ne cible pas, lui, un jeu d’instructions compatible avec le Pi B+
C’est vraiment bizarre. Comme il n’est pas question de l’utiliser comme cross-compilateur, en théorie, l’hôte et la cible devraient être identiques
L’image est probablement Raspbian. Je ne vois pas de raison de ne pas le supposer
La sortie de la commande dpkg-architecture et le contenu du fichier /etc/os-release seraient utiles
Sans cela, il est difficile de faire un commentaire utile
Le titre est malheureusement sensationnaliste
Il s’agit d’un changement de cible par défaut, et clang peut toujours compiler des binaires pour le Pi B+
Il suffit de préciser explicitement l’architecture. Il vaudrait donc mieux modifier légèrement le titre pour faire apparaître plus clairement qu’il s’agit d’un changement de configuration par défaut
Si, même en compilant sur la machine cible elle-même, on n’arrive pas à produire un binaire pour cette machine cible, cela ne me paraît pas si sensationnaliste que ça
Je trouve intéressant que, pour déboguer pourquoi un programme ne s’exécute pas sur ARM, on puisse apparemment procéder à peu près de cette manière
J’ai un build Unity Linux qui ne s’exécute pas dans un conteneur ; même en passant le flag amd64 à l’exécution de Docker, Unity mono tente d’utiliser un appel système indisponible
J’ai trouvé un contournement et je n’ai donc pas encore débogué. J’ai activé le mode développement et modifié les paramètres de build pour ne plus utiliser mono
Un jour, il faudra que je m’y replonge pour en apprendre davantage
1 commentaires
Avis sur Hacker News
Quand on regarde l’histoire des générations ARM utilisées par le Raspberry Pi, on est surpris de voir à quel point la puce était ancienne
Même en 2014, quand le Raspberry Pi B+ est sorti, le cœur ARM1176 utilisé datait de 2003, il avait donc déjà 11 ans
Il n’est donc pas étonnant que, lorsqu’on compile sur une autre plateforme comme un Raspberry Pi plus récent, il faille éventuellement préciser les flags d’architecture pour produire du code compatible
En revanche, si l’architecture correcte n’est pas sélectionnée par défaut même lorsqu’on compile sur le Raspberry Pi B+ lui-même, cela ressemble à une erreur de configuration dans les valeurs par défaut de la distribution
Pour ce genre de produit, pour des raisons de coût, on met rarement plus de puissance de calcul que nécessaire
L’article dit : « j’ai pris un binaire depuis l’hôte de build, un Pi 4B bien plus rapide, et j’ai essayé de l’exécuter sur cette vieille machine, ce qui a provoqué une illegal instruction »
C’est un peu comme essayer d’exécuter sur un vieux PC avec Windows XP un .EXE compilé avec un MSVC récent sous Windows 11
Il est même assez probable que toute la distribution Pi qui tourne sur le Pi 4B ne fonctionne pas sur le B+, et que le noyau ait été compilé de la même manière
Il me semble que le billet ou l’article ne le traite pas clairement, mais est-ce que ce n’est pas un bug ?
En cherchant dans les bugs LLVM, j’ai trouvé une entrée qui ressemblait beaucoup au même problème, mais elle datait de 2012 et était fermée. À lire les derniers commentaires, on pouvait avoir l’impression que ce n’était peut-être pas vraiment corrigé, mais je n’ai fait que survoler, donc j’ai peut-être mal compris
https://github.com/llvm/llvm-project/issues/13989
En relisant, l’article dit à la fin que si l’on passe explicitement la cible, on obtient un programme qui fonctionne. Dans ce cas, ça ressemble à une sorte de bug de configuration ; sous Unix, j’aurais tendance à penser que la cible par défaut est le processeur courant, mais je n’en suis pas sûr
Le bug que j’ai lié semblait être un problème où du code incorrect était généré même lorsque la cible était correctement définie ; heureusement, cela ne semble plus être le cas aujourd’hui
Le problème de Rachel a été résolu en indiquant au compilateur de cibler armv6 ; ce bug a donc déjà été corrigé et semble distinct de ce problème
ClickHouse, la base de données sur laquelle je travaille, fait pas mal d’efforts pour rester compatible avec du matériel très ancien
Le binaire ARM standard exige Armv8.2, datant de 2016, et peut être utilisé à partir du Raspberry Pi 2. Le binaire x86 tourne sur du matériel d’environ 2010 disposant de SSE4.2 et des instructions pclmul* pour le CRC rapide
Nous construisons aussi des binaires pour les systèmes n’ayant que Armv8.0 et SSE2, mais ils ne sont pas testés en CI. Le script d’installation rapide télécharge et décompresse le binaire adapté à l’hôte cible
Je trouve qu’il est globalement difficile de trouver le bon équilibre entre la rétrocompatibilité et l’exploitation des fonctionnalités CPU des générations AArch64 récentes
https://en.wikipedia.org/wiki/AArch64
Il y a eu un nombre étonnamment élevé d’institutions aux budgets serrés — par exemple des universités de pays émergents — ou d’utilisateurs amateurs qui n’ont pas les moyens de mettre leur matériel à niveau
Techniquement, ce qui a été assez pénible, c’est que les flags CPU de /proc/cpuinfo ne correspondent pas toujours aux flags -march= passés au compilateur. Par exemple, ils apparaissent différemment, comme "lrcpc" et "rcpc"
Pour que cela fonctionne correctement, il faut en pratique maintenir deux ensembles de flags
Le problème semble très probablement être que, dans le paquet clang-13 actuel de bookworm, la cible configurée a changé
Plus précisément, avec bullseye et clang-11, la cible par défaut est armv6k-unknown-linux-gnueabihf, tandis qu’avec bookworm et clang-13, c’est arm-unknown-linux-gnueabihf
Ou alors il se peut que la valeur par défaut de cette configuration de build ait changé côté LLVM
Cela dit, en comparant [1] et [2], on voit dans le fichier rules un test propre du type « si DEB_HOST_ARCH vaut armhf, définir LLVM_HOST_TRIPLE sur armv6k », ce qui semble confirmer un changement de configuration de build
[1] http://raspbian.raspberrypi.org/raspbian/pool/main/l/llvm-to...
[2] http://raspbian.raspberrypi.org/raspbian/pool/main/l/llvm-to...
Il ne s’agit probablement pas d’un changement intentionnel
Comme des commentaires voisins l’ont mentionné à propos de /etc/env.d/gcc, il est assez possible que cela vienne de la façon dont les informations sont lues depuis l’environnement
Le triplet par défaut devrait être quelque chose comme arm-unknown-linux si clang ne trouve pas, ou ne reçoit pas, d’informations plus précises, mais le mécanisme qui fournit une cible plus spécifique semble cassé
Cela peut vouloir dire qu’il n’y a pas de buildbot ARMv6, ou bien qu’il y en a un mais que, là-bas, la configuration implicite fonctionne encore correctement
LLVM est un très bon cross-compilateur. On peut compiler de n’importe quelle cible vers n’importe quelle autre sans gros problème
Clang est un peu moins séduisant sur ce point. S’il a été compilé avec la prise en charge de la cible et qu’on peut lui indiquer correctement pour quelle cible compiler, il fera probablement ce qu’il faut. Dans cet article aussi, la supposition était fausse, mais une fois des informations supplémentaires fournies, il a fonctionné correctement
La situation des bibliothèques d’exécution est encore pire. Même si l’on compile pour une cible comme armv4, il faut trouver la libc correspondante, entre autres, et il peut être nécessaire d’indiquer au compilateur l’emplacement de ces bibliothèques et en-têtes ; sur ce point, les détails restent encore flous
J’ai rencontré un problème similaire en compilant des binaires pour un ancien NAS Synology
clang/clang++ lisent les flags de cible et les profils dans /etc/env.d/gcc, et c’est au système d’exploitation de les maintenir correctement
Sur ce système d’exploitation, cette gestion ne semble pas avoir été faite correctement
Mon SBC Gentoo ARM basé sur une architecture armv4 plus ancienne continue de fonctionner correctement, même avec les dernières mises à jour de gcc/clang
grep CTARGET /etc/env.d/gcc -r/etc/env.d/gcc/armv4tl-softfloat-linux-gnueabi-11.3.0:CTARGET="armv4tl-softfloat-linux-gnueabi"Clang n’a pas de fonctionnalité qui lit ce répertoire, il ne faut donc pas supposer qu’il existe aussi sur d’autres distributions
C’est simplement que la configuration des compilateurs de Gentoo lit la variable d’environnement CTARGET pour choisir la cible, et que Gentoo utilise /etc/env.d pour définir cette valeur
L’article ne précise pas s’il s’agit de Debian ou de Raspbian, ni, si c’est Debian, du port armel ou du port armhf
Sans cette information, discuter de l’ensemble d’instructions pour lequel LLVM compile n’a pas beaucoup de sens. Cela dépend de la configuration de cible native de LLVM
Pour référence, llvm-toolchaim-snapshot de Debian prend toujours en charge armel, qui utilise encore ARMv5T comme base. En revanche, il y a actuellement un bug distinct dans la bibliothèque OpenMP de LLVM, ce qui empêche la compilation de réussir
C’est vraiment bizarre. Comme il n’est pas question de l’utiliser comme cross-compilateur, en théorie, l’hôte et la cible devraient être identiques
L’image est probablement Raspbian. Je ne vois pas de raison de ne pas le supposer
La sortie de la commande
dpkg-architectureet le contenu du fichier/etc/os-releaseseraient utilesSans cela, il est difficile de faire un commentaire utile
Le titre est malheureusement sensationnaliste
Il s’agit d’un changement de cible par défaut, et clang peut toujours compiler des binaires pour le Pi B+
Il suffit de préciser explicitement l’architecture. Il vaudrait donc mieux modifier légèrement le titre pour faire apparaître plus clairement qu’il s’agit d’un changement de configuration par défaut
Je trouve intéressant que, pour déboguer pourquoi un programme ne s’exécute pas sur ARM, on puisse apparemment procéder à peu près de cette manière
J’ai un build Unity Linux qui ne s’exécute pas dans un conteneur ; même en passant le flag amd64 à l’exécution de Docker, Unity mono tente d’utiliser un appel système indisponible
J’ai trouvé un contournement et je n’ai donc pas encore débogué. J’ai activé le mode développement et modifié les paramètres de build pour ne plus utiliser mono
Un jour, il faudra que je m’y replonge pour en apprendre davantage