Deshabilitar JIT en Safari 6 para solucionar errores JIT de JavaScript severos
safari6 (3)
En mi opinión, la solución correcta es informar el error a Apple y luego solucionarlo en su código (seguramente utilizando una declaración a a = a + 1;
separado a = a + 1;
a menos que el JIT sea aún peor de lo que pensaba!). Sin embargo, sí apesta. Aquí hay una lista de cosas comunes que también puede intentar incluir en la función para hacer que se des-optimice y no use JIT:
- Excepciones
- ''con'' declaración
- usando argumentos objeto, por ejemplo, argumentos.
- eval ()
El problema con ellos es si el motor de Javascript está optimizado para JIT antes de que solucionen ese error, en cuyo caso volverá a fallar. Así que, informe y solución!
Encontramos un problema grave con la interpretación de nuestro código Javascript que solo ocurre en iOS 5 / Safari 6 (la versión actual del iPad) que creemos que se debe a un error crítico en el compilador Just in Time JS en Safari. (Consulte las actualizaciones a continuación para ver las versiones más afectadas y las versiones que parecen contener una solución).
Originalmente, encontramos el problema en nuestras demos en línea de nuestra biblioteca: las demostraciones fallan más o menos al azar, pero esto sucede solo la segunda vez (o incluso más tarde) que se ejecuta el mismo código. Es decir, si ejecuta la parte del código una vez, todo funciona bien, sin embargo, las ejecuciones posteriores bloquean la aplicación.
Interesantemente, al ejecutar el mismo código en Chrome para iOS, el problema no se muestra, lo que creemos que se debe a las capacidades JIT que faltan en la vista web que se usa en Chrome para iOS.
Después de muchos problemas, finalmente creemos que encontramos al menos una pieza problemática de código:
var a = 0; // counter for index
for (var b = this.getStart(); b !== null; b = b.getNext()) // iterate over all cells
b.$f = a++; // assign index to cell and then increment
En esencia, este es un bucle for simple que asigna su índice a cada celda en una lista de datos vinculados. El problema aquí es la operación de incremento posterior en el cuerpo del bucle. El recuento actual se asigna al campo y se actualiza después de evaluar la expresión, básicamente lo mismo que asignarle primero y luego incrementarlo en uno.
Esto funciona bien en todos los navegadores que probamos y en Safari las primeras veces, y luego de repente parece que la variable de contador a se incrementa primero y luego se asigna el resultado, como una operación de preincremento.
He creado un violín que muestra el problema aquí: http://jsfiddle.net/yGuy/L6t5G/
Al ejecutar el ejemplo en un iPad 2 con iOS 6 y todas las actualizaciones, el resultado es correcto para las primeras 2 ejecuciones en mi caso y en la tercera ejecución idéntica, de repente, el último elemento de la lista tiene un valor asignado que está desactivado en uno (la salida al hacer clic en el botón "haga clic en mí" cambia de "de 0 a 500" a "de 0 a 501")
Curiosamente, si cambia de pestaña o si espera un poco, puede suceder que de repente los resultados sean correctos durante dos o más carreras. Parece como si Safari a veces se restablece en cachés JIT.
Entonces, como creo que el equipo de Safari puede tardar mucho en solucionar este error (que aún no he reportado) y puede haber otros errores similares como este que acechan en el JIT que son igualmente difíciles de encontrar, me gustaría saber si hay una manera de deshabilitar la funcionalidad JIT en Safari. Por supuesto, esto ralentizaría nuestro código (que ya requiere mucha CPU), pero es mejor lento que fallar.
Actualización : Como era de esperar, no solo se ve afectado el operador de incremento posterior, sino también el operador de reducción posterior. Menos sorprendente y más preocupante es que no importa si se asigna el valor, por lo que buscar una asignación en el código existente no es suficiente. Por ejemplo, el siguiente código b.$f = (a++ % 2 == 0) ? 1 : 2;
b.$f = (a++ % 2 == 0) ? 1 : 2;
donde el valor de las variables no se asigna, pero solo se usa para la condición de operador ternario, también "falla" en el sentido de que a veces se elige la rama incorrecta. Actualmente parece que el problema solo puede evitarse si los operadores de correos no se utilizan en absoluto.
Actualización : el mismo problema no solo existe en los dispositivos iOS, sino también en Mac OSX en Safari 6 y en la versión más reciente de Safari 5: han sido probados y se han visto afectados por el error: Mac OS 10.7.4, Safari 5.1.7 Mac OS X 10.8.2, WebKit Nightly r132968: Safari 6.0.1 (8536.26.14, 537+). Curiosamente, estos no parecen verse afectados: iPad 2 (Mobile) Safari 5.1.7 y iPad 1 Mobile Safari 5.1. He reportado estos problemas a Apple, pero aún no he recibido ninguna respuesta.
Actualización : El error ha sido reportado como Webkit bug 109036 . Apple aún no ha respondido a mi informe de errores, todas las versiones actuales de Safari (febrero de 2013) en iOS y MacOS siguen afectadas por el problema.
Actualización del 27 de febrero de 2013 : ¡Parece que el error ha sido corregido por el equipo de Webkit here ! ¡De hecho fue un problema con el JIT y los post-operadores! Los comentarios indican que el error podría haber afectado más código, por lo que podría ser que los Heisenbugs más misteriosos hayan sido corregidos, ¡ahora!
Actualización de octubre de 2013 : la solución finalmente se convirtió en el código de producción: iOS 7.0.2, al menos en iPad2, ya no parece sufrir este error. Sin embargo, no verifiqué todas las versiones intermedias, ya que solucionamos el problema hace mucho tiempo.
En realidad, el error del bucle FOR todavía está presente en Safari en iOS 7.0.4 en iPhone 4 y iPad 2. El fallo del bucle puede ser significativamente más simple que la ilustración anterior, y rastrea varios pases a través del código para golpear. Cambiar a un bucle WHILE permite una ejecución adecuada.
Código de error:
function zf(num,digs)
{
var out = "";
var n = Math.abs(num);
for (digs; digs>0||n>0; digs--)
{
out = n%10 + out;
n = Math.floor(n/10);
}
return num<0?"-"+out:out;
}
Código exitoso:
function zf(num,digs)
{
var out = "";
var n = Math.abs(num);
do
{
out = n%10 + out;
n = Math.floor(n/10);
}
while (--digs>0||n>0)
return num<0?"-"+out:out;
}
Los bloques Try-catch parecen deshabilitar el compilador JIT en Safari 6 en Lion para la parte directamente dentro del bloque try
( este código funcionó para mí en Safari 6.0.1 7536.26.14 y OS X Lion ).
// test function
utility.test = function(){
try {
var a = 0; // counter for index
for (var b = this.getStart(); b !== null; b = b.getNext()) // iterate over all cells
b.$f = a++; // assign index to cell and then increment
}
catch (e) { throw e }
this.$f5 = !1; // random code
};
Esto es, al menos, un comportamiento documentado de la versión actual de V8 de Google (consulte la presentación de Google I / O en V8 ), pero no lo sé de Safari.
Si desea deshabilitarlo para todo el script, una solución sería compilar su JS para envolver el contenido de cada función dentro de un try-catch con una herramienta como el burrito .
Buen trabajo en hacer esto reproducible!