Ich denke, die meisten von Ihnen wissen, dass dies goto
ein reserviertes Schlüsselwort in der Java-Sprache ist, aber nicht tatsächlich verwendet wird. Und Sie wissen wahrscheinlich auch, dass goto
es 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 goto
und ifeq
, ifle
, iflt
usw.
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_w
Opcode gibt. Während goto
ein 2-Byte-Verzweigungsversatz verwendet wird, goto_w
wird 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_w
zukunftssicher, wie einige der anderen *_w
Opcodes. Mir fällt aber auch ein, dass goto_w
die beiden möglicherweise signifikanteren Bytes auf Null und die beiden weniger signifikanten Bytes goto
wie 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_w
s 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_w
mit dem aktuellen Grenzwert von 65535 verwenden könnte, außer um zu zeigen, dass dies möglich ist.
// ... 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).Es gibt keinen Grund zu verwenden,
goto_w
wenn der Zweig in eine passtgoto
. 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 Adresse0 … +65535
Bereich.Beispielsweise hat die folgende Methode
goto_w
am Anfang eine Anweisung:Demo auf Ideone
quelle
Main
mitmethodWithLargeJump()
kompiliert auf fast 400KB.finally
Blö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 …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.
quelle