Spitfire's Tears

Sunday, 17 August 2025
|
Écrit par
Grégory Soutadé

Groupe Spitfire's Tears

Fête de la musique 2024 à Mouans Sartoux. Une bien belle édition avec plein de bons groupes. Notamment un groupe de rock totalement déjanté. En fin de soirée, après s'être attardé sur quelques reprises de Green Day, il est temps de rentrer. C'est alors qu'en passant à côté de la scène métal (peu après le lavoir), je reste accroché. Sur les planches se produit une formation détonante. Le son est clairement dégueulasse, mais si l'on en fait abstraction, on capte des mélodies accrocheuses. Autre particularité que l'on découvre petit à petit : il y a 3 chanteurs (2 femmes et 1 homme), 2 guitaristes, 1 bassiste et un batteur. C'est donc une formation plutôt atypique (et complexe à gérer). À la fin du show, je note leur Instagram : @the_spitband. Dès le lendemain, je vais consulter la littérature à leur sujet. Selon les dates, il y a un doute sur l'usage du The, mais le groupe se nomme Spitfire's Tears. Et, bonne nouvelle, un album est en cours de préparation. Dès à présent, 3 titres sont disponibles sur SoundCloud.

Le Spitfire étant un avion de chasse mythique de la seconde guerre mondiale. De conception Britannique, il a été de toutes les batailles : guerre aérienne en Angleterre, bombardements sur la France et enfin appui des troupes au sol lors de la reconquête du territoire français. La classe Supermarine Spitfire (maintes fois modernisée) est restée en activité jusqu'en 1961 !

Rise (2025)

EP Rise

