Apakah ada kompiler untuk JVM yang menggunakan goto "lebar"?

47

Saya pikir sebagian besar dari Anda tahu bahwa gotokata kunci yang disediakan dalam bahasa Jawa tetapi tidak benar-benar digunakan. Dan Anda mungkin juga tahu itu gotoadalah opcode Java Virtual Machine (JVM). Saya rasa semua struktur aliran kontrol canggih dari Jawa, Scala dan Kotlin yang, pada tingkat JVM, dilaksanakan dengan menggunakan beberapa kombinasi dari gotodan ifeq, ifle, iflt, dll

Melihat spesifikasi JVM https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.goto_w Saya melihat ada juga goto_wopcode. Sedangkan gotomengambil cabang offset 2-byte, goto_wmengambil cabang offset 4-byte. Spesifikasi menyatakan itu

Meskipun instruksi goto_w mengambil offset cabang 4-byte, faktor lain membatasi ukuran metode hingga 65535 byte (§4.11). Batas ini dapat dinaikkan pada rilis Java Virtual Machine yang akan datang.

Bagi saya kedengarannya seperti goto_wpembuktian di masa depan, seperti beberapa *_wopcode lainnya . Tetapi itu juga terjadi pada saya bahwa mungkin goto_wdapat digunakan dengan dua byte lebih signifikan memusatkan perhatian dan dua byte kurang signifikan sama seperti untuk goto, dengan penyesuaian yang diperlukan.

Sebagai contoh, dengan diberikan Java Switch-Case (atau 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.

kita bisa menulis ulang sebagai

     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.

Saya belum benar-benar mencoba ini, karena saya mungkin telah membuat kesalahan dengan mengubah "nomor baris" untuk mengakomodasi goto_w. Tetapi karena itu ada dalam spesifikasi, itu harus dimungkinkan untuk melakukannya.

Pertanyaan saya adalah apakah ada alasan kompiler atau generator bytecode lainnya dapat digunakan goto_wdengan batas 65535 saat ini selain untuk menunjukkan bahwa hal itu dapat dilakukan?

Alonso del Arte
sumber

Jawaban:

51

Ukuran kode metode bisa sebesar 64K.

Offset cabang pendek gotoadalah bilangan bulat 16-bit yang ditandatangani: dari -32768 hingga 32767.

Jadi, offset pendek tidak cukup untuk melakukan lompatan dari awal metode 65K ke akhir.

Bahkan javacterkadang memancarkan goto_w. Berikut ini sebuah contoh:

public class WideGoto {

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

Mengurai dengan 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
sumber
// ... repeat 10K times ...Kompilasi itu? Saya tahu ada batasan untuk ukuran satu sumber kelas ... tapi saya tidak tahu persis apa itu (pembuatan kode adalah satu-satunya waktu saya melihat sesuatu yang benar-benar memukulnya).
Elliott Frisch
3
@ElliottFrisch Ya. Selama ukuran bytecode metode tidak melebihi 65535, dan panjang kolam konstan juga kurang dari 65535.
apangin
18
Keren. Terima kasih. 64k seharusnya cukup untuk siapa pun kurasa. ;)
Elliott Frisch
3
@ElliottFrisch - Tips hat pada referensi.
TJ Crowder
34

Tidak ada alasan untuk digunakan goto_wsaat cabang cocok dengan a goto. Tetapi Anda tampaknya telah melewatkan bahwa cabang adalah relatif , menggunakan offset yang ditandatangani, karena cabang juga dapat mundur.

Anda tidak melihatnya ketika melihat output dari alat seperti javap, karena menghitung alamat target absolut yang dihasilkan sebelum mencetak.

Jadi gotojangkauan -327678 … +32767‬tidak selalu cukup untuk mengatasi setiap kemungkinan lokasi target dalam 0 … +65535jangkauan.

Misalnya, metode berikut akan memiliki goto_winstruksi di awal:

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 di Ideone

Compiled from "Main.java"
class LargeJump {
  public static void methodWithLargeJump(int);
    Code:
       0: iload_0
       1: ifeq          9
       4: goto_w        57567
Holger
sumber
7
Wow luar biasa. Proyek Java terbesar saya, dengan beberapa paket dan beberapa lusin kelas di antaranya, mengkompilasi hingga hampir 200KB. Tetapi Anda Maindengan methodWithLargeJump()kompilasi hingga hampir 400KB.
Alonso del Arte
4
Itu menunjukkan seberapa banyak Jawa dioptimalkan untuk kasus umum ...
Holger
1
Bagaimana Anda menemukan bahwa penyalahgunaan tabel lompat? Kode yang dihasilkan mesin?
Elliott Frisch
14
@ElliottFrisch Saya hanya perlu mengingat bahwa finallyblok diduplikasi untuk aliran normal dan luar biasa (wajib sejak Java 6). Jadi sepuluh dari mereka menyiratkan × 2¹⁰, maka, switch selalu memiliki target default, jadi bersama dengan iload, perlu sepuluh byte plus padding. Saya juga menambahkan pernyataan nontrivial di setiap cabang untuk mencegah optimisasi. Batas pemanfaatan adalah topik berulang, ekspresi bersarang , lambda , bidang , konstruktor ...
Holger
2
Menariknya, ekspresi bersarang dan banyak konstruktor juga mencapai batasan implementasi compiler, tidak hanya batas bytecode. Ada juga T&J tentang ukuran file kelas maks (mungkin saya secara tidak sadar ingat jawaban Tagir ketika menulis jawaban ini). Akhirnya max nama paket panjang dan, di sisi JVM, max bersarang disinkronkan . Sepertinya, orang tetap penasaran.
Holger
5

Tampaknya dalam beberapa kompiler (dicoba pada 1.6.0 dan 11.0.7), jika suatu metode cukup besar yang pernah membutuhkan goto_w, ia menggunakan secara eksklusif goto_w. Bahkan ketika ia memiliki lompatan yang sangat lokal, ia masih menggunakan goto_w.

David G.
sumber
1
Kenapa bisa begitu? Apakah ada hubungannya dengan caching instruksi?
Alexander - Reinstate Monica
@ Alexander-ReinstateMonica Mungkin hanya kemudahan implementasi.
David G.