คอมไพเลอร์ใด ๆ สำหรับ JVM ใช้ goto "wide" หรือไม่?


47

ฉันคิดว่าคุณส่วนใหญ่รู้ว่าgotoเป็นคำหลักที่สงวนไว้ในภาษา Java แต่ไม่ได้ใช้จริง และคุณก็อาจจะรู้ว่าgotoเป็น opcode Java Virtual Machine (JVM) ฉันคิดว่าทุกคนที่มีความซับซ้อนของโครงสร้างการควบคุมการไหลของ Java, Scala และ Kotlin กำลังที่ระดับ JVM, ดำเนินการโดยใช้การรวมกันของบางgotoและifeq, ifle, ifltฯลฯ

ดูที่ข้อกำหนด JVM https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.goto_wฉันเห็นว่ามันมีgoto_wopcode เช่นกัน ในขณะที่gotoออฟเซ็ตสาขา 2 ไบต์goto_wจะใช้ออฟเซ็ตสาขา4 ไบต์ สเป็คระบุว่า

แม้ว่าคำสั่งgoto_wใช้ออฟเซ็ตสาขา 4 ไบต์ปัจจัยอื่น ๆ จำกัด ขนาดของวิธีการเป็น 65535 ไบต์ (§4.11) ขีด จำกัด นี้อาจเพิ่มใน Java Virtual Machine รุ่นที่วางจำหน่ายในอนาคต

เสียงที่ฉันชอบgoto_wคือการพิสูจน์ในอนาคตเหมือนกับ*_wopcodes อื่น ๆ แต่ก็ยังเกิดขึ้นกับผมว่าอาจจะgoto_wสามารถนำมาใช้กับทั้งสองไบต์สำคัญมากพุ่งออกมาและทั้งสองไบต์อย่างมีนัยสำคัญน้อยลงเช่นเดียวกับgotoที่มีการปรับเปลี่ยนตามความจำเป็น

