c# - remarks - ¿Por qué se utiliza el código de operación IL ''br.s'' en este caso?
remarks c# (3)
Para fines educativos, estoy aprendiendo un poco de IL (principalmente porque tenía curiosidad por lo que le pasa a ''%'' bajo el capó (que resulta ser rem) y comencé a hacer una digresión ...).
Escribí un método, simplemente volviéndome verdadero para desglosar un poco las cosas y me preguntaba sobre el código de operación ''br.s'':
.method public hidebysig static bool ReturnTrue() cil managed
{
// Code size 7 (0x7)
.maxstack 1
.locals init ([0] bool CS$1$0000)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: stloc.0
IL_0003: br.s IL_0005
IL_0005: ldloc.0
IL_0006: ret
} // End of method Primes::ReturnTrue
Después de que ldc.i4.1 empuja 1 en la pila y stloc.0 coloca esto en el 0º local, básicamente br.s (hasta donde sé) hace un ''goto'' a ldloc.0 en la línea IL_0005.
¿Por qué es esto? ¿Por qué simplemente no hay una línea IL_0004 por lo que podría omitirse?
Esa rama es para fines de depuración, el valor de retorno se ha calculado y almacenado y ahora se puede "llamar" al depurador. Es lo mismo con el NOP
en la entrada del método.
Con respecto a IL_0004
, como dice IL_0004
, br.s tiene una dirección y no cabe en "una fila", hay un byte aquí (no sé qué tan familiar está con el direccionamiento, pero una instrucción generalmente es un byte , es decir, 8 bits, así como la dirección o el desplazamiento, normalmente 8, 16 o 32 bits. En este caso, tenemos un código de operación de 8 bits con un desplazamiento de 8 bits. Wikipedia tiene un buen artículo sobre CIL-OP-codigos ).
Además, digamos que su método tiene múltiples retornos y, por ejemplo, a través de -branches, todos saltan al final, IL_0005
en su caso, por lo que solo se necesita un punto de interrupción en el retorno de la función.
Este es un artefacto muy común de un analizador de descenso recursivo , como el que usa el compilador de C #. Deshacerse de estas ramas requiere un optimizador de mirilla .
Es probable que ocurra cuando el compilador ha optimizado una operación trivial cuyo resultado se puede determinar en el momento de la compilación. El compilador de C # no tiene un optimizador de mirilla porque no es necesario, el jitter se encarga de eliminar estas ramas innecesarias. Poner el optimizador en el jitter es, en general, una estrategia ganadora, de la que se benefician todos los compiladores de idiomas. Mantiene a los compiladores muy simples y el gasto considerable de escribir y mantener un optimizador de código en solo uno (o algunos) lugares.
Ahí no es donde termina el optimizador de fluctuaciones, todo su método desaparecerá en el tiempo de ejecución. Con una alta probabilidad de que cualquier código que llame al método también se optimice sustancialmente ya que el valor de retorno de su método se conoce en el momento de la compilación. Ver este tipo de MSIL es, de otro modo, un fuerte indicio de que su código puede simplificarse fácilmente o tiene un error :)
No sucede en Visual Studio 2013, parece que Dev finalmente lo solucionó. Se verá así en VS2013.
.method public hidebysig static bool ReturnTrue() cil managed
{
.maxstack 1
ldc.i4.1
ret
} // End of method Primes::ReturnTrue