Des compilateurs pour la JVM utilisent-ils le goto «large»?

47

Je pense que la plupart d'entre vous savent que gotoc'est un mot-clé réservé dans le langage Java mais qu'il n'est pas réellement utilisé. Et vous savez probablement aussi qu'il gotos'agit d'un opcode Java Virtual Machine (JVM). Je pense toutes les structures sophistiquées de contrôle de flux de Java, Scala et Kotlin sont, au niveau JVM, mis en œuvre en utilisant une combinaison de gotoet ifeq, ifle, iflt, etc.

En regardant la spécification JVM https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.goto_w Je vois qu'il y a aussi un goto_wopcode. Alors que gotoprend un décalage de branche de 2 octets, goto_wprend un décalage de branche de 4 octets. La spécification indique que

Bien que l' instruction goto_w prenne un décalage de branche de 4 octets, d'autres facteurs limitent la taille d'une méthode à 65 535 octets (§4.11). Cette limite pourrait être relevée dans une future version de la machine virtuelle Java.

Cela me semble être à l' goto_wépreuve du temps, comme certains des autres *_wopcodes. Mais il me vient également à l'esprit que peut-être goto_wpourrait être utilisé avec les deux octets plus significatifs mis à zéro et les deux octets moins significatifs identiques à goto, avec des ajustements selon les besoins.

Par exemple, étant donné ce Java Switch-Case (ou Scala Match-Case):

     12: lookupswitch  {
                112785: 48 // case "red"
               3027034: 76 // case "green"
              98619139: 62 // case "blue"
               default: 87
          }
      48: aload_2
      49: ldc           #17                 // String red
      51: invokevirtual #18
            // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      54: ifeq          87
      57: iconst_0
      58: istore_3
      59: goto          87
      62: aload_2
      63: ldc           #19                 // String green
      65: invokevirtual #18
            // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      68: ifeq          87
      71: iconst_1
      72: istore_3
      73: goto          87
      76: aload_2
      77: ldc           #20                 // String blue
      79: invokevirtual #18 
      // etc.

nous pourrions le réécrire

     12: lookupswitch  { 
                112785: 48
               3027034: 78
              98619139: 64
               default: 91
          }
      48: aload_2
      49: ldc           #17                 // String red
      51: invokevirtual #18
            // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      54: ifeq          91 // 00 5B
      57: iconst_0
      58: istore_3
      59: goto_w        91 // 00 00 00 5B
      64: aload_2
      65: ldc           #19                 // String green
      67: invokevirtual #18
            // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      70: ifeq          91
      73: iconst_1
      74: istore_3
      75: goto_w          91
      79: aload_2
      81: ldc           #20                 // String blue
      83: invokevirtual #18 
      // etc.

Je n'ai pas vraiment essayé cela, car j'ai probablement fait une erreur en changeant les "numéros de ligne" pour tenir compte du par goto_w. Mais puisque c'est dans la spécification, il devrait être possible de le faire.

Ma question est de savoir s'il y a une raison qu'un compilateur ou un autre générateur de bytecode pourrait utiliser goto_wavec la limite 65535 actuelle autre que pour montrer que cela peut être fait?

Alonso del Arte
la source

Réponses:

51

La taille du code de méthode peut atteindre 64 Ko.

Le décalage de branche du court gotoest un entier signé de 16 bits: de -32768 à 32767.

Ainsi, le décalage court n'est pas suffisant pour faire un saut du début de la méthode 65K à la fin.

Même javacémet parfois goto_w. Voici un exemple:

public class WideGoto {

    public static void main(String[] args) {
        for (int i = 0; i < 1_000_000_000; ) {
            i += 123456;
            // ... repeat 10K times ...
        }
    }
}

Décompilation avec javap -c:

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: ldc           #2
       5: if_icmplt     13
       8: goto_w        50018     // <<< Here it is! A jump to the end of the loop
          ...