ตัวอย่างเช่นกำหนด Java Switch-Case (หรือ 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.

เราสามารถเขียนใหม่เป็น

     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.

ฉันไม่ได้ลองสิ่งนี้จริง ๆ เพราะฉันอาจทำผิดพลาดในการเปลี่ยน "หมายเลขบรรทัด" เพื่อรองรับgoto_wของ แต่เนื่องจากมันอยู่ในสเป็คมันควรจะเป็นไปได้ที่จะทำมัน

คำถามของฉันคือว่ามีเหตุผลที่คอมไพเลอร์หรือตัวกำเนิดอื่น ๆ ของ bytecode อาจใช้goto_wกับวงเงิน 65535 ปัจจุบันนอกเหนือจากที่แสดงว่าสามารถทำได้หรือไม่

คำตอบ:


51

ขนาดของรหัสวิธีสามารถมีขนาดใหญ่เท่ากับ 64K

ออฟเซ็ตย่อยของชอร์ตgotoเป็นจำนวนเต็ม 16 บิตที่ลงนามแล้วตั้งแต่ -32768 ถึง 32767

ดังนั้นการชดเชยระยะสั้นไม่เพียงพอที่จะกระโดดจากจุดเริ่มต้นของวิธี 65K ไปยังจุดสิ้นสุด

แม้บางครั้งส่งเสียงjavac goto_wนี่คือตัวอย่าง:

public class WideGoto {

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

ถอดรหัสกับ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 ...รวบรวมว่า? ฉันรู้ว่ามีการ จำกัด ขนาดของคลาสซอร์สเดี่ยว ... แต่ฉันไม่รู้ว่ามันแม่นยำเพียงใด (การสร้างโค้ดเป็นครั้งเดียวที่ฉันเห็นบางสิ่งที่กระทบจริง ๆ )
Elliott Frisch

3
@ElliottFrisch มันทำ ตราบใดที่ขนาดไบต์ของวิธีการนั้นไม่เกิน 65535 และความยาวพูลคงที่นั้นน้อยกว่า 65535
apangin

18
เย็น. ขอบคุณ 64k น่าจะเพียงพอสำหรับทุกคนที่ฉันเดา ;)
Elliott Frisch

3
@ElliottFrisch - คำแนะนำการอ้างอิงหมวก
TJ Crowder

34

ไม่มีเหตุผลที่จะใช้ไม่เป็นเมื่อเหมาะกับสาขาเป็นgoto_w gotoแต่คุณดูเหมือนจะพลาดไปว่าสาขานั้นสัมพันธ์กันโดยใช้ออฟเซ็ตที่เซ็นชื่อเนื่องจากสาขาสามารถย้อนกลับได้

คุณไม่สังเกตเห็นมันเมื่อดูผลลัพธ์ของเครื่องมือเช่นjavapมันจะคำนวณที่อยู่เป้าหมายสัมบูรณ์ที่เกิดขึ้นก่อนที่จะพิมพ์

ดังนั้นgoto's ช่วงของการ-327678 … +32767‬ไม่เพียงพอที่จะอยู่สถานที่แต่ละเป้าหมายที่เป็นไปได้ในเสมอ0 … +65535ช่วง

ตัวอย่างเช่นวิธีการต่อไปนี้จะมีgoto_wคำแนะนำในตอนต้น:

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

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

7
ว้าวน่าทึ่งมาก โปรเจ็กต์ Java ที่ใหญ่ที่สุดของฉันพร้อมแพ็กเกจไม่กี่คลาสและคลาสไม่กี่โหลที่คอมไพล์เกือบ 200KB แต่Mainด้วยmethodWithLargeJump()คอมไพล์ของคุณเกือบ 400KB
อลอนโซ่เดลอาร์เต้

4
นั่นแสดงให้เห็นว่า Java ได้รับการปรับให้เหมาะสมกับกรณีทั่วไปมากน้อยเพียงใด ...
Holger

1
คุณค้นพบวิธีที่ไม่เหมาะสมของตารางกระโดดอย่างไร รหัสที่สร้างขึ้นจากเครื่อง?
Elliott Frisch

14
@ElliottFrisch ฉันแค่จำได้ว่าfinallyบล็อกนั้นมีการทำซ้ำสำหรับโฟลว์ปกติและยอดเยี่ยม (บังคับตั้งแต่ Java 6) ดังนั้นการทำรังจำนวนสิบหมายถึง×2¹⁰ดังนั้นสวิตช์จะมีเป้าหมายเริ่มต้นเสมอดังนั้นเมื่อรวมกับ iload จะต้องใช้สิบไบต์พร้อมช่องว่างภายใน ฉันยังได้เพิ่มคำสั่งที่ไม่น่าสนใจในแต่ละสาขาเพื่อป้องกันการปรับให้เหมาะสม ข้อ จำกัด การใช้ประโยชน์เป็นหัวข้อที่เกิดขึ้นซ้ำ ๆ , การแสดงออกที่ซ้อนกัน , lambdas , ฟิลด์ , คอนสตรัคเตอร์
โฮลเกอร์

2
ที่น่าสนใจนิพจน์ที่ซ้อนกันและตัวสร้างจำนวนมากก็มีข้อ จำกัด ในการใช้งานคอมไพเลอร์ด้วย นอกจากนี้ยังมีคำถามเกี่ยวกับขนาดไฟล์สูงสุดของคลาส (บางทีฉันอาจจำคำตอบของ Tagir โดยไม่รู้ตัวเมื่อเขียนคำตอบนี้) ในที่สุดความยาวสูงสุดชื่อแพคเกจและในด้าน JVM, แม็กซ์ซ้อนกันทำข้อมูลให้ตรงกัน ดูเหมือนว่าผู้คนยังคงอยากรู้อยากเห็นอยู่เสมอ
Holger

5

ปรากฏว่าในคอมไพเลอร์บางตัว (ลองใน 1.6.0 และ 11.0.7) หากวิธีการมีขนาดใหญ่พอที่จะต้องมี goto_w มันจะใช้goto_w โดยเฉพาะ แม้ว่าจะมีการกระโดดในพื้นที่มาก แต่ก็ยังใช้ goto_w


1
ทำไมจึงเป็นเช่นนั้น เป็นเรื่องเกี่ยวกับการสอนแคชหรือไม่?
Alexander - Reinstate Monica

@ Alexander-ReinstateMonica อาจเป็นเรื่องง่ายในการปรับใช้
David G.
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.