Il faudra pourtant attendre plus d'un an pour qu'il se matérialise avec la sortie fin juin 2025 de leur EP Rise. Entre temps, le groupe Mouansois a continué les concerts en PACA et s'est offert un passage en résidence d'artiste afin de travailler leur show et leur musique. L'EP est donc enregistré avec toutes ces nouvelles influences. Néanmoins, c'est une période charnière dans la vie de ces jeunes adultes. Le groupe perd ainsi une chanteuse (qui à priori était toujours présente lors de l'enregistrement).

Petite déception à la découverte de la playlist : il n'y a qu'un titre original. Seconde déception : le ré enregistrement étouffe parfois un peu trop le chant tandis que la basse est trop prononcée. Quoi qu'il en soit cet EP est composé de 4 titres pêchus, mélange de rock et de métal très rafraîchissant. Il attaque fort avec Keep Believin' à la fois mélodique et puissant. Les chanteurs montrent toute l'étendue de leur talent. Puis vient le titre phare du groupe Rather Be Dead Than Cool, dont on pourra retrouver une version acoustique ici de toute beauté. C'est la plus belle composition avec des cœurs puissants et un refrain qui tourne en boucle dans la tête. Obsessed est le petit nouveau. Il est le calme avant la tempête. Une belle balade rock. Dans sa nouvelle version The Last Call I Can Make a été complètement retravaillé par rapport au premier enregistrement, avec en prime une minute supplémentaire. Le titre est plus complexe et je préfère clairement cette version qui nous transporte littéralement dans un film d'horreur. Une très belle réalisation en somme.

Même si le contenu est un peu maigre pour le moment. Spitfire Tears est un groupe vraiment prometteur et j'espère qu'ils pourront prendre encore plus d'envergure dans les prochaines années !

Quinze ans !

Monday, 04 August 2025
|
Écrit par
Grégory Soutadé

Encore un pallier de franchi pour le blog ! Quinze années de présence continue en ligne, c'est une belle réussite. Pour fêter ça, j'ai sorti mon éditeur favoris et ajouté un mode sombre ! Qu'est ce qui me motive encore à tapoter sur le clavier après tout ce temps ? Je pourrais naturellement citer le goût d'écrire des articles ainsi que l'envie de mettre en lumière ce que l'on ne trouve que trop peu ailleurs (ou même pas du tout). D'ailleurs, si je devais définir mes sections préférées, je dirais que ce sont celles qui racontent des histoires, qui exposent des points de vues argumentées. Viens ensuite la section balade et, de manière générale, chaque article qui contient des photos jolies/artistiques. Côté statistiques, la réalité est plus mitigée : ce sont les articles techniques (surtout en Anglais) qui sont le plus consultés.

Comme pour beaucoup de créateurs, la fréquentation est source de motivation. Quand elle est importante, on se sent redevable moralement vis-à-vis d'un public d'inconnus, sans pour autant qu'il n'y ait eu ni contrat, ni transaction financière. Ce qui nous pousse à produire plus de contenu. Même si j'ai une toute petite base de lecteurs réguliers, je constate qu'il y a globalement moins de connexions sur le blog (le trafic est principalement dirigé vers la forge). De ce fait, j'avoue avoir écrit un peu moins d'articles cette saison, après 2 années plus intenses. Néanmoins, cette baisse ne démotive pas le marcheur/coureur qui est en moi : il faut continuer à avancer même si le chemin est difficile. Car je rédige avant tout pour moi mon plaisir. Et quel plaisir de pouvoir se re plonger dans mes anciennes publications lorsque je recherche une information. Je suis souvent étonné et ravi de re découvrir ces bouts de texte et d'images, un peu comme s'ils avaient été écrits par quelqu'un d'autre. C'est l'avantage du blog par rapport aux plateformes en flux continu : les articles sont référencés et facilement accessibles des années plus tard !

Concernant le bilan de l'année écoulée, et bien c'est malheureusement la guerre qui tient le haut de panier. Le petit poucet Ukrainien résiste encore et toujours à l'envahisseur, qui ne cesse de grignoter du territoire de toutes pars malgré le coût humain faramineux. Il n'est désormais plus rare d'entendre voler dans le ciel plusieurs centaines de drones chaque nuit (avec un pic de 700 le 8 juillet). De son côté, Kiev ne se démonte pas et réplique également avec des drones (des cibles sont régulièrement touchées sur tout le territoire russe) car c'est le meilleur moyen pour supporter son infériorité numérique. L'Oblast russe de Koursk a été totalement "libéré" (notamment grâce aux soldats Nord Coréens) et ne peut plus servir de monnaie d'échange. La russie s'est même payé le luxe d'ouvrir deux nouveaux fronts dans le Nord (Soumy) et l'Est du pays (Kharkiv). Heureusement, parmi toutes ces mauvaises nouvelles, le nœud stratégique que constitue Pokrovsk tient toujours. Longtemps chancelant, le soutien Américain semble avoir repris (pour combien de temps ?). Non loin de là, c'est Israël qui continu de frapper tous ses voisins, dans une guerre visant l'anéantissement du Hamas (et le maintien au pouvoir de l'actuel premier ministre), quelles que soient les pertes civiles. Il s'agit clairement de crimes de guerre, voire carrément de génocide si on ne regarde que la bande de Gaza. Après 1 an et demi de blocus total et une famine qui s'installe de plus en plus durement, les nations occidentales commencent à réagir, même si le soutien Américain reste entier.

Avec ce genre d'actualité, on ne parle plus beaucoup d'écologie. Pourtant, la terre souffre toujours autant et de manière très évidente : inondations en région Parisienne, vague de chaleur hors normes pour un moins de juin (36°C dans le nord de l'Europe), nouvelle vague prévue début août. Pour compléter un tableau déjà bien noir : Les cadeaux empoisonnés du président Macron se retournent désormais contre les Français. Il était fier de déclarer le quoi qu'il en coûte pendant la pandémie. Tout autant que de supprimer la taxe d'habitation (20 milliards d'euros par an sur les 44 qui manquent au budget actuel) afin d'augmenter le pouvoir d'achat. Résultat, même dans un contexte de taux d'intérêts historiquement bas, la dette a explosée depuis 2020 : quasiment 500 milliards d'euros supplémentaires, dépassant ainsi le seuil symbolique des 100% du PIB ! Mais comme il est tabou de prononcer le mot "impôt", histoire de ne pas faire fuir les électeurs, il envoie ses premiers ministres successifs se casser les dents lorsqu'ils doivent élaborer un budget censé redresser les finances du pays. Quelles sont les solutions proposées ? Détruire les services publics déjà à bout de souffle, supprimer des "niches" (économies de bout de chandelle). Pire encore, supprimer des jours fériés : autant dire annihiler la culture et la mémoire pour récolter quelques maigres cotisations extraordinaires ! Ils détruisent le pays sur le long terme, mais aucun d'entre eux n'a les courage de prendre les vrais décisions et d'augmenter les impôts pour réparer les mauvaises décisions qui ont été les leurs. Finalement, ces deux thèmes sont assez proches : on ne peut pas faire indéfiniment de la merde, pour notre plaisir immédiat, sans avoir à le payer un jour. Mais en tant qu'individu totalement irresponsable, on espère que ce sont les autres qui vont payer à notre place...

