Aller au contenu principal
BlockchainMar 28, 2026

Deep EVM #7 : Boucles et conditionnels efficaces en gas dans Yul

OS
Open Soft Team

Engineering Team

Les boucles en Yul

Yul offre un seul type de boucle : for. Mais cette boucle est suffisamment flexible pour exprimer des patterns while, do-while et de parcours de tableaux.

Syntaxe de base

for { let i := 0 } lt(i, 10) { i := add(i, 1) } {
    // Corps de la boucle
}

La structure est for { init } condition { post } { body }. Chaque partie compile directement en opcodes JUMP/JUMPI — il n’y a pas d’abstraction intermédiaire.

Boucle while

for { } condition { } {
    // Corps — exécuté tant que condition est vraie
}

Incrémentation en pré vs post

En Yul, l’incrément est toujours explicite avec add. Il n’y a pas de i++ — vous écrivez i := add(i, 1). Cela coûte exactement PUSH1 + ADD + SWAP + POP = 12 gas par itération.

Comparaison avec Solidity (mode unchecked) :

// Solidity unchecked — compile en un code similaire
unchecked {
    for (uint256 i; i < 10; ++i) { ... }
}

Le Yul produit un bytecode presque identique au unchecked Solidity. L’avantage de Yul est le contrôle total : vous pouvez structurer la boucle pour minimiser les opérations de pile.

Les conditionnels en Yul

if

Yul n’a pas de else. Si vous avez besoin de branches multiples, utilisez switch ou enchaînez des if :

if iszero(value) {
    revert(0, 0)
}

switch

switch selector
case 0x70a08231 {
    // balanceOf
}
case 0xa9059cbb {
    // transfer
}
default {
    revert(0, 0)
}

Le switch en Yul compile en une chaîne d’EQ + JUMPI — similaire au dispatcher if-else de Solidity. Ce n’est pas une table de saut O(1) ; c’est du O(N) dans le pire cas.

Optimisations pratiques

Déroulage de boucle

Pour les boucles courtes avec un nombre fixe d’itérations, déroulez manuellement :

// Au lieu de boucler 4 fois :
mstore(add(ptr, 0x00), val0)
mstore(add(ptr, 0x20), val1)
mstore(add(ptr, 0x40), val2)
mstore(add(ptr, 0x60), val3)

Économise le coût de la condition et de l’incrément à chaque itération (environ 18 gas par itération).

Compteur décroissant

// Plus efficace : compter vers le bas
for { let i := n } gt(i, 0) { i := sub(i, 1) } {
    // Le test `gt(i, 0)` est 1 gas moins cher que `lt(i, n)` car
    // il n'a pas besoin de charger n depuis la pile
}

Arrêt anticipé

Sortez de la boucle dès que possible avec break :

for { let i := 0 } lt(i, length) { i := add(i, 1) } {
    let element := mload(add(ptr, mul(i, 0x20)))
    if eq(element, target) {
        result := i
        break
    }
}

Benchmarks de gas

PatternGas par itération (Solidity)Gas par itération (Yul)Économie
Boucle standard~95~7818 %
Avec unchecked~80~783 %
Accès tableau~110~8523 %
Copie mémoire~120~9025 %

Les économies sont les plus significatives pour les boucles qui accèdent à la mémoire ou au stockage, où l’overhead de Solidity (vérifications de limites, ABI encoding) est le plus élevé.

Conclusion

Les boucles et conditionnels en Yul offrent un contrôle fin sur le gas. Les gains les plus importants viennent de l’élimination des vérifications de limites de Solidity, du déroulage des boucles courtes et de l’utilisation de compteurs décroissants.

Dans le prochain article, nous mettrons en pratique toutes ces techniques en construisant un échange de tokens en Yul pur.