Используют ли какие-либо компиляторы для JVM "широкий" переход? - PullRequest
48 голосов
/ 18 апреля 2020

Я думаю, что большинство из вас знает, что goto является зарезервированным ключевым словом в языке Java, но на самом деле оно не используется. И вы, вероятно, также знаете, что goto - это код операции Java Virtual Machine (JVM). Я считаю, что все сложные структуры потоков управления Java, Scala и Kotlin на уровне JVM реализованы с использованием некоторой комбинации goto и ifeq, ifle, iflt и др. c.

Глядя на JVM spe c https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms -6.5.goto_w Я вижу, что есть также код операции goto_w. Принимая во внимание, что goto принимает 2-байтовое смещение ветви, goto_w принимает 4-байтовое смещение ветви. Спецификация c утверждает, что

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

Мне кажется, что goto_w ориентирован на будущее, как и некоторые другие *_w коды операций. Но мне также кажется, что, возможно, 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 с. Но так как он в спецификации c, это должно быть возможно сделать.

Мой вопрос заключается в том, есть ли причина, по которой компилятор или другой генератор байт-кода может использовать goto_w с текущим пределом 65535, другим чем показать что это можно сделать?

Ответы [ 3 ]

51 голосов
/ 18 апреля 2020

Размер кода метода может достигать 64 КБ.

Смещение ветви короткого 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
          ...
34 голосов
/ 18 апреля 2020

Нет смысла использовать goto_w, когда ветвь вписывается в goto. Но вы, кажется, упустили, что ветви относительно , используя смещение со знаком, поскольку ветвь может также go назад.

Вы не замечаете этого, глядя на вывод инструмента типа javap, так как он вычисляет результирующий абсолютный целевой адрес перед печатью.

Так что диапазон goto -327678 … +32767‬ не всегда достаточен для адресации каждого возможного целевого местоположения в 0 … +65535 range.

Например, следующий метод в начале будет иметь инструкцию 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
…
5 голосов
/ 19 апреля 2020

Похоже, что в некоторых компиляторах (пробовал в 1.6.0 и 11.0.7), если метод достаточно велик, когда-либо нужен goto_w, он использует исключительно goto_w. Даже когда у него очень локальные переходы, он все равно использует goto_w.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...