Me imagino que la mayoría de ustedes saben que goto
es una palabra clave reservada en el lenguaje Java pero que en realidad no se usa. Y probablemente también sepa que goto
es un código de operación de Java Virtual Machine (JVM). Creo que todas las estructuras de flujo de control sofisticados de Java, Scala y Kotlin son, a nivel JVM, implementado usando una combinación de goto
y ifeq
, ifle
, iflt
, etc.
Mirando la especificación JVM https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.goto_w veo que también hay un goto_w
código de operación. Mientras que goto
toma un desplazamiento de rama de 2 bytes, goto_w
toma un desplazamiento de rama de 4 bytes. La especificación establece que
Aunque la instrucción goto_w toma un desplazamiento de rama de 4 bytes, otros factores limitan el tamaño de un método a 65535 bytes (§4.11). Este límite puede aumentarse en una versión futura de Java Virtual Machine.
Me parece que goto_w
es a prueba de futuro, como algunos de los otros *_w
códigos de operación. Pero también se me ocurre que tal vez goto_w
podría usarse con los dos bytes más significativos puestos a cero y los dos bytes menos significativos de la misma manera goto
, con los ajustes necesarios.
Por ejemplo, dado este caso de conmutador de Java (o caso de coincidencia de Scala):
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.
podríamos reescribirlo 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.
Realmente no he intentado esto, ya que probablemente he cometido un error al cambiar los "números de línea" para acomodar el goto_w
s. Pero como está en la especificación, debería ser posible hacerlo.
Mi pregunta es si hay una razón por la que un compilador u otro generador de bytecode podría usar goto_w
el límite actual de 65535 además de mostrar que se puede hacer.
// ... repeat 10K times ...
Eso compila? Sé que hay un límite para el tamaño de una sola clase de origen ... pero no sé cuál es precisamente (la generación de código es la única vez que he visto algo realmente golpearlo).No hay razón para usar
goto_w
cuando la rama encaja en agoto
. Pero parece haber pasado por alto que las ramas son relativas , utilizando un desplazamiento firmado, ya que una rama también puede ir hacia atrás.No lo nota cuando mira la salida de una herramienta como
javap
, ya que calcula la dirección de destino absoluta resultante antes de imprimir.Por lo tanto
goto
, el rango de-327678 … +32767
no siempre es suficiente para abordar cada posible ubicación objetivo en el0 … +65535
rango.Por ejemplo, el siguiente método tendrá una
goto_w
instrucción al principio:Demo en Ideone
fuente
Main
conmethodWithLargeJump()
compila casi 400KB.finally
bloques se duplican para un flujo normal y excepcional (obligatorio desde Java 6). Por lo tanto, anidar diez de ellos implica × 2¹⁰, entonces, el interruptor siempre tiene un objetivo predeterminado, por lo que junto con el iload, necesita diez bytes más relleno. También agregué una declaración no trivial en cada rama para evitar optimizaciones. La explotación de los límites es un tema recurrente, expresiones anidadas , lambdas , campos , constructores ...Parece que en algunos compiladores (probados en 1.6.0 y 11.0.7), si un método es lo suficientemente grande como para necesitar goto_w, usa exclusivamente goto_w. Incluso cuando tiene saltos muy locales, todavía usa goto_w.
fuente