Dans un registre plus positif, j'ai pu profiter de quelques jours de congés pour développer un projet que j'avais en tête depuis un certains temps : Drycat. l'objectif est de rendre secret un petit texte ou un fichier. Le logiciel génère alors un certain nombre de clés (parties) qui seront distribuées à différentes personnes. Pour reconstituer le secret originel, il faudra assembler les clés distribuées (nombres défini à la génération). Je trouve le principe génial ! Il s'appuie sur une théorie mathématique ancienne définie par Adi Shamir qui consiste à reconstruire une courbe par interpolation. D'ailleurs, il existe déjà une bibliothèque Javascript qui gère le cœur du projet, mais je lui ai ajouté une interface graphique moderne, avec QRCode, envoie de mail, chiffrement des fichiers, intégration d'OpenPGP. Le tout s'exécutant (quasiment) entièrement dans le navigateur ! Cerise sur le gâteau : mon frère a mis sa patte en modifiant le CSS. Ce projet n'est pas encore très populaire, mais il me permet de partager avec mes proches mes mots de passe (et autres informations personnelles) dans le cas où je ne serai plus en mesure d'administrer ma vie en ligne. Quant à libgourou, il continue d'être utilisé plus largement, avec 2 contributions et quelques tickets clôturés. Il draine une grand partie du trafic.

Statistiques 2024/2025

Nombre d'articles publiés Visites Données envoyées Pages affichées Trafic par domaine Systèmes d'exploitation Type d'IP

Top 10 :

Un top 10 qui représente 60% du trafic total du blog. La moyenne quotidienne (tous sites confondus) s'établit à 50 visites/jour. Le nombre de connexions IPv6 est en progression (de l'ordre de 40% ces derniers mois).

CRC16-CCITT optimization for ARM Cortex M4

Sunday, 13 July 2025
|
Écrit par
Grégory Soutadé

(Ooops) I did it again ! After doing the CRC32 optimization, I tried the same for CRC16-CCITT. This one is harder (but not so hard) to optimize for a C compiler because in modern CPU we mainly have 32 bits/64 bits registers, but for CRC16, we have to play with 16 bits values, split into upper and lower parts which need shift + mask operations.

Whatever, context is the same : target is ARM Cortex M4, no data/instruction cache, SRAM memory and GCC 9. One interesting point is that this time, I didn't target armv6, but armv7 (we'll see why later). Figures are still impressive, with a gain between 50% and 60% !

  • -Os compilation : 21.7 milliseconds
  • -O2 compilation : 17.2 milliseconds
  • -Os + optimizations : 8.8 milliseconds

I used the same optimization tricks than CRC32 (see article) plus this ones :

Use specific instruction if you can

ARMv7 instruction set provides thumb2 instructions which contains bitfield extraction. This is really really (yes 2 times) nice ! instruction ubfx (and variants) allows to extract a specified range of bits from a register and thus avoid to do shift + mask (2 instructions).

Be careful on instruction size

Thumb2 is really nice because you can mix 16 bits and 32 bits instructions. But, in order to save space (thus speed), you have to carefully choose your instructions. The case here is :

lsrs r5, r5, #24 C equivalent r5 = r5 >> 24

and

ubfx r5, r5, #24, #8 C equivalent r5 = (r5 & 0xff000000) >> 24

They both do have the same result but the first one is an "old" instruction and can be encoded on 16 bits while the second is new and is encoded into 32 bits.

Don't take care on unused register part

At some point, I do a 32 bits xor operation which generate random values on bits 31..15. But we don't care because we have to focus on 16 bits lower part.

Here is the optimized function. Whole C file can be found here. Optimization is effective for 16 bytes blocks (aligned).

