Я думаю, что большинство из вас знает, что goto
это зарезервированное ключевое слово в языке Java, но на самом деле оно не используется. И вы, вероятно, также знаете, что goto
это код операции виртуальной машины Java (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 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
s. Но так как это в спецификации, должно быть возможно сделать это.
Мой вопрос заключается в том, есть ли причина, по которой компилятор или другой генератор байт-кода может использовать goto_w
с текущим пределом 65535, кроме того, чтобы показать, что это можно сделать?
// ... repeat 10K times ...
Что компилирует? Я знаю, что существует ограничение на размер одного исходного класса ... но я не знаю, что это такое (генерация кода - единственный раз, когда я видел, что что-то действительно достигло этого).Нет смысла использовать,
goto_w
когда ветвь вписывается вgoto
. Но вы, кажется, упустили, что ветви являются относительными , используя смещение со знаком, поскольку ветвь также может идти назад.Вы не замечаете этого, глядя на выходные данные инструмента, подобного
javap
, поскольку он вычисляет результирующий абсолютный целевой адрес перед печатью.Так
goto
что диапазона-327678 … +32767
не всегда достаточно, чтобы охватить каждое возможное целевое местоположение в0 … +65535
диапазоне.Например, следующий метод будет иметь
goto_w
инструкцию в начале:Демо на Ideone
источник
Main
сmethodWithLargeJump()
компиляциями почти 400 КБ.finally
блоки дублируются для нормального и исключительного потока (обязательно с Java 6). Таким образом, вложение десяти из них подразумевает × 2¹⁰, тогда у switch всегда есть цель по умолчанию, поэтому вместе с iload требуется десять байтов плюс заполнение. Я также добавил нетривиальный оператор в каждую ветку, чтобы предотвратить оптимизацию. Эксплуатация лимитов - это повторяющаяся тема, вложенные выражения , лямбды , поля , конструкторы ...Похоже, что в некоторых компиляторах (пробовал в 1.6.0 и 11.0.7), если метод достаточно велик, когда-либо нужен goto_w, он использует исключительно goto_w. Даже когда у него очень локальные переходы, он все равно использует goto_w.
источник