Comment le décalage pour une instruction de saut JVM peut-il être 32768?

Lors de l’écriture d’une réponse à une question sur les décalages de code d’octet JVM , j’ai remarqué quelque chose dans le comportement de javac et des fichiers de classe résultants que je ne peux pas expliquer:

Lors de la compilation d’une classe comme celle-ci

class FarJump { public static void main(Ssortingng args[]) { call(0, 1); } public static void call(int x, int y) { if (x < y) { y++; y++; // ... (10921 times - too much code to post here!) y++; y++; } System.out.println(y); } } 

alors le code d’octet résultant contiendra l’instruction if_icmpge suivante:

 public static void call(int, int); Code: 0: iload_0 1: iload_1 2: if_icmpge 32768 5: iinc 1, 1 8: iinc 1, 1 ... 

Selon la documentation des instructions de saut, l’offset (qui est 32768 dans ce cas) est calculé comme suit:

Si la comparaison réussit, les termes Branchbyte1 et Branchbyte2 non signés sont utilisés pour construire un décalage signé de 16 bits, le décalage étant calculé comme suit (branchbyte1 << 8) | branchbyte2 .

Donc, le décalage est dit être une valeur signée 16 bits. Toutefois, la valeur maximale qu’une valeur signée de 16 bits peut contenir est 32767 et non 32768.

Le fichier de classe résultant semble toujours être valide et peut être exécuté normalement.

J’ai jeté un œil à la vérification du bytecode dans OpenJDK , et il me semble que cela n’est valable que parce que les parenthèses sont mal placées:

 int jump = (((signed char)(code[offset+1])) << 8) + code[offset+2]; 

Il lancera le premier octet du caractère signed char . Ensuite, il appliquera le décalage et appenda le deuxième octet. Je m’attendais à ce que ce soit

 int jump = (((signed char)(code[offset+1]) << 8)) + code[offset+2]; 

ou peut-être même

 int jump = (signed char)((code[offset+1]) << 8) + code[offset+2]); 

mais je ne suis pas familier avec les promotions de types et les mises en garde spécifiques au compilateur de changer de types signés et non signés, donc je ne suis pas sûr qu’il y ait une signification plus profonde derrière ce casting

Un décalage de saut de 32 768 est-il conforme à la spécification? Et le code de calcul des sauts dans OpenJDK a-t-il un sens à cet égard?

L’argument de if_icmpge est un décalage, mais javap montre la cible du saut sous forme de position absolue. C’est-à-dire que javap devrait afficher un getstatic à 32768: et non à 32770: (c’est-à-dire 2 + 32768).

J’ai écrit un code scala simple pour générer du code pour aller plus loin. Pour toutes sortes d’instructions de saut, le décalage est signé pour permettre le saut en arrière et en avant.

Si le décalage est inférieur à 0x7FFF, je vois l’instruction goto, et si le décalage est supérieur à 0x7FFF, je vois l’instruction goto_w.

Ainsi, une méthode en Java est limitée à 65535 octets, car LineNumberTable, LocalVariableTable, exception_table … et est limitée à 65535 octets. La machine virtuelle Java utilise les instructions goto / goto_w pour sauter le décalage 16/32 signé en fonction des besoins.