Eu acho que a maioria de vocês sabe que goto
é uma palavra-chave reservada na linguagem Java, mas na verdade não é usada. E você provavelmente também sabe que esse goto
é um código de operação da Java Virtual Machine (JVM). Eu conto todas as estruturas de controle de fluxo sofisticados de Java, Scala e Kotlin são, ao nível JVM, implementado usando uma combinação de goto
e ifeq
, ifle
, iflt
, etc.
Observando as especificações da JVM https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.goto_w , vejo também que há um goto_w
código de operação. Considerando que goto
leva um deslocamento de ramificação de 2 bytes, goto_w
leva um deslocamento de ramificação de 4 bytes. A especificação afirma que
Embora a instrução goto_w use um deslocamento de ramificação de 4 bytes, outros fatores limitam o tamanho de um método a 65535 bytes (§4.11). Esse limite pode ser aumentado em uma versão futura da Java Virtual Machine.
Parece-me goto_w
à prova de futuro, como alguns dos outros *_w
opcodes. Mas também me ocorre que talvez goto_w
possa ser usado com os dois bytes mais significativos zerados e os dois bytes menos significativos iguais, como goto
com os ajustes necessários.
Por exemplo, dado este Java Switch-Case (ou 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.
poderíamos reescrevê-lo como
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.
Na verdade, eu não tentei isso, pois provavelmente cometi um erro ao alterar os "números de linha" para acomodar os goto_w
s. Mas como está na especificação, deve ser possível fazê-lo.
Minha pergunta é se existe uma razão pela qual um compilador ou outro gerador de bytecode pode usar goto_w
com o atual limite 65535 diferente de mostrar que isso pode ser feito?
// ... repeat 10K times ...
Isso compila? Eu sei que há um limite para o tamanho de uma única classe de origem ... mas não sei exatamente o que é (a geração de código é a única vez que eu vejo algo realmente acontecer).Não há razão para usar
goto_w
quando o ramo se encaixa em agoto
. Mas você parece ter esquecido que as ramificações são relativas , usando um deslocamento assinado, pois uma ramificação também pode retroceder.Você não percebe isso quando olha para a saída de uma ferramenta
javap
, pois calcula o endereço de destino absoluto resultante antes da impressão.Portanto
goto
, o intervalo de-327678 … +32767
nem sempre é suficiente para abordar cada local-alvo possível no0 … +65535
intervalo.Por exemplo, o método a seguir terá uma
goto_w
instrução no início:Demonstração sobre Ideone
fonte
Main
commethodWithLargeJump()
compila para quase 400KB.finally
blocos são duplicados para um fluxo normal e excepcional (obrigatório desde o Java 6). Portanto, aninhar dez deles implica em × 2¹⁰; o comutador sempre tem um destino padrão; portanto, junto com o iload, ele precisa de dez bytes mais o preenchimento. Também adicionei uma declaração não trivial em cada ramo para evitar otimizações. Explorar limites é um tópico recorrente, expressões aninhadas , lambdas , campos , construtores …Parece que em alguns compiladores (tentados em 1.6.0 e 11.0.7), se um método é grande o suficiente para a necessidade de goto_w, ele usa exclusivamente goto_w. Mesmo quando tem saltos muito locais, ainda usa goto_w.
fonte