Estamos en el proceso de migrar una aplicación a .NET 4.0 (desde 3.5). Uno de los problemas que estamos corriendo en sólo es reproducible en condiciones muy específicas:¿Puedo detener la ejecución de .NET 4 tail-call?
- Sólo en un comunicado de construir
- Sólo con la optimización activada y/o información de depuración se establece en AP-solamente.
Con esto quiero decir, si inhabilito la optimización y conjunto de información de depuración al completo, el problema desaparece.
El código en cuestión funciona bien en .NET 3.5, en modo de lanzamiento con la optimización, etc. habilitado, y lo ha hecho durante mucho tiempo.
Realmente no quiero sugerir que haya un error en el compilador de C#, entonces realmente mi pregunta es si hay alguna técnica que pueda usar para rastrear lo que podríamos estar haciendo mal para causar una optimización incorrecta.
Estoy en el proceso de intentar reducir este problema a un pequeño caso de prueba para que pueda publicar algún código aquí.
Editar:
He rastreado el problema a lo siguiente:
Tenemos este código en el constructor de un formulario:
public ConnectionForm()
{
LocalControlUtil.Configure("ConnectionForm", "Username", usernameLabel);
LocalControlUtil.Configure("ConnectionForm", "Password", passwordLabel);
LocalControlUtil.Configure("ConnectionForm", "Domain", domainLabel);
LocalControlUtil.Configure("ConnectionForm", "Cancel", cancelButton);
LocalControlUtil.Configure("ConnectionForm", "OK", okButton);
}
Estas llamadas son en cierta código de localización personalizado. El constructor para este formulario se llama desde otro ensamblado. El método LocalControlUtil.Configure
llama al Assembly.GetCallingAssembly()
, que devuelve el valor correcto para todas las llamadas anteriores, excepto la última.
Puedo reordenar las líneas de arriba, agregar nuevas o eliminar las actuales, y cada vez que es la última línea que no funciona.
Supongo que se trata de JIT que incluye la última llamada al método al lugar donde se llamó al constructor (en otro conjunto). Agregar [MethodImpl(MethodImplOptions.NoInlining)]
al constructor anterior soluciona el problema.
¿Alguien sabe por qué sucede esto? Me parece extraño que la última línea solo pueda estar en línea. ¿Es este nuevo comportamiento en .NET 4.0?
Edición 2:
He reducido esto abajo ahora a una eliminación de llamada final, supongo causada por el new tail-call stuff in .NET 4.
En el código anterior, se elimina la última llamada a LocalControlUtil.Configure
en el constructor y se pone en el método de llamada, que está en otro ensamblado. Como el método llama al Assembly.GetCallingAssembly
, no recuperamos el ensamblaje correcto.
¿Hay alguna manera de evitar que el compilador (o el JIT o lo que sea que haga esto) elimine la llamada final?
Vi una pregunta similar en SO el otro día y definitivamente parecía que había un error en el compilador ya que se estaba optimizando incorrectamente. Entonces * es * posible, pero según lo entiendo, es bastante raro. –
Recuerdo haber leído sobre esto en un blog. De alguna manera, el compilador hace que ciertos objetos se eliminen demasiado pronto. El ejemplo fue un método de cronómetro que solo ejecutó 1 ciclo antes de fallar. Desafortunadamente no tengo un enlace a la publicación. – PiZzL3
¿Puede darnos más detalles sobre el sabor del problema? Por ejemplo, hay muchas maneras en que los cálculos de punto flotante pueden diferir cuando se cambian las configuraciones de optimización. Existen numerosas formas en que los tiempos de subprocesos, los tiempos de recolección de basura, etc., pueden cambiar. ¿Cuál es la naturaleza del problema? –