uint16_t crc16_ccitt_opt16(
        const unsigned char*     block,
        unsigned int            blockLength,
        uint16_t          crc)
{
    /* unsigned int i; */

    /* for(i=0U; i<blockLength; i++){ */
    /*     uint16_t tmp = (crc >> 8) ^ (uint16_t) block[i]; */
    /*     crc = ((uint16_t)(crc << 8U)) ^ crc16_ccitt_table[tmp]; */
    /* } */

/*
      r0 -> s
      r1 -> len
      r2 -> crc16val
      r3 -> crc16tab
      r4 -> curval[0]
      r5 -> (crc >> 8) ^ (uint16_t) block[i]
      r6 -> crc16_ccitt_table[(crc >> 8) ^ (uint16_t) block[i])
      r7 -> curval[1]
      r8 -> curval[2]
      r9 -> curval[3]
     */
    __asm__ volatile (
        "mov r0, %1\n"
        "mov r1, %2\n"
        "mov r2, %3\n"
        "mov r3, %4\n"

        "push {r7, r8, r9}\n"

        "crc16_opt16_loop:\n"
        "ldm r0!, {r4, r7, r8, r9}\n"

        // curval[0]
        "eor r5, r4, r2, lsr #8\n"
        "uxtb r5, r5\n"
        "ldrh r6, [r3, r5, lsl #1]\n"
        "eor r2, r6, r2, lsl #8\n"

        "eor r5, r4, r2\n"
        "ubfx r5, r5, #8, #8\n\n"
        "ldrh r6, [r3, r5, lsl #1]\n"
        "eor r2, r6, r2, lsl #8\n"

        "eor r5, r4, r2, lsl #8\n"
        "ubfx r5, r5, #16, #8\n\n"
        "ldrh r6, [r3, r5, lsl #1]\n"
        "eor r2, r6, r2, lsl #8\n"

        "eor r5, r4, r2, lsl #16\n"
        "lsrs r5, r5, #24\n\n"
        "ldrh r6, [r3, r5, lsl #1]\n"
        "eor r2, r6, r2, lsl #8\n"

        // curval[1]        
        "eor r5, r7, r2, lsr #8\n"
        "uxtb r5, r5\n"
        "ldrh r6, [r3, r5, lsl #1]\n"
        "eor r2, r6, r2, lsl #8\n"

        "eor r5, r7, r2\n"
        "ubfx r5, r5, #8, #8\n\n"
        "ldrh r6, [r3, r5, lsl #1]\n"
        "eor r2, r6, r2, lsl #8\n"

        "eor r5, r7, r2, lsl #8\n"
        "ubfx r5, r5, #16, #8\n\n"
        "ldrh r6, [r3, r5, lsl #1]\n"
        "eor r2, r6, r2, lsl #8\n"

        "eor r5, r7, r2, lsl #16\n" 
        "lsrs r5, r5, #24\n\n"
        "ldrh r6, [r3, r5, lsl #1]\n"
        "eor r2, r6, r2, lsl #8\n"

        // curval[2]        
        "eor r5, r8, r2, lsr #8\n"
        "uxtb r5, r5\n"
        "ldrh r6, [r3, r5, lsl #1]\n"
        "eor r2, r6, r2, lsl #8\n"

        "eor r5, r8, r2\n"
        "ubfx r5, r5, #8, #8\n\n"
        "ldrh r6, [r3, r5, lsl #1]\n"
        "eor r2, r6, r2, lsl #8\n"

        "eor r5, r8, r2, lsl #8\n"
        "ubfx r5, r5, #16, #8\n\n"
        "ldrh r6, [r3, r5, lsl #1]\n"
        "eor r2, r6, r2, lsl #8\n"

        "eor r5, r8, r2, lsl #16\n"
        "lsrs r5, r5, #24\n\n"
        "ldrh r6, [r3, r5, lsl #1]\n"
        "eor r2, r6, r2, lsl #8\n"

        // curval[3]        
        "eor r5, r9, r2, lsr #8\n"
        "uxtb r5, r5\n"
        "ldrh r6, [r3, r5, lsl #1]\n"
        "eor r2, r6, r2, lsl #8\n"

        "eor r5, r9, r2\n"
        "ubfx r5, r5, #8, #8\n\n"
        "ldrh r6, [r3, r5, lsl #1]\n"
        "eor r2, r6, r2, lsl #8\n"

        "eor r5, r9, r2, lsl #8\n"
        "ubfx r5, r5, #16, #8\n\n"
        "ldrh r6, [r3, r5, lsl #1]\n"
        "eor r2, r6, r2, lsl #8\n"

        "eor r5, r9, r2, lsl #16\n"
        "lsrs r5, r5, #24\n\n"
        "ldrh r6, [r3, r5, lsl #1]\n"

        // Last two lines inverted
        "subs r1, r1, #16\n"
        "eor r2, r6, r2, lsl #8\n"

        "bne crc16_opt16_loop\n"

        "pop {r7, r8, r9}\n"
        "strh r2, %0\n"
        : "=m" (crc)
        : "r" (block), "r" (blockLength), "r" (crc), "r" (crc16_ccitt_table)
          // Missing r7-r9, manually save it
        : "r0", "r1", "r2", "r3", "r4", "r5", "r6"
        );

    return crc;
}