apangin
la source
// ... repeat 10K times ...Ça compile? Je sais qu'il y a une limite à la taille d'une classe source unique ... mais je ne sais pas ce que c'est précisément (la génération de code est la seule fois où j'ai vu quelque chose l'atteindre).
Elliott Frisch
3
@ElliottFrisch Oui. Tant que la taille du bytecode de la méthode ne dépasse pas 65535 et que la longueur constante du pool est également inférieure à 65535.
Apangin
18
Cool. Merci. 64k devrait être suffisant pour quiconque je suppose. ;)
Elliott Frisch
3
@ElliottFrisch - Conseils chapeau à la référence.
TJ Crowder le
34

Il n'y a aucune raison de l'utiliser goto_wlorsque la branche s'inscrit dans un goto. Mais vous semblez avoir oublié que les branches sont relatives , en utilisant un décalage signé, car une branche peut aussi reculer.

Vous ne le remarquez pas lorsque vous regardez la sortie d'un outil comme javap, car il calcule l'adresse cible absolue résultante avant l'impression.

Ainsi, gotola plage de -327678 … +32767‬n'est pas toujours suffisante pour adresser chaque emplacement cible possible dans la 0 … +65535plage.

Par exemple, la méthode suivante aura une goto_winstruction au début:

public static void methodWithLargeJump(int i) {
    for(; i == 0;) {
        try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1: 
        try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1: 
        try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1: 
        try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1: 
        try {x();} finally { switch(i){ case 1: try {x();} finally { switch(i){ case 1: 
        } } } } } } } } } } } } } } } } } } } } 
    }
}
static void x() {}

Démo sur Ideone

Compiled from "Main.java"
class LargeJump {
  public static void methodWithLargeJump(int);
    Code:
       0: iload_0
       1: ifeq          9
       4: goto_w        57567
…
Holger
la source
7
Waouh incroyable. Mon plus grand projet Java, avec quelques packages et quelques dizaines de classes entre eux, se compile à près de 200 Ko. Mais votre Mainavec methodWithLargeJump()compile à près de 400 Ko.
Alonso del Arte
4
Cela montre à quel point Java est optimisé pour le cas courant ...
Holger
1
Comment avez-vous découvert cet abus des tables de saut? Code généré par la machine?
Elliott Frisch
14
@ElliottFrisch Je n'ai eu qu'à rappeler que les finallyblocs sont dupliqués pour un flux normal et exceptionnel (obligatoire depuis Java 6). L'imbrication de dix d'entre eux implique donc × 2¹⁰, puis, le commutateur a toujours une cible par défaut, donc avec l'iload, il a besoin de dix octets plus le remplissage. J'ai également ajouté une déclaration non triviale dans chaque branche pour éviter les optimisations. L'exploitation des limites est un sujet récurrent, les expressions imbriquées , les lambdas , les champs , les constructeurs
Holger
2
Fait intéressant, les expressions imbriquées et de nombreux constructeurs atteignent également les limites d'implémentation du compilateur, pas seulement les limites de bytecode. Il y avait aussi un Q&A sur la taille maximale du fichier de classe (peut-être que je me suis souvenu inconsciemment de la réponse de Tagir lors de l'écriture de cette réponse). Enfin , longueur maximale du nom de package et, du côté de la JVM, max imbriquée synchronisée . Il semble que les gens restent curieux.
Holger
5

Il semble que dans certains compilateurs (essayés en 1.6.0 et 11.0.7), si une méthode est suffisamment grande pour avoir toujours besoin de goto_w, elle utilise exclusivement goto_w. Même lorsqu'il a des sauts très locaux, il utilise toujours goto_w.

David G.
la source
1
Pourquoi est-ce possible? Est-ce lié à la mise en cache des instructions?
Alexander - Reinstate Monica
@ Alexander-ReinstateMonica Probablement simplement une facilité de mise en œuvre.
David G.19