Verwenden Compiler für die JVM das "breite" goto?

47

Ich denke, die meisten von Ihnen wissen, dass dies gotoein reserviertes Schlüsselwort in der Java-Sprache ist, aber nicht tatsächlich verwendet wird. Und Sie wissen wahrscheinlich auch, dass gotoes sich um einen JVM-Opcode (Java Virtual Machine) handelt. Ich rechne damit , alle hoch entwickelten Kontrollflussstrukturen von Java, Scala und Kotlin wird, auf der Ebene JVM, implementiert eine Kombination aus gotound ifeq, ifle, ifltusw.

Wenn ich mir die JVM-Spezifikation https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.goto_w ansehe, sehe ich, dass es auch einen goto_wOpcode gibt. Während gotoein 2-Byte-Verzweigungsversatz verwendet wird, goto_wwird ein 4-Byte-Verzweigungsversatz verwendet. Die Spezifikation besagt das

Obwohl der Befehl goto_w einen 4-Byte-Verzweigungsversatz benötigt, begrenzen andere Faktoren die Größe einer Methode auf 65535 Byte (§4.11). Diese Grenze kann in einer zukünftigen Version der Java Virtual Machine angehoben werden.

Es klingt für mich goto_wzukunftssicher, wie einige der anderen *_wOpcodes. Mir fällt aber auch ein, dass goto_wdie beiden möglicherweise signifikanteren Bytes auf Null und die beiden weniger signifikanten Bytes gotowie bei den erforderlichen Anpassungen verwendet werden könnten .

Beispiel: Java Switch-Case (oder 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.

wir könnten es umschreiben als

     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.

Ich habe dies nicht wirklich versucht, da ich wahrscheinlich einen Fehler gemacht habe, die "Zeilennummern" zu ändern, um die goto_ws aufzunehmen. Aber da es in der Spezifikation enthalten ist, sollte es möglich sein, dies zu tun.

Meine Frage ist, ob es einen Grund gibt, den ein Compiler oder ein anderer Bytecode-Generator goto_wmit dem aktuellen Grenzwert von 65535 verwenden könnte, außer um zu zeigen, dass dies möglich ist.

Alonso del Arte
quelle

Antworten:

51

Die Größe des Methodencodes kann bis zu 64 KB betragen.

Der Verzweigungsversatz des Kurzschlusses gotoist eine vorzeichenbehaftete 16-Bit-Ganzzahl: von -32768 bis 32767.

Der kurze Versatz reicht also nicht aus, um vom Anfang der 65K-Methode zum Ende zu springen.

Sogar javacmanchmal emittiert goto_w. Hier ist ein Beispiel:

public class WideGoto {

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

Dekompilieren mit 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
quelle
// ... repeat 10K times ...Das kompiliert? Ich weiß, dass die Größe einer einzelnen Quellklasse begrenzt ist ... aber ich weiß nicht genau, was es ist (Codegenerierung ist das einzige Mal, dass ich gesehen habe, dass etwas tatsächlich getroffen wurde).
Elliott Frisch
3
@ElliottFrisch Das tut es. Solange die Bytecode-Größe der Methode 65535 nicht überschreitet und die konstante
Poollänge
18
Cool. Vielen Dank. 64k sollten für jeden ausreichen, denke ich. ;)
Elliott Frisch
3
@ElliottFrisch - Tipps Hut bei Referenz.
TJ Crowder
34

Es gibt keinen Grund zu verwenden, goto_wwenn der Zweig in eine passt goto. Sie scheinen jedoch übersehen zu haben, dass die Zweige mit einem vorzeichenbehafteten Versatz relativ sind, da ein Zweig auch rückwärts gehen kann.

Sie bemerken es nicht, wenn Sie die Ausgabe eines Werkzeugs wie betrachten javap, da es die resultierende absolute Zieladresse vor dem Drucken berechnet.

So goto‚s - Bereich von -327678 … +32767‬nicht immer genug jede mögliche Zielposition in der Adresse 0 … +65535Bereich.

Beispielsweise hat die folgende Methode goto_wam Anfang eine Anweisung:

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() {}

Demo auf Ideone

Compiled from "Main.java"
class LargeJump {
  public static void methodWithLargeJump(int);
    Code:
       0: iload_0
       1: ifeq          9
       4: goto_w        57567
…
Holger
quelle
7
Wow wunderbar. Mein größtes Java-Projekt mit ein paar Paketen und ein paar Dutzend Klassen dazwischen wird auf fast 200 KB kompiliert. Aber Ihr Mainmit methodWithLargeJump()kompiliert auf fast 400KB.
Alonso del Arte
4
Das zeigt, wie viel Java für den allgemeinen Fall optimiert ist ...
Holger
1
Wie haben Sie diesen Missbrauch von Sprungtischen entdeckt? Maschinengenerierter Code?
Elliott Frisch
14
@ElliottFrisch Ich musste mich nur daran erinnern, dass finallyBlöcke für einen normalen und außergewöhnlichen Fluss dupliziert werden (obligatorisch seit Java 6). Wenn Sie also zehn davon verschachteln, bedeutet dies × 2¹ then. Der Switch hat also immer ein Standardziel. Zusammen mit dem iload benötigt er zehn Bytes plus Auffüllung. Ich habe außerdem in jedem Zweig eine nicht triviale Anweisung hinzugefügt, um Optimierungen zu verhindern. Das Ausnutzen von Grenzen ist ein wiederkehrendes Thema, verschachtelte Ausdrücke , Lambdas , Felder , Konstruktoren
Holger
2
Interessanterweise stoßen verschachtelte Ausdrücke und viele Konstruktoren auch auf Einschränkungen bei der Compiler-Implementierung, nicht nur auf Bytecode-Beschränkungen. Es gab auch Fragen und Antworten zur Dateigröße der maximalen Klasse (vielleicht erinnerte ich mich unbewusst an Tagirs Antwort, als ich diese Antwort schrieb). Schließlich wird die maximale Länge des Paketnamens und auf der JVM-Seite die maximale Verschachtelung synchronisiert . Scheint, die Leute bleiben immer neugierig.
Holger
5

Es scheint, dass in einigen Compilern (versucht in 1.6.0 und 11.0.7) eine Methode, die groß genug ist, um goto_w jemals zu benötigen, ausschließlich goto_w verwendet. Selbst wenn es sehr lokale Sprünge hat, verwendet es immer noch goto_w.

David G.
quelle
1
Warum könnte das sein? Hat das etwas mit dem Zwischenspeichern von Anweisungen zu tun?
Alexander - Monica
@ Alexander-ReinstateMonica Wahrscheinlich nur einfache Implementierung.
David G.