jueves, 25 de julio de 2013

MinGW y las calling conventions

Ya hace más de 6 meses que no vemos publicada una nueva versión "oficial" de ZinjaI. Es normal que en el primer tercio del año los esfuerzos se dirijan mayormente a PSeInt, y este año se han presentado motivaciones adicionales para que así sea aún en mayor medida y por algo más de tiempo. Sin embargo, el desarrollo de ZinjaI nunca se detiene por completo, y quienes observan de tanto en tanto el repositorio saben que siempre hay algo nuevo, aunque solo sea un bug. Pero esta vez hay una razón más para retrasar el lanzamiento de una nueva versión, y esta es un cambio importante en mingw (el port del compilador gcc a Windows). Siempre lleva tiempo actualizar gcc ya que hay que "armarlo" por partes, compilar algunas bibliotecas que agrego en ZinjaI, y luego acomodar y empaquetar todo, pero esta nueva versión tiene algo especial. En qué consiste este cambio y cómo nos afecta a los usuarios de ZinjaI es lo que voy a tratar de explicar en este post.

Siendo directo, voy a decir que lo que cambió y traerá problemas fue la convención de llamadas a funciones que se utiliza por defecto para métodos. Esto es, cómo traduce el compilador una llamada a función a código ensamblador. Se deben "poner de acuerdo" el bloque de código que invoca a la función, y el bloque de código de la función misma, en detalles tales como cómo se van a pasar los argumentos y los valores de retorno, y quién es responsable de eliminar sus temporales y variables locales luego de que la función finalice, etc. Por ejemplo, el código que llama a la función puede colocar los argumentos en registros del micro, o puede colocar allí las direcciones de memoria donde están en verdad los argumentos, o puede colocarlos en la pila, y en cualquier caso también hay que decir en qué registros, o en qué orden dentro de la pila estarán, para que la función sepa cómo leerlos. Todos estos detalles forman lo que se conoce como calling convention. Hay algunos más o menos estándar. Lo importante, es que si compilamos un cpp que usa una función, y luego compilamos el cpp que implementa dicha función, para que el programa funcione, ambos deben asumir la misma convención de llamadas. De lo contrario, el programa explotará con un segfault (o el cartelito de "No Enviar" en Windows).

El programador puede forzar una convención de llamada declarando la función con un "decorado" adicional (una palabra clave que va en el prototipo antes del tipo de retorno), que varía de compilador en compilador. Normalmente esto no lo hacemos, pero seguramente lo vieron en algún .h de alguna biblioteca, palabras parecidas a cdecl, stdcall, fastcall, etc (estos son los nombres con los que se conoce a estas convenciones, pero no son estándar, las palabras claves específicas varían según el compilador).

El hecho de que mingw haya cambiado la convención por defecto implica que los objetos compilados con versiones viejas no se pueden enlazar con objetos compilados con versiones nuevas. Esto aplica a combinaciones de objetos de un mismo proyecto y a bibliotecas. Lo primero significa que si abrimos un proyecto que habíamos compilado con la versión vieja, cambiamos un fuente y volvemos a compilar ese fuente, se compilará y enlazará sin problemas pero el programá explotará al ejecutarse. En este caso se soluciona fácilmente obligando a ZinjaI a recompilar todos los objetos con el nuevo compilador, lo cual se logra simplemente eliminando los viejos con la opción "Limpiar" del menú "Ejecución". El segundo caso, cuando hay bibliotecas de por medio, es más complicado, porque usualmente no tenemos los fuentes de las biblitocas (no los necesitamos), y aún teniendolos suele ser engorroso compilarlos. Si utilizan bibliotecas que se distribuyen con ZinjaI no tendrán problema, al instalar ZinjaI se actualizan. Si son bibliotecas que instalaron mediante un complemento, tampoco debería haber problemas, iré publicando los complementos actualizados para que los reinstalen y ya. Pero si son bibliotecas que descargaron o compilaron de otro lado por su cuenta, tendrán que volver a hacerlo nuevamente por su cuenta.

Un ejemplo es SFML. La versión 1.6 que se ofrece en binario en el sitio de SFML está compilada con un mingw viejo, mientras que la versión 2.0 está compilada con el nuevo. Esto quiere decir que si no actualizan ZinjaI no pueden utilizar la 2.0, pero si lo hacen ya no compilarán sus viejos juegos desarrollados para la 1.6. Como los binarios de esta biblioteca los ofrezco como complemento en el sitio de ZinjaI, en este caso particular lo solucionarán fácilmente, pero deben tener en cuenta estas cosas al descargar otras por su cuenta y verificar con qué versión de mingw-gcc fueron compiladas. La versión del quiebre (a partir de la cual esto cambió) es la 4.7.0.

Por completitud tengo que aclarar que si miran en las "release notes" de mingw, hay otro cambio en la ABI (interfaz binaria) que también puede generar problemas, pero está relacionado a los campos de bits y eso es algo que la gran mayoría no utiliza.

Espero haber sido más o menos claro, ya que seguramente esto va a traer más de un dolor de cabeza cuando publique esta actualización. De todas formas la idea es que ZinjaI muestre un mensaje de advertencia al abrir un proyecto guardado por una versión anterior, ofrezca hacer una limpieza de objetos viejos inmediatamente, y agregue un link a este post para los más curiosos.

No hay comentarios:

Publicar un comentario