在Safari 6中禁用JIT以解决严重的Javascript JIT错误

时间:2012-10-30 20:21:54

标签: javascript safari jit safari6

我们发现我们的Javascript代码的解释存在严重问题,这些问题仅发生在iOS 5 / Safari 6(当时的iPad版本)上,我们认为这是由于Safari中的Just in Time JS编译器中的关键错误。 (请参阅下面的更新,了解更多受影响的版本和现在似乎包含修补程序的版本。)

我们最初在我们库的在线demos中发现了这个问题:演示或多或少地随机崩溃,但这只是第二次(甚至更晚)发生相同的代码。即如果您运行一部分代码,一切正常,但后续运行会使应用程序崩溃。

有趣的是,在Chrome for iOS中执行相同的代码问题并未显示,我们认为这是因为Chrome for iOS中使用的Webview缺少JIT功能。

经过大量的摆弄,我们终于认为我们发现了至少一个有问题的代码:

  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 

本质上,这是一个简单的for循环,它将链表中的每个单元格分配给它的索引。这里的问题是循环体中的后增量操作。当前计数被分配给字段并在计算表达式后更新,基本上与首先分配a然后将其递增1相同。

这在我们测试的所有浏览器和Safari中的前几次都可以正常工作,然后突然看起来似乎计数器变量a先递增,然后分配结果,就像预增量操作一样。 / p>

我创建了一个小提琴,在这里显示问题:http://jsfiddle.net/yGuy/L6t5G/

在带有iOS 6的iPad 2上运行示例并且所有更新结果在我的情况下前两次运行都是正常的,并且在第三次运行中突然显示列表中的最后一个元素已分配的值被关闭一个(单击“单击我”按钮时的输出从“从0到500”变为“从0到501”)

有趣的是,如果你切换标签,或稍等一下,可能会发生突然结果是正确的两次左右的运行!似乎Safari有时会重置是JIT缓存。

因为我认为Safari团队可能需要很长时间来修复这个bug(我还没有报告),并且可能还有其他类似的错误,比如潜伏在JIT中同样难以找到的,我想知道是否有办法在Safari中禁用JIT功能。当然,这会降低我们的代码(已经非常占用CPU),但速度比崩溃慢。

更新: 不出所料,它不仅仅是受影响的后增量运算符,还有后减量运算符。更令人惊讶和更令人担忧的是,如果赋值,则没有区别,因此在现有代码中查找赋值是不够的。例如。以下代码b.$f = (a++ % 2 == 0) ? 1 : 2;,其中变量值未分配但仅用于三元运算符条件也“失败”,因为有时选择了错误的分支。目前看起来似乎只有在根本不使用邮政运营商时才能避免这个问题。

更新: 同样的问题不仅存在于iOS设备中,而且还存在于Safari 6中的 Mac OSX 和最新的Safari 5中: 这些已经过测试,发现受到了这个bug的影响: 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+)。有趣的是,这些似乎受到影响:iPad 2(移动)Safari 5.1.7和iPad 1 Mobile Safari 5.1。我已向Apple报告了这些问题但尚未收到任何回复。

更新: 该bug已被报告为Webkit bug 109036。 Apple仍未对我的错误报告做出回应,目前(2013年2月)iOS和MacOS上的所有Safari版本仍然受到此问题的影响。

2013年2月27日更新: Webkit团队here似乎已经修复了错误!这确实是JIT和后期运营商的问题!评论表明,更多的代码可能已经受到了bug的影响,所以现在可能已经修复了更神秘的Heisenbugs!

2013年10月更新: 该修复最终使其成为生产代码:至少在iPad2上的iOS 7.0.2似乎不再受此错误的影响。但是,我没有检查所有的中间版本,因为我们很久以前就解决了这个问题。

3 个答案:

答案 0 :(得分:9)

Try-catch块似乎在Lion上的Safari 6上禁用了try块(this code worked for me on Safari 6.0.1 7536.26.14 and OS X Lion)内部的JIT编译器。

// 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
};

这至少是当前版本的Google V8的记录行为(请参阅Google I/O presentation on V8),但我不了解Safari。

如果要为整个脚本禁用它,一种解决方案是编译JS,使用burrito等工具将每个函数的内容包装在try-catch中。

使这个可重复的好工作!

答案 1 :(得分:1)

IMO,正确的解决方案是向Apple报告错误,然后在您的代码中解决它(当然使用单独的a = a + 1;语句将起作用,除非JIT比您想象的更糟糕!)。但确实很糟糕。这里有一个常见的东西列表,您也可以尝试使用该函数使其去优化而不使用JIT:

  • 例外
  • 'with'statement
  • 使用arguments对象,例如arguments.callee的
  • 的eval()

问题在于,如果Javascript引擎在修复该bug之前优化了JIT,那么你就会重新崩溃。所以,报告和解决方法!

答案 2 :(得分:1)

实际上,在iPhone 4和iPad 2上的iOS 7.0.4上的Safari中仍然存在FOR循环错误。循环失败可能比上面的插图简单得多,并且它可以通过代码耙几次。更改为WHILE循环可以正确执行。

代码失败:

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; 
} 

成功的代码:

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; 
}