We can see that computation is not the same for all parts of the 32 bits register while it was really symmetric in CRC32.

Code has to be compiled with minimum -O1 or -Os option

For comparison, the (quite good) code generated by GCC 12 with -Os, working on a single byte :

 594:   428b            cmp     r3, r1
 596:   d100            bne.n   59a <crc16_ccitt+0x12>
 598:   bd30            pop     {r4, r5, pc}
 59a:   f813 2b01       ldrb.w  r2, [r3], #1
 59e:   0204            lsls    r4, r0, #8
 5a0:   b2a4            uxth    r4, r4
 5a2:   ea82 2210       eor.w   r2, r2, r0, lsr #8
 5a6:   f835 2012       ldrh.w  r2, [r5, r2, lsl #1]
 5aa:   ea84 0002       eor.w   r0, r4, r2
 5ae:   e7f1            b.n     594 <crc16_ccitt+0xc>

It's clearly focus on Armv6 compatibility as it use masking operation + shift at lines 59e and 5a0.

Dorothy : the way

Saturday, 21 June 2025
|
Écrit par
Grégory Soutadé

 The way (2025) 

Trois ans après Gifts from the Holy Ghost, Dorothy sort son nouvel album The way. Signe que le groupe prend de l'ampleur, on pourra y trouver une collaboration avec Slash sur le titre Tombstone Town. Le style y est plus rock que sur les précédents opus, avec plus de solos, même s'il y a énormément d'arrangements qui viennent polluer l'ensemble (il faudrait carrément supprimer le synthé). Pourtant, on sent bien que le fond est vraiment intéressant. Il faut dire que le groupe a quasiment changé puisque peu de temps après la sortie de Gifts from the Holy Ghost, les deux guitaristes et le batteur ont été remplacés, de quoi largement influencer la composition musicale. Le seul survivant reste le bassiste, présent depuis le clash avec la formation originelle (2017).

Au hasard de l'écoute, il y a un titre qui nous éclate à la figure : MUD ! Poussé le volume à fond, on ressent toute la puissance de cette chanteuse exceptionnelle, qui se permet un growl de toute beauté !

Pour profiter pleinement de cet album, il faudrait assister à un de leurs concerts afin d'avoir une version plus dépouillée. Malheureusement, malgré de nombreuses dates prévues, il n'y a rien en dehors des États-Unis.

CRC32 optimization for ARM Cortex M4

Sunday, 15 June 2025
|
Écrit par
Grégory Soutadé

At work I played with an ultra low power SoC powered by a single core ARM Cortex M4. To check some data integrity, we have to use CRC32 but there is no hardware peripheral to speed up computation and ARMv6 doesn't have special instruction for this (it starts from Armv8.1). After some researches, I was surprised not to find any optimized implementation on Internet. So, I wrote it by myself and the result is quite impressive : my version is ~50% faster ! Some figures (on ~30KB of data) :

  • -Os compilation : 19.4 milliseconds
  • -02 compilation : 15.2 milliseconds
  • -0s + optimizations : 8.2 milliseconds

Here, we have to consider that Cortex M4 doesn't have Data nor Instruction cache and memory accesses are done on a SRAM. Compilation is done in thumb mode with GCC 9.

Original version is the one from Wang Yaofu licensed under Apache2. It's quite simple and very academic. C primitives doesn't allows to optimize this algorithm so much because CRC has to be processed byte by byte, so we have to do some assembly !

I used multiple optimization tricks :

Use all registers available

The idea is to play with registers from r0 to 10 and not be limited to r0-r5 as commonly used.

Unroll loop

Avoid to break CPU pipeline by doing some checks + jump. Code is bigger and repetitive, but faster. We may write macro to reduce source code, not my choice here.

Do memory burst instead of unitary access

Especially when there is no cache, memory burst accesses are really faster. Here we load 4*32 bits at a time and keep all data into registers. Bytes outside burst window are computed using non optimized version.

Use shifts and rotates within load and eor instructions

ARM instructions allows to shift registers values within load and eor (and some other instructions) without having to do it in a separate line.

Avoid pipeline register lock

When it's possible, we can invert assembly lines to avoid working on the same registers on consecutive instructions (and thus avoid to lock them).

Update condition flags in sub instruction

Use subs variant to update EQ flag and avoid to check it for 0 in a separate instruction.

Do aligned accesses

In the calling function there is some code to inject "s" as a 32 bits aligned pointer (extra bytes processed by standard code).

Here is the optimized function. Whole C file can be found here

/**
 * Optimized version of _update_crc32 for 16 bytes blocks
 */
static void _update_crc32_opt16(const unsigned char *s, unsigned int len)
{
    /* unsigned int i; */

    /* for (i = 0;  i < len;  i++) { */
    /*     crc32val = crc32_tab[(crc32val ^ s[i]) & 0xFF] ^ ((crc32val >> 8) & 0x00FFFFFF); */
    /* } */

    /*
      r0 -> s
      r1 -> len
      r2 -> crc32val
      r3 -> crc32tab
      r4 -> curval[0]
      r5 -> (crc32val ^ s[i]) & 0xFF
      r6 -> crc32_tab[(crc32val ^ s[i]) & 0xFF]
      r7 -> curval[1]
      r8 -> curval[2]
      r9 -> curval[3]
     */
    __asm__ volatile (
        "mov r0, %1\n"
        "mov r1, %2\n"
        "mov r2, %3\n"
        "mov r3, %4\n"

        "push {r7, r8, r9}\n"

        "crc32_opt16_loop:\n"
        "ldm r0!, {r4, r7, r8, r9}\n"

        // curval[0]
        "eor r5, r2, r4\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r4, ror #8\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r4, ror #16\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r4, ror #24\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        // curval[1]        
        "eor r5, r2, r7\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r7, ror #8\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r7, ror #16\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r7, ror #24\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        // curval[2]        
        "eor r5, r2, r8\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r8, ror #8\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r8, ror #16\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r8, ror #24\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        // curval[3]        
        "eor r5, r2, r9\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r9, ror #8\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r9, ror #16\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"
        "eor r2, r6, r2, lsr #8\n"

        "eor r5, r2, r9, ror #24\n"
        "uxtb r5, r5\n"
        "ldr r6, [r3, r5, lsl #2]\n"

        // Last two lines inverted
        "subs r1, r1, #16\n"
        "eor r2, r6, r2, lsr #8\n"

        "bne crc32_opt16_loop\n"

        "pop {r7, r8, r9}\n"
        "str r2, %0\n"
        : "=m" (crc32val)
        : "r" (s), "r" (len), "r" (crc32val), "r" (crc32_tab)
          // Missing r7-r9, manually save it
        : "r0", "r1", "r2", "r3", "r4", "r5", "r6"
        );
}

Code has to be compiled with minimum -O1 or -Os option

For comparison, the (quite good) code generated by GCC 12 with -Os, working on a single byte :

 570:   4288            cmp     r0, r1
 572:   d100            bne.n   576 <_update_crc32+0x12>
 574:   bd30            pop     {r4, r5, pc}
 576:   6814            ldr     r4, [r2, #0]
 578:   f810 3b01       ldrb.w  r3, [r0], #1
 57c:   4063            eors    r3, r4
 57e:   b2db            uxtb    r3, r3
 580:   f855 3023       ldr.w   r3, [r5, r3, lsl #2]
 584:   ea83 2314       eor.w   r3, r3, r4, lsr #8
 588:   6013            str     r3, [r2, #0]
 58a:   e7f1            b.n     570 <_update_crc32+0xc>

Not so far from mine, but main weakness is result write at each loop, which is really time consuming.

Dernier gif les joies du code Quand le retour de ma fonction n'a absolument rien à voir avec ce que j'attendais