Чи використовують будь-які компілятори для JVM "широкий" goto?


47

Думаю, більшість із вас знає, що gotoце ключове слово на мові Java, але воно фактично не використовується. І ви, мабуть, також знаєте, що gotoце опкод Java Virtual Machine (JVM). Я вважаю , всі складні структури потоку управління Java, Scala і Котлин є, на рівні віртуальної машини Java, реалізовані з використанням деякої комбінації gotoі ifeq, ifle, ifltі т.д.

Дивлячись на специфікацію JVM https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.goto_w, я бачу, що існує також goto_wопкод. Тоді gotoяк 2-байтний зсув гілки goto_wприймає 4-байтове зміщення гілки. Специфікація стверджує, що

Хоча інструкція goto_w приймає 4-байтове зміщення гілки, інші фактори обмежують розмір методу до 65535 байт (§4.11). Ця межа може бути підвищена в майбутньому випуску віртуальної машини Java.

Мені це здається, що це впевненість у goto_wмайбутньому, як і деякі інші *_wопкоди. Але мені також здається, що, можливо, goto_wможна було б використовувати два більш значущих байти, нульові і два менш значні байти такі ж, як і для goto, з необхідними коригуваннями.

Наприклад, враховуючи цей Java-перемикач (або 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_ws. Але оскільки це в специфікації, це має бути можливо зробити.

Моє запитання: чи є причина, яку компілятор або інший генератор байт-коду може використовувати 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 ...Що компілює? Я знаю, що існує обмеження на розмір одного класу джерел ... але я не знаю, що це саме (генерація коду - це єдиний раз, коли я бачив, що щось насправді вдарило).
Елліот Фріш

3
@ElliottFrisch Це так. Поки розмір методу байт-коду не перевищує 65535, а постійна довжина пулу також менше 65535.
apangin

18
Класно. Дякую. 64 к. Повинно вистачити для будь-кого, хто здогадується. ;)
Елліот Фріш

3
@ElliottFrisch - Шапка підказок за посиланням.
TJ Crowder

34

Немає підстав використовувати, goto_wколи гілка вписується в а goto. Але ви, здається, пропустили, що гілки є відносними , використовуючи підписаний зсув, оскільки гілка також може йти назад.

Ви цього не помічаєте, дивлячись на вихід такого інструменту javap, як він обчислює отриману абсолютну цільову адресу перед друком.

Отже goto, діапазону -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, що містить кілька пакетів і кілька десятків класів між ними, складає майже 200 КБ. Але ваш Mainз methodWithLargeJump()компіляціями майже до 400 КБ.
Алонсо дель Арте

4
Це демонструє, наскільки Java оптимізована для загальної справи ...
Холгер

1
Як ви виявили це зловживання столиками зі стрибками? Машиногенерований код?
Елліот Фріш

14
@ElliottFrisch Мені довелося лише згадати, що finallyблоки дублюються для нормального та виняткового потоку (обов'язковий з Java 6). Отже, десять з них вказують на × 2¹⁰, то, перемикач завжди має ціль за замовчуванням, тому разом з iload йому потрібно десять байтів плюс прокладка. Я також додав нетривіальне твердження у кожну гілку, щоб запобігти оптимізації. Використання меж - це повторювана тема, вкладені вирази , лямбда , поля , конструктори
Holger

2
Цікаво, що вкладені вирази та безліч конструкторів також впливають на обмеження реалізації компілятора, а не лише на обмеження байтового коду. Також було запитання щодо розміру файлу максимального класу (можливо, я підсвідомо пам’ятав відповідь Тагіра, коли писав цю відповідь). Нарешті, максимальна довжина назви пакета та, з боку JVM, максимальна вкладена синхронізація . Здається, люди продовжують залишатися цікавими.
Холгер

5

Здається, що в деяких компіляторах (спробували в 1.6.0 і 11.0.7), якщо метод досить великий, коли-небудь потрібен goto_w, він використовує виключно goto_w. Навіть коли він має дуже локальні стрибки, він все ще використовує goto_w.


1
Чому це могло бути? Це щось стосується кешування інструкцій?
Олександр - Відновіть Моніку

@ Alexander-ReinstateMonica Напевно, простота реалізації.
Девід Г.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.