JVM için herhangi bir derleyici “geniş” goto kullanıyor mu?


47

Çoğunuzun gotoJava dilinde ayrılmış bir anahtar kelime olduğunu , ancak aslında kullanılmadığını bildiğinizi düşünüyorum . Ve muhtemelen gotobunun bir Java Sanal Makinesi (JVM) opcode olduğunu da biliyorsunuz . Ben, JVM düzeyinde, bazı kombinasyonu kullanılarak uygulanan tüm Java, Scala ve KOTLIN sofistike kontrol akış yapıları hesaba katmak gotove ifeq, ifle, ifltvb

JVM spesifikasyonlarına bakarak https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.goto_w Bir de goto_wopcode olduğunu görüyorum . Oysa gotoofset 2 baytlık dalı alır, goto_wofset bir 4 baytlık dalı alır. Spec diyor ki

Goto_w komutu 4 baytlık bir şube ofseti alsa da , diğer faktörler bir yöntemin boyutunu 65535 bayt ile sınırlar (§4.11). Bu sınır, Java Sanal Makinesi'nin gelecekteki bir sürümünde yükseltilebilir.

Bana goto_wgelecekteki kanıtlar gibi geliyor , tıpkı diğer *_wopcodlar gibi. Ama aynı zamanda bana göre goto_w, daha önemli iki bayt sıfırlanmış ve daha az önemli iki bayt, gotogerektiği gibi ayarlamalar ile kullanılabilir.

Örneğin, bu Java Switch-Case (veya Scala Match-Case) göz önüne alındığında:

     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.

olarak yeniden yazabiliriz

     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.

Aslında s denemek için "satır numaraları" değiştirerek bir hata yaptım çünkü ben gerçekten denemedim goto_w. Ancak spesifikasyonda olduğundan, bunu yapmak mümkün olmalıdır.

Benim sorum derleyici veya başka bir bayt kodu oluşturucunun goto_wyapılabileceğini göstermek dışında geçerli 65535 sınırı ile kullanmak için bir nedeni olup olmadığını ?

Yanıtlar:


51

Yöntem kodunun boyutu 64K kadar büyük olabilir.

Kısa dalın ofseti gotoişaretli bir 16 bit tam sayıdır: -32768'den 32767'ye.

Bu nedenle, kısa ofset, 65K yönteminin başından sonuna kadar bir sıçrama yapmak için yeterli değildir.

javacBazen bile yayılır goto_w. İşte bir örnek:

public class WideGoto {

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

İle ayrıştırma 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
          ...

// ... repeat 10K times ...Bu derler mi? Tek bir kaynak sınıfının boyutu için bir sınır olduğunu biliyorum ... ama tam olarak ne olduğunu bilmiyorum (kod üretimi bir şey gerçekten vurmak gördüm tek zamandır).
Elliott Frisch

3
@ElliottFrisch Öyle. Yöntemin bayt kodu boyutu
65535'i

18
Güzel. Teşekkürler. 64k sanırım herkes için yeterli olmalı. ;)
Elliott Frisch

3
@ElliottFrisch - Referansta ipuçları şapka.
TJ Crowder

34

goto_wŞube a'ya oturduğunda kullanmak için hiçbir sebep yoktur goto. Ancak , bir dal da geriye gidebileceğinden, imzalı bir ofset kullanarak dalların göreceli olduğunu kaçırmış görünüyorsunuz .

Böyle bir aracın çıktısına bakarken bunu fark etmezsiniz javap, çünkü yazdırmadan önce ortaya çıkan mutlak hedef adresini hesaplar.

Bu nedenle gotoaralığı, aralıktaki -327678 … +32767‬olası her hedef yeri ele almak için her zaman yeterli değildir 0 … +65535.

Örneğin, aşağıdaki yöntemin goto_wbaşında bir talimat olacaktır :

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

Ideone hakkında demo

Compiled from "Main.java"
class LargeJump {
  public static void methodWithLargeJump(int);
    Code:
       0: iload_0
       1: ifeq          9
       4: goto_w        57567
…

7
İnanılmaz vay. En büyük Java projem, birkaç paket ve aralarında birkaç düzine ders ile neredeyse 200KB derliyor. Ama senin Mainile methodWithLargeJump()neredeyse 400KB kadar derlemesinin.
Alonso del Arte

4
Bu, ortak durum için ne kadar Java'nın optimize edildiğini gösterir ...
Holger

1
Atlama masalarının kötüye kullanımını nasıl keşfettiniz? Makine tarafından oluşturulan kod?
Elliott Frisch

14
@ElliottFrisch Sadece finallyblokların normal ve istisnai akış için çoğaltıldığını hatırlamak zorunda kaldım (Java 6'dan beri zorunlu). Bu yüzden on tanesini iç içe yerleştirmek × 2¹⁰ anlamına gelir, daha sonra anahtarın her zaman varsayılan bir hedefi vardır, bu nedenle iload ile birlikte on bayt artı dolguya ihtiyaç duyar. Ayrıca optimizasyonları önlemek için her şubeye önemsiz bir ifade ekledim. Sınırları kullanmak tekrar eden bir konu, iç içe ifadeler , lambdalar , alanlar , inşaatçılar
Holger

2
İlginç bir şekilde, iç içe ifadeler ve birçok kurucu da sadece bayt kodu sınırlamalarını değil, derleyici uygulama sınırlamalarını da etkiledi. Ayrıca max sınıf dosya boyutu hakkında bir soru cevap vardı (belki de bilinçsizce bu cevabı yazarken Tagir'in cevabını hatırladım). Son olarak maksimum paket adı uzunluğu ve JVM tarafında maksimum iç içe senkronize . Görünüşe göre insanlar meraklı olmaya devam ediyor.
Holger

5

Bazı derleyicilerde (1.6.0 ve 11.0.7'de denenmiştir), eğer bir yöntem yeterince büyükse goto_w'a ihtiyaç duyuyorsa, sadece goto_w kullanır . Çok yerel atlamalar olsa bile, hala goto_w kullanır.


1
Neden olabilir? Talimat önbelleğe almayla ilgili bir şey var mı?
Alexander - Monica'yı eski

@ Alexander-ReinstateMonica Muhtemelen uygulama kolaylığı.
David G.